]> git.saurik.com Git - apple/mdnsresponder.git/commitdiff
mDNSResponder-1310.40.42.tar.gz macos-1101 v1310.40.42
authorApple <opensource@apple.com>
Thu, 19 Nov 2020 01:06:26 +0000 (01:06 +0000)
committerApple <opensource@apple.com>
Thu, 19 Nov 2020 01:06:26 +0000 (01:06 +0000)
260 files changed:
Clients/DNS-SD.VisualStudio/dns-sd.vcxproj [changed mode: 0755->0644]
Clients/ExplorerPlugin/ExplorerPlugin.vcxproj [changed mode: 0755->0644]
Clients/ExplorerPlugin/ExplorerPluginLocRes.vcxproj [changed mode: 0755->0644]
Clients/ExplorerPlugin/ExplorerPluginRes.vcxproj [changed mode: 0755->0644]
Clients/PrinterSetupWizard/PrinterSetupWizard.vcxproj [changed mode: 0755->0644]
Clients/PrinterSetupWizard/PrinterSetupWizardLocRes.vcxproj [changed mode: 0755->0644]
Clients/PrinterSetupWizard/PrinterSetupWizardRes.vcxproj [changed mode: 0755->0644]
Clients/dns-sd.c
Clients/dnssdutil/DNSMessage.c
Clients/dnssdutil/DNSMessage.h
Clients/dnssdutil/DNSServerDNSSEC.c [new file with mode: 0644]
Clients/dnssdutil/DNSServerDNSSEC.h [new file with mode: 0644]
Clients/dnssdutil/TestUtils.h
Clients/dnssdutil/TestUtils.m
Clients/dnssdutil/dns-rcode-func-autogen [new file with mode: 0755]
Clients/dnssdutil/dns-rr-func-autogen [new file with mode: 0755]
Clients/dnssdutil/dnssdutil-entitlements.plist
Clients/dnssdutil/dnssdutil.c
Clients/mDNSNetMonitor.VisualStudio/mDNSNetMonitor.vcxproj [changed mode: 0755->0644]
Clients/mDNSNetMonitor.VisualStudio/mDNSNetMonitor.vcxproj.filters [changed mode: 0755->0644]
Clients/srputil/srputil-entitlements.plist [new file with mode: 0644]
Clients/srputil/srputil.c [new file with mode: 0644]
DSO/dso-transport.c
DSO/dso.c
DSO/dso.h
Makefile
Platforms/ADK/Thread/Makefile [new file with mode: 0644]
Platforms/ADK/Thread/adk-mem-parse.py [new file with mode: 0644]
ServiceRegistration/Makefile
ServiceRegistration/config-parse.c [new file with mode: 0644]
ServiceRegistration/config-parse.h [new file with mode: 0644]
ServiceRegistration/dns-msg.h
ServiceRegistration/dnssd-proxy.c
ServiceRegistration/dnssd-relay.c [new file with mode: 0644]
ServiceRegistration/fromwire.c
ServiceRegistration/hmac-macos.c [new file with mode: 0644]
ServiceRegistration/hmac-mbedtls.c [new file with mode: 0644]
ServiceRegistration/hmac-openssl.c [new file with mode: 0644]
ServiceRegistration/ioloop.c
ServiceRegistration/ioloop.h
ServiceRegistration/keydump.c
ServiceRegistration/log_srp.m [new file with mode: 0644]
ServiceRegistration/macos-ioloop.c [new file with mode: 0644]
ServiceRegistration/posix.c [new file with mode: 0644]
ServiceRegistration/ra-tester/ra-tester.c [new file with mode: 0644]
ServiceRegistration/route.c [new file with mode: 0644]
ServiceRegistration/route.h [new file with mode: 0644]
ServiceRegistration/sign-macos.c [new file with mode: 0644]
ServiceRegistration/sign-mbedtls.c
ServiceRegistration/srp-api.h [new file with mode: 0644]
ServiceRegistration/srp-client-entitlements.plist [new file with mode: 0644]
ServiceRegistration/srp-client.c [new file with mode: 0644]
ServiceRegistration/srp-crypto.h
ServiceRegistration/srp-dns-proxy.c [new file with mode: 0644]
ServiceRegistration/srp-gw.c
ServiceRegistration/srp-gw.h [new file with mode: 0644]
ServiceRegistration/srp-ioloop.c [new file with mode: 0644]
ServiceRegistration/srp-mdns-proxy.c [new file with mode: 0644]
ServiceRegistration/srp-mdns-proxy.h [new file with mode: 0644]
ServiceRegistration/srp-parse.c [new file with mode: 0644]
ServiceRegistration/srp-proxy.h [new file with mode: 0644]
ServiceRegistration/srp-simple.c [deleted file]
ServiceRegistration/srp-thread.c [new file with mode: 0644]
ServiceRegistration/srp-thread.h [new file with mode: 0644]
ServiceRegistration/srp-tls.h [new file with mode: 0644]
ServiceRegistration/srp.h
ServiceRegistration/tls-mbedtls.c [new file with mode: 0644]
ServiceRegistration/towire.c
ServiceRegistration/verify-macos.c [new file with mode: 0644]
ServiceRegistration/verify-mbedtls.c
ServiceRegistration/wireutils.c [new file with mode: 0644]
mDNSCore/CryptoAlg.c [deleted file]
mDNSCore/CryptoAlg.h [deleted file]
mDNSCore/DNSCommon.c
mDNSCore/DNSCommon.h
mDNSCore/dnsproxy.c
mDNSCore/dnsproxy.h
mDNSCore/dnssec.c [deleted file]
mDNSCore/dnssec.h [deleted file]
mDNSCore/mDNS.c [changed mode: 0755->0644]
mDNSCore/mDNSDebug.h
mDNSCore/mDNSEmbeddedAPI.h
mDNSCore/nsec.c [deleted file]
mDNSCore/nsec.h [deleted file]
mDNSCore/nsec3.c [deleted file]
mDNSCore/nsec3.h [deleted file]
mDNSCore/uDNS.c
mDNSCore/uDNS.h
mDNSMacOSX/ApplePlatformFeatures.h
mDNSMacOSX/CryptoSupport.c [deleted file]
mDNSMacOSX/CryptoSupport.h [deleted file]
mDNSMacOSX/D2D.c
mDNSMacOSX/D2D.h
mDNSMacOSX/DNS64.c
mDNSMacOSX/DNS64.h
mDNSMacOSX/DNSHeuristics.h [new file with mode: 0644]
mDNSMacOSX/DNSHeuristics.m [new file with mode: 0644]
mDNSMacOSX/DNSHeuristicsInternal.h [new file with mode: 0644]
mDNSMacOSX/DNSProxySupport.c
mDNSMacOSX/DNSSECSupport.c [deleted file]
mDNSMacOSX/DNSSECSupport.h [deleted file]
mDNSMacOSX/FeatureFlags/mDNSResponder.plist [new file with mode: 0644]
mDNSMacOSX/HTTPUtilities.h [new file with mode: 0644]
mDNSMacOSX/HTTPUtilities.m [new file with mode: 0644]
mDNSMacOSX/LoggingProfiles/AppleInternal/com.apple.mdns.plist [new file with mode: 0644]
mDNSMacOSX/LoggingProfiles/AppleInternal/com.apple.srp-mdns-proxy.plist [new file with mode: 0644]
mDNSMacOSX/LoggingProfiles/com.apple.mDNSResponder.plist
mDNSMacOSX/LoggingProfiles/com.apple.srp-mdns-proxy.plist [new file with mode: 0644]
mDNSMacOSX/Private/advertising_proxy_services.c [new file with mode: 0644]
mDNSMacOSX/Private/advertising_proxy_services.h [new file with mode: 0644]
mDNSMacOSX/Private/cti-services.c [new file with mode: 0644]
mDNSMacOSX/Private/cti-services.h [new file with mode: 0644]
mDNSMacOSX/Private/dns_services.c
mDNSMacOSX/Private/dns_services.h
mDNSMacOSX/QuerierSupport.c [new file with mode: 0644]
mDNSMacOSX/QuerierSupport.h [new file with mode: 0644]
mDNSMacOSX/Scripts/bonjour-mcast-diagnose
mDNSMacOSX/SymptomReporter.c
mDNSMacOSX/SymptomReporter.h
mDNSMacOSX/Tests/Unit Tests/CNameRecordTest.m
mDNSMacOSX/Tests/Unit Tests/CacheOrderTest.m
mDNSMacOSX/Tests/Unit Tests/DNSHeuristicsTest.m [new file with mode: 0644]
mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Crypto/CanonicalMethodsTest.m [new file with mode: 0644]
mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Crypto/DigestCalculationTest.m [new file with mode: 0644]
mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Crypto/NSEC3HashTest.m [new file with mode: 0644]
mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Utility/BaseNEncodingDecodingTest.m [new file with mode: 0644]
mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Utility/ListTMethodsTest.m [new file with mode: 0644]
mDNSMacOSX/Tests/Unit Tests/HelperFunctionTest.m
mDNSMacOSX/Tests/Unit Tests/LocalOnlyTimeoutTest.m
mDNSMacOSX/Tests/Unit Tests/LocalOnlyWithInterfacesTest.m
mDNSMacOSX/Tests/Unit Tests/PathEvaluationTest.m
mDNSMacOSX/Tests/Unit Tests/SuspiciousReplyTest.m [deleted file]
mDNSMacOSX/Tests/mDNSResponder.plist
mDNSMacOSX/Tests/mDNSResponderTests-Entitlements.plist [new file with mode: 0644]
mDNSMacOSX/com.apple.srp-mdns-proxy.plist [new file with mode: 0644]
mDNSMacOSX/command_line_client_entitlements/dns-sd-entitlements.plist
mDNSMacOSX/daemon.c
mDNSMacOSX/dnssd.c
mDNSMacOSX/dnssd_analytics.c [new file with mode: 0644]
mDNSMacOSX/dnssd_analytics.h [new file with mode: 0644]
mDNSMacOSX/dnssd_descriptions.m [new file with mode: 0644]
mDNSMacOSX/dnssd_object.h
mDNSMacOSX/dnssd_object.m
mDNSMacOSX/dnssd_private.h
mDNSMacOSX/dnssd_server.c
mDNSMacOSX/dnssd_server.h
mDNSMacOSX/dnssd_svcb.c [new file with mode: 0644]
mDNSMacOSX/dnssd_svcb.h [new file with mode: 0644]
mDNSMacOSX/dnssd_xpc.c
mDNSMacOSX/dnssd_xpc.h
mDNSMacOSX/dnssec_v2/dnssec_v2.c [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2.h [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2_client.c [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2_client.h [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2_crypto.c [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2_crypto.h [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2_embedded.h [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2_helper.c [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2_helper.h [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2_log.h [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2_retrieval.c [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2_retrieval.h [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2_structs.c [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2_structs.h [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2_trust_anchor.c [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2_trust_anchor.h [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2_validation.c [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/dnssec_v2_validation.h [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/utilities/base_encoding/base_n.c [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/utilities/base_encoding/base_n.h [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/utilities/list/list.c [new file with mode: 0644]
mDNSMacOSX/dnssec_v2/utilities/list/list.h [new file with mode: 0644]
mDNSMacOSX/helper-main.c
mDNSMacOSX/helper-stubs.c
mDNSMacOSX/helper.c
mDNSMacOSX/mDNSMacOSX.c
mDNSMacOSX/mDNSMacOSX.h
mDNSMacOSX/mDNSResponder-entitlements.plist
mDNSMacOSX/mDNSResponder.sb
mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj
mDNSMacOSX/mDNSResponder.xcodeproj/xcshareddata/xcschemes/Build All.xcscheme
mDNSMacOSX/mDNSResponder.xcodeproj/xcshareddata/xcschemes/mDNSResponder.xcscheme
mDNSMacOSX/mdns.c [deleted file]
mDNSMacOSX/mdns_object.h [deleted file]
mDNSMacOSX/mdns_object.m [deleted file]
mDNSMacOSX/mdns_objects/log_mdns.m [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_address.c [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_address.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_base.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_dns_service.c [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_dns_service.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_helpers.c [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_helpers.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_interface_monitor.c [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_interface_monitor.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_internal.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_managed_defaults.c [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_managed_defaults.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_message.c [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_message.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_object.c [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_object.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_objects.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_objects.m [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_powerlog.c [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_powerlog.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_private.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_resolver.c [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_resolver.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_set.c [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_set.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_symptoms.c [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_symptoms.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_tlv.c [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_tlv.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_trust.c [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_trust.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_trust_checks.h [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_trust_checks.m [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_xpc.c [new file with mode: 0644]
mDNSMacOSX/mdns_objects/mdns_xpc.h [new file with mode: 0644]
mDNSMacOSX/mdns_private.h [deleted file]
mDNSMacOSX/ra-tester-entitlements.plist [new file with mode: 0644]
mDNSMacOSX/srp-mdns-proxy-entitlements.plist [new file with mode: 0644]
mDNSMacOSX/srp-mdns-proxy.plist [new file with mode: 0644]
mDNSMacOSX/uDNSPathEvaluation.c [new file with mode: 0644]
mDNSMacOSX/uDNSPathEvalulation.c [deleted file]
mDNSMacOSX/utilities/bundle_utilities.h [new file with mode: 0644]
mDNSMacOSX/utilities/bundle_utilities.m [new file with mode: 0644]
mDNSMacOSX/utilities/setup_assistant_helper.h [new file with mode: 0644]
mDNSMacOSX/utilities/setup_assistant_helper.m [new file with mode: 0644]
mDNSMacOSX/utilities/system_utilities.c [deleted file]
mDNSMacOSX/utilities/system_utilities.h
mDNSMacOSX/utilities/system_utilities.m [new file with mode: 0644]
mDNSMacOSX/xpc_services/xpc_client_advertising_proxy.h [new file with mode: 0644]
mDNSMacOSX/xpc_services/xpc_client_dns_proxy.h
mDNSMacOSX/xpc_services/xpc_clients.h
mDNSMacOSX/xpc_services/xpc_service_dns_proxy.c
mDNSMacOSX/xpc_services/xpc_service_log_utility.c
mDNSMacOSX/xpc_services/xpc_service_log_utility.h
mDNSPosix/Makefile
mDNSPosix/mDNSPosix.c
mDNSResponder.sln [changed mode: 0755->0644]
mDNSShared/ClientRequests.c
mDNSShared/ClientRequests.h
mDNSShared/CommonServices.h
mDNSShared/PlatformCommon.c
mDNSShared/dns_sd.h
mDNSShared/dns_sd_internal.h
mDNSShared/dns_sd_private.h
mDNSShared/dnssd_clientshim.c
mDNSShared/dnssd_clientstub.c
mDNSShared/dnssd_clientstub_apple.c [new file with mode: 0644]
mDNSShared/dnssd_clientstub_apple.h [new file with mode: 0644]
mDNSShared/dnssd_ipc.c
mDNSShared/dnssd_ipc.h
mDNSShared/uds_daemon.c
mDNSShared/uds_daemon.h
unittests/unittest_common.c
unittests/unittest_common.h

old mode 100755 (executable)
new mode 100644 (file)
index 8ed154a..97cf47b
@@ -1,6 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>\r
 <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
   <ItemGroup Label="ProjectConfigurations">\r
+    <ProjectConfiguration Include="Debug|ARM64">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Debug|Win32">\r
       <Configuration>Debug</Configuration>\r
       <Platform>Win32</Platform>\r
@@ -9,6 +13,10 @@
       <Configuration>Debug</Configuration>\r
       <Platform>x64</Platform>\r
     </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|ARM64">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Release|Win32">\r
       <Configuration>Release</Configuration>\r
       <Platform>Win32</Platform>\r
   <PropertyGroup Label="Globals">\r
     <ProjectGuid>{AA230639-E115-4A44-AA5A-44A61235BA50}</ProjectGuid>\r
     <Keyword>Win32Proj</Keyword>\r
+    <WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>\r
   </PropertyGroup>\r
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
     <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
     <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
     <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
     <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
   <ImportGroup Label="ExtensionSettings">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
+  </ImportGroup>\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
+  </ImportGroup>\r
   <PropertyGroup Label="UserMacros" />\r
   <PropertyGroup>\r
     <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</LinkIncremental>\r
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>\r
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</LinkIncremental>\r
   </PropertyGroup>\r
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
     <ClCompile>\r
       <Optimization>Disabled</Optimization>\r
       <AdditionalIncludeDirectories>../../mDNSShared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
       <PreprocessorDefinitions>WIN32;_WIN32;_DEBUG;_CONSOLE;NOT_HAVE_GETOPT;NOT_HAVE_SETLINEBUF;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
-      <MinimalRebuild>true</MinimalRebuild>\r
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r
       <PrecompiledHeader>\r
       <Optimization>Disabled</Optimization>\r
       <AdditionalIncludeDirectories>../../mDNSShared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
       <PreprocessorDefinitions>WIN32;_WIN32;_DEBUG;_CONSOLE;NOT_HAVE_GETOPT;NOT_HAVE_SETLINEBUF;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
-      <MinimalRebuild>true</MinimalRebuild>\r
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r
       <PrecompiledHeader>\r
       <AdditionalManifestFiles>DNS-SD64.manifest;%(AdditionalManifestFiles)</AdditionalManifestFiles>\r
     </Manifest>\r
   </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">\r
+    <Midl />\r
+    <ClCompile>\r
+      <Optimization>Disabled</Optimization>\r
+      <AdditionalIncludeDirectories>../../mDNSShared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>WIN32;_WIN32;_DEBUG;_CONSOLE;NOT_HAVE_GETOPT;NOT_HAVE_SETLINEBUF;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r
+    </ClCompile>\r
+    <ResourceCompile>\r
+      <AdditionalIncludeDirectories>../../mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ResourceCompile>\r
+    <Link>\r
+      <AdditionalOptions>/NXCOMPAT /DYNAMICBASE %(AdditionalOptions)</AdditionalOptions>\r
+      <AdditionalDependencies>../../mDNSWindows/DLL/$(Platform)/$(Configuration)/dnssd.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>\r
+      <OutputFile>$(OutDir)dns-sd.exe</OutputFile>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+      <ProgramDatabaseFile>$(OutDir)dns-sd.pdb</ProgramDatabaseFile>\r
+      <SubSystem>Console</SubSystem>\r
+    </Link>\r
+    <Manifest>\r
+      <AdditionalManifestFiles>DNS-SD64.manifest;%(AdditionalManifestFiles)</AdditionalManifestFiles>\r
+    </Manifest>\r
+  </ItemDefinitionGroup>\r
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
     <ClCompile>\r
       <Optimization>MaxSpeed</Optimization>\r
@@ -252,6 +316,47 @@ if not exist "$(DSTROOT)\WINDOWS\system32\$(Platform)"            mkdir "$(DSTRO
 if not exist "$(DSTROOT)\Program Files\Bonjour SDK\Samples\C"               mkdir "$(DSTROOT)\Program Files\Bonjour SDK\Samples\C"\r
 xcopy /I/Y "$(TargetPath)"                                                                           "$(DSTROOT)\WINDOWS\system32\$(Platform)"\r
 :END\r
+</Command>\r
+    </PostBuildEvent>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">\r
+    <Midl />\r
+    <ClCompile>\r
+      <Optimization>MaxSpeed</Optimization>\r
+      <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>\r
+      <OmitFramePointers>true</OmitFramePointers>\r
+      <AdditionalIncludeDirectories>../../mDNSShared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>WIN32;_WIN32;NDEBUG;_CONSOLE;NOT_HAVE_GETOPT;NOT_HAVE_SETLINEBUF;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <StringPooling>true</StringPooling>\r
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r
+      <FunctionLevelLinking>true</FunctionLevelLinking>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r
+    </ClCompile>\r
+    <ResourceCompile>\r
+      <AdditionalIncludeDirectories>../../mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ResourceCompile>\r
+    <Link>\r
+      <AdditionalOptions>/NXCOMPAT /DYNAMICBASE %(AdditionalOptions)</AdditionalOptions>\r
+      <AdditionalDependencies>../../mDNSWindows/DLL/$(Platform)/$(Configuration)/dnssd.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>\r
+      <OutputFile>$(OutDir)dns-sd.exe</OutputFile>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+      <SubSystem>Console</SubSystem>\r
+      <OptimizeReferences>true</OptimizeReferences>\r
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+    </Link>\r
+    <Manifest>\r
+      <AdditionalManifestFiles>DNS-SD64.manifest;%(AdditionalManifestFiles)</AdditionalManifestFiles>\r
+    </Manifest>\r
+    <PostBuildEvent>\r
+      <Command>if not "%RC_XBS%" == "YES" goto END\r
+if not exist "$(DSTROOT)\WINDOWS\system32\$(Platform)"            mkdir "$(DSTROOT)\WINDOWS\system32\$(Platform)"\r
+if not exist "$(DSTROOT)\Program Files\Bonjour SDK\Samples\C"               mkdir "$(DSTROOT)\Program Files\Bonjour SDK\Samples\C"\r
+xcopy /I/Y "$(TargetPath)"                                                                           "$(DSTROOT)\WINDOWS\system32\$(Platform)"\r
+:END\r
 </Command>\r
     </PostBuildEvent>\r
   </ItemDefinitionGroup>\r
old mode 100755 (executable)
new mode 100644 (file)
index 810c90e..6f63148
@@ -1,6 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>\r
 <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
   <ItemGroup Label="ProjectConfigurations">\r
+    <ProjectConfiguration Include="Debug|ARM64">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Debug|Win32">\r
       <Configuration>Debug</Configuration>\r
       <Platform>Win32</Platform>\r
@@ -9,6 +13,10 @@
       <Configuration>Debug</Configuration>\r
       <Platform>x64</Platform>\r
     </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|ARM64">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Release|Win32">\r
       <Configuration>Release</Configuration>\r
       <Platform>Win32</Platform>\r
       <Configuration>Release</Configuration>\r
       <Platform>x64</Platform>\r
     </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Template|ARM64">\r
+      <Configuration>Template</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Template|Win32">\r
       <Configuration>Template</Configuration>\r
       <Platform>Win32</Platform>\r
   </ItemGroup>\r
   <PropertyGroup Label="Globals">\r
     <ProjectGuid>{BB8AC1B5-6587-4163-BDC6-788B157705CA}</ProjectGuid>\r
+    <WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>\r
   </PropertyGroup>\r
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|x64'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">\r
+    <ConfigurationType>DynamicLibrary</ConfigurationType>\r
+    <UseOfMfc>Static</UseOfMfc>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">\r
+    <ConfigurationType>DynamicLibrary</ConfigurationType>\r
+    <UseOfMfc>Static</UseOfMfc>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
   <ImportGroup Label="ExtensionSettings">\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Template|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
+  </ImportGroup>\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
+  </ImportGroup>\r
   <PropertyGroup Label="UserMacros" />\r
   <PropertyGroup>\r
     <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</LinkIncremental>\r
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>\r
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</LinkIncremental>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">\r
+    <OutDir>$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir>$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">\r
+    <OutDir>$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir>$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|x64'">\r
+    <OutDir>$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir>$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
   </PropertyGroup>\r
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
     <CustomBuildStep>\r
     <ClCompile>\r
       <Optimization>Disabled</Optimization>\r
       <AdditionalIncludeDirectories>..\..\mDNSShared;..\..\mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
-      <PreprocessorDefinitions>_USRDLL;WIN32;_DEBUG;_WINDOWS;DEBUG=1;ENABLE_DOT_LOCAL_NAMES;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <PreprocessorDefinitions>_USRDLL;WIN32;_DEBUG;_WINDOWS;DEBUG=1;ENABLE_DOT_LOCAL_NAMES;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
       <StringPooling>true</StringPooling>\r
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r
     <ClCompile>\r
       <Optimization>Disabled</Optimization>\r
       <AdditionalIncludeDirectories>..\..\mDNSShared;..\..\mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
-      <PreprocessorDefinitions>_USRDLL;WIN32;_DEBUG;_WINDOWS;DEBUG=1;ENABLE_DOT_LOCAL_NAMES;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <PreprocessorDefinitions>_USRDLL;WIN32;_DEBUG;_WINDOWS;DEBUG=1;ENABLE_DOT_LOCAL_NAMES;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
       <StringPooling>true</StringPooling>\r
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r
       <AdditionalManifestFiles>res\ExplorerPlugin64.manifest;%(AdditionalManifestFiles)</AdditionalManifestFiles>\r
     </Manifest>\r
   </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">\r
+    <CustomBuildStep>\r
+      <Message>\r
+      </Message>\r
+      <Command>\r
+      </Command>\r
+      <Outputs>%(Outputs)</Outputs>\r
+    </CustomBuildStep>\r
+    <Midl>\r
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <MkTypLibCompatible>true</MkTypLibCompatible>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <TypeLibraryName>$(OutDir)$(ProjectName).tlb</TypeLibraryName>\r
+    </Midl>\r
+    <ClCompile>\r
+      <Optimization>Disabled</Optimization>\r
+      <AdditionalIncludeDirectories>..\..\mDNSShared;..\..\mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>_USRDLL;WIN32;_DEBUG;_WINDOWS;DEBUG=1;ENABLE_DOT_LOCAL_NAMES;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <StringPooling>true</StringPooling>\r
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r
+      <BufferSecurityCheck>true</BufferSecurityCheck>\r
+      <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <PrecompiledHeaderOutputFile>\r
+      </PrecompiledHeaderOutputFile>\r
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>\r
+      <ObjectFileName>$(IntDir)</ObjectFileName>\r
+      <ProgramDataBaseFileName>$(IntDir)vc80.pdb</ProgramDataBaseFileName>\r
+      <BrowseInformation>true</BrowseInformation>\r
+      <WarningLevel>Level4</WarningLevel>\r
+      <TreatWarningAsError>false</TreatWarningAsError>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r
+      <CallingConvention>StdCall</CallingConvention>\r
+      <CompileAs>Default</CompileAs>\r
+    </ClCompile>\r
+    <ResourceCompile>\r
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <Culture>0x0409</Culture>\r
+      <AdditionalIncludeDirectories>../../mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ResourceCompile>\r
+    <Link>\r
+      <AdditionalOptions>/IGNORE:4089 /NXCOMPAT /DYNAMICBASE %(AdditionalOptions)</AdditionalOptions>\r
+      <AdditionalDependencies>../../mDNSWindows/DLLStub/$(Platform)/$(Configuration)/dnssdStatic.lib;uafxcwd.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>\r
+      <OutputFile>$(OutDir)ExplorerPlugin.dll</OutputFile>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <IgnoreSpecificDefaultLibraries>uafxcwd.lib;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>\r
+      <ModuleDefinitionFile>./$(ProjectName).def</ModuleDefinitionFile>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+      <ProgramDatabaseFile>$(OutDir)$(ProjectName).pdb</ProgramDatabaseFile>\r
+      <SubSystem>Windows</SubSystem>\r
+      <ImportLibrary>$(OutDir)$(ProjectName).lib</ImportLibrary>\r
+    </Link>\r
+    <Manifest>\r
+      <AdditionalManifestFiles>res\ExplorerPlugin64.manifest;%(AdditionalManifestFiles)</AdditionalManifestFiles>\r
+    </Manifest>\r
+  </ItemDefinitionGroup>\r
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
     <CustomBuildStep>\r
       <Message>\r
       <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>\r
       <OmitFramePointers>true</OmitFramePointers>\r
       <AdditionalIncludeDirectories>..\..\mDNSShared;..\..\mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
-      <PreprocessorDefinitions>_USRDLL;WIN32;NDEBUG;_WINDOWS;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <PreprocessorDefinitions>_USRDLL;WIN32;NDEBUG;_WINDOWS;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
       <StringPooling>true</StringPooling>\r
       <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r
       <BufferSecurityCheck>true</BufferSecurityCheck>\r
@@ -299,7 +424,7 @@ xcopy /I/Y "$(TargetPath)"
       <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>\r
       <OmitFramePointers>true</OmitFramePointers>\r
       <AdditionalIncludeDirectories>..\..\mDNSShared;..\..\mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
-      <PreprocessorDefinitions>_USRDLL;WIN32;NDEBUG;_WINDOWS;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <PreprocessorDefinitions>_USRDLL;WIN32;NDEBUG;_WINDOWS;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
       <StringPooling>true</StringPooling>\r
       <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r
       <BufferSecurityCheck>true</BufferSecurityCheck>\r
@@ -348,6 +473,74 @@ xcopy /I/Y "$(TargetPath)"
 if not exist "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)"   mkdir "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)"\r
 xcopy /I/Y "$(TargetPath)"                                                                  "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)"\r
 :END\r
+</Command>\r
+    </PostBuildEvent>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">\r
+    <CustomBuildStep>\r
+      <Message>\r
+      </Message>\r
+    </CustomBuildStep>\r
+    <Midl>\r
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <MkTypLibCompatible>true</MkTypLibCompatible>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <TypeLibraryName>$(OutDir)$(ProjectName).tlb</TypeLibraryName>\r
+    </Midl>\r
+    <ClCompile>\r
+      <Optimization>MaxSpeed</Optimization>\r
+      <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>\r
+      <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>\r
+      <OmitFramePointers>true</OmitFramePointers>\r
+      <AdditionalIncludeDirectories>..\..\mDNSShared;..\..\mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>_USRDLL;WIN32;NDEBUG;_WINDOWS;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <StringPooling>true</StringPooling>\r
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r
+      <BufferSecurityCheck>true</BufferSecurityCheck>\r
+      <FunctionLevelLinking>false</FunctionLevelLinking>\r
+      <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <PrecompiledHeaderOutputFile>\r
+      </PrecompiledHeaderOutputFile>\r
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>\r
+      <ObjectFileName>$(IntDir)</ObjectFileName>\r
+      <ProgramDataBaseFileName>$(IntDir)vc80.pdb</ProgramDataBaseFileName>\r
+      <BrowseInformation>true</BrowseInformation>\r
+      <WarningLevel>Level4</WarningLevel>\r
+      <TreatWarningAsError>false</TreatWarningAsError>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <CompileAs>Default</CompileAs>\r
+    </ClCompile>\r
+    <ResourceCompile>\r
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <Culture>0x0409</Culture>\r
+      <AdditionalIncludeDirectories>../../mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ResourceCompile>\r
+    <Link>\r
+      <AdditionalOptions>/IGNORE:4089 /NXCOMPAT /DYNAMICBASE %(AdditionalOptions)</AdditionalOptions>\r
+      <AdditionalDependencies>../../mDNSWindows/DLLStub/$(Platform)/$(Configuration)/dnssdStatic.lib;ws2_32.lib;uafxcw.lib;%(AdditionalDependencies)</AdditionalDependencies>\r
+      <OutputFile>$(OutDir)$(ProjectName).dll</OutputFile>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <IgnoreSpecificDefaultLibraries>uafxcw.lib;%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>\r
+      <ModuleDefinitionFile>./$(ProjectName).def</ModuleDefinitionFile>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+      <ProgramDatabaseFile>$(IntDir)$(ProjectName).pdb</ProgramDatabaseFile>\r
+      <SubSystem>Windows</SubSystem>\r
+      <OptimizeReferences>\r
+      </OptimizeReferences>\r
+      <EnableCOMDATFolding>\r
+      </EnableCOMDATFolding>\r
+      <ImportLibrary>$(IntDir)$(ProjectName).lib</ImportLibrary>\r
+    </Link>\r
+    <Manifest>\r
+      <AdditionalManifestFiles>res\ExplorerPlugin64.manifest;%(AdditionalManifestFiles)</AdditionalManifestFiles>\r
+    </Manifest>\r
+    <PostBuildEvent>\r
+      <Command>if not "%RC_XBS%" == "YES" goto END\r
+if not exist "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)"   mkdir "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)"\r
+xcopy /I/Y "$(TargetPath)"                                                                  "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)"\r
+:END\r
 </Command>\r
     </PostBuildEvent>\r
   </ItemDefinitionGroup>\r
old mode 100755 (executable)
new mode 100644 (file)
index fb96fd3..c812c5a
@@ -1,6 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>\r
 <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
   <ItemGroup Label="ProjectConfigurations">\r
+    <ProjectConfiguration Include="Debug|ARM64">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Debug|Win32">\r
       <Configuration>Debug</Configuration>\r
       <Platform>Win32</Platform>\r
@@ -9,6 +13,10 @@
       <Configuration>Debug</Configuration>\r
       <Platform>x64</Platform>\r
     </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|ARM64">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Release|Win32">\r
       <Configuration>Release</Configuration>\r
       <Platform>Win32</Platform>\r
       <Configuration>Release</Configuration>\r
       <Platform>x64</Platform>\r
     </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Template|ARM64">\r
+      <Configuration>Template</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Template|Win32">\r
       <Configuration>Template</Configuration>\r
       <Platform>Win32</Platform>\r
   <PropertyGroup Label="Globals">\r
     <ProjectGuid>{1643427B-F226-4AD6-B413-97DA64D5C6B4}</ProjectGuid>\r
     <RootNamespace>ExplorerPluginLocRes</RootNamespace>\r
+    <WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>\r
   </PropertyGroup>\r
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|x64'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">\r
+    <ConfigurationType>DynamicLibrary</ConfigurationType>\r
+    <UseOfMfc>Static</UseOfMfc>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">\r
+    <ConfigurationType>DynamicLibrary</ConfigurationType>\r
+    <UseOfMfc>Static</UseOfMfc>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
   <ImportGroup Label="ExtensionSettings">\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Template|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
+  </ImportGroup>\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
+  </ImportGroup>\r
   <PropertyGroup Label="UserMacros" />\r
   <PropertyGroup>\r
     <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</LinkIncremental>\r
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>\r
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</LinkIncremental>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">ExplorerPluginLocalized</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.dll</TargetExt>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">ExplorerPluginLocalized</TargetName>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">ExplorerPluginLocalized</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">.dll</TargetExt>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">ExplorerPluginLocalized</TargetName>\r
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">ExplorerPluginLocalized</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.dll</TargetExt>\r
+    <TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">.dll</TargetExt>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">ExplorerPluginLocalized</TargetName>\r
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">ExplorerPluginLocalized</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.dll</TargetExt>\r
+    <TargetExt Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">.dll</TargetExt>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Template|x64'">ExplorerPluginLocalized</TargetName>\r
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">ExplorerPluginLocalized</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Template|x64'">.dll</TargetExt>\r
+    <TargetExt Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">.dll</TargetExt>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Template|x64'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
   </PropertyGroup>\r
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
     <Midl>\r
@@ -223,6 +285,62 @@ if not exist $(OutDir)ExplorerPlugin.Resources\en.lproj mkdir $(OutDir)ExplorerP
       <TargetMachine>MachineX64</TargetMachine>\r
     </Link>\r
   </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">\r
+    <Midl>\r
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <MkTypLibCompatible>true</MkTypLibCompatible>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <TypeLibraryName>$(OutDir)$(ProjectName).tlb</TypeLibraryName>\r
+    </Midl>\r
+    <ClCompile>\r
+      <Optimization>Disabled</Optimization>\r
+      <AdditionalIncludeDirectories>..\..\mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;DEBUG=1;ENABLE_DOT_LOCAL_NAMES;WINVER=0x0400;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <StringPooling>true</StringPooling>\r
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r
+      <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <PrecompiledHeaderOutputFile>\r
+      </PrecompiledHeaderOutputFile>\r
+      <AssemblerListingLocation>.\Debug/</AssemblerListingLocation>\r
+      <ObjectFileName>.\Debug/</ObjectFileName>\r
+      <ProgramDataBaseFileName>.\Debug/</ProgramDataBaseFileName>\r
+      <BrowseInformation>true</BrowseInformation>\r
+      <WarningLevel>Level4</WarningLevel>\r
+      <TreatWarningAsError>false</TreatWarningAsError>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r
+      <CallingConvention>StdCall</CallingConvention>\r
+      <CompileAs>Default</CompileAs>\r
+    </ClCompile>\r
+    <ResourceCompile>\r
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <Culture>0x0409</Culture>\r
+      <AdditionalIncludeDirectories>../../mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ResourceCompile>\r
+    <PreLinkEvent>\r
+      <Message>Building Output Directories</Message>\r
+      <Command>if not exist $(OutDir)ExplorerPlugin.Resources mkdir $(OutDir)ExplorerPlugin.Resources\r
+if not exist $(OutDir)ExplorerPlugin.Resources\en.lproj mkdir $(OutDir)ExplorerPlugin.Resources\en.lproj\r
+</Command>\r
+    </PreLinkEvent>\r
+    <Link>\r
+      <AdditionalOptions>/MACHINE:I386 /IGNORE:4089  %(AdditionalOptions)</AdditionalOptions>\r
+      <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>\r
+      <OutputFile>$(OutDir)ExplorerPluginLocalized.dll</OutputFile>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>\r
+      <ModuleDefinitionFile>\r
+      </ModuleDefinitionFile>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+      <ProgramDatabaseFile>$(OutDir)$(ProjectName).pdb</ProgramDatabaseFile>\r
+      <SubSystem>Windows</SubSystem>\r
+      <NoEntryPoint>true</NoEntryPoint>\r
+      <ImportLibrary>$(OutDir)$(ProjectName).lib</ImportLibrary>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
     <Midl>\r
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
@@ -361,6 +479,75 @@ if not exist $(OutDir)ExplorerPlugin.Resources\en.lproj mkdir $(OutDir)ExplorerP
 if not exist "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources\en.lproj"   mkdir "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources\en.lproj"\r
 xcopy /I/Y "$(TargetPath)"                                                                                                                          "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources\en.lproj"\r
 :END\r
+</Command>\r
+    </PostBuildEvent>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">\r
+    <Midl>\r
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <MkTypLibCompatible>true</MkTypLibCompatible>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <TypeLibraryName>$(OutDir)$(ProjectName).tlb</TypeLibraryName>\r
+    </Midl>\r
+    <ClCompile>\r
+      <Optimization>MaxSpeed</Optimization>\r
+      <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>\r
+      <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>\r
+      <OmitFramePointers>true</OmitFramePointers>\r
+      <AdditionalIncludeDirectories>..\..\mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;WINVER=0x0400;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <StringPooling>true</StringPooling>\r
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r
+      <BufferSecurityCheck>false</BufferSecurityCheck>\r
+      <FunctionLevelLinking>false</FunctionLevelLinking>\r
+      <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <PrecompiledHeaderOutputFile>\r
+      </PrecompiledHeaderOutputFile>\r
+      <AssemblerListingLocation>.\Release/</AssemblerListingLocation>\r
+      <ObjectFileName>.\Release/</ObjectFileName>\r
+      <ProgramDataBaseFileName>.\Release/</ProgramDataBaseFileName>\r
+      <BrowseInformation>true</BrowseInformation>\r
+      <WarningLevel>Level4</WarningLevel>\r
+      <TreatWarningAsError>false</TreatWarningAsError>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <CompileAs>Default</CompileAs>\r
+    </ClCompile>\r
+    <ResourceCompile>\r
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <Culture>0x0409</Culture>\r
+      <AdditionalIncludeDirectories>../../mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ResourceCompile>\r
+    <PreLinkEvent>\r
+      <Message>Building Output Directories</Message>\r
+      <Command>if not exist $(OutDir)ExplorerPlugin.Resources mkdir $(OutDir)ExplorerPlugin.Resources\r
+if not exist $(OutDir)ExplorerPlugin.Resources\en.lproj mkdir $(OutDir)ExplorerPlugin.Resources\en.lproj\r
+</Command>\r
+    </PreLinkEvent>\r
+    <Link>\r
+      <AdditionalOptions>/MACHINE:I386 /IGNORE:4089  %(AdditionalOptions)</AdditionalOptions>\r
+      <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>\r
+      <OutputFile>$(OutDir)ExplorerPluginLocalized.dll</OutputFile>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>\r
+      <ModuleDefinitionFile>\r
+      </ModuleDefinitionFile>\r
+      <ProgramDatabaseFile>\r
+      </ProgramDatabaseFile>\r
+      <SubSystem>Windows</SubSystem>\r
+      <OptimizeReferences>\r
+      </OptimizeReferences>\r
+      <EnableCOMDATFolding>\r
+      </EnableCOMDATFolding>\r
+      <NoEntryPoint>true</NoEntryPoint>\r
+      <ImportLibrary>$(IntDir)$(ProjectName).lib</ImportLibrary>\r
+    </Link>\r
+    <PostBuildEvent>\r
+      <Command>if not "%RC_XBS%" == "YES" goto END\r
+if not exist "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources\en.lproj"   mkdir "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources\en.lproj"\r
+xcopy /I/Y "$(TargetPath)"                                                                                                                          "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources\en.lproj"\r
+:END\r
 </Command>\r
     </PostBuildEvent>\r
   </ItemDefinitionGroup>\r
@@ -374,6 +561,11 @@ xcopy /I/Y "$(TargetPath)"
       <OutputFile>$(OutDir)ExplorerPluginLocalized.dll</OutputFile>\r
     </Link>\r
   </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">\r
+    <Link>\r
+      <OutputFile>$(OutDir)ExplorerPluginLocalized.dll</OutputFile>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
   <ItemGroup>\r
     <ClInclude Include="resource_loc_res.h" />\r
   </ItemGroup>\r
old mode 100755 (executable)
new mode 100644 (file)
index 4fb6490..690d252
@@ -1,6 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>\r
 <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
   <ItemGroup Label="ProjectConfigurations">\r
+    <ProjectConfiguration Include="Debug|ARM64">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Debug|Win32">\r
       <Configuration>Debug</Configuration>\r
       <Platform>Win32</Platform>\r
@@ -9,6 +13,10 @@
       <Configuration>Debug</Configuration>\r
       <Platform>x64</Platform>\r
     </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|ARM64">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Release|Win32">\r
       <Configuration>Release</Configuration>\r
       <Platform>Win32</Platform>\r
       <Configuration>Release</Configuration>\r
       <Platform>x64</Platform>\r
     </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Template|ARM64">\r
+      <Configuration>Template</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Template|Win32">\r
       <Configuration>Template</Configuration>\r
       <Platform>Win32</Platform>\r
   </ItemGroup>\r
   <PropertyGroup Label="Globals">\r
     <ProjectGuid>{871B1492-B4A4-4B57-9237-FA798484D7D7}</ProjectGuid>\r
+    <WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>\r
   </PropertyGroup>\r
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|x64'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">\r
+    <ConfigurationType>DynamicLibrary</ConfigurationType>\r
+    <UseOfMfc>Static</UseOfMfc>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">\r
+    <ConfigurationType>DynamicLibrary</ConfigurationType>\r
+    <UseOfMfc>Static</UseOfMfc>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
   <ImportGroup Label="ExtensionSettings">\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Template|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
+  </ImportGroup>\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
+  </ImportGroup>\r
   <PropertyGroup Label="UserMacros" />\r
   <PropertyGroup>\r
     <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</LinkIncremental>\r
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">false</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>\r
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</LinkIncremental>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">ExplorerPluginResources</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.dll</TargetExt>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">ExplorerPluginResources</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.dll</TargetExt>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">ExplorerPluginResources</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">.dll</TargetExt>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">ExplorerPluginResources</TargetName>\r
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">ExplorerPluginResources</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.dll</TargetExt>\r
+    <TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">.dll</TargetExt>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">ExplorerPluginResources</TargetName>\r
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">ExplorerPluginResources</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.dll</TargetExt>\r
+    <TargetExt Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">.dll</TargetExt>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Template|x64'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">$(Platform)\$(Configuration)\ExplorerPlugin.Resources\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Template|x64'">ExplorerPluginResources</TargetName>\r
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">ExplorerPluginResources</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Template|x64'">.dll</TargetExt>\r
+    <TargetExt Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">.dll</TargetExt>\r
   </PropertyGroup>\r
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
     <Midl>\r
       <TargetMachine>MachineX64</TargetMachine>\r
     </Link>\r
   </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">\r
+    <Midl>\r
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <MkTypLibCompatible>true</MkTypLibCompatible>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <TypeLibraryName>$(OutDir)$(ProjectName).tlb</TypeLibraryName>\r
+    </Midl>\r
+    <ClCompile>\r
+      <Optimization>Disabled</Optimization>\r
+      <AdditionalIncludeDirectories>..\..\mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;DEBUG=1;ENABLE_DOT_LOCAL_NAMES;WINVER=0x0400;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <StringPooling>true</StringPooling>\r
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r
+      <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <PrecompiledHeaderOutputFile>\r
+      </PrecompiledHeaderOutputFile>\r
+      <AssemblerListingLocation>.\Debug/</AssemblerListingLocation>\r
+      <ObjectFileName>.\Debug/</ObjectFileName>\r
+      <ProgramDataBaseFileName>.\Debug/</ProgramDataBaseFileName>\r
+      <BrowseInformation>true</BrowseInformation>\r
+      <WarningLevel>Level4</WarningLevel>\r
+      <TreatWarningAsError>false</TreatWarningAsError>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r
+      <CallingConvention>StdCall</CallingConvention>\r
+      <CompileAs>Default</CompileAs>\r
+    </ClCompile>\r
+    <ResourceCompile>\r
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <Culture>0x0409</Culture>\r
+      <AdditionalIncludeDirectories>../../mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ResourceCompile>\r
+    <PreLinkEvent>\r
+      <Message>Building Output Directories</Message>\r
+      <Command>if not exist Debug\ExplorerPlugin.Resources mkdir Debug\ExplorerPlugin.Resources</Command>\r
+    </PreLinkEvent>\r
+    <Link>\r
+      <AdditionalOptions>/MACHINE:I386 /IGNORE:4089  %(AdditionalOptions)</AdditionalOptions>\r
+      <OutputFile>$(OutDir)ExplorerPluginResources.dll</OutputFile>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>\r
+      <ModuleDefinitionFile>\r
+      </ModuleDefinitionFile>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+      <ProgramDatabaseFile>$(OutDir)$(ProjectName).pdb</ProgramDatabaseFile>\r
+      <SubSystem>Windows</SubSystem>\r
+      <NoEntryPoint>true</NoEntryPoint>\r
+      <ImportLibrary>$(OutDir)$(ProjectName).lib</ImportLibrary>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
     <Midl>\r
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
@@ -352,6 +467,74 @@ if not exist "Release\ExplorerPlugin.Resources" mkdir "Release\ExplorerPlugin.Re
 if not exist "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources"   mkdir "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources"\r
 xcopy /I/Y "$(TargetPath)"                                                                                                            "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources"\r
 :END\r
+</Command>\r
+    </PostBuildEvent>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">\r
+    <Midl>\r
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <MkTypLibCompatible>true</MkTypLibCompatible>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <TypeLibraryName>$(OutDir)$(ProjectName).tlb</TypeLibraryName>\r
+    </Midl>\r
+    <ClCompile>\r
+      <Optimization>MaxSpeed</Optimization>\r
+      <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>\r
+      <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>\r
+      <OmitFramePointers>true</OmitFramePointers>\r
+      <AdditionalIncludeDirectories>..\..\mDNSShared;..\..\mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;WINVER=0x0400;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <StringPooling>true</StringPooling>\r
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r
+      <BufferSecurityCheck>false</BufferSecurityCheck>\r
+      <FunctionLevelLinking>false</FunctionLevelLinking>\r
+      <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <PrecompiledHeaderOutputFile>\r
+      </PrecompiledHeaderOutputFile>\r
+      <AssemblerListingLocation>.\Release/</AssemblerListingLocation>\r
+      <ObjectFileName>.\Release/</ObjectFileName>\r
+      <ProgramDataBaseFileName>.\Release/</ProgramDataBaseFileName>\r
+      <BrowseInformation>true</BrowseInformation>\r
+      <WarningLevel>Level4</WarningLevel>\r
+      <TreatWarningAsError>false</TreatWarningAsError>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <CompileAs>Default</CompileAs>\r
+    </ClCompile>\r
+    <ResourceCompile>\r
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <Culture>0x0409</Culture>\r
+      <AdditionalIncludeDirectories>../../mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ResourceCompile>\r
+    <PreLinkEvent>\r
+      <Message>Building Output Directories</Message>\r
+      <Command>if not exist Release mkdir Release\r
+if not exist "Release\ExplorerPlugin.Resources" mkdir "Release\ExplorerPlugin.Resources"\r
+</Command>\r
+    </PreLinkEvent>\r
+    <Link>\r
+      <AdditionalOptions>/MACHINE:I386 /IGNORE:4089  %(AdditionalOptions)</AdditionalOptions>\r
+      <OutputFile>$(OutDir)ExplorerPluginResources.dll</OutputFile>\r
+      <SuppressStartupBanner>true</SuppressStartupBanner>\r
+      <IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries)</IgnoreSpecificDefaultLibraries>\r
+      <ModuleDefinitionFile>\r
+      </ModuleDefinitionFile>\r
+      <ProgramDatabaseFile>\r
+      </ProgramDatabaseFile>\r
+      <SubSystem>Windows</SubSystem>\r
+      <OptimizeReferences>\r
+      </OptimizeReferences>\r
+      <EnableCOMDATFolding>\r
+      </EnableCOMDATFolding>\r
+      <NoEntryPoint>true</NoEntryPoint>\r
+      <ImportLibrary>$(IntDir)$(ProjectName).lib</ImportLibrary>\r
+    </Link>\r
+    <PostBuildEvent>\r
+      <Command>if not "%RC_XBS%" == "YES" goto END\r
+if not exist "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources"   mkdir "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources"\r
+xcopy /I/Y "$(TargetPath)"                                                                                                            "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources"\r
+:END\r
 </Command>\r
     </PostBuildEvent>\r
   </ItemDefinitionGroup>\r
@@ -365,6 +548,11 @@ xcopy /I/Y "$(TargetPath)"
       <OutputFile>$(OutDir)ExplorerPluginResources.dll</OutputFile>\r
     </Link>\r
   </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">\r
+    <Link>\r
+      <OutputFile>$(OutDir)ExplorerPluginResources.dll</OutputFile>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
   <ItemGroup>\r
     <ClInclude Include="resource_res.h" />\r
   </ItemGroup>\r
old mode 100755 (executable)
new mode 100644 (file)
index 563597e..809f78c
@@ -1,6 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>\r
 <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
   <ItemGroup Label="ProjectConfigurations">\r
+    <ProjectConfiguration Include="Debug|ARM64">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Debug|Win32">\r
       <Configuration>Debug</Configuration>\r
       <Platform>Win32</Platform>\r
@@ -9,6 +13,10 @@
       <Configuration>Debug</Configuration>\r
       <Platform>x64</Platform>\r
     </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|ARM64">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Release|Win32">\r
       <Configuration>Release</Configuration>\r
       <Platform>Win32</Platform>\r
       <Configuration>Release</Configuration>\r
       <Platform>x64</Platform>\r
     </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Template|ARM64">\r
+      <Configuration>Template</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Template|Win32">\r
       <Configuration>Template</Configuration>\r
       <Platform>Win32</Platform>\r
   <PropertyGroup Label="Globals">\r
     <ProjectGuid>{B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}</ProjectGuid>\r
     <Keyword>MFCProj</Keyword>\r
+    <WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>\r
   </PropertyGroup>\r
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|x64'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseOfMfc>Static</UseOfMfc>\r
+    <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <UseOfMfc>Static</UseOfMfc>\r
+    <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
   <ImportGroup Label="ExtensionSettings">\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Template|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
+  </ImportGroup>\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
+  </ImportGroup>\r
   <PropertyGroup Label="UserMacros" />\r
   <PropertyGroup>\r
     <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</LinkIncremental>\r
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>\r
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</LinkIncremental>\r
     <IncludePath Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IncludePath)</IncludePath>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">PrinterWizard</TargetName>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">PrinterWizard</TargetName>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">PrinterWizard</TargetName>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">PrinterWizard</TargetName>\r
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">PrinterWizard</TargetName>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">PrinterWizard</TargetName>\r
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">PrinterWizard</TargetName>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Template|x64'">PrinterWizard</TargetName>\r
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">PrinterWizard</TargetName>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">\r
+    <OutDir>$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir>$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">\r
+    <OutDir>$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir>$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|x64'">\r
+    <OutDir>$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir>$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
   </PropertyGroup>\r
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
     <Midl>\r
     <ClCompile>\r
       <Optimization>Disabled</Optimization>\r
       <AdditionalIncludeDirectories>.;../../mDNSWindows;../../mDNSShared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
-      <PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUGS;DEBUG=1;WINVER=0x0501;_WIN32_WINNT=0x0501;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUGS;DEBUG=1;WINVER=0x0501;_WIN32_WINNT=0x0501;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
       <StringPooling>true</StringPooling>\r
-      <MinimalRebuild>true</MinimalRebuild>\r
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r
       <BufferSecurityCheck>true</BufferSecurityCheck>\r
     <ClCompile>\r
       <Optimization>Disabled</Optimization>\r
       <AdditionalIncludeDirectories>.;../../mDNSWindows;../../mDNSShared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
-      <PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUGS;DEBUG=1;WINVER=0x0501;_WIN32_WINNT=0x0501;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUGS;DEBUG=1;WINVER=0x0501;_WIN32_WINNT=0x0501;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
       <StringPooling>true</StringPooling>\r
-      <MinimalRebuild>true</MinimalRebuild>\r
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r
       <BufferSecurityCheck>true</BufferSecurityCheck>\r
       <AdditionalManifestFiles>res\PrinterSetupWizard64.manifest;%(AdditionalManifestFiles)</AdditionalManifestFiles>\r
     </Manifest>\r
   </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">\r
+    <Midl>\r
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <MkTypLibCompatible>false</MkTypLibCompatible>\r
+    </Midl>\r
+    <ClCompile>\r
+      <Optimization>Disabled</Optimization>\r
+      <AdditionalIncludeDirectories>.;../../mDNSWindows;../../mDNSShared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUGS;DEBUG=1;WINVER=0x0501;_WIN32_WINNT=0x0501;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <StringPooling>true</StringPooling>\r
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r
+      <BufferSecurityCheck>true</BufferSecurityCheck>\r
+      <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>\r
+      <WarningLevel>Level4</WarningLevel>\r
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r
+      <CallingConvention>Cdecl</CallingConvention>\r
+      <DisableSpecificWarnings>4702;%(DisableSpecificWarnings)</DisableSpecificWarnings>\r
+    </ClCompile>\r
+    <ResourceCompile>\r
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <Culture>0x0409</Culture>\r
+      <AdditionalIncludeDirectories>$(IntDir);../../mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ResourceCompile>\r
+    <Link>\r
+      <AdditionalOptions>/NXCOMPAT /DYNAMICBASE %(AdditionalOptions)</AdditionalOptions>\r
+      <AdditionalDependencies>../../mDNSWindows/DLLStub/$(Platform)/$(Configuration)/dnssdStatic.lib;ws2_32.lib;iphlpapi.lib;winspool.lib;setupapi.lib;%(AdditionalDependencies)</AdditionalDependencies>\r
+      <OutputFile>$(OutDir)PrinterWizard.exe</OutputFile>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+      <ProgramDatabaseFile>$(IntDir)$(ProjectName).pdb</ProgramDatabaseFile>\r
+      <SubSystem>Windows</SubSystem>\r
+      <EntryPointSymbol>wWinMainCRTStartup</EntryPointSymbol>\r
+    </Link>\r
+    <Manifest>\r
+      <AdditionalManifestFiles>res\PrinterSetupWizard64.manifest;%(AdditionalManifestFiles)</AdditionalManifestFiles>\r
+    </Manifest>\r
+  </ItemDefinitionGroup>\r
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
     <Midl>\r
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
       <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>\r
       <OmitFramePointers>true</OmitFramePointers>\r
       <AdditionalIncludeDirectories>.;../../mDNSWindows;../../mDNSShared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
-      <PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;WINVER=0x0501;_WIN32_WINNT=0x0501;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;WINVER=0x0501;_WIN32_WINNT=0x0501;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
       <StringPooling>true</StringPooling>\r
-      <MinimalRebuild>false</MinimalRebuild>\r
       <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r
       <FunctionLevelLinking>true</FunctionLevelLinking>\r
       <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>\r
@@ -248,9 +354,8 @@ xcopy /I/Y "$(TargetPath)"
       <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>\r
       <OmitFramePointers>true</OmitFramePointers>\r
       <AdditionalIncludeDirectories>.;../../mDNSWindows;../../mDNSShared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
-      <PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;WINVER=0x0501;_WIN32_WINNT=0x0501;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;WINVER=0x0501;_WIN32_WINNT=0x0501;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
       <StringPooling>true</StringPooling>\r
-      <MinimalRebuild>false</MinimalRebuild>\r
       <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r
       <FunctionLevelLinking>true</FunctionLevelLinking>\r
       <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>\r
@@ -288,6 +393,57 @@ xcopy /I/Y "$(TargetPath)"
 if not exist "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)"   mkdir "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)"\r
 xcopy /I/Y "$(TargetPath)"                                                                  "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)"\r
 :END\r
+</Command>\r
+    </PostBuildEvent>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">\r
+    <Midl>\r
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <MkTypLibCompatible>false</MkTypLibCompatible>\r
+    </Midl>\r
+    <ClCompile>\r
+      <Optimization>MaxSpeed</Optimization>\r
+      <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>\r
+      <OmitFramePointers>true</OmitFramePointers>\r
+      <AdditionalIncludeDirectories>.;../../mDNSWindows;../../mDNSShared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;WINVER=0x0501;_WIN32_WINNT=0x0501;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <StringPooling>true</StringPooling>\r
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r
+      <FunctionLevelLinking>true</FunctionLevelLinking>\r
+      <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>\r
+      <WarningLevel>Level4</WarningLevel>\r
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r
+      <DisableSpecificWarnings>4702;%(DisableSpecificWarnings)</DisableSpecificWarnings>\r
+    </ClCompile>\r
+    <ResourceCompile>\r
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <Culture>0x0409</Culture>\r
+      <AdditionalIncludeDirectories>$(IntDir);../../mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ResourceCompile>\r
+    <Link>\r
+      <AdditionalOptions>/NXCOMPAT /DYNAMICBASE %(AdditionalOptions)</AdditionalOptions>\r
+      <AdditionalDependencies>../../mDNSWindows/DLLStub/$(Platform)/$(Configuration)/dnssdStatic.lib;ws2_32.lib;iphlpapi.lib;winspool.lib;setupapi.lib;%(AdditionalDependencies)</AdditionalDependencies>\r
+      <OutputFile>$(OutDir)PrinterWizard.exe</OutputFile>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+      <ProgramDatabaseFile>$(IntDir)$(ProjectName).pdb</ProgramDatabaseFile>\r
+      <SubSystem>Windows</SubSystem>\r
+      <OptimizeReferences>\r
+      </OptimizeReferences>\r
+      <EnableCOMDATFolding>\r
+      </EnableCOMDATFolding>\r
+      <EntryPointSymbol>wWinMainCRTStartup</EntryPointSymbol>\r
+    </Link>\r
+    <Manifest>\r
+      <AdditionalManifestFiles>res\PrinterSetupWizard64.manifest;%(AdditionalManifestFiles)</AdditionalManifestFiles>\r
+    </Manifest>\r
+    <PostBuildEvent>\r
+      <Command>if not "%RC_XBS%" == "YES" goto END\r
+if not exist "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)"   mkdir "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)"\r
+xcopy /I/Y "$(TargetPath)"                                                                  "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)"\r
+:END\r
 </Command>\r
     </PostBuildEvent>\r
   </ItemDefinitionGroup>\r
@@ -304,6 +460,7 @@ xcopy /I/Y "$(TargetPath)"
     <ClCompile Include="..\..\mDNSWindows\loclibrary.c">\r
       <DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">4201;%(DisableSpecificWarnings)</DisableSpecificWarnings>\r
       <DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">4201;%(DisableSpecificWarnings)</DisableSpecificWarnings>\r
+      <DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">4201;%(DisableSpecificWarnings)</DisableSpecificWarnings>\r
     </ClCompile>\r
     <ClCompile Include="Logger.cpp" />\r
     <ClCompile Include="..\..\mDNSWindows\WinServices.cpp" />\r
@@ -341,8 +498,10 @@ xcopy /I/Y "$(TargetPath)"
     <CustomBuildStep Include="res\PrinterSetupWizard.manifest">\r
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>\r
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>\r
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</ExcludedFromBuild>\r
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>\r
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>\r
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>\r
     </CustomBuildStep>\r
   </ItemGroup>\r
   <ItemGroup>\r
old mode 100755 (executable)
new mode 100644 (file)
index 86affb8..00b7e7a
@@ -1,6 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>\r
 <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
   <ItemGroup Label="ProjectConfigurations">\r
+    <ProjectConfiguration Include="Debug|ARM64">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Debug|Win32">\r
       <Configuration>Debug</Configuration>\r
       <Platform>Win32</Platform>\r
@@ -9,6 +13,10 @@
       <Configuration>Debug</Configuration>\r
       <Platform>x64</Platform>\r
     </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|ARM64">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Release|Win32">\r
       <Configuration>Release</Configuration>\r
       <Platform>Win32</Platform>\r
       <Configuration>Release</Configuration>\r
       <Platform>x64</Platform>\r
     </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Template|ARM64">\r
+      <Configuration>Template</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Template|Win32">\r
       <Configuration>Template</Configuration>\r
       <Platform>Win32</Platform>\r
   <PropertyGroup Label="Globals">\r
     <ProjectGuid>{967F5375-0176-43D3-ADA3-22EE25551C37}</ProjectGuid>\r
     <Keyword>MFCProj</Keyword>\r
+    <WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>\r
   </PropertyGroup>\r
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|x64'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">\r
+    <ConfigurationType>DynamicLibrary</ConfigurationType>\r
+    <UseOfMfc>Static</UseOfMfc>\r
+    <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">\r
+    <ConfigurationType>DynamicLibrary</ConfigurationType>\r
+    <UseOfMfc>Static</UseOfMfc>\r
+    <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
   <ImportGroup Label="ExtensionSettings">\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Template|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
+  </ImportGroup>\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
+  </ImportGroup>\r
   <PropertyGroup Label="UserMacros" />\r
   <PropertyGroup>\r
     <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</LinkIncremental>\r
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>\r
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">$(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Template|x64'">$(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">$(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">PrinterWizardLocalized</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.dll</TargetExt>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">PrinterWizardLocalized</TargetName>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">PrinterWizardLocalized</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">.dll</TargetExt>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">PrinterWizardLocalized</TargetName>\r
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">PrinterWizardLocalized</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.dll</TargetExt>\r
+    <TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">.dll</TargetExt>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">PrinterWizardLocalized</TargetName>\r
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">PrinterWizardLocalized</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.dll</TargetExt>\r
+    <TargetExt Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">.dll</TargetExt>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Template|x64'">PrinterWizardLocalized</TargetName>\r
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">PrinterWizardLocalized</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Template|x64'">.dll</TargetExt>\r
+    <TargetExt Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">.dll</TargetExt>\r
   </PropertyGroup>\r
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
     <Midl>\r
@@ -197,6 +259,49 @@ if not exist $(OutDir)PrinterWizard.Resources\en.lproj mkdir $(OutDir)PrinterWiz
       <TargetMachine>MachineX64</TargetMachine>\r
     </Link>\r
   </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">\r
+    <Midl>\r
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <MkTypLibCompatible>false</MkTypLibCompatible>\r
+    </Midl>\r
+    <ClCompile>\r
+      <Optimization>Disabled</Optimization>\r
+      <AdditionalIncludeDirectories>..\..\mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUGS;DEBUG=1;WINVER=0x0400;UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <StringPooling>true</StringPooling>\r
+      <MinimalRebuild>true</MinimalRebuild>\r
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r
+      <BufferSecurityCheck>true</BufferSecurityCheck>\r
+      <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <WarningLevel>Level4</WarningLevel>\r
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r
+      <CallingConvention>Cdecl</CallingConvention>\r
+      <DisableSpecificWarnings>4702;%(DisableSpecificWarnings)</DisableSpecificWarnings>\r
+    </ClCompile>\r
+    <ResourceCompile>\r
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <Culture>0x0409</Culture>\r
+      <AdditionalIncludeDirectories>$(IntDir);../../mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ResourceCompile>\r
+    <PreLinkEvent>\r
+      <Message>Building Output Directories</Message>\r
+      <Command>if not exist $(OutDir)PrinterWizard.Resources mkdir $(OutDir)PrinterWizard.Resources\r
+if not exist $(OutDir)PrinterWizard.Resources\en.lproj mkdir $(OutDir)PrinterWizard.Resources\en.lproj\r
+</Command>\r
+    </PreLinkEvent>\r
+    <Link>\r
+      <OutputFile>$(OutDir)PrinterWizardLocalized.dll</OutputFile>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+      <SubSystem>Windows</SubSystem>\r
+      <EntryPointSymbol>\r
+      </EntryPointSymbol>\r
+      <NoEntryPoint>true</NoEntryPoint>\r
+      <ImportLibrary>$(OutDir)Localized.lib</ImportLibrary>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
     <Midl>\r
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
@@ -305,6 +410,60 @@ if not exist $(OutDir)PrinterWizard.Resources\en.lproj mkdir $(OutDir)PrinterWiz
 if not exist "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources\en.lproj"   mkdir "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources\en.lproj"\r
 xcopy /I/Y "$(TargetPath)"                                                                                                                         "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources\en.lproj"\r
 :END\r
+</Command>\r
+    </PostBuildEvent>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">\r
+    <Midl>\r
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <MkTypLibCompatible>false</MkTypLibCompatible>\r
+    </Midl>\r
+    <ClCompile>\r
+      <Optimization>MaxSpeed</Optimization>\r
+      <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>\r
+      <OmitFramePointers>true</OmitFramePointers>\r
+      <AdditionalIncludeDirectories>..\..\mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;WINVER=0x0400;UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <StringPooling>true</StringPooling>\r
+      <MinimalRebuild>false</MinimalRebuild>\r
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r
+      <FunctionLevelLinking>true</FunctionLevelLinking>\r
+      <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <WarningLevel>Level4</WarningLevel>\r
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r
+      <DisableSpecificWarnings>4702;%(DisableSpecificWarnings)</DisableSpecificWarnings>\r
+    </ClCompile>\r
+    <ResourceCompile>\r
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <Culture>0x0409</Culture>\r
+      <AdditionalIncludeDirectories>$(IntDir);../../mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ResourceCompile>\r
+    <PreLinkEvent>\r
+      <Message>Building Output Directories</Message>\r
+      <Command>if not exist $(OutDir)PrinterWizard.Resources mkdir $(OutDir)PrinterWizard.Resources\r
+if not exist $(OutDir)PrinterWizard.Resources\en.lproj mkdir $(OutDir)PrinterWizard.Resources\en.lproj\r
+</Command>\r
+    </PreLinkEvent>\r
+    <Link>\r
+      <OutputFile>$(OutDir)PrinterWizardLocalized.dll</OutputFile>\r
+      <GenerateDebugInformation>false</GenerateDebugInformation>\r
+      <SubSystem>Windows</SubSystem>\r
+      <OptimizeReferences>\r
+      </OptimizeReferences>\r
+      <EnableCOMDATFolding>\r
+      </EnableCOMDATFolding>\r
+      <EntryPointSymbol>\r
+      </EntryPointSymbol>\r
+      <NoEntryPoint>true</NoEntryPoint>\r
+      <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>\r
+    </Link>\r
+    <PostBuildEvent>\r
+      <Command>if not "%RC_XBS%" == "YES" goto END\r
+if not exist "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources\en.lproj"   mkdir "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources\en.lproj"\r
+xcopy /I/Y "$(TargetPath)"                                                                                                                         "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources\en.lproj"\r
+:END\r
 </Command>\r
     </PostBuildEvent>\r
   </ItemDefinitionGroup>\r
@@ -318,6 +477,11 @@ xcopy /I/Y "$(TargetPath)"
       <OutputFile>$(OutDir)PrinterWizardLocalized.dll</OutputFile>\r
     </Link>\r
   </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">\r
+    <Link>\r
+      <OutputFile>$(OutDir)PrinterWizardLocalized.dll</OutputFile>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
   <ItemGroup>\r
     <ClInclude Include="resource_loc_dll.h" />\r
   </ItemGroup>\r
old mode 100755 (executable)
new mode 100644 (file)
index 16befa7..2de3d7b
@@ -1,6 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>\r
 <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
   <ItemGroup Label="ProjectConfigurations">\r
+    <ProjectConfiguration Include="Debug|ARM64">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Debug|Win32">\r
       <Configuration>Debug</Configuration>\r
       <Platform>Win32</Platform>\r
@@ -9,6 +13,10 @@
       <Configuration>Debug</Configuration>\r
       <Platform>x64</Platform>\r
     </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|ARM64">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Release|Win32">\r
       <Configuration>Release</Configuration>\r
       <Platform>Win32</Platform>\r
       <Configuration>Release</Configuration>\r
       <Platform>x64</Platform>\r
     </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Template|ARM64">\r
+      <Configuration>Template</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Template|Win32">\r
       <Configuration>Template</Configuration>\r
       <Platform>Win32</Platform>\r
   <PropertyGroup Label="Globals">\r
     <ProjectGuid>{CFCCB176-6CAA-472B-B0A2-90511C8E2E52}</ProjectGuid>\r
     <Keyword>MFCProj</Keyword>\r
+    <WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>\r
   </PropertyGroup>\r
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|x64'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">\r
+    <ConfigurationType>DynamicLibrary</ConfigurationType>\r
+    <UseOfMfc>Static</UseOfMfc>\r
+    <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
     <ConfigurationType>DynamicLibrary</ConfigurationType>\r
     <UseOfMfc>Static</UseOfMfc>\r
     <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">\r
+    <ConfigurationType>DynamicLibrary</ConfigurationType>\r
+    <UseOfMfc>Static</UseOfMfc>\r
+    <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
   <ImportGroup Label="ExtensionSettings">\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Template|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
+  </ImportGroup>\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
     <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" />\r
+  </ImportGroup>\r
   <PropertyGroup Label="UserMacros" />\r
   <PropertyGroup>\r
     <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\PrinterWizard.Resources\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\PrinterWizard.Resources\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(Platform)\$(Configuration)\PrinterWizard.Resources\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</LinkIncremental>\r
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\PrinterWizard.Resources\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\PrinterWizard.Resources\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(Platform)\$(Configuration)\PrinterWizard.Resources\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>\r
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</LinkIncremental>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">PrinterWizardResources</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.dll</TargetExt>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">PrinterWizardResources</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.dll</TargetExt>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">$(Platform)\$(Configuration)\PrinterWizard.Resources\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">PrinterWizardResources</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Template|Win32'">.dll</TargetExt>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">PrinterWizardResources</TargetName>\r
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">PrinterWizardResources</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.dll</TargetExt>\r
+    <TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">.dll</TargetExt>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">PrinterWizardResources</TargetName>\r
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">PrinterWizardResources</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.dll</TargetExt>\r
+    <TargetExt Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">.dll</TargetExt>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Template|x64'">$(Platform)\$(Configuration)\PrinterWizard.Resources\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">$(Platform)\$(Configuration)\PrinterWizard.Resources\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <TargetName Condition="'$(Configuration)|$(Platform)'=='Template|x64'">PrinterWizardResources</TargetName>\r
+    <TargetName Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">PrinterWizardResources</TargetName>\r
     <TargetExt Condition="'$(Configuration)|$(Platform)'=='Template|x64'">.dll</TargetExt>\r
+    <TargetExt Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">.dll</TargetExt>\r
   </PropertyGroup>\r
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
     <Midl>\r
       <TargetMachine>MachineX64</TargetMachine>\r
     </Link>\r
   </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">\r
+    <Midl>\r
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <MkTypLibCompatible>false</MkTypLibCompatible>\r
+    </Midl>\r
+    <ClCompile>\r
+      <Optimization>Disabled</Optimization>\r
+      <AdditionalIncludeDirectories>..\..\mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUGS;DEBUG=1;WINVER=0x0400;UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <StringPooling>true</StringPooling>\r
+      <MinimalRebuild>true</MinimalRebuild>\r
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r
+      <BufferSecurityCheck>true</BufferSecurityCheck>\r
+      <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <WarningLevel>Level4</WarningLevel>\r
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r
+      <CallingConvention>Cdecl</CallingConvention>\r
+      <DisableSpecificWarnings>4702;%(DisableSpecificWarnings)</DisableSpecificWarnings>\r
+    </ClCompile>\r
+    <ResourceCompile>\r
+      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <Culture>0x0409</Culture>\r
+      <AdditionalIncludeDirectories>$(IntDir);../../mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ResourceCompile>\r
+    <PreLinkEvent>\r
+      <Message>Building Output Directories</Message>\r
+      <Command>if not exist Debug\PrinterWizard.Resources mkdir Debug\PrinterWizard.Resources</Command>\r
+    </PreLinkEvent>\r
+    <Link>\r
+      <OutputFile>$(OutDir)PrinterWizardResources.dll</OutputFile>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+      <SubSystem>Windows</SubSystem>\r
+      <EntryPointSymbol>\r
+      </EntryPointSymbol>\r
+      <NoEntryPoint>true</NoEntryPoint>\r
+      <ImportLibrary>$(OutDir)Localized.lib</ImportLibrary>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
     <Midl>\r
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
@@ -301,6 +404,60 @@ if not exist "Release\PrinterWizard.Resources" mkdir "Release\PrinterWizard.Reso
 if not exist "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources"   mkdir "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources"\r
 xcopy /I/Y "$(TargetPath)"                                                                                                           "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources"\r
 :END\r
+</Command>\r
+    </PostBuildEvent>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">\r
+    <Midl>\r
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <MkTypLibCompatible>false</MkTypLibCompatible>\r
+    </Midl>\r
+    <ClCompile>\r
+      <Optimization>MaxSpeed</Optimization>\r
+      <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>\r
+      <OmitFramePointers>true</OmitFramePointers>\r
+      <AdditionalIncludeDirectories>..\..\mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;WINVER=0x0400;UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <StringPooling>true</StringPooling>\r
+      <MinimalRebuild>false</MinimalRebuild>\r
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r
+      <FunctionLevelLinking>true</FunctionLevelLinking>\r
+      <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <WarningLevel>Level4</WarningLevel>\r
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r
+      <DisableSpecificWarnings>4702;%(DisableSpecificWarnings)</DisableSpecificWarnings>\r
+    </ClCompile>\r
+    <ResourceCompile>\r
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <Culture>0x0409</Culture>\r
+      <AdditionalIncludeDirectories>$(IntDir);../../mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ResourceCompile>\r
+    <PreLinkEvent>\r
+      <Message>Building Output Directories</Message>\r
+      <Command>if not exist Release mkdir Release\r
+if not exist "Release\PrinterWizard.Resources" mkdir "Release\PrinterWizard.Resources"\r
+</Command>\r
+    </PreLinkEvent>\r
+    <Link>\r
+      <OutputFile>$(OutDir)PrinterWizardResources.dll</OutputFile>\r
+      <GenerateDebugInformation>false</GenerateDebugInformation>\r
+      <SubSystem>Windows</SubSystem>\r
+      <OptimizeReferences>\r
+      </OptimizeReferences>\r
+      <EnableCOMDATFolding>\r
+      </EnableCOMDATFolding>\r
+      <EntryPointSymbol>\r
+      </EntryPointSymbol>\r
+      <NoEntryPoint>true</NoEntryPoint>\r
+      <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>\r
+    </Link>\r
+    <PostBuildEvent>\r
+      <Command>if not "%RC_XBS%" == "YES" goto END\r
+if not exist "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources"   mkdir "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources"\r
+xcopy /I/Y "$(TargetPath)"                                                                                                           "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources"\r
+:END\r
 </Command>\r
     </PostBuildEvent>\r
   </ItemDefinitionGroup>\r
@@ -314,6 +471,11 @@ xcopy /I/Y "$(TargetPath)"
       <OutputFile>$(OutDir)PrinterWizardResources.dll</OutputFile>\r
     </Link>\r
   </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Template|ARM64'">\r
+    <Link>\r
+      <OutputFile>$(OutDir)PrinterWizardResources.dll</OutputFile>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
   <ItemGroup>\r
     <ClInclude Include="resource_dll.h" />\r
   </ItemGroup>\r
index 357a478f56208adadd4377b610beace44a3d9dae..947b9dda35bab943e9bf864dbd7b2e9a8c3ef568 100644 (file)
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2002-2015 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2020 Apple Inc. All rights reserved.
  *
  * Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple Inc.
  * ("Apple") in consideration of your agreement to the following terms, and your
@@ -173,7 +173,7 @@ static const char kFilePathSep = '/';
 #undef _DNS_SD_LIBDISPATCH
 #endif
 #include "dns_sd.h"
-#include "dns_sd_internal.h"
+#include "dns_sd_private.h"
 #include "ClientCommon.h"
 
 
@@ -318,6 +318,8 @@ static uint16_t GetRRType(const char *s)
     else if (!strcasecmp(s, "ds"      )) return(kDNSServiceType_DS);
     else if (!strcasecmp(s, "rrsig"   )) return(kDNSServiceType_RRSIG);
     else if (!strcasecmp(s, "nsec"    )) return(kDNSServiceType_NSEC);
+    else if (!strcasecmp(s, "SVCB"    )) return(kDNSServiceType_SVCB);
+    else if (!strcasecmp(s, "HTTPS"   )) return(kDNSServiceType_HTTPS);
     else if (!strcasecmp(s, "ANY"     )) return(kDNSServiceType_ANY);
     else return(atoi(s));
 }
@@ -389,6 +391,8 @@ static char *DNSTypeName(unsigned short rr_type)
         case kDNSServiceType_AXFR:       return("AXFR");
         case kDNSServiceType_MAILB:      return("MAILB");
         case kDNSServiceType_MAILA:      return("MAILA");
+        case kDNSServiceType_SVCB:       return("SVCB");
+        case kDNSServiceType_HTTPS:      return("HTTPS");
         case kDNSServiceType_ANY:        return("ANY");
         default:            
         {
@@ -540,7 +544,6 @@ static void print_usage(const char *arg0, int print_all)
         fprintf(stderr, "\n");
         fprintf(stderr, "%s -A                              (Test Adding/Updating/Deleting a record)\n", arg0);
         fprintf(stderr, "%s -C <name> <rrtype> <rrclass>           (Query; reconfirming each result)\n", arg0);
-        fprintf(stderr, "%s -D <name> <rrtype> <rrclass>                (Validate query with DNSSEC)\n", arg0);
         fprintf(stderr, "%s -I           (Test registering and then immediately updating TXT record)\n", arg0);
         fprintf(stderr, "%s -N                                     (Test adding a large NULL record)\n", arg0);
         fprintf(stderr, "%s -M              (Test creating a registration with multiple TXT records)\n", arg0);
@@ -548,14 +551,12 @@ static void print_usage(const char *arg0, int print_all)
         fprintf(stderr, "%s -T                                    (Test creating a large TXT record)\n", arg0);
         fprintf(stderr, "%s -U                                          (Test updating a TXT record)\n", arg0);
         fprintf(stderr, "%s -ble                                  (Use kDNSServiceInterfaceIndexBLE)\n", arg0);
-        fprintf(stderr, "%s -g v4/v6/v4v6 <hostname>             (Validate address info with DNSSEC)\n", arg0);
         fprintf(stderr, "%s -i <Interface>         (Run dns-sd cmd on a specific interface (en0/en1)\n", arg0);
         fprintf(stderr, "%s -includep2p                        (Set kDNSServiceFlagsIncludeP2P flag)\n", arg0);
         fprintf(stderr, "%s -includeAWDL                      (Set kDNSServiceFlagsIncludeAWDL flag)\n", arg0);
         fprintf(stderr, "%s -intermediates            (Set kDNSServiceFlagsReturnIntermediates flag)\n", arg0);
         fprintf(stderr, "%s -ku                               (Set kDNSServiceFlagsKnownUnique flag)\n", arg0);
         fprintf(stderr, "%s -lo                          (Run dns-sd cmd using local only interface)\n", arg0);
-        fprintf(stderr, "%s -optional                    (Set kDNSServiceFlagsValidateOptional flag)\n", arg0);
         fprintf(stderr, "%s -p2p                                  (Use kDNSServiceInterfaceIndexP2P)\n", arg0);
         fprintf(stderr, "%s -tc                    (Set kDNSServiceFlagsBackgroundTrafficClass flag)\n", arg0);
         fprintf(stderr, "%s -test                                  (Run basic API input range tests)\n", arg0);
@@ -564,6 +565,7 @@ static void print_usage(const char *arg0, int print_all)
         fprintf(stderr, "%s -timeout                              (Set kDNSServiceFlagsTimeout flag)\n", arg0);
         fprintf(stderr, "%s -unicastResponse              (Set kDNSServiceFlagsUnicastResponse flag)\n", arg0);
         fprintf(stderr, "%s -autoTrigger                      (Set kDNSServiceFlagsAutoTrigger flag)\n", arg0);
+        fprintf(stderr, "%s -enableDNSSEC              (Enable DNSSEC validation for the '-Q' query)\n", arg0);
     }
 }
 
@@ -1049,6 +1051,12 @@ static void DNSSD_API qr_reply(DNSServiceRef sdref, const DNSServiceFlags flags,
     char rr_type[RR_TYPE_SIZE];
     char rr_class[6];
     DNSServiceFlags check_flags = flags;//local flags for dnssec status checking
+    int8_t enable_dnssec = ((check_flags & kDNSServiceFlagsEnableDNSSEC) != 0);
+    static int8_t enabled_dnssec_before = -1;
+
+    if (enabled_dnssec_before == -1) {
+        enabled_dnssec_before = enable_dnssec;
+    }
 
     (void)sdref;    // Unused
     (void)ifIndex;  // Unused
@@ -1058,10 +1066,7 @@ static void DNSSD_API qr_reply(DNSServiceRef sdref, const DNSServiceFlags flags,
 
     if (num_printed++ == 0)
     {
-        if (operation == 'D') 
-            printf("Timestamp     A/R if %-30s%-6s%-7s%-18s Rdata\n", "Name", "Type", "Class", "DNSSECStatus"); 
-        else     
-            printf("Timestamp     A/R    Flags if %-30s%-6s%-7s Rdata\n", "Name", "Type", "Class");
+        printf("Timestamp     A/R    Flags if %-30s%-6s%-7s%s Rdata\n", "Name", "Type", "Class", enable_dnssec ? " DNSSECResult  " : "");
     }
     printtimestamp();
 
@@ -1078,75 +1083,69 @@ static void DNSSD_API qr_reply(DNSServiceRef sdref, const DNSServiceFlags flags,
 
     if (!errorCode) //to avoid printing garbage in rdata
     {
-        if (!(check_flags & (kDNSServiceFlagsValidate | kDNSServiceFlagsValidateOptional)))
-        {
-            switch (rrtype)
-            {
-                case kDNSServiceType_A:
-                    snprintf(rdb, sizeof(rdb), "%d.%d.%d.%d", rd[0], rd[1], rd[2], rd[3]);
-                    break;
-    
-                case kDNSServiceType_NS:
-                case kDNSServiceType_CNAME:
-                case kDNSServiceType_PTR:
-                case kDNSServiceType_DNAME:
-                    snprintd(p, sizeof(rdb), &rd);
-                    break;
-
-                case kDNSServiceType_SOA:
-                    p += snprintd(p, rdb + sizeof(rdb) - p, &rd);           // mname
-                    p += snprintf(p, rdb + sizeof(rdb) - p, " ");
-                    p += snprintd(p, rdb + sizeof(rdb) - p, &rd);           // rname
-                         snprintf(p, rdb + sizeof(rdb) - p, " Ser %d Ref %d Ret %d Exp %d Min %d",
-                             ntohl(((uint32_t*)rd)[0]), ntohl(((uint32_t*)rd)[1]), ntohl(((uint32_t*)rd)[2]), ntohl(((uint32_t*)rd)[3]), ntohl(((uint32_t*)rd)[4]));
-                    break;
-
-                case kDNSServiceType_AAAA:
-                    snprintf(rdb, sizeof(rdb), "%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X",
-                        rd[0x0], rd[0x1], rd[0x2], rd[0x3], rd[0x4], rd[0x5], rd[0x6], rd[0x7],
-                        rd[0x8], rd[0x9], rd[0xA], rd[0xB], rd[0xC], rd[0xD], rd[0xE], rd[0xF]);
-                    break;
-
-                case kDNSServiceType_SRV:
-                    p += snprintf(p, rdb + sizeof(rdb) - p, "%d %d %d ",        // priority, weight, port
-                             ntohs(*(unsigned short*)rd), ntohs(*(unsigned short*)(rd+2)), ntohs(*(unsigned short*)(rd+4)));
-                    rd += 6;
-                         snprintd(p, rdb + sizeof(rdb) - p, &rd);               // target host
-                    break;
-
-                case kDNSServiceType_DS:
-                case kDNSServiceType_DNSKEY:
-                case kDNSServiceType_NSEC:
-                case kDNSServiceType_RRSIG:
-                    ParseDNSSECRecords(rrtype, rdb, sizeof(rdb), rd, rdlen);
-                    break;
-
-                default: 
-                    snprintf(rdb, sizeof(rdb), "%d bytes%s", rdlen, rdlen ? ":" : ""); 
-                    unknowntype = 1;
-                    break;
-            }   
-        }   
-        else 
+        switch (rrtype)
         {
-            strncpy(rdb, "----", sizeof(rdb));
-            //Clear all o/p bits, and then check for dnssec status
-            check_flags &= ~kDNSServiceOutputFlags;
-            if (check_flags & kDNSServiceFlagsSecure)
-                strncpy(dnssec_status, "Secure", sizeof(dnssec_status));
-            else if (check_flags & kDNSServiceFlagsInsecure)
-                strncpy(dnssec_status, "Insecure", sizeof(dnssec_status));
-            else if (check_flags & kDNSServiceFlagsIndeterminate)
-                strncpy(dnssec_status, "Indeterminate", sizeof(dnssec_status));
-            else if (check_flags & kDNSServiceFlagsBogus) 
-                strncpy(dnssec_status, "Bogus", sizeof(dnssec_status));
+            case kDNSServiceType_A:
+                snprintf(rdb, sizeof(rdb), "%d.%d.%d.%d", rd[0], rd[1], rd[2], rd[3]);
+                break;
+
+            case kDNSServiceType_NS:
+            case kDNSServiceType_CNAME:
+            case kDNSServiceType_PTR:
+            case kDNSServiceType_DNAME:
+                snprintd(p, sizeof(rdb), &rd);
+                break;
+
+            case kDNSServiceType_SOA:
+                p += snprintd(p, rdb + sizeof(rdb) - p, &rd);           // mname
+                p += snprintf(p, rdb + sizeof(rdb) - p, " ");
+                p += snprintd(p, rdb + sizeof(rdb) - p, &rd);           // rname
+                     snprintf(p, rdb + sizeof(rdb) - p, " Ser %d Ref %d Ret %d Exp %d Min %d",
+                         ntohl(((uint32_t*)rd)[0]), ntohl(((uint32_t*)rd)[1]), ntohl(((uint32_t*)rd)[2]), ntohl(((uint32_t*)rd)[3]), ntohl(((uint32_t*)rd)[4]));
+                break;
+
+            case kDNSServiceType_AAAA:
+                snprintf(rdb, sizeof(rdb), "%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X",
+                    rd[0x0], rd[0x1], rd[0x2], rd[0x3], rd[0x4], rd[0x5], rd[0x6], rd[0x7],
+                    rd[0x8], rd[0x9], rd[0xA], rd[0xB], rd[0xC], rd[0xD], rd[0xE], rd[0xF]);
+                break;
+
+            case kDNSServiceType_SRV:
+                p += snprintf(p, rdb + sizeof(rdb) - p, "%d %d %d ",        // priority, weight, port
+                         ntohs(*(unsigned short*)rd), ntohs(*(unsigned short*)(rd+2)), ntohs(*(unsigned short*)(rd+4)));
+                rd += 6;
+                     snprintd(p, rdb + sizeof(rdb) - p, &rd);               // target host
+                break;
+
+            case kDNSServiceType_DS:
+            case kDNSServiceType_DNSKEY:
+            case kDNSServiceType_NSEC:
+            case kDNSServiceType_RRSIG:
+                ParseDNSSECRecords(rrtype, rdb, sizeof(rdb), rd, rdlen);
+                break;
+
+            default:
+                snprintf(rdb, sizeof(rdb), "%d bytes%s", rdlen, rdlen ? ":" : "");
+                unknowntype = 1;
+                break;
         }
     }
 
-    if (operation == 'D')
-        printf("%s%3d %-30s%-6s%-7s%-18s %s", op, ifIndex, fullname, rr_type, rr_class, dnssec_status, rdb);
+    if (check_flags & kDNSServiceFlagsSecure)
+        strncpy(dnssec_status, "Secure            ", sizeof(dnssec_status));
+    else if (check_flags & kDNSServiceFlagsInsecure)
+        strncpy(dnssec_status, "Insecure          ", sizeof(dnssec_status));
+    else if (check_flags & kDNSServiceFlagsIndeterminate)
+        strncpy(dnssec_status, "Indeterminate     ", sizeof(dnssec_status));
+    else if (check_flags & kDNSServiceFlagsBogus)
+        strncpy(dnssec_status, "Bogus             ", sizeof(dnssec_status));
     else
-        printf("%s%9X%3d %-30s%-7s%-6s %s", op, flags, ifIndex, fullname, rr_type, rr_class, rdb);
+        strncpy(dnssec_status, "                  ", sizeof(dnssec_status));
+
+    printf("%s%9X%3d %-30s%-7s%-6s %s%s",
+        op, flags, ifIndex, fullname, rr_type, rr_class, enabled_dnssec_before ? dnssec_status : "", rdb);
+
+
     if (unknowntype)
     { 
         while (rd < end) 
@@ -1154,8 +1153,10 @@ static void DNSSD_API qr_reply(DNSServiceRef sdref, const DNSServiceFlags flags,
     }
     if (errorCode)
     {
-        if (errorCode == kDNSServiceErr_NoSuchRecord) 
+        if (errorCode == kDNSServiceErr_NoSuchRecord)
             printf("    No Such Record");
+        else if (errorCode == kDNSServiceErr_NoAuth)
+            printf("    No Authorization");
         else if (errorCode == kDNSServiceErr_Timeout)
         {
             printf("    No Such Record\n");
@@ -1203,15 +1204,13 @@ static void DNSSD_API addrinfo_reply(DNSServiceRef sdref, const DNSServiceFlags
     DNSServiceFlags check_flags = flags;
        (void) sdref;
        (void) context;
+    unsigned char enable_dnssec = ((check_flags & kDNSServiceFlagsEnableDNSSEC) != 0);
 
     EXIT_IF_LIBDISPATCH_FATAL_ERROR(errorCode);
 
     if (num_printed++ == 0)
     {
-        if (operation == 'g') 
-            printf("Timestamp     A/R if %-25s %-44s %-18s\n", "Hostname", "Address", "DNSSECStatus");
-        else  
-            printf("Timestamp     A/R    Flags if %-38s %-44s %s\n", "Hostname", "Address", "TTL");
+        printf("Timestamp     A/R    Flags if %-38s %-44s %s%s\n", "Hostname", "Address", "TTL", enable_dnssec ? "DNSSECResult" : "");
     }
     printtimestamp();
    
@@ -1232,26 +1231,20 @@ static void DNSSD_API addrinfo_reply(DNSServiceRef sdref, const DNSServiceFlags
             b[0x8], b[0x9], b[0xA], b[0xB], b[0xC], b[0xD], b[0xE], b[0xF], if_name);
     }
 
-    //go through this only if you have a dnssec validation status
-    if (!errorCode && (check_flags & (kDNSServiceFlagsValidate | kDNSServiceFlagsValidateOptional)))
+    if (enable_dnssec)
     {
-        strncpy(addr, "----", sizeof(addr));
-        //Clear all o/p bits, and then check for dnssec status
-        check_flags &= ~kDNSServiceOutputFlags;
         if (check_flags & kDNSServiceFlagsSecure)
-            strncpy(dnssec_status, "Secure", sizeof(dnssec_status));
+            strncpy(dnssec_status, " Secure", sizeof(dnssec_status));
         else if (check_flags & kDNSServiceFlagsInsecure)
-            strncpy(dnssec_status, "Insecure", sizeof(dnssec_status));
+            strncpy(dnssec_status, " Insecure", sizeof(dnssec_status));
         else if (check_flags & kDNSServiceFlagsIndeterminate)
-            strncpy(dnssec_status, "Indeterminate", sizeof(dnssec_status));
-        else if (check_flags & kDNSServiceFlagsBogus) 
-            strncpy(dnssec_status, "Bogus", sizeof(dnssec_status));
+            strncpy(dnssec_status, " Indeterminate", sizeof(dnssec_status));
+        else if (check_flags & kDNSServiceFlagsBogus)
+            strncpy(dnssec_status, " Bogus", sizeof(dnssec_status));
     }
-    
-    if (operation == 'g')
-        printf("%s%3d %-25s %-44s %-18s", op, interfaceIndex, hostname, addr, dnssec_status);
-    else
-        printf("%s%9X%3d %-38s %-44s %d", op, flags, interfaceIndex, hostname, addr, ttl);
+
+    printf("%s%9X%3d %-38s %-44s %d%s", op, flags, interfaceIndex, hostname, addr, ttl, enable_dnssec ? dnssec_status : "");
+
     if (errorCode)
     {
         if (errorCode == kDNSServiceErr_NoSuchRecord) 
@@ -1863,7 +1856,7 @@ int main(int argc, char **argv)
     char buffer[TypeBufferSize], *typ, *dom;
     int opi;
     DNSServiceFlags flags = 0;
-    int optional = 0;
+    unsigned char enable_dnssec = 0;
 
     // Extract the program name from argv[0], which by convention contains the path to this executable.
     // Note that this is just a voluntary convention, not enforced by the kernel --
@@ -2026,12 +2019,12 @@ int main(int argc, char **argv)
                printf("Setting kDNSServiceFlagsAutoTrigger\n");
            }
 
-           if (argc > 1 && !strcasecmp(argv[1], "-optional"))
+           if (argc > 1 && !strcasecmp(argv[1], "-enableDNSSEC"))
            {
                argc--;
                argv++;
-               optional = 1;
-               printf("Setting DNSSEC optional flag\n");
+            enable_dnssec = 1;
+               printf("Enable DNSSEC validation for the '-Q' query\n");
            }
 
            if (argc > 2 && !strcmp(argv[1], "-i"))
@@ -2120,7 +2113,6 @@ int main(int argc, char **argv)
         err = RegisterService(&client, argv[opi+0], gettype(buffer, argv[opi+1]), argv[opi+2], argv[opi+4], argv[opi+3], argc-(opi+6), argv+(opi+6), flags);
         break;
 
-    case 'D':
     case 'q':
     case 'Q':
     case 'C':   {
@@ -2128,20 +2120,14 @@ int main(int argc, char **argv)
         flags |= kDNSServiceFlagsReturnIntermediates;
         if (operation == 'q')
             flags |= kDNSServiceFlagsSuppressUnusable;
-        if (argc < opi+1) 
+        if (enable_dnssec)
+            flags |= kDNSServiceFlagsEnableDNSSEC;
+        if (argc < opi+1)
             goto Fail;
         rrtype = (argc <= opi+1) ? kDNSServiceType_A  : GetRRType(argv[opi+1]);
         rrclass = (argc <= opi+2) ? kDNSServiceClass_IN : GetRRClass(argv[opi+2]);
         if (rrtype == kDNSServiceType_TXT || rrtype == kDNSServiceType_PTR)
             flags |= kDNSServiceFlagsLongLivedQuery;
-        if (operation == 'D')
-        {
-            flags |= kDNSServiceFlagsSuppressUnusable;
-            if (optional)
-                flags |= kDNSServiceFlagsValidateOptional;
-            else
-                flags |= kDNSServiceFlagsValidate;
-        }
         err = DNSServiceQueryRecord(&client, flags, opinterface, argv[opi+0], rrtype, rrclass, qr_reply, NULL);
         break;
     }
@@ -2205,18 +2191,9 @@ int main(int argc, char **argv)
         break;
     }
 
-    case 'g':
     case 'G':   {
         flags |= kDNSServiceFlagsReturnIntermediates;
 
-        if (operation == 'g')
-        {
-            flags |= kDNSServiceFlagsSuppressUnusable;
-            if (optional)
-                flags |= kDNSServiceFlagsValidateOptional;
-            else
-                flags |= kDNSServiceFlagsValidate;
-        }
         if (argc != opi+2) 
             goto Fail;
         else 
index 68561ee6bf3d23af55c42b08163a8fa33e8aff92..dc1137d146491baee0277fc54b6fcc67eaa7f16c 100644 (file)
 /*
-       Copyright (c) 2016-2019 Apple Inc. All rights reserved.
+       Copyright (c) 2016-2020 Apple Inc. All rights reserved.
 */
 
 #include "DNSMessage.h"
+#include <CoreUtils/CoreUtils.h>
+#include <stdlib.h>
 
 //===========================================================================================================================
+// MARK: - Local Prototypes
+
+static Boolean _NameIsPrivate( const char * const inDomainNameStr );
+static OSStatus
+       _AppendDomainNameString( DataBuffer *inDB, Boolean inPrivacy, const char *inDomainNameStr );
+static OSStatus
+       _AppendDomainNameStringEx( DataBuffer *inDB, const char *inSeparator, Boolean inPrivacy, const char *inDomainNameStr );
+static OSStatus
+       _AppendOPTRDataString(
+               DataBuffer *    inDB,
+               const uint8_t * inRDataPtr,
+               const uint8_t * inRDataEnd,
+               Boolean                 inPrivacy );
+static OSStatus
+       _AppendSVCBRDataString(
+               DataBuffer *    inDB,
+               const uint8_t * inRDataPtr,
+               const uint8_t * inRDataEnd,
+               Boolean                 inPrivacy );
+static OSStatus
+       _AppendIPv4Address(
+               DataBuffer *    inDB,
+               const char *    inSeparator,
+               const uint8_t   inAddrBytes[ STATIC_PARAM 4 ],
+               Boolean                 inPrivacy );
+static OSStatus
+       _AppendIPv6Address(
+               DataBuffer *    inDB,
+               const char *    inSeparator,
+               const uint8_t   inAddrBytes[ STATIC_PARAM 16 ],
+               Boolean                 inPrivacy );
+static OSStatus
+       _AppendIPAddress(
+               DataBuffer *    inDB,
+               const char *    inSeparator,
+               const uint8_t * inAddrPtr,
+               int                             inAddrLen,
+               Boolean                 inPrivacy );
+static void *  _GetCULibHandle( void );
+static Boolean _MemIsAllZeros( const uint8_t *inMemPtr, size_t inMemLen );
+static Boolean _IPv4AddressIsWhitelisted( const uint8_t inAddrBytes[ STATIC_PARAM 4 ] );
+static Boolean _IPv6AddressIsWhitelisted( const uint8_t inAddrBytes[ STATIC_PARAM 16 ] );
+static int
+       _DNSMessagePrintObfuscatedIPAddress(
+               char *                  inBufPtr,
+               size_t                  inBufLen,
+               const uint8_t * inAddrBytes,
+               size_t                  inAddrLen );
+
+//===========================================================================================================================
+// MARK: - CoreUtils Framework Soft Linking
+
+#define _DEFINE_GET_CU_SYM_ADDR( SYMBOL )                                                              \
+       static __typeof__( SYMBOL ) *   _GetCUSymAddr_ ## SYMBOL( void )        \
+       {                                                                                                                                       \
+               static dispatch_once_t                          sOnce = 0;                                      \
+               static __typeof__( SYMBOL ) *           sAddr = NULL;                           \
+                                                                                                                                               \
+               dispatch_once( &sOnce,                                                                                  \
+               ^{                                                                                                                              \
+                       void * const handle = _GetCULibHandle();                                        \
+                       require_return( handle );                                                                       \
+                       sAddr = (__typeof__( SYMBOL ) *) dlsym( handle, #SYMBOL );      \
+               } );                                                                                                                    \
+               return sAddr;                                                                                                   \
+       }                                                                                                                                       \
+extern int _DNSMessageDummyVariable
+
+_DEFINE_GET_CU_SYM_ADDR( Base64EncodeCopyEx );
+_DEFINE_GET_CU_SYM_ADDR( DataBuffer_Append );
+_DEFINE_GET_CU_SYM_ADDR( DataBuffer_AppendF );
+_DEFINE_GET_CU_SYM_ADDR( DataBuffer_Detach );
+_DEFINE_GET_CU_SYM_ADDR( DataBuffer_Free );
+_DEFINE_GET_CU_SYM_ADDR( DataBuffer_Init );
+_DEFINE_GET_CU_SYM_ADDR( SecondsToYMD_HMS );
+_DEFINE_GET_CU_SYM_ADDR( SNPrintF );
+
+#define _CallCUVoidFunction( NAME, UNAVAILABLE_RETURN_VALUE, ... )     \
+       ( likely( _GetCUSymAddr_ ## NAME() ) ?                          \
+               ( ( ( _GetCUSymAddr_ ## NAME() )( __VA_ARGS__ ) ), kNoErr ) : ( UNAVAILABLE_RETURN_VALUE ) )
+
+#define _CallCUFunction( NAME, UNAVAILABLE_RETURN_VALUE, ... ) \
+       ( likely( _GetCUSymAddr_ ## NAME() ) ?                  \
+               ( ( _GetCUSymAddr_ ## NAME() )( __VA_ARGS__ ) ) : ( UNAVAILABLE_RETURN_VALUE ) )
+
+#define _Base64EncodeCopyEx( ... )             _CallCUFunction( Base64EncodeCopyEx, kUnsupportedErr, __VA_ARGS__ )
+#define _DataBuffer_Init( ... )                        _CallCUVoidFunction( DataBuffer_Init, kUnsupportedErr, __VA_ARGS__ )
+#define _DataBuffer_Free( ... )                        _CallCUVoidFunction( DataBuffer_Free, kUnsupportedErr, __VA_ARGS__ )
+#define _DataBuffer_Append( ... )              _CallCUFunction( DataBuffer_Append, kUnsupportedErr, __VA_ARGS__ )
+#define _DataBuffer_AppendF( ... )             _CallCUFunction( DataBuffer_AppendF, kUnsupportedErr, __VA_ARGS__ )
+#define _DataBuffer_Detach( ... )              _CallCUFunction( DataBuffer_Detach, kUnsupportedErr, __VA_ARGS__ )
+#define _SecondsToYMD_HMS( ... )               _CallCUVoidFunction( SecondsToYMD_HMS, kUnsupportedErr, __VA_ARGS__ )
+#define _SNPrintF( ... )                               _CallCUFunction( SNPrintF, kUnsupportedErr, __VA_ARGS__ )
+
+//===========================================================================================================================
+// MARK: - Public Functions
 
 #define IsCompressionByte( X )         ( ( ( X ) & 0xC0 ) == 0xC0 )
 
@@ -130,6 +228,23 @@ exit:
 
 //===========================================================================================================================
 
+static OSStatus
+       _DNSMessageExtractRecordEx(
+               const uint8_t *         inMsgPtr,
+               size_t                          inMsgLen,
+               const uint8_t *         inPtr,
+               uint8_t                         outName[ kDomainNameLengthMax ],
+               uint16_t *                      outType,
+               uint16_t *                      outClass,
+               uint32_t *                      outTTL,
+               const uint8_t **        outRDataPtr,
+               size_t *                        outRDataLen,
+               uint8_t *                       inBufPtr,
+               size_t                          inBufLen,
+               size_t *                        outCopiedRDataLen,
+               size_t *                        outExpandedRDataLen,
+               const uint8_t **        outPtr );
+
 OSStatus
        DNSMessageExtractRecord(
                const uint8_t *         inMsgPtr,
@@ -143,32 +258,8 @@ OSStatus
                size_t *                        outRDataLen,
                const uint8_t **        outPtr )
 {
-       OSStatus                                                        err;
-       const uint8_t * const                           msgEnd = inMsgPtr + inMsgLen;
-       const uint8_t *                                         ptr;
-       const dns_fixed_fields_record *         fields;
-       const uint8_t *                                         rdata;
-       size_t                                                          rdLength;
-       
-       err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inPtr, outName, &ptr );
-       require_noerr_quiet( err, exit );
-       require_action_quiet( (size_t)( msgEnd - ptr ) >= sizeof( *fields ), exit, err = kUnderrunErr );
-       
-       fields  = (const dns_fixed_fields_record *) ptr;
-       rdata   = ptr + sizeof( *fields );
-       
-       rdLength = dns_fixed_fields_record_get_rdlength( fields );
-       require_action_quiet( (size_t)( msgEnd - rdata ) >= rdLength , exit, err = kUnderrunErr );
-       
-       if( outType )           *outType                = dns_fixed_fields_record_get_type( fields );
-       if( outClass )          *outClass               = dns_fixed_fields_record_get_class( fields );
-       if( outTTL )            *outTTL                 = dns_fixed_fields_record_get_ttl( fields );
-       if( outRDataPtr )       *outRDataPtr    = rdata;
-       if( outRDataLen )       *outRDataLen    = rdLength;
-       if( outPtr )            *outPtr                 = &rdata[ rdLength ];
-       
-exit:
-       return( err );
+       return( _DNSMessageExtractRecordEx( inMsgPtr, inMsgLen, inPtr, outName, outType, outClass, outTTL,
+               outRDataPtr, outRDataLen, NULL, 0, NULL, NULL, outPtr ) );
 }
 
 //===========================================================================================================================
@@ -191,7 +282,6 @@ OSStatus    DNSMessageGetAnswerSection( const uint8_t *inMsgPtr, size_t inMsgLen, c
                err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, NULL, NULL, NULL, &ptr );
                require_noerr_quiet( err, exit );
        }
-       
        if( outPtr ) *outPtr = ptr;
        err = kNoErr;
        
@@ -201,6 +291,74 @@ exit:
 
 //===========================================================================================================================
 
+OSStatus
+       DNSMessageGetOptRecord(
+               const uint8_t *         inMsgPtr,
+               size_t                          inMsgLen,
+               const uint8_t **        outOptPtr,
+               size_t *                        outOptLen )
+{
+       OSStatus                                err;
+       const DNSHeader *               hdr;
+       const uint8_t *                 ptr;
+       const uint8_t *                 optPtr;
+       size_t                                  optLen;
+       uint32_t                                skipCount, additionalCount, i;
+       
+       err = DNSMessageGetAnswerSection( inMsgPtr, inMsgLen, &ptr );
+       require_noerr_quiet( err, exit );
+       
+       hdr = (DNSHeader *) inMsgPtr;
+       skipCount = DNSHeaderGetAnswerCount( hdr ) + DNSHeaderGetAuthorityCount( hdr );
+       for( i = 0; i < skipCount; ++i )
+       {
+               uint16_t                type;
+               
+               err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, NULL, &type, NULL, NULL, NULL, NULL, &ptr );
+               require_noerr_quiet( err, exit );
+               
+               // Make sure that there are no OPT records in the answer and authority sections.
+               // See <https://tools.ietf.org/html/rfc6891#section-6.1.1>.
+               
+               require_action_quiet( type != kDNSRecordType_OPT, exit, err = kMalformedErr );
+       }
+       optPtr = NULL;
+       optLen = 0;
+       additionalCount = DNSHeaderGetAdditionalCount( hdr );
+       for( i = 0; i < additionalCount; ++i )
+       {
+               const uint8_t *         namePtr;
+               uint16_t                        type;
+               
+               namePtr = ptr;
+               err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, NULL, &type, NULL, NULL, NULL, NULL, &ptr );
+               require_noerr_quiet( err, exit );
+               
+               if( type != kDNSRecordType_OPT ) continue;
+               
+               // Make sure that there's only one OPT record.
+               // See <https://tools.ietf.org/html/rfc6891#section-6.1.1>.
+               
+               require_action_quiet( !optPtr, exit, err = kMalformedErr );
+               
+               // The OPT record's name must be 0 (root domain).
+               // See <https://tools.ietf.org/html/rfc6891#section-6.1.2>.
+               
+               require_action_quiet( *namePtr == 0, exit, err = kMalformedErr );
+               
+               optPtr = namePtr;
+               optLen = (size_t)( ptr - optPtr );
+               check( optLen >= sizeof( dns_fixed_fields_opt ) );
+       }
+       if( outOptPtr ) *outOptPtr = optPtr;
+       if( outOptLen ) *outOptLen = optLen;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+
 OSStatus
        DNSMessageWriteQuery(
                uint16_t                inMsgID,
@@ -243,6 +401,477 @@ exit:
 
 //===========================================================================================================================
 
+uint8_t *
+       DNSMessageCollapse(
+               const uint8_t * const   inMsgPtr,
+               const size_t                    inMsgLen,
+               size_t * const                  outMsgLen,
+               OSStatus * const                outError )
+{
+       OSStatus                                err;
+       uint8_t *                               newMsg  = NULL;
+       const DNSHeader *               hdr;
+       const uint8_t *                 ptr;
+       const uint8_t *                 answerSection;
+       uint8_t *                               bufPtr  = NULL;
+       size_t                                  bufLen;
+       uint8_t *                               dst;
+       const uint8_t *                 lim;
+       size_t                                  qnameLen;
+       unsigned int                    questionCount, answerCount, i, j;
+       uint32_t                                minCNameTTL;
+       uint16_t                                qtype, qclass;
+       uint8_t                                 qname[ kDomainNameLengthMax ];
+       uint8_t                                 target[ kDomainNameLengthMax ];
+       
+       require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr );
+       
+       hdr = (DNSHeader *) inMsgPtr;
+       questionCount = DNSHeaderGetQuestionCount( hdr );
+       require_action_quiet( questionCount == 1, exit, err = kCountErr );
+       
+       ptr = (const uint8_t *) &hdr[ 1 ];
+       err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, qname, &qtype, &qclass, &ptr );
+       require_noerr_quiet( err, exit );
+       require_action_quiet( qclass == kDNSClassType_IN, exit, err = kTypeErr );
+       
+       qnameLen = DomainNameLength( qname );
+       answerSection = ptr;
+       
+       // The initial target name is the QNAME.
+       
+       memcpy( target, qname, qnameLen );
+       
+       // Starting with QNAME, follow the CNAME chain, if any, to the end.
+       
+       minCNameTTL = UINT32_MAX;
+       answerCount = DNSHeaderGetAnswerCount( hdr );
+       for( i = 0; i < answerCount; ++i )
+       {
+               Boolean                         followedCNAME;
+               
+               ptr = answerSection;
+               followedCNAME = false;
+               for( j = 0; j < answerCount; ++j )
+               {
+                       const uint8_t *         rdataPtr;
+                       uint32_t                        ttl;
+                       uint16_t                        type, class;
+                       uint8_t                         name[ kDomainNameLengthMax ];
+                       
+                       err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, name, &type, &class, &ttl, &rdataPtr, NULL, &ptr );
+                       require_noerr_quiet( err, exit );
+                       
+                       if( type  != kDNSRecordType_CNAME )             continue;
+                       if( class != qclass )                                   continue;
+                       if( !DomainNameEqual( name, target ) )  continue;
+                       
+                       err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, rdataPtr, target, NULL );
+                       require_noerr_quiet( err, exit );
+                       
+                       minCNameTTL = Min( minCNameTTL, ttl );
+                       followedCNAME = true;
+               }
+               if( !followedCNAME ) break;
+       }
+       
+       // The target is now either QNAME or the end of the CNAME chain if there was one.
+       // Iterate over the answer section twice.
+       // The first iteration determines how much space is needed in the collapsed message for answer records.
+       // The second iteration writes the collapsed message.
+       
+       bufPtr  = NULL;
+       bufLen  = kDNSHeaderLength + qnameLen + sizeof( dns_fixed_fields_question );
+       dst             = NULL;
+       lim             = NULL;
+       for( i = 0; i < 2; ++i )
+       {
+               DNSHeader *                     newHdr;
+               unsigned int            newAnswerCount;
+               
+               ptr = answerSection;
+               newAnswerCount = 0;
+               for( j = 0; j < answerCount; ++j )
+               {
+                       const uint8_t *                                 recordPtr;
+                       dns_fixed_fields_record *               fields;
+                       size_t                                                  copiedRDataLen, expandedRDataLen;
+                       uint32_t                                                ttl;
+                       uint16_t                                                type, class;
+                       uint8_t                                                 name[ kDomainNameLengthMax ];
+                       
+                       recordPtr = ptr;
+                       err = _DNSMessageExtractRecordEx( inMsgPtr, inMsgLen, ptr, name, &type, &class, &ttl, NULL, NULL,
+                               NULL, 0, NULL, &expandedRDataLen, &ptr );
+                       require_noerr_quiet( err, exit );
+                       
+                       if( type  != qtype )                                    continue;
+                       if( class != qclass )                                   continue;
+                       if( !DomainNameEqual( name, target ) )  continue;
+                       if( !bufPtr )
+                       {
+                               // Add the amount of space needed for this record.
+                               // Note that the record's name will be a compression pointer to the QNAME.
+                               
+                               bufLen += ( kDNSCompressionPointerLength + sizeof( *fields ) + expandedRDataLen );
+                       }
+                       else
+                       {
+                               // Write the record's name as a compression pointer to QNAME, which is located right after the header.
+                               
+                               require_action_quiet( ( lim - dst ) >= kDNSCompressionPointerLength, exit, err = kInternalErr );
+                               DNSMessageWriteLabelPointer( dst, kDNSHeaderLength );
+                               dst += kDNSCompressionPointerLength;
+                               
+                               // Write the record's fixed field values.
+                               
+                               require_action_quiet( ( (size_t)( lim - dst ) ) >= sizeof( *fields ), exit, err = kInternalErr );
+                               fields = (dns_fixed_fields_record *) dst;
+                               ttl = Min( ttl, minCNameTTL );
+                               dns_fixed_fields_record_init( fields, type, class, ttl, (uint16_t) expandedRDataLen );
+                               dst += sizeof( *fields );
+                               
+                               // Write the record's expanded RDATA.
+                               
+                               require_action_quiet( ( (size_t)( lim - dst ) ) >= expandedRDataLen, exit, err = kInternalErr );
+                               err = _DNSMessageExtractRecordEx( inMsgPtr, inMsgLen, recordPtr, NULL, NULL, NULL, NULL, NULL, NULL,
+                                       dst, expandedRDataLen, &copiedRDataLen, NULL, NULL );
+                               require_noerr_quiet( err, exit );
+                               
+                               dst += copiedRDataLen;
+                               ++newAnswerCount;
+                       }
+               }
+               if( !bufPtr )
+               {
+                       dns_fixed_fields_question *             fields;
+                       
+                       // Allocate memory for the collapsed message.
+                       
+                       bufPtr = (uint8_t *) calloc( 1, bufLen );
+                       require_action_quiet( bufPtr, exit, err = kNoMemoryErr );
+                       
+                       dst = bufPtr;
+                       lim = &bufPtr[ bufLen ];
+                       
+                       // Write a tentative header based on the original header.
+                       
+                       require_action_quiet( ( (size_t)( lim - dst ) ) >= sizeof( *newHdr ), exit, err = kInternalErr );
+                       newHdr = (DNSHeader *) dst;
+                       memcpy( newHdr, hdr, sizeof( *newHdr ) );
+                       dst += sizeof( *newHdr );
+                       
+                       DNSHeaderSetAnswerCount( newHdr, 0 );
+                       DNSHeaderSetAuthorityCount( newHdr, 0 );
+                       DNSHeaderSetAdditionalCount( newHdr, 0 );
+                       
+                       // Write the question section.
+                       
+                       require_action_quiet( ( (size_t)( lim - dst ) ) >= qnameLen, exit, err = kInternalErr );
+                       memcpy( dst, qname, qnameLen );
+                       dst += qnameLen;
+                       
+                       require_action_quiet( ( (size_t)( lim - dst ) ) >= sizeof( *fields ), exit, err = kInternalErr );
+                       fields = (dns_fixed_fields_question *) dst;
+                       dns_fixed_fields_question_init( fields, qtype, qclass );
+                       dst += sizeof( *fields );
+                       
+                       DNSHeaderSetQuestionCount( newHdr, 1 );
+               }
+               else
+               {
+                       // Finally, set the answer count.
+                       
+                       require_action_quiet( bufLen >= sizeof( *newHdr ), exit, err = kInternalErr );
+                       newHdr = (DNSHeader *) bufPtr;
+                       DNSHeaderSetAnswerCount( newHdr, newAnswerCount );
+                       break;
+               }
+       }
+       if( outMsgLen ) *outMsgLen = (size_t)( dst - bufPtr );
+       newMsg = bufPtr;
+       bufPtr = NULL;
+       
+exit:
+       if( outError ) *outError = err;
+       ForgetMem( &bufPtr );
+       return( newMsg );
+}
+
+//===========================================================================================================================
+
+static OSStatus
+       _DNSMessageExtractRData(
+               const uint8_t * inMsgPtr,
+               size_t                  inMsgLen,
+               const uint8_t * inRDataPtr,
+               size_t                  inRDataLen,
+               unsigned int    inType,
+               uint8_t *               inBufPtr,
+               size_t                  inBufLen,
+               size_t *                outCopiedRDataLen,
+               size_t *                outExpandedRDataLen );
+
+static OSStatus
+       _DNSMessageExtractRecordEx(
+               const uint8_t * const   inMsgPtr,
+               const size_t                    inMsgLen,
+               const uint8_t * const   inPtr,
+               uint8_t                                 outName[ kDomainNameLengthMax ],
+               uint16_t * const                outType,
+               uint16_t * const                outClass,
+               uint32_t * const                outTTL,
+               const uint8_t ** const  outRDataPtr,
+               size_t * const                  outRDataLen,
+               uint8_t * const                 inBufPtr,
+               const size_t                    inBufLen,
+               size_t * const                  outCopiedRDataLen,
+               size_t * const                  outExpandedRDataLen,
+               const uint8_t **                outPtr )
+{
+       OSStatus                                                        err;
+       const uint8_t * const                           msgEnd = inMsgPtr + inMsgLen;
+       const uint8_t *                                         ptr;
+       const dns_fixed_fields_record *         fields;
+       const uint8_t *                                         rdata;
+       size_t                                                          rdLength, copiedLen, expandedLen;
+       uint16_t                                                        type;
+       
+       err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inPtr, outName, &ptr );
+       require_noerr_quiet( err, exit );
+       require_action_quiet( (size_t)( msgEnd - ptr ) >= sizeof( *fields ), exit, err = kUnderrunErr );
+       
+       fields          = (const dns_fixed_fields_record *) ptr;
+       rdata           = ptr + sizeof( *fields );
+       rdLength        = dns_fixed_fields_record_get_rdlength( fields );
+       require_action_quiet( (size_t)( msgEnd - rdata ) >= rdLength , exit, err = kUnderrunErr );
+       
+       type = dns_fixed_fields_record_get_type( fields );
+       err = _DNSMessageExtractRData( inMsgPtr, inMsgLen, rdata, rdLength, type, inBufPtr, inBufLen, &copiedLen, &expandedLen );
+       require_noerr_quiet( err, exit );
+       
+       if( outType )                           *outType                                = type;
+       if( outClass )                          *outClass                               = dns_fixed_fields_record_get_class( fields );
+       if( outTTL )                            *outTTL                                 = dns_fixed_fields_record_get_ttl( fields );
+       if( outRDataPtr )                       *outRDataPtr                    = rdata;
+       if( outRDataLen )                       *outRDataLen                    = rdLength;
+       if( outCopiedRDataLen )         *outCopiedRDataLen              = copiedLen;
+       if( outExpandedRDataLen )       *outExpandedRDataLen    = expandedLen;
+       if( outPtr )                            *outPtr                                 = &rdata[ rdLength ];
+       
+exit:
+       return( err );
+}
+
+static OSStatus
+       _DNSMessageExtractRData(
+               const uint8_t * const   inMsgPtr,
+               const size_t                    inMsgLen,
+               const uint8_t * const   inRDataPtr,
+               const size_t                    inRDataLen,
+               const unsigned int              inType,
+               uint8_t * const                 inBufPtr,
+               const size_t                    inBufLen,
+               size_t * const                  outCopiedRDataLen,
+               size_t * const                  outExpandedRDataLen )
+{
+       OSStatus                                        err;
+       const uint8_t *                         ptr;
+       const uint8_t * const           rdataEnd = &inRDataPtr[ inRDataLen ];
+       size_t                                          copyLen, expandedLen;
+       uint8_t                                         name1[ kDomainNameLengthMax ];
+       uint8_t                                         name2[ kDomainNameLengthMax ];
+       
+       // According to <https://tools.ietf.org/html/rfc1123#section-6.1.3.5>:
+       //
+       //      Compression relies on knowledge of the format of data
+       //      inside a particular RR.  Hence compression must only be
+       //      used for the contents of well-known, class-independent
+       //      RRs, and must never be used for class-specific RRs or
+       //      RR types that are not well-known.  The owner name of an
+       //      RR is always eligible for compression.
+       //
+       // Therefore, compressed domain names in RDATA is only handled for record types from
+       // <https://tools.ietf.org/html/rfc1035#section-3.3>.
+       
+       switch( inType )
+       {
+               case kDNSRecordType_CNAME:      // <https://tools.ietf.org/html/rfc1035#section-3.3.1>
+               case kDNSRecordType_MB:         // <https://tools.ietf.org/html/rfc1035#section-3.3.3>
+               case kDNSRecordType_MD:         // <https://tools.ietf.org/html/rfc1035#section-3.3.4>
+               case kDNSRecordType_MF:         // <https://tools.ietf.org/html/rfc1035#section-3.3.5>
+               case kDNSRecordType_MG:         // <https://tools.ietf.org/html/rfc1035#section-3.3.6>
+               case kDNSRecordType_MR:         // <https://tools.ietf.org/html/rfc1035#section-3.3.8>
+               case kDNSRecordType_NS:         // <https://tools.ietf.org/html/rfc1035#section-3.3.11>
+               case kDNSRecordType_PTR:        // <https://tools.ietf.org/html/rfc1035#section-3.3.12>
+               {
+                       // The RDATA consists of one domain name.
+                       
+                       err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inRDataPtr, name1, &ptr );
+                       require_noerr_quiet( err, exit );
+                       require_action_quiet( ptr == rdataEnd, exit, err = kMalformedErr );
+                       
+                       expandedLen     = DomainNameLength( name1 );
+                       copyLen         = Min( inBufLen, expandedLen );
+                       memcpy( inBufPtr, name1, copyLen );
+                       break;
+               }
+               case kDNSRecordType_MINFO:      // <https://tools.ietf.org/html/rfc1035#section-3.3.7>
+               {
+                       uint8_t *                                       dst = inBufPtr;
+                       const uint8_t * const           lim = &inBufPtr[ inBufLen ];
+                       size_t                                          nameLen1, nameLen2;
+                       
+                       // The RDATA consists of two domain names.
+                       
+                       err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inRDataPtr, name1, &ptr );
+                       require_noerr_quiet( err, exit );
+                       
+                       err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, ptr, name2, &ptr );
+                       require_noerr_quiet( err, exit );
+                       require_action_quiet( ptr == rdataEnd, exit, err = kMalformedErr );
+                       
+                       nameLen1        = DomainNameLength( name1 );
+                       nameLen2        = DomainNameLength( name2 );
+                       expandedLen     = nameLen1 + nameLen2;
+                       
+                       copyLen = (size_t)( lim - dst );
+                       copyLen = Min( copyLen, nameLen1 );
+                       memcpy( dst, name1, copyLen );
+                       dst += copyLen;
+                       
+                       copyLen = (size_t)( lim - dst );
+                       copyLen = Min( copyLen, nameLen2 );
+                       memcpy( dst, name2, copyLen );
+                       dst += copyLen;
+                       
+                       copyLen = (size_t)( dst - inBufPtr );
+                       break;
+               }
+               case kDNSRecordType_MX:         // <https://tools.ietf.org/html/rfc1035#section-3.3.9>
+               {
+                       uint8_t *                                       dst = inBufPtr;
+                       const uint8_t * const           lim = &inBufPtr[ inBufLen ];
+                       const uint8_t *                         exchange;
+                       size_t                                          nameLen1;
+                       
+                       // The RDATA format is a 2-octet preference value followed by exchange, which is a domain name.
+                       
+                       require_action_quiet( inRDataLen > 2, exit, err = kMalformedErr );
+                       exchange = &inRDataPtr[ 2 ];
+                       
+                       err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, exchange, name1, &ptr );
+                       require_noerr_quiet( err, exit );
+                       require_action_quiet( ptr == rdataEnd, exit, err = kMalformedErr );
+                       
+                       nameLen1        = DomainNameLength( name1 );
+                       expandedLen     = 2 + nameLen1;
+                       
+                       copyLen = (size_t)( lim - dst );
+                       copyLen = Min( copyLen, 2 );
+                       memcpy( dst, inRDataPtr, copyLen );
+                       dst += copyLen;
+                       
+                       copyLen = (size_t)( lim - dst );
+                       copyLen = Min( copyLen, nameLen1 );
+                       memcpy( dst, name1, copyLen );
+                       dst += copyLen;
+                       
+                       copyLen = (size_t)( dst - inBufPtr );
+                       break;
+               }
+               case kDNSRecordType_SOA:        // <https://tools.ietf.org/html/rfc1035#section-3.3.13>
+               {
+                       uint8_t *                                       dst = inBufPtr;
+                       const uint8_t * const           lim = &inBufPtr[ inBufLen ];
+                       size_t                                          nameLen1, nameLen2;
+                       
+                       // MNAME is a domain name.
+                       
+                       err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inRDataPtr, name1, &ptr );
+                       require_noerr_quiet( err, exit );
+                       
+                       // RNAME is a domain name.
+                       
+                       err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, ptr, name2, &ptr );
+                       require_noerr_quiet( err, exit );
+                       
+                       // MNAME and RNAME are followed by fixed-sized fields.
+                       
+                       require_action_quiet( ( rdataEnd - ptr ) == sizeof( dns_fixed_fields_soa ), exit, err = kMalformedErr );
+                       nameLen1        = DomainNameLength( name1 );
+                       nameLen2        = DomainNameLength( name2 );
+                       expandedLen     = nameLen1 + nameLen2 + sizeof( dns_fixed_fields_soa );
+                       
+                       copyLen = (size_t)( lim - dst );
+                       copyLen = Min( copyLen, nameLen1 );
+                       memcpy( dst, name1, copyLen );
+                       dst += copyLen;
+                       
+                       copyLen = (size_t)( lim - dst );
+                       copyLen = Min( copyLen, nameLen2 );
+                       memcpy( dst, name2, copyLen );
+                       dst += copyLen;
+                       
+                       copyLen = (size_t)( lim - dst );
+                       copyLen = Min( copyLen, sizeof( dns_fixed_fields_soa ) );
+                       memcpy( dst, ptr, copyLen );
+                       dst += copyLen;
+                       
+                       copyLen = (size_t)( dst - inBufPtr );
+                       break;
+               }       
+               default:
+               case kDNSRecordType_HINFO:      // <https://tools.ietf.org/html/rfc1035#section-3.3.2>
+               case kDNSRecordType_NULL:       // <https://tools.ietf.org/html/rfc1035#section-3.3.10>
+               case kDNSRecordType_TXT:        // <https://tools.ietf.org/html/rfc1035#section-3.3.14>
+               {
+                       // The RDATA contains no compressed domain names.
+                       
+                       expandedLen     = inRDataLen;
+                       copyLen         = Min( inBufLen, expandedLen );
+                       memcpy( inBufPtr, inRDataPtr, copyLen );
+                       break;
+               }
+       }
+       err = kNoErr;
+       
+       if( outCopiedRDataLen )         *outCopiedRDataLen              = copyLen;
+       if( outExpandedRDataLen )       *outExpandedRDataLen    = expandedLen;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+
+OSStatus
+       DomainNameAppendDomainName(
+               uint8_t                 inName[ STATIC_PARAM kDomainNameLengthMax ],
+               const uint8_t * inOtherName,
+               uint8_t **              outEnd )
+{
+       OSStatus                        err;
+       size_t                          newLen;
+       const size_t            adjustedLen     = DomainNameLength( inName ) - 1; // Don't count the root label.
+       const size_t            otherLen        = DomainNameLength( inOtherName );
+       
+       require_action_quiet( adjustedLen <= kDomainNameLengthMax, exit, err = kSizeErr );
+       require_action_quiet( otherLen <= kDomainNameLengthMax, exit, err = kSizeErr );
+       
+       newLen = adjustedLen + otherLen;
+       require_action_quiet( newLen <= kDomainNameLengthMax, exit, err = kSizeErr );
+       
+       memcpy( &inName[ adjustedLen ], inOtherName, otherLen );
+       if( outEnd ) *outEnd = &inName[ newLen ];
+       err = kNoErr;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+
 OSStatus
        DomainNameAppendString(
                uint8_t                 inDomainName[ STATIC_PARAM kDomainNameLengthMax ],
@@ -298,7 +927,6 @@ OSStatus
                root = dst;
                *root = 0;
        }
-       
        if( outEnd ) *outEnd = root + 1;
        err = kNoErr;
        
@@ -308,6 +936,10 @@ exit:
 
 //===========================================================================================================================
 
+#define _isupper_ascii( X )            ( ( (X) >= 'A' ) && ( (X) <= 'Z' ) )
+
+static void    _DomainNameLower( uint8_t *inName );
+
 OSStatus       DomainNameDupEx( const uint8_t *inName, Boolean inLower, uint8_t **outNamePtr, size_t *outNameLen )
 {
        OSStatus                        err;
@@ -317,29 +949,8 @@ OSStatus   DomainNameDupEx( const uint8_t *inName, Boolean inLower, uint8_t **outN
        namePtr = (uint8_t *) malloc( nameLen );
        require_action_quiet( namePtr, exit, err = kNoMemoryErr );
        
-       if( inLower )
-       {
-               const uint8_t *         src;
-               uint8_t *                       dst;
-               unsigned int            len;
-               
-               src = inName;
-               dst = namePtr;
-               while( ( len = *src ) != 0 )
-               {
-                       *dst++ = *src++;
-                       while( len-- > 0 )
-                       {
-                               const int c = *src++;
-                               *dst++ = (uint8_t) tolower_safe( c );
-                       }
-               }
-               *dst = 0;
-       }
-       else
-       {
-               memcpy( namePtr, inName, nameLen );
-       }
+       memcpy( namePtr, inName, nameLen );
+       if( inLower ) _DomainNameLower( namePtr );
        
        *outNamePtr = namePtr;
        if( outNameLen ) *outNameLen = nameLen;
@@ -349,8 +960,26 @@ exit:
        return( err );
 }
 
+static void    _DomainNameLower( uint8_t *inName )
+{
+       uint8_t *               ptr;
+       int                             len;
+       
+       ptr = inName;
+       while( ( len = *ptr++ ) != 0 )
+       {
+               while( len-- > 0 )
+               {
+                       if( _isupper_ascii( *ptr ) ) *ptr += 32;
+                       ++ptr;
+               }
+       }
+}
+
 //===========================================================================================================================
 
+#define _tolower_ascii( X )            ( _isupper_ascii( X ) ? ( (X) + 32 ) : (X) )
+
 Boolean        DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 )
 {
        const uint8_t *         p1 = inName1;
@@ -358,15 +987,15 @@ Boolean   DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 )
        if( p1 == p2 ) return( true );
        for( ;; )
        {
-               int                     len1 = *p1++;
-               const int       len2 = *p2++;
+               int                     len1    = *p1++;
+               const int       len2    = *p2++;
                if( len1 != len2 )      return( false );
                if( len1 == 0 )         return( true );
                while( len1-- > 0 )
                {
                        const int               c1 = *p1++;
                        const int               c2 = *p2++;
-                       if( tolower_safe( c1 ) != tolower_safe( c2 ) ) return( false );
+                       if( _tolower_ascii( c1 ) != _tolower_ascii( c2 ) ) return( false );
                }
        }
 }
@@ -385,23 +1014,61 @@ OSStatus
 
 //===========================================================================================================================
 
-OSStatus
-       DomainNameToString(
-               const uint8_t *         inName,
-               const uint8_t *         inLimit,
-               char                            outString[ STATIC_PARAM kDNSServiceMaxDomainName ],
-               const uint8_t **        outPtr )
+size_t DomainNameLength( const uint8_t *inName )
 {
-       OSStatus                        err;
        const uint8_t *         label;
-       uint8_t                         labelLen;
-       const uint8_t *         nextLabel;
-       char *                          dst;
-       const uint8_t *         src;
+       int                                     len;
        
-       require_action_quiet( !inLimit || ( inName < inLimit ), exit, err = kUnderrunErr );
-       
-       // Convert each label up until the root label, i.e., the zero-length label.
+       for( label = inName; ( len = *label ) != 0; label = &label[ 1 + len ] ) {}
+       return( (size_t)( label - inName ) + 1 );
+}
+
+//===========================================================================================================================
+
+int    DomainNameLabelCount( const uint8_t * const inName )
+{
+       const uint8_t *         label;
+       const uint8_t *         nextLabel;
+       int                                     labelCount, result;
+       unsigned int            labelLen;
+       
+       labelCount = 0;
+       for( label = inName; ( labelLen = *label ) != 0; label = nextLabel )
+       {
+               require_action_quiet( labelLen <= kDomainLabelLengthMax, exit, result = -1 );
+               
+               nextLabel = &label[ 1 + labelLen ];
+               require_action_quiet( ( nextLabel - inName ) < kDomainNameLengthMax, exit, result = -1 );
+               
+               ++labelCount;
+       }
+       result = labelCount;
+       
+exit:
+       return( result );
+}
+
+//===========================================================================================================================
+
+#define _isprint_ascii( X )            ( ( (X) >= 32 ) && ( (X) <= 126 ) )
+
+OSStatus
+       DomainNameToString(
+               const uint8_t *         inName,
+               const uint8_t *         inLimit,
+               char                            outString[ STATIC_PARAM kDNSServiceMaxDomainName ],
+               const uint8_t **        outPtr )
+{
+       OSStatus                        err;
+       const uint8_t *         label;
+       uint8_t                         labelLen;
+       const uint8_t *         nextLabel;
+       char *                          dst;
+       const uint8_t *         src;
+       
+       require_action_quiet( !inLimit || ( ( inLimit - inName ) > 0 ), exit, err = kUnderrunErr );
+       
+       // Convert each label up until the root label, i.e., the zero-length label.
        
        dst = outString;
        for( label = inName; ( labelLen = label[ 0 ] ) != 0; label = nextLabel )
@@ -414,8 +1081,7 @@ OSStatus
                
                for( src = &label[ 1 ]; src < nextLabel; ++src )
                {
-                       // Only allow 7-bit ASCII characters.
-                       if( ( *src >= 32 ) && ( *src <= 126 ) )
+                       if( _isprint_ascii( *src ) )
                        {
                                if( ( *src == '.' ) || ( *src == '\\' ) ||  ( *src == ' ' ) ) *dst++ = '\\';
                                *dst++ = (char) *src;
@@ -442,3 +1108,1828 @@ OSStatus
 exit:
        return( err );
 }
+
+//===========================================================================================================================
+
+Boolean        DomainLabelEqual( const uint8_t *inLabel1, const uint8_t *inLabel2 )
+{
+       const uint8_t *         p1 = inLabel1;
+       const uint8_t *         p2 = inLabel2;
+       if( p1 == p2 ) return( true );
+       int                     len1    = *p1++;
+       const int       len2    = *p2++;
+       if( len1 != len2 ) return( false );
+       while( len1-- > 0 )
+       {
+               const int               c1 = *p1++;
+               const int               c2 = *p2++;
+               if( _tolower_ascii( c1 ) != _tolower_ascii( c2 ) ) return( false );
+       }
+       return( true );
+}
+
+//===========================================================================================================================
+// This code was autogenerated on 2020-06-30 by dns-rr-func-autogen version 1.3
+// Data source URL: https://www.iana.org/assignments/dns-parameters/dns-parameters-4.csv
+// Overrides: none
+
+const char *   DNSRecordTypeValueToString( int inValue )
+{
+       const char *            string;
+
+       if( 0 ) {}
+       else if( ( inValue >= 1 ) && ( inValue <= 53 ) )
+       {
+               static const char * const               sNames_1_53[] =
+               {
+                       "A",            //   1
+                       "NS",           //   2
+                       "MD",           //   3
+                       "MF",           //   4
+                       "CNAME",        //   5
+                       "SOA",          //   6
+                       "MB",           //   7
+                       "MG",           //   8
+                       "MR",           //   9
+                       "NULL",         //  10
+                       "WKS",          //  11
+                       "PTR",          //  12
+                       "HINFO",        //  13
+                       "MINFO",        //  14
+                       "MX",           //  15
+                       "TXT",          //  16
+                       "RP",           //  17
+                       "AFSDB",        //  18
+                       "X25",          //  19
+                       "ISDN",         //  20
+                       "RT",           //  21
+                       "NSAP",         //  22
+                       "NSAP-PTR",     //  23
+                       "SIG",          //  24
+                       "KEY",          //  25
+                       "PX",           //  26
+                       "GPOS",         //  27
+                       "AAAA",         //  28
+                       "LOC",          //  29
+                       "NXT",          //  30
+                       "EID",          //  31
+                       "NIMLOC",       //  32
+                       "SRV",          //  33
+                       "ATMA",         //  34
+                       "NAPTR",        //  35
+                       "KX",           //  36
+                       "CERT",         //  37
+                       "A6",           //  38
+                       "DNAME",        //  39
+                       "SINK",         //  40
+                       "OPT",          //  41
+                       "APL",          //  42
+                       "DS",           //  43
+                       "SSHFP",        //  44
+                       "IPSECKEY",     //  45
+                       "RRSIG",        //  46
+                       "NSEC",         //  47
+                       "DNSKEY",       //  48
+                       "DHCID",        //  49
+                       "NSEC3",        //  50
+                       "NSEC3PARAM",   //  51
+                       "TLSA",         //  52
+                       "SMIMEA",       //  53
+               };
+               string = sNames_1_53[ inValue - 1 ];
+       }
+       else if( ( inValue >= 55 ) && ( inValue <= 65 ) )
+       {
+               static const char * const               sNames_55_65[] =
+               {
+                       "HIP",          //  55
+                       "NINFO",        //  56
+                       "RKEY",         //  57
+                       "TALINK",       //  58
+                       "CDS",          //  59
+                       "CDNSKEY",      //  60
+                       "OPENPGPKEY",   //  61
+                       "CSYNC",        //  62
+                       "ZONEMD",       //  63
+                       "SVCB",         //  64
+                       "HTTPS",        //  65
+               };
+               string = sNames_55_65[ inValue - 55 ];
+       }
+       else if( ( inValue >= 99 ) && ( inValue <= 109 ) )
+       {
+               static const char * const               sNames_99_109[] =
+               {
+                       "SPF",          //  99
+                       "UINFO",        // 100
+                       "UID",          // 101
+                       "GID",          // 102
+                       "UNSPEC",       // 103
+                       "NID",          // 104
+                       "L32",          // 105
+                       "L64",          // 106
+                       "LP",           // 107
+                       "EUI48",        // 108
+                       "EUI64",        // 109
+               };
+               string = sNames_99_109[ inValue - 99 ];
+       }
+       else if( ( inValue >= 249 ) && ( inValue <= 260 ) )
+       {
+               static const char * const               sNames_249_260[] =
+               {
+                       "TKEY",         // 249
+                       "TSIG",         // 250
+                       "IXFR",         // 251
+                       "AXFR",         // 252
+                       "MAILB",        // 253
+                       "MAILA",        // 254
+                       "ANY",          // 255
+                       "URI",          // 256
+                       "CAA",          // 257
+                       "AVC",          // 258
+                       "DOA",          // 259
+                       "AMTRELAY",     // 260
+               };
+               string = sNames_249_260[ inValue - 249 ];
+       }
+       else if( ( inValue >= 32768 ) && ( inValue <= 32769 ) )
+       {
+               static const char * const               sNames_32768_32769[] =
+               {
+                       "TA",           // 32768
+                       "DLV",          // 32769
+               };
+               string = sNames_32768_32769[ inValue - 32768 ];
+       }
+       else if( inValue == 65535 )
+       {
+               static const char * const               sNames_65535[] =
+               {
+                       "Reserved",     // 65535
+               };
+               string = sNames_65535[ inValue - 65535 ];
+       }
+       else
+       {
+               string = NULL;
+       }
+       return( string );
+}
+
+
+//===========================================================================================================================
+// This code was autogenerated on 2020-06-30 by dns-rr-func-autogen version 1.3
+// Data source URL: https://www.iana.org/assignments/dns-parameters/dns-parameters-4.csv
+// Overrides: none
+
+typedef struct
+{
+       const char *            name;
+       uint16_t                        value;
+
+}      _DNSRecordTypeItem;
+
+static int     _DNSRecordTypeStringToValueCmp( const void *inKey, const void *inElement );
+
+uint16_t       DNSRecordTypeStringToValue( const char *inString )
+{
+       // The name-value table is sorted by name in ascending lexicographical order to allow going from name to
+       // value in logarithmic time via a binary search.
+
+       static const _DNSRecordTypeItem         sTable[] =
+       {
+               { "A",              1 },
+               { "A6",            38 },
+               { "AAAA",          28 },
+               { "AFSDB",         18 },
+               { "AMTRELAY",     260 },
+               { "ANY",          255 },
+               { "APL",           42 },
+               { "ATMA",          34 },
+               { "AVC",          258 },
+               { "AXFR",         252 },
+               { "CAA",          257 },
+               { "CDNSKEY",       60 },
+               { "CDS",           59 },
+               { "CERT",          37 },
+               { "CNAME",          5 },
+               { "CSYNC",         62 },
+               { "DHCID",         49 },
+               { "DLV",        32769 },
+               { "DNAME",         39 },
+               { "DNSKEY",        48 },
+               { "DOA",          259 },
+               { "DS",            43 },
+               { "EID",           31 },
+               { "EUI48",        108 },
+               { "EUI64",        109 },
+               { "GID",          102 },
+               { "GPOS",          27 },
+               { "HINFO",         13 },
+               { "HIP",           55 },
+               { "HTTPS",         65 },
+               { "IPSECKEY",      45 },
+               { "ISDN",          20 },
+               { "IXFR",         251 },
+               { "KEY",           25 },
+               { "KX",            36 },
+               { "L32",          105 },
+               { "L64",          106 },
+               { "LOC",           29 },
+               { "LP",           107 },
+               { "MAILA",        254 },
+               { "MAILB",        253 },
+               { "MB",             7 },
+               { "MD",             3 },
+               { "MF",             4 },
+               { "MG",             8 },
+               { "MINFO",         14 },
+               { "MR",             9 },
+               { "MX",            15 },
+               { "NAPTR",         35 },
+               { "NID",          104 },
+               { "NIMLOC",        32 },
+               { "NINFO",         56 },
+               { "NS",             2 },
+               { "NSAP",          22 },
+               { "NSAP-PTR",      23 },
+               { "NSEC",          47 },
+               { "NSEC3",         50 },
+               { "NSEC3PARAM",    51 },
+               { "NULL",          10 },
+               { "NXT",           30 },
+               { "OPENPGPKEY",    61 },
+               { "OPT",           41 },
+               { "PTR",           12 },
+               { "PX",            26 },
+               { "Reserved",   65535 },
+               { "RKEY",          57 },
+               { "RP",            17 },
+               { "RRSIG",         46 },
+               { "RT",            21 },
+               { "SIG",           24 },
+               { "SINK",          40 },
+               { "SMIMEA",        53 },
+               { "SOA",            6 },
+               { "SPF",           99 },
+               { "SRV",           33 },
+               { "SSHFP",         44 },
+               { "SVCB",          64 },
+               { "TA",         32768 },
+               { "TALINK",        58 },
+               { "TKEY",         249 },
+               { "TLSA",          52 },
+               { "TSIG",         250 },
+               { "TXT",           16 },
+               { "UID",          101 },
+               { "UINFO",        100 },
+               { "UNSPEC",       103 },
+               { "URI",          256 },
+               { "WKS",           11 },
+               { "X25",           19 },
+               { "ZONEMD",        63 },
+       };
+       const _DNSRecordTypeItem *                      item;
+
+       item = (_DNSRecordTypeItem *) bsearch( inString, sTable, sizeof( sTable ) / sizeof( sTable[ 0 ] ),
+               sizeof( sTable[ 0 ] ), _DNSRecordTypeStringToValueCmp );
+       return( item ? item->value : 0 );
+}
+
+static int     _DNSRecordTypeStringToValueCmp( const void *inKey, const void *inElement )
+{
+       const _DNSRecordTypeItem * const                item = (const _DNSRecordTypeItem *) inElement;
+       return( strcasecmp( (const char *) inKey, item->name ) );
+}
+
+//===========================================================================================================================
+// This code was autogenerated on 2020-06-15 by dns-rcode-func-autogen version 1.0
+// Data source URL: https://www.iana.org/assignments/dns-parameters/dns-parameters-6.csv
+
+const char *   DNSRCodeToString( const int inValue )
+{
+       switch( inValue )
+       {
+               case kDNSRCode_NoError:     return( "NoError" );
+               case kDNSRCode_FormErr:     return( "FormErr" );
+               case kDNSRCode_ServFail:    return( "ServFail" );
+               case kDNSRCode_NXDomain:    return( "NXDomain" );
+               case kDNSRCode_NotImp:      return( "NotImp" );
+               case kDNSRCode_Refused:     return( "Refused" );
+               case kDNSRCode_YXDomain:    return( "YXDomain" );
+               case kDNSRCode_YXRRSet:     return( "YXRRSet" );
+               case kDNSRCode_NXRRSet:     return( "NXRRSet" );
+               case kDNSRCode_NotAuth:     return( "NotAuth" );
+               case kDNSRCode_NotZone:     return( "NotZone" );
+               case kDNSRCode_DSOTYPENI:   return( "DSOTYPENI" );
+               default:                    return( NULL );
+       }
+}
+
+//===========================================================================================================================
+// This code was autogenerated on 2020-06-15 by dns-rcode-func-autogen version 1.0
+// Data source URL: https://www.iana.org/assignments/dns-parameters/dns-parameters-6.csv
+
+typedef struct
+{
+       const char *            name;
+       int                                     value;
+       
+}      _DNSRCodeTableEntry;
+
+static int     _DNSRCodeFromStringCmp( const void *inKey, const void *inElement );
+
+int    DNSRCodeFromString( const char * const inString )
+{
+       // The name-value table is sorted by name in ascending lexicographical order to allow going from name to
+       // value in logarithmic time via a binary search.
+       
+       static const _DNSRCodeTableEntry                sTable[] =
+       {
+               { "DSOTYPENI",  kDNSRCode_DSOTYPENI },
+               { "FormErr",    kDNSRCode_FormErr   },
+               { "NoError",    kDNSRCode_NoError   },
+               { "NotAuth",    kDNSRCode_NotAuth   },
+               { "NotImp",     kDNSRCode_NotImp    },
+               { "NotZone",    kDNSRCode_NotZone   },
+               { "NXDomain",   kDNSRCode_NXDomain  },
+               { "NXRRSet",    kDNSRCode_NXRRSet   },
+               { "Refused",    kDNSRCode_Refused   },
+               { "ServFail",   kDNSRCode_ServFail  },
+               { "YXDomain",   kDNSRCode_YXDomain  },
+               { "YXRRSet",    kDNSRCode_YXRRSet   }
+       };
+       const _DNSRCodeTableEntry *                     entry;
+       
+       entry = (_DNSRCodeTableEntry *) bsearch( inString, sTable, sizeof( sTable ) / sizeof( sTable[ 0 ] ),
+               sizeof( sTable[ 0 ] ), _DNSRCodeFromStringCmp );
+       return( entry ? entry->value : -1 );
+}
+
+static int     _DNSRCodeFromStringCmp( const void * const inKey, const void * const inElement )
+{
+       const _DNSRCodeTableEntry * const               entry = (const _DNSRCodeTableEntry *) inElement;
+       return( strcasecmp( (const char *) inKey, entry->name ) );
+}
+
+//===========================================================================================================================
+// Note: Unknown resource record types and classes are represented as text using the convention described in
+// <https://tools.ietf.org/html/rfc3597#section-5>.
+
+static const char *    _DNSOpCodeToString( int inOpCode );
+
+typedef struct
+{
+       unsigned int            flag;
+       const char *            desc;
+       
+}      _DNSHeaderFlagDesc;
+
+static const _DNSHeaderFlagDesc                kDNSHeaderFlagsDescs[] =
+{
+       { kDNSHeaderFlag_AuthAnswer,                    "AA" },
+       { kDNSHeaderFlag_Truncation,                    "TC" },
+       { kDNSHeaderFlag_RecursionDesired,              "RD" },
+       { kDNSHeaderFlag_RecursionAvailable,    "RA" },
+       { kDNSHeaderFlag_Z,                                             "Z"  },
+       { kDNSHeaderFlag_AuthenticData,                 "AD" },
+       { kDNSHeaderFlag_CheckingDisabled,              "CD" },
+};
+
+OSStatus
+       DNSMessageToString(
+               const uint8_t *                                 inMsgPtr,
+               size_t                                                  inMsgLen,
+               const DNSMessageToStringFlags   inFlags,
+               char **                                                 outString )
+{
+       OSStatus                                err;
+       DataBuffer                              db;
+       const DNSHeader *               hdr;
+       const uint8_t *                 ptr;
+       const char *                    separator;
+       char *                                  rdataStr        = NULL;
+       const char *                    str;
+       size_t                                  len;
+       uint32_t                                qCount, aCount, authCount, addCount, i, totalCount;
+       uint8_t *                               name;
+       const uint8_t *                 nameCur;
+       uint8_t                                 dbBuf[ 256 ];
+       char                                    nameStr[ kDNSServiceMaxDomainName ];
+       uint8_t                                 nameBuf1[ kDomainNameLengthMax ];
+       uint8_t                                 nameBuf2[ kDomainNameLengthMax ];
+       const Boolean                   oneLine         = ( inFlags & kDNSMessageToStringFlag_OneLine )         ? true : false;
+       const Boolean                   isMDNS          = ( inFlags & kDNSMessageToStringFlag_MDNS )            ? true : false;
+       const Boolean                   rawRData        = ( inFlags & kDNSMessageToStringFlag_RawRData )        ? true : false;
+       const Boolean                   privacy         = ( inFlags & kDNSMessageToStringFlag_Privacy )         ? true : false;
+       const Boolean                   headerOnly      = ( inFlags & kDNSMessageToStringFlag_HeaderOnly )      ? true : false;
+       const Boolean                   bodyOnly        = ( inFlags & kDNSMessageToStringFlag_BodyOnly )        ? true : false;
+       
+       err = _DataBuffer_Init( &db, dbBuf, sizeof( dbBuf ), SIZE_MAX );
+       require_noerr_quiet( err, exit );
+       #define _AppendF( ... ) \
+               do { err = _DataBuffer_AppendF( &db, __VA_ARGS__ ); require_noerr_quiet( err, exit ); } while( 0 )
+       
+       require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr );
+       
+       hdr                     = (DNSHeader *) inMsgPtr;
+       qCount          = DNSHeaderGetQuestionCount( hdr );
+       aCount          = DNSHeaderGetAnswerCount( hdr );
+       authCount       = DNSHeaderGetAuthorityCount( hdr );
+       addCount        = DNSHeaderGetAdditionalCount( hdr );
+       separator       = "";
+       if( !bodyOnly )
+       {
+               const unsigned int              id              = DNSHeaderGetID( hdr );
+               const unsigned int              flags   = DNSHeaderGetFlags( hdr );
+               const int                               opcode  = DNSFlagsGetOpCode( flags );
+               const int                               rcode   = DNSFlagsGetRCode( flags );
+               
+               if( oneLine )
+               {
+                       _AppendF( "id: 0x%04X (%u), flags: 0x%04X (%c/",
+                               id, id, flags, ( flags & kDNSHeaderFlag_Response ) ? 'R' : 'Q' );
+               }
+               else
+               {
+                       _AppendF( "ID:               0x%04X (%u)\n", id, id );
+                       _AppendF( "Flags:            0x%04X %c/", flags, ( flags & kDNSHeaderFlag_Response ) ? 'R' : 'Q' );
+               }
+               str = _DNSOpCodeToString( opcode );
+               if( str )       _AppendF( "%s", str );
+               else            _AppendF( "OPCODE%d", opcode );
+               for( i = 0; i < countof( kDNSHeaderFlagsDescs ); ++i )
+               {
+                       const _DNSHeaderFlagDesc * const                flagDesc = &kDNSHeaderFlagsDescs[ i ];
+                       
+                       if( flags & flagDesc->flag ) _AppendF( ", %s", flagDesc->desc );
+               }
+               str = DNSRCodeToString( rcode );
+               if( str )       _AppendF( ", %s", str );
+               else            _AppendF( ", RCODE%d", rcode );
+               if( oneLine )
+               {
+                       _AppendF( "), counts: %u/%u/%u/%u", qCount, aCount, authCount, addCount );
+                       separator = ", ";
+               }
+               else
+               {
+                       _AppendF( "\n" );
+                       _AppendF( "Question count:   %u\n", qCount );
+                       _AppendF( "Answer count:     %u\n", aCount );
+                       _AppendF( "Authority count:  %u\n", authCount );
+                       _AppendF( "Additional count: %u\n", addCount );
+               }
+       }
+       if( headerOnly ) goto done;
+       
+       ptr                     = (const uint8_t *) &hdr[ 1 ];
+       name            = nameBuf1;
+       nameCur         = NULL;
+       for( i = 0; i < qCount; ++i )
+       {
+               uint16_t                qtype, qclass;
+               Boolean                 isQU;
+               
+               err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, name, &qtype, &qclass, &ptr );
+               require_noerr_quiet( err, exit );
+               
+               isQU = ( isMDNS && ( qclass & kMDNSClassUnicastResponseBit ) ) ? true : false;
+               if( isMDNS ) qclass &= ~kMDNSClassUnicastResponseBit;
+               if( oneLine )
+               {
+                       _AppendF( "%s", separator );
+                       if( !nameCur || !DomainNameEqual( name, nameCur ) )
+                       {
+                               err = DomainNameToString( name, NULL, nameStr, NULL );
+                               require_noerr_quiet( err, exit );
+                               
+                               if( privacy && _NameIsPrivate( nameStr ) ) _AppendF( "%~s ", nameStr );
+                               else                                                                       _AppendF( "%s ",  nameStr );
+                               nameCur = name;
+                               name    = ( name == nameBuf1 ) ? nameBuf2 : nameBuf1;
+                               *name = 0;
+                       }
+                       if( qclass == kDNSClassType_IN )        _AppendF( "IN" );
+                       else                                                            _AppendF( "CLASS%u", qclass );
+                       if( isMDNS ) _AppendF( " %s", isQU ? "QU" : "QM" );
+                       str = DNSRecordTypeValueToString( qtype );
+                       if( str )       _AppendF( " %s?", str );
+                       else            _AppendF( " TYPE%u?", qtype );
+                       separator = ", ";
+               }
+               else
+               {
+                       if( i == 0 ) _AppendF( "\nQUESTION SECTION\n" );
+                       err = DomainNameToString( name, NULL, nameStr, NULL );
+                       require_noerr_quiet( err, exit );
+                       
+                       if( privacy && _NameIsPrivate( nameStr ) ) _AppendF( "%~-30s", nameStr );
+                       else                                                                       _AppendF( "%-30s",  nameStr );
+                       _AppendF( " %2s", isMDNS ? ( isQU ? "QU" : "QM" ) : "" );
+                       if( qclass == kDNSClassType_IN )        _AppendF( " IN" );
+                       else                                                            _AppendF( " CLASS%u", qclass );
+                       str = DNSRecordTypeValueToString( qtype );
+                       if( str )       _AppendF( " %-5s\n", str );
+                       else            _AppendF( " TYPE%u\n", qtype );
+               }
+       }
+       totalCount = aCount + authCount + addCount;
+       for( i = 0; i < totalCount; ++i )
+       {
+               const uint8_t *         rdataPtr;
+               size_t                          rdataLen;
+               const char *            typeStr;
+               uint32_t                        ttl;
+               uint16_t                        type, class;
+               Boolean                         cacheFlush;
+               
+               err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, name, &type, &class, &ttl, &rdataPtr, &rdataLen, &ptr );
+               require_noerr_quiet( err, exit );
+               
+               err = DomainNameToString( name, NULL, nameStr, NULL );
+               require_noerr_quiet( err, exit );
+               
+               cacheFlush = ( isMDNS && ( class & kMDNSClassCacheFlushBit ) ) ? true : false;
+               if( isMDNS ) class &= ~kMDNSClassCacheFlushBit;
+               
+               if( oneLine ) 
+               {
+                       _AppendF( "%s", separator );
+                       if( !nameCur || !DomainNameEqual( name, nameCur ) )
+                       {
+                               err = DomainNameToString( name, NULL, nameStr, NULL );
+                               require_noerr_quiet( err, exit );
+                               
+                               if( privacy && _NameIsPrivate( nameStr ) ) _AppendF( "%~s ", nameStr );
+                               else                                                                       _AppendF( "%s ",  nameStr );
+                               nameCur = name;
+                               name    = ( name == nameBuf1 ) ? nameBuf2 : nameBuf1;
+                               *name = 0;
+                       }
+                       if( type == kDNSRecordType_OPT )
+                       {
+                               if( cacheFlush ) _AppendF( "CF " );
+                               _AppendF( "OPT %u", class );
+                               if( ttl == 0 )  _AppendF( " 0" );
+                               else                    _AppendF( " 0x%08X", ttl );
+                       }
+                       else
+                       {
+                               _AppendF( "%u", ttl );
+                               if( cacheFlush ) _AppendF( " CF" );
+                               if( class == kDNSClassType_IN ) _AppendF( " IN" );
+                               else                                                    _AppendF( " CLASS%u", class );
+                               typeStr = DNSRecordTypeValueToString( type );
+                               if( typeStr )   _AppendF( " %s", typeStr );
+                               else                    _AppendF( " TYPE%u", type );
+                       }
+                       separator = ", ";
+               }
+               else
+               {
+                       if(      ( aCount    != 0 ) && ( i ==   0                    ) ) _AppendF( "\nANSWER SECTION\n" );
+                       else if( ( authCount != 0 ) && ( i ==   aCount               ) ) _AppendF( "\nAUTHORITY SECTION\n" );
+                       else if( ( addCount  != 0 ) && ( i == ( aCount + authCount ) ) ) _AppendF( "\nADDITIONAL SECTION\n" );
+                       
+                       if( type == kDNSRecordType_OPT )
+                       {
+                               if( privacy && _NameIsPrivate( nameStr ) ) _AppendF( "%~s", nameStr );
+                               else                                                                       _AppendF( "%s",  nameStr );
+                               _AppendF( "%s OPT %u", cacheFlush ? " CF" : "", class );
+                               if( ttl == 0 ) _AppendF( " 0" );
+                               else               _AppendF( " 0x%08X", ttl );
+                       }
+                       else
+                       {
+                               if( privacy ) _AppendF( "%~-42s", nameStr );
+                               else              _AppendF( "%-42s",  nameStr );
+                               _AppendF( " %6u %2s", ttl, cacheFlush ? "CF" : "" );
+                               if( class == kDNSClassType_IN ) _AppendF( " IN" );
+                               else                                                    _AppendF( " CLASS%u", class );
+                               typeStr = DNSRecordTypeValueToString( type );
+                               if( typeStr )   _AppendF( " %-5s", typeStr );
+                               else                    _AppendF( " TYPE%u", type );
+                       }
+               }
+               if( !rawRData ) DNSRecordDataToStringEx( rdataPtr, rdataLen, type, inMsgPtr, inMsgLen, privacy, &rdataStr );
+               if( rdataStr )
+               {
+                       _AppendF( " %s", rdataStr );
+                       ForgetMem( &rdataStr );
+               }
+               else
+               {
+                       if( privacy ) _AppendF( " [%zu B]", rdataLen );
+                       else              _AppendF( " %#H", rdataPtr, (int) rdataLen, (int) rdataLen );
+               }
+               
+               if( oneLine )
+               {
+                       if( type == kDNSRecordType_CNAME )
+                       {
+                               if( DNSMessageExtractDomainName( inMsgPtr, inMsgLen, rdataPtr, name, NULL ) == kNoErr )
+                               {
+                                       nameCur = name;
+                                       name    = ( name == nameBuf1 ) ? nameBuf2 : nameBuf1;
+                               }
+                               *name = 0;
+                       }
+               }
+               else
+               {
+                       _AppendF( "\n" );
+               }
+       }
+       #undef _AppendF
+done:
+       err = _DataBuffer_Append( &db, "", 1 ); // NUL terminator.
+       require_noerr_quiet( err, exit );
+       
+       err = _DataBuffer_Detach( &db, (uint8_t **) outString, &len );
+       require_noerr_quiet( err, exit );
+       
+exit:
+       FreeNullSafe( rdataStr );
+       _DataBuffer_Free( &db );
+       return( err );
+}
+
+// See <https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5>.
+
+static const char *    _DNSOpCodeToString( int inOpCode )
+{
+       const char *            string;
+       
+       if( ( inOpCode >= 0 ) && ( inOpCode <= 6 ) )
+       {
+               static const char * const               sNames[] =
+               {
+                       "Query",        // 0
+                       "IQuery",       // 1
+                       "Status",       // 2
+                       NULL,           // 3 (Unassigned)
+                       "Notify",       // 4
+                       "Update",       // 5
+                       "DSO",          // 6
+               };
+               string = sNames[ inOpCode ];
+       }
+       else
+       {
+               string = NULL;
+       }
+       return( string );
+}
+
+//===========================================================================================================================
+
+static OSStatus
+       _DNSRecordDataAppendTypeBitMap(
+               DataBuffer *            inDB,
+               const uint8_t *         inPtr,
+               const uint8_t *         inEnd,
+               const uint8_t **        outPtr );
+
+OSStatus
+       DNSRecordDataToStringEx(
+               const void * const      inRDataPtr,
+               const size_t            inRDataLen,
+               const int                       inRecordType,
+               const void * const      inMsgPtr,
+               const size_t            inMsgLen,
+               const Boolean           inPrivacy,
+               char ** const           outString )
+{
+       OSStatus                                        err;
+       const uint8_t * const           rdataPtr        = (uint8_t *) inRDataPtr;
+       const uint8_t * const           rdataEnd        = rdataPtr + inRDataLen;
+       const void * const                      msgPtr          = inMsgPtr;
+       const uint8_t *                         ptr;
+       DataBuffer                                      db;
+       size_t                                          len;
+       uint8_t                                         dbBuf[ 256 ];
+       char                                            domainNameStr[ kDNSServiceMaxDomainName ];
+       
+       err = _DataBuffer_Init( &db, dbBuf, sizeof( dbBuf ), SIZE_MAX );
+       require_noerr_quiet( err, exit );
+       
+       // A Record
+       
+       if( inRecordType == kDNSRecordType_A )
+       {
+               require_action_quiet( inRDataLen == 4, exit, err = kMalformedErr );
+               
+               err = _AppendIPv4Address( &db, NULL, rdataPtr, inPrivacy );
+               require_noerr_quiet( err, exit );
+       }
+       
+       // AAAA Record
+       
+       else if( inRecordType == kDNSRecordType_AAAA )
+       {
+               require_action_quiet( inRDataLen == 16, exit, err = kMalformedErr );
+               
+               err = _AppendIPv6Address( &db, NULL, rdataPtr, inPrivacy );
+               require_noerr_quiet( err, exit );
+       }
+       
+       // PTR, CNAME, or NS Record
+       
+       else if( ( inRecordType == kDNSRecordType_PTR )   ||
+                        ( inRecordType == kDNSRecordType_CNAME ) ||
+                        ( inRecordType == kDNSRecordType_NS ) )
+       {
+               if( msgPtr )
+               {
+                       err = DNSMessageExtractDomainNameString( msgPtr, inMsgLen, rdataPtr, domainNameStr, NULL );
+                       require_noerr_quiet( err, exit );
+               }
+               else
+               {
+                       err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, NULL );
+                       require_noerr_quiet( err, exit );
+               }
+               err = _AppendDomainNameString( &db, inPrivacy, domainNameStr );
+               require_noerr_quiet( err, exit );
+       }
+       
+       // SRV Record
+       
+       else if( inRecordType == kDNSRecordType_SRV )
+       {
+               const dns_fixed_fields_srv *            fields;
+               const uint8_t *                                         target;
+               
+               require_action_quiet( inRDataLen > sizeof( dns_fixed_fields_srv ), exit, err = kMalformedErr );
+               
+               fields = (const dns_fixed_fields_srv *) rdataPtr;
+               target = (const uint8_t *) &fields[ 1 ];
+               if( msgPtr )
+               {
+                       err = DNSMessageExtractDomainNameString( msgPtr, inMsgLen, target, domainNameStr, NULL );
+                       require_noerr_quiet( err, exit );
+               }
+               else
+               {
+                       err = DomainNameToString( target, rdataEnd, domainNameStr, NULL );
+                       require_noerr_quiet( err, exit );
+               }
+               err = _DataBuffer_AppendF( &db, "%u %u %u ",
+                       dns_fixed_fields_srv_get_priority( fields ),
+                       dns_fixed_fields_srv_get_weight( fields ),
+                       dns_fixed_fields_srv_get_port( fields ) );
+               require_noerr_quiet( err, exit );
+               
+               err = _AppendDomainNameString( &db, inPrivacy, domainNameStr );
+               require_noerr_quiet( err, exit );
+       }
+       
+       // TXT or HINFO Record
+       // See <https://tools.ietf.org/html/rfc1035#section-3.3.14> and <https://tools.ietf.org/html/rfc1035#section-3.3.2>.
+       
+       else if( ( inRecordType == kDNSRecordType_TXT ) || ( inRecordType == kDNSRecordType_HINFO ) )
+       {
+               require_action_quiet( inRDataLen > 0, exit, err = kMalformedErr );
+               
+               if( inPrivacy )
+               {
+                       err = _DataBuffer_AppendF( &db, "[%zu B]", inRDataLen );
+                       require_noerr_quiet( err, exit );
+               }
+               else
+               {
+                       if( inRDataLen == 1 )
+                       {
+                               err = _DataBuffer_AppendF( &db, "%#H", rdataPtr, (int) inRDataLen, (int) inRDataLen );
+                               require_noerr_quiet( err, exit );
+                       }
+                       else
+                       {
+                               err = _DataBuffer_AppendF( &db, "%#{txt}", rdataPtr, (size_t) inRDataLen );
+                               require_noerr_quiet( err, exit );
+                       }
+               }
+       }
+       
+       // SOA Record
+       
+       else if( inRecordType == kDNSRecordType_SOA )
+       {
+               const dns_fixed_fields_soa *            fields;
+               
+               // Get MNAME.
+               
+               if( msgPtr )
+               {
+                       err = DNSMessageExtractDomainNameString( msgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
+                       require_noerr_quiet( err, exit );
+                       require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
+               }
+               else
+               {
+                       err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
+                       require_noerr_quiet( err, exit );
+               }
+               err = _AppendDomainNameString( &db, inPrivacy, domainNameStr );
+               require_noerr_quiet( err, exit );
+               
+               // Get RNAME.
+               
+               if( msgPtr )
+               {
+                       err = DNSMessageExtractDomainNameString( msgPtr, inMsgLen, ptr, domainNameStr, &ptr );
+                       require_noerr_quiet( err, exit );
+               }
+               else
+               {
+                       err = DomainNameToString( ptr, rdataEnd, domainNameStr, &ptr );
+                       require_noerr_quiet( err, exit );
+               }
+               err = _AppendDomainNameStringEx( &db, " ", inPrivacy, domainNameStr );
+               require_noerr_quiet( err, exit );
+               
+               require_action_quiet( ( rdataEnd - ptr ) == sizeof( dns_fixed_fields_soa ), exit, err = kMalformedErr );
+               
+               fields = (const dns_fixed_fields_soa *) ptr;
+               err = _DataBuffer_AppendF( &db, " %u %u %u %u %u",
+                       dns_fixed_fields_soa_get_serial( fields ),
+                       dns_fixed_fields_soa_get_refresh( fields ),
+                       dns_fixed_fields_soa_get_retry( fields ),
+                       dns_fixed_fields_soa_get_expire( fields ),
+                       dns_fixed_fields_soa_get_minimum( fields ) );
+               require_noerr_quiet( err, exit );
+       }
+       
+       // NSEC Record
+       
+       else if( inRecordType == kDNSRecordType_NSEC )
+       {
+               if( msgPtr )
+               {
+                       err = DNSMessageExtractDomainNameString( msgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
+                       require_noerr_quiet( err, exit );
+               }
+               else
+               {
+                       err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
+                       require_noerr_quiet( err, exit );
+               }
+               require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
+               
+               err = _AppendDomainNameString( &db, inPrivacy, domainNameStr );
+               require_noerr_quiet( err, exit );
+               
+               err = _DNSRecordDataAppendTypeBitMap( &db, ptr, rdataEnd, NULL );
+               require_noerr_quiet( err, exit );
+       }
+       
+       // MX Record
+       
+       else if( inRecordType == kDNSRecordType_MX )
+       {
+               uint16_t                        preference;
+               const uint8_t *         exchange;
+               
+               require_action_quiet( ( rdataEnd - rdataPtr ) > 2, exit, err = kMalformedErr );
+               
+               preference      = ReadBig16( rdataPtr );
+               exchange        = &rdataPtr[ 2 ];
+               
+               if( msgPtr )
+               {
+                       err = DNSMessageExtractDomainNameString( msgPtr, inMsgLen, exchange, domainNameStr, NULL );
+                       require_noerr_quiet( err, exit );
+               }
+               else
+               {
+                       err = DomainNameToString( exchange, rdataEnd, domainNameStr, NULL );
+                       require_noerr_quiet( err, exit );
+               }
+               err = _DataBuffer_AppendF( &db, "%u", preference );
+               require_noerr_quiet( err, exit );
+               
+               err = _AppendDomainNameStringEx( &db, " ", inPrivacy, domainNameStr );
+               require_noerr_quiet( err, exit );
+       }
+       
+       // DNSKEY Record (see <https://tools.ietf.org/html/rfc4034#section-2.2>)
+       
+       else if( inRecordType == kDNSRecordType_DNSKEY )
+       {
+               const dns_fixed_fields_dnskey *         fields;
+               char *                                                          publicKeyBase64;
+               
+               require_action_quiet( ( (size_t)( rdataEnd - rdataPtr ) ) > sizeof( *fields ), exit, err = kMalformedErr );
+               
+               fields = (const dns_fixed_fields_dnskey *) rdataPtr;
+               err = _DataBuffer_AppendF( &db, "%u %u %u",
+                       dns_fixed_fields_dnskey_get_flags( fields ),
+                       dns_fixed_fields_dnskey_get_protocol( fields ),
+                       dns_fixed_fields_dnskey_get_algorithm( fields ) );
+               require_noerr_quiet( err, exit );
+               
+               // Public Key
+               
+               ptr = (const uint8_t *) &fields[ 1 ];
+               publicKeyBase64 = NULL;
+               err = _Base64EncodeCopyEx( ptr, (size_t)( rdataEnd - ptr ), kBase64Flags_None, &publicKeyBase64, NULL );
+               require_noerr_quiet( err, exit );
+               
+               err = _DataBuffer_AppendF( &db, " %s", publicKeyBase64 );
+               ForgetMem( &publicKeyBase64 );
+               require_noerr_quiet( err, exit );
+       }
+       
+       // RRSIG Record (see <https://tools.ietf.org/html/rfc4034#section-3.2>)
+       
+       else if( inRecordType == kDNSRecordType_RRSIG )
+       {
+               const dns_fixed_fields_rrsig *          fields;
+               const char *                                            typeStr;
+               int                                                                     year, month, day, hour, minute, second;
+               char *                                                          signatureBase64;
+               
+               require_action_quiet( ( (size_t)( rdataEnd - rdataPtr ) ) > sizeof( *fields ), exit, err = kMalformedErr );
+               
+               fields  = (const dns_fixed_fields_rrsig *) rdataPtr;
+               typeStr = DNSRecordTypeValueToString( dns_fixed_fields_rrsig_get_type_covered( fields ) );
+               if( typeStr )
+               {
+                       err = _DataBuffer_AppendF( &db, "%s", typeStr );
+                       require_noerr_quiet( err, exit );
+               }
+               else
+               {
+                       err = _DataBuffer_AppendF( &db, "TYPE%u", dns_fixed_fields_rrsig_get_type_covered( fields ) );
+                       require_noerr_quiet( err, exit );
+               }
+               err = _DataBuffer_AppendF( &db, " %u %u %u",
+                       dns_fixed_fields_rrsig_get_algorithm( fields ),
+                       dns_fixed_fields_rrsig_get_labels( fields ),
+                       dns_fixed_fields_rrsig_get_original_ttl( fields ) );
+               require_noerr_quiet( err, exit );
+               
+               year    = 0;
+               month   = 0;
+               day             = 0;
+               hour    = 0;
+               minute  = 0;
+               second  = 0;
+               err = _SecondsToYMD_HMS( ( INT64_C_safe( kDaysToUnixEpoch ) * kSecondsPerDay ) +
+                       dns_fixed_fields_rrsig_get_signature_expiration( fields ), &year, &month, &day, &hour, &minute, &second );
+               require_noerr_quiet( err, exit );
+               
+               err = _DataBuffer_AppendF( &db, " %u%02u%02u%02u%02u%02u", year, month, day, hour, minute, second );
+               require_noerr_quiet( err, exit );
+               
+               err = _SecondsToYMD_HMS( ( INT64_C_safe( kDaysToUnixEpoch ) * kSecondsPerDay ) +
+                       dns_fixed_fields_rrsig_get_signature_inception( fields ), &year, &month, &day, &hour, &minute, &second );
+               require_noerr_quiet( err, exit );
+               
+               err = _DataBuffer_AppendF( &db, " %u%02u%02u%02u%02u%02u", year, month, day, hour, minute, second );
+               require_noerr_quiet( err, exit );
+               
+               err = _DataBuffer_AppendF( &db, " %u", dns_fixed_fields_rrsig_get_key_tag( fields ) );
+               require_noerr_quiet( err, exit );
+               
+               // Signer's name
+               
+               ptr = (const uint8_t *) &fields[ 1 ];
+               err = DomainNameToString( ptr, rdataEnd, domainNameStr, &ptr );
+               require_noerr_quiet( err, exit );
+               
+               err = _AppendDomainNameStringEx( &db, " ", inPrivacy, domainNameStr );
+               require_noerr_quiet( err, exit );
+               
+               // Signature
+               
+               signatureBase64 = NULL;
+               err = _Base64EncodeCopyEx( ptr, (size_t)( rdataEnd - ptr ), kBase64Flags_None, &signatureBase64, NULL );
+               require_noerr_quiet( err, exit );
+               
+               err = _DataBuffer_AppendF( &db, " %s", signatureBase64 );
+               FreeNullSafe( signatureBase64 );
+               require_noerr_quiet( err, exit );
+       }
+       
+       // NSEC3 Record (see <https://tools.ietf.org/html/rfc5155#section-3.3>)
+       
+       else if( inRecordType == kDNSRecordType_NSEC3 )
+       {
+               const dns_fixed_fields_nsec3 *          fields;
+               size_t                                                          saltLen, hashLen, rem;
+               const uint8_t *                                         hashEnd;
+               
+               require_action_quiet( ( (size_t)( rdataEnd - rdataPtr ) ) > sizeof( *fields ), exit, err = kMalformedErr );
+               
+               fields = (const dns_fixed_fields_nsec3 *) rdataPtr;
+               err = _DataBuffer_AppendF( &db, "%u %u %u",
+                       dns_fixed_fields_nsec3_get_hash_alg( fields ),
+                       dns_fixed_fields_nsec3_get_flags( fields ),
+                       dns_fixed_fields_nsec3_get_iterations( fields ) );
+               require_noerr_quiet( err, exit );
+               
+               ptr = (const uint8_t *) &fields[ 1 ];
+               require_action_quiet( ( rdataEnd - ptr ) >= 1, exit, err = kMalformedErr );
+               
+               saltLen = *ptr++;
+               require_action_quiet( ( (size_t)( rdataEnd - ptr ) ) >= saltLen, exit, err = kMalformedErr );
+               err = _DataBuffer_AppendF( &db, " %.4H", ptr, (int) saltLen, (int) saltLen );
+               require_noerr_quiet( err, exit );
+               
+               ptr += saltLen;
+               require_action_quiet( ( rdataEnd - ptr ) >= 1, exit, err = kMalformedErr );
+               
+               hashLen = *ptr++;
+               require_action_quiet( ( (size_t)( rdataEnd - ptr ) ) >= hashLen, exit, err = kMalformedErr );
+               
+               if( hashLen > 0 )
+               {
+                       err = _DataBuffer_AppendF( &db, " " );
+                       require_noerr_quiet( err, exit );
+               }
+               
+               // Unpadded Base 32 Encoding with Extended Hex Alphabet (see <https://tools.ietf.org/html/rfc4648#section-7>)
+               // A full quantum is 40 bits, i.e., five concatenated 8-bit input groups are treated as eight concatenated 5-bit
+               // groups.
+               
+               hashEnd = ptr + hashLen;
+               while( ( rem = (size_t)( hashEnd - ptr ) ) > 0 )
+               {
+                       uint64_t                                quantum;
+                       size_t                                  encodedLen;
+                       char                                    encodedBuf[ 8 ];
+                       static const char               kBase32ExtendedHex[] = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
+                       
+                       check_compile_time_code( sizeof_string( kBase32ExtendedHex ) == 32 );
+                       
+                       quantum = 0;
+                       switch( rem )
+                       {
+                               default:
+                               case 5: quantum |=   (uint64_t)ptr[ 4 ];                        // Bits 32 - 39
+                               case 4: quantum |= ( (uint64_t)ptr[ 3 ] ) <<  8;        // Bits 24 - 31
+                               case 3: quantum |= ( (uint64_t)ptr[ 2 ] ) << 16;        // Bits 16 - 23
+                               case 2: quantum |= ( (uint64_t)ptr[ 1 ] ) << 24;        // Bits  8 - 15
+                               case 1: quantum |= ( (uint64_t)ptr[ 0 ] ) << 32;        // Bits  0 -  7
+                       }
+                       ptr += ( ( rem > 5 ) ? 5 : rem );
+                       
+                       encodedLen = 0;
+                       switch( rem )
+                       {
+                               default:
+                               case 5:
+                                       encodedBuf[ 7 ] = kBase32ExtendedHex[   quantum         & 0x1F ]; // Bits 35 - 39
+                                       encodedLen = 8;
+                                       
+                               case 4:
+                                       encodedBuf[ 6 ] = kBase32ExtendedHex[ ( quantum >>  5 ) & 0x1F ]; // Bits 30 - 34
+                                       encodedBuf[ 5 ] = kBase32ExtendedHex[ ( quantum >> 10 ) & 0x1F ]; // Bits 25 - 29
+                                       if( encodedLen == 0 ) encodedLen = 7;
+                                       
+                               case 3:
+                                       encodedBuf[ 4 ] = kBase32ExtendedHex[ ( quantum >> 15 ) & 0x1F ]; // Bits 20 - 24
+                                       if( encodedLen == 0 ) encodedLen = 5;
+                                       
+                               case 2:
+                                       encodedBuf[ 3 ] = kBase32ExtendedHex[ ( quantum >> 20 ) & 0x1F ]; // Bits 15 - 19
+                                       encodedBuf[ 2 ] = kBase32ExtendedHex[ ( quantum >> 25 ) & 0x1F ]; // Bits 10 - 14
+                                       if( encodedLen == 0 ) encodedLen = 4;
+                                       
+                               case 1:
+                                       encodedBuf[ 1 ] = kBase32ExtendedHex[ ( quantum >> 30 ) & 0x1F ]; // Bits 5  - 9
+                                       encodedBuf[ 0 ] = kBase32ExtendedHex[ ( quantum >> 35 ) & 0x1F ]; // Bits 0  - 4
+                                       if( encodedLen == 0 ) encodedLen = 2;
+                       }
+                       err = _DataBuffer_Append( &db, encodedBuf, encodedLen );
+                       require_noerr_quiet( err, exit );
+               }
+               err = _DNSRecordDataAppendTypeBitMap( &db, ptr, rdataEnd, NULL );
+               require_noerr_quiet( err, exit );
+       }
+       
+       // DS Record (see <https://tools.ietf.org/html/rfc4034#section-5.3>)
+       
+       else if( inRecordType == kDNSRecordType_DS )
+       {
+               const dns_fixed_fields_ds *             fields;
+               const uint8_t *                                 digestPtr;
+               size_t                                                  digestLen;
+               
+               require_action_quiet( ( (size_t)( rdataEnd - rdataPtr ) ) >= sizeof( *fields ), exit, err = kMalformedErr );
+               
+               fields = (const dns_fixed_fields_ds *) rdataPtr;
+               err = _DataBuffer_AppendF( &db, "%u %u %u",
+                       dns_fixed_fields_ds_get_key_tag( fields ),
+                       dns_fixed_fields_ds_get_algorithm( fields ),
+                       dns_fixed_fields_ds_get_digest_type( fields ) );
+               require_noerr_quiet( err, exit );
+               
+               digestPtr = (const uint8_t *) &fields[ 1 ];
+               digestLen = (size_t)( rdataEnd - digestPtr );
+               if( digestLen > 0 )
+               {
+                       err = _DataBuffer_AppendF( &db, " %.4H", digestPtr, (int) digestLen, (int) digestLen );
+                       require_noerr_quiet( err, exit );
+               }
+       }
+       
+       // HTTPS or SVCB Record
+       
+       else if( ( inRecordType == kDNSRecordType_HTTPS ) || ( inRecordType == kDNSRecordType_SVCB ) )
+       {
+               err = _AppendSVCBRDataString( &db, rdataPtr, rdataEnd, inPrivacy );
+               require_noerr_quiet( err, exit );
+       }
+       
+       // OPT Record (see <https://tools.ietf.org/html/rfc6891#section-6.1.2>)
+       
+       else if( inRecordType == kDNSRecordType_OPT )
+       {
+               err = _AppendOPTRDataString( &db, rdataPtr, rdataEnd, inPrivacy );
+               require_noerr_quiet( err, exit );
+       }
+       
+       // Unhandled record type
+       
+       else
+       {
+               err = kNotHandledErr;
+               goto exit;
+       }
+       err = _DataBuffer_Append( &db, "", 1 ); // NUL terminator.
+       require_noerr_quiet( err, exit );
+       
+       err = _DataBuffer_Detach( &db, (uint8_t **) outString, &len );
+       require_noerr_quiet( err, exit );
+       
+exit:
+       _DataBuffer_Free( &db );
+       return( err );
+}
+
+static OSStatus
+       _DNSRecordDataAppendTypeBitMap(
+               DataBuffer *            inDB,
+               const uint8_t *         inPtr,
+               const uint8_t *         inEnd,
+               const uint8_t **        outPtr )
+{
+       OSStatus                        err;
+       const uint8_t *         ptr = inPtr;
+       int                                     bitmapLen;
+       
+       while( ( inEnd - ptr ) > 0 )
+       {
+               int             windowBlock, i;
+               
+               require_action_quiet( ( inEnd - ptr ) > 2, exit, err = kMalformedErr );
+               
+               windowBlock     = *ptr++;
+               bitmapLen       = *ptr++;
+               require_action_quiet( ( bitmapLen >= 1 ) && ( bitmapLen <= 32 ) , exit, err = kMalformedErr );
+               require_action_quiet( ( inEnd - ptr ) >= bitmapLen, exit, err = kMalformedErr );
+               
+               for( i = 0; i < BitArray_MaxBits( bitmapLen ); ++i )
+               {
+                       const int               windowBase = windowBlock * 256;
+                       
+                       if( BitArray_GetBit( ptr, bitmapLen, i ) )
+                       {
+                               int                                     recordType;
+                               const char *            typeStr;
+                               char                            typeBuf[ 32 ];
+                               
+                               recordType = windowBase + i;
+                               typeStr = DNSRecordTypeValueToString( recordType );
+                               if( !typeStr )
+                               {
+                                       snprintf( typeBuf, sizeof( typeBuf ), "TYPE%u", recordType );
+                                       typeStr = typeBuf;
+                               }
+                               err = _DataBuffer_AppendF( inDB, " %s", typeStr );
+                               require_noerr_quiet( err, exit );
+                       }
+               }
+               ptr += bitmapLen;
+       }
+       if( outPtr ) *outPtr = ptr;
+       err = kNoErr;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+// Based on reference implementation from <https://tools.ietf.org/html/rfc4034#appendix-B>.
+
+uint16_t       DNSComputeDNSKeyTag( const void *inRDataPtr, size_t inRDataLen )
+{
+       const uint8_t * const           rdataPtr = (const uint8_t *) inRDataPtr;
+       uint32_t                                        accumulator;
+       size_t                                          i;
+       
+       accumulator = 0;
+       for( i = 0; i < inRDataLen; ++i )
+       {
+               accumulator += ( i & 1 ) ? rdataPtr[ i ] : (uint32_t)( rdataPtr[ i ] << 8 );
+       }
+       accumulator += ( accumulator >> 16 ) & UINT32_C( 0xFFFF );
+       return( accumulator & UINT32_C( 0xFFFF ) );
+}
+
+//===========================================================================================================================
+
+int    DNSMessagePrintObfuscatedString( char *inBufPtr, size_t inBufLen, const char *inString )
+{
+       if( _NameIsPrivate( inString ) )
+       {
+               return( _SNPrintF( inBufPtr, inBufLen, "%~s", inString ) );
+       }
+       else
+       {
+               return( _SNPrintF( inBufPtr, inBufLen, "%s", inString ) );
+       }
+}
+
+//===========================================================================================================================
+
+int    DNSMessagePrintObfuscatedIPv4Address( char *inBufPtr, size_t inBufLen, const uint32_t inAddr )
+{
+       uint8_t         addrBytes[ 4 ];
+       
+       WriteBig32( addrBytes, inAddr );
+       if( !_IPv4AddressIsWhitelisted( addrBytes ) )
+       {
+               return( _DNSMessagePrintObfuscatedIPAddress( inBufPtr, inBufLen, addrBytes, sizeof( addrBytes ) ) );
+       }
+       else
+       {
+               return( _SNPrintF( inBufPtr, inBufLen, "%#.4a", &inAddr ) );
+       }
+}
+
+//===========================================================================================================================
+
+int    DNSMessagePrintObfuscatedIPv6Address( char *inBufPtr, size_t inBufLen, const uint8_t inAddr[ STATIC_PARAM 16 ] )
+{
+       if( !_IPv6AddressIsWhitelisted( inAddr ) )
+       {
+               return( _DNSMessagePrintObfuscatedIPAddress( inBufPtr, inBufLen, inAddr, 16 ) );
+       }
+       else
+       {
+               return( _SNPrintF( inBufPtr, inBufLen, "%.16a", inAddr ) );
+       }
+}
+
+//===========================================================================================================================
+// MARK: - Helper Functions
+
+static Boolean _NameIsPrivate( const char * const inDomainNameStr )
+{
+       if( strcasecmp( inDomainNameStr, "." )                          == 0 ) return( false );
+       if( strcasecmp( inDomainNameStr, "ipv4only.arpa." )     == 0 ) return( false );
+       return( true );
+}
+
+//===========================================================================================================================
+
+static OSStatus
+       _AppendDomainNameString(
+               DataBuffer * const      inDB,
+               const Boolean           inPrivacy,
+               const char * const      inDomainNameStr )
+{
+       return( _AppendDomainNameStringEx( inDB, NULL, inPrivacy, inDomainNameStr ) );
+}
+
+//===========================================================================================================================
+
+static OSStatus
+       _AppendDomainNameStringEx(
+               DataBuffer * const      inDB,
+               const char * const      inSeparator,
+               const Boolean           inPrivacy,
+               const char * const      inDomainNameStr )
+{
+       OSStatus                                err;
+       const char * const              sep = inSeparator ? inSeparator : "";
+       
+       if( inPrivacy && _NameIsPrivate( inDomainNameStr ) )
+       {
+               err = _DataBuffer_AppendF( inDB, "%s%~s", sep, inDomainNameStr );
+               require_noerr_quiet( err, exit );
+       }
+       else
+       {
+               err = _DataBuffer_AppendF( inDB, "%s%s", sep, inDomainNameStr );
+               require_noerr_quiet( err, exit );
+       }
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus
+       _AppendOPTRDataString(
+               DataBuffer * const              inDB,
+               const uint8_t * const   inRDataPtr,
+               const uint8_t * const   inRDataEnd,
+               const Boolean                   inPrivacy )
+{
+       OSStatus                        err;
+       const uint8_t *         ptr;
+       const char *            sep;
+       
+       ptr = inRDataPtr;
+       require_action_quiet( ptr <= inRDataEnd, exit, err = kRangeErr );
+       
+       sep = "";
+       while( ptr < inRDataEnd )
+       {
+               const dns_fixed_fields_option *         fields;
+               const uint8_t *                                         data;
+               unsigned int                                            code, length;
+               
+               require_action_quiet( ( (size_t)( inRDataEnd - ptr ) ) >= sizeof( *fields ), exit, err = kUnderrunErr );
+               fields = (const dns_fixed_fields_option *) ptr;
+               code    = dns_fixed_fields_option_get_code( fields );
+               length  = dns_fixed_fields_option_get_length( fields );
+               ptr = (const uint8_t *) &fields[ 1 ];
+               
+               require_action_quiet( ( (size_t)( inRDataEnd - ptr ) ) >= length, exit, err = kUnderrunErr );
+               data = ptr;
+               ptr += length;
+               
+               err = _DataBuffer_AppendF( inDB, "%s{", sep );
+               require_noerr_quiet( err, exit );
+               
+               if( code == kDNSEDNS0OptionCode_Padding )
+               {
+                       err = _DataBuffer_AppendF( inDB, "Padding" );
+                       require_noerr_quiet( err, exit );
+               }
+               else
+               {
+                       err = _DataBuffer_AppendF( inDB, "CODE%u", code );
+                       require_noerr_quiet( err, exit );
+               }
+               err = _DataBuffer_AppendF( inDB, ", " );
+               require_noerr_quiet( err, exit );
+               
+               if( inPrivacy )
+               {
+                       err = _DataBuffer_AppendF( inDB, "[%u B]", length );
+                       require_noerr_quiet( err, exit );
+               }
+               else
+               {
+                       if( ( code == kDNSEDNS0OptionCode_Padding ) && _MemIsAllZeros( data, length ) )
+                       {
+                               err = _DataBuffer_AppendF( inDB, "<%u zero bytes>", length );
+                               require_noerr_quiet( err, exit );
+                       }
+                       else
+                       {
+                               err = _DataBuffer_AppendF( inDB, "'%H'", data, (int) length, (int) length );
+                               require_noerr_quiet( err, exit );
+                       }
+               }
+               err = _DataBuffer_AppendF( inDB, "}" );
+               require_noerr_quiet( err, exit );
+               
+               sep = ", ";
+       }
+       err = kNoErr;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+
+static const char *    _DNSSVCBKeyToString( int inValue );
+
+static OSStatus
+       _AppendSVCBRDataString(
+               DataBuffer * const              inDB,
+               const uint8_t * const   inRDataPtr,
+               const uint8_t * const   inRDataEnd,
+               const Boolean                   inPrivacy )
+{
+       OSStatus                                                        err;
+       const uint8_t *                                         ptr;
+       const char *                                            sep;
+       const dns_fixed_fields_svcb *           fields;
+       char                                                            domainNameStr[ kDNSServiceMaxDomainName ];
+       
+       ptr = inRDataPtr;
+       require_action_quiet( ptr <= inRDataEnd, exit, err = kRangeErr );
+       require_action_quiet( ( (size_t)( inRDataEnd - ptr ) ) >= sizeof( *fields ), exit, err = kMalformedErr );
+       
+       // SvcFieldPriority
+       
+       fields = (const dns_fixed_fields_svcb *) ptr;
+       err = _DataBuffer_AppendF( inDB, "%u", dns_fixed_fields_svcb_get_priority( fields ) );
+       require_noerr_quiet( err, exit );
+       
+       // SvcDomainName
+       
+       ptr = (const uint8_t *) &fields[ 1 ];
+       err = DomainNameToString( ptr, inRDataEnd, domainNameStr, &ptr );
+       require_noerr_quiet( err, exit );
+       
+       err = _AppendDomainNameStringEx( inDB, " ", inPrivacy, domainNameStr );
+       require_noerr_quiet( err, exit );
+       
+       // SvcFieldValue
+       // Follows types for <https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-00>
+       
+       while( ptr < inRDataEnd )
+       {
+               const dns_fixed_fields_svcb_param *             paramFields;
+               const uint8_t *                                                 limit;
+               const char *                                                    keyStr;
+               int                                                                             key;
+               unsigned int                                                    valLen;
+               
+               require_action_quiet( ( (size_t)( inRDataEnd - ptr ) ) >= sizeof( *paramFields ), exit, err = kUnderrunErr );
+               
+               paramFields = (const dns_fixed_fields_svcb_param *) ptr;
+               key             = dns_fixed_fields_svcb_param_get_key( paramFields );
+               valLen  = dns_fixed_fields_svcb_param_get_value_length( paramFields );
+               
+               keyStr = _DNSSVCBKeyToString( key );
+               if( keyStr )
+               {
+                       err = _DataBuffer_AppendF( inDB, " %s=\"", keyStr );
+                       require_noerr_quiet( err, exit );
+               }
+               else
+               {
+                       err = _DataBuffer_AppendF( inDB, " key%u=\"", key );
+                       require_noerr_quiet( err, exit );
+               }
+               ptr = (const uint8_t *) &paramFields[ 1 ];
+               require_action_quiet( ( (size_t)( inRDataEnd - ptr ) ) >= valLen, exit, err = kUnderrunErr );
+               
+               switch( key )
+               {
+                       case kDNSSVCParamKey_Mandatory:
+                       {
+                               // List of 16-bit keys
+                               // See <https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-01#section-6.5>.
+
+                               require_action_quiet( ( valLen % 2 ) == 0, exit, err = kMalformedErr );
+
+                               sep = NULL;
+                               limit = &ptr[ valLen ];
+                               while( ptr < limit )
+                               {
+                                       int                             mandatoryKey;
+                                       const char *    mandatoryKeyStr;
+
+                                       mandatoryKey = ReadBig16( ptr );
+                                       ptr += 2;
+
+                                       mandatoryKeyStr = _DNSSVCBKeyToString( mandatoryKey );
+                                       if( sep )
+                                       {
+                                               err = _DataBuffer_AppendF( inDB, "%s", sep );
+                                               require_noerr_quiet( err, exit );
+                                       }
+                                       if( mandatoryKeyStr )
+                                       {
+                                               err = _DataBuffer_AppendF( inDB, "%s", mandatoryKeyStr );
+                                               require_noerr_quiet( err, exit );
+                                       }
+                                       else
+                                       {
+                                               err = _DataBuffer_AppendF( inDB, "key%u", mandatoryKey );
+                                               require_noerr_quiet( err, exit );
+                                       }
+                                       sep = ",";
+                               }
+                               break;
+                       }
+
+                       case kDNSSVCParamKey_ALPN:
+                       {
+                               // Length-prefixed ALPN protocol ID octet sequences
+                               // See <https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-01#section-6.1>.
+                               
+                               sep = NULL;
+                               limit = &ptr[ valLen ];
+                               while( ptr < limit )
+                               {
+                                       const uint8_t *                 alpnLimit;
+                                       const unsigned int              alpnLen = *ptr++;
+                                       
+                                       require_action_quiet( ( (size_t)( limit - ptr ) ) >= alpnLen, exit, err = kMalformedErr );
+                                       
+                                       if( sep )
+                                       {
+                                               err = _DataBuffer_AppendF( inDB, "%s", sep );
+                                               require_noerr_quiet( err, exit );
+                                       }
+                                       for( alpnLimit = &ptr[ alpnLen ]; ptr < alpnLimit; ++ptr )
+                                       {
+                                               const int               c = *ptr;
+                                               
+                                               if( _isprint_ascii( c ) )
+                                               {
+                                                       if( ( c == ',' ) || ( c == '\\' ) )
+                                                       {
+                                                               // Escape commas and backslashes.
+                                                               
+                                                               err = _DataBuffer_AppendF( inDB, "\\%c", c );
+                                                               require_noerr_quiet( err, exit );
+                                                       }
+                                                       else
+                                                       {
+                                                               err = _DataBuffer_AppendF( inDB, "%c", c );
+                                                               require_noerr_quiet( err, exit );
+                                                       }
+                                               }
+                                               else
+                                               {
+                                                       // Use a three digit decimal escape code (\DDD) for non-printable octets.
+                                                       
+                                                       err = _DataBuffer_AppendF( inDB, "\\%03d", c );
+                                                       require_noerr_quiet( err, exit );
+                                               }
+                                       }
+                                       sep = ",";
+                               }
+                               break;
+                       }
+                       case kDNSSVCParamKey_Port:
+                       {
+                               unsigned int            port;
+                               
+                               // 16-bit Integer
+                               // See <https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-01#section-6.2>.
+                               
+                               require_action_quiet( valLen == 2, exit, err = kMalformedErr );
+                               
+                               port = ReadBig16( ptr );
+                               ptr += valLen;
+                               
+                               err = _DataBuffer_AppendF( inDB, "%u", port );
+                               require_noerr_quiet( err, exit );
+                               break;
+                       }
+                       case kDNSSVCParamKey_IPv4Hint:
+                       {
+                               // IPv4 address list
+                               // See <https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-01#section-6.4>.
+                               
+                               require_action_quiet( ( valLen % 4 ) == 0, exit, err = kMalformedErr );
+                               
+                               sep = "";
+                               for( limit = &ptr[ valLen ]; ptr < limit; ptr += 4 )
+                               {
+                                       err = _AppendIPv4Address( inDB, sep, ptr, inPrivacy );
+                                       require_noerr_quiet( err, exit );
+                                       sep = ",";
+                               }
+                               break;
+                       }
+                       case kDNSSVCParamKey_IPv6Hint:
+                       {
+                               // IPv6 address list
+                               // See <https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-01#section-6.4>.
+                               
+                               require_action_quiet( ( valLen % 16 ) == 0, exit, err = kMalformedErr );
+                               
+                               sep = "";
+                               for( limit = &ptr[ valLen ]; ptr < limit; ptr += 16 )
+                               {
+                                       err = _AppendIPv6Address( inDB, sep, ptr, inPrivacy );
+                                       require_noerr_quiet( err, exit );
+                                       sep = ",";
+                               }
+                               break;
+                       }
+                       default:
+                       {
+                               // Other keys.
+                               // See <https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-01#section-2.1.1>.
+                               
+                               if( inPrivacy )
+                               {
+                                       err = _DataBuffer_AppendF( inDB, "<%u redacted bytes>", valLen );
+                                       require_noerr_quiet( err, exit );
+                                       ptr += valLen;
+                               }
+                               else
+                               {
+                                       for( limit = &ptr[ valLen ]; ptr < limit; ++ptr )
+                                       {
+                                               const int               c = *ptr;
+                                               
+                                               if( ( c >= 0x21 ) && ( c <= 0x7E ) )
+                                               {
+                                                       // Visible characters are printed.
+                                                       
+                                                       switch( c )
+                                                       {
+                                                               // Escape reserved characters.
+                                                               
+                                                               case '"':
+                                                               case ';':
+                                                               case '(':
+                                                               case ')':
+                                                               case '\\':
+                                                                       err = _DataBuffer_AppendF( inDB, "\\%c", c );
+                                                                       require_noerr_quiet( err, exit );
+                                                                       break;
+                                                               
+                                                               default:
+                                                                       err = _DataBuffer_AppendF( inDB, "%c", c );
+                                                                       require_noerr_quiet( err, exit );
+                                                                       break;
+                                                       }
+                                               }
+                                               else
+                                               {
+                                                       // Invisible characters use a three digit decimal escape code (\DDD).
+                                                       
+                                                       err = _DataBuffer_AppendF( inDB, "\\%03d", c );
+                                                       require_noerr_quiet( err, exit );
+                                               }
+                                       }
+                               }
+                               break;
+                       }
+               }
+               err = _DataBuffer_AppendF( inDB, "\"" );
+               require_noerr_quiet( err, exit );
+       }
+       
+exit:
+       return( err );
+}
+
+static const char *    _DNSSVCBKeyToString( const int inValue )
+{
+       switch( inValue )
+       {
+               case kDNSSVCParamKey_Mandatory:         return( "mandatory" );
+               case kDNSSVCParamKey_ALPN:                      return( "alpn" );
+               case kDNSSVCParamKey_NoDefaultALPN:     return( "no-default-alpn" );
+               case kDNSSVCParamKey_Port:                      return( "port" );
+               case kDNSSVCParamKey_IPv4Hint:          return( "ipv4hint" );
+               case kDNSSVCParamKey_ECHConfig:         return( "echconfig" );
+               case kDNSSVCParamKey_IPv6Hint:          return( "ipv6hint" );
+               case kDNSSVCParamKey_DOHURI:            return( "dohuri" );
+               default:                                                        return( NULL );
+       }
+}
+
+//===========================================================================================================================
+
+static OSStatus
+       _AppendIPv4Address(
+               DataBuffer * const      inDB,
+               const char * const      inSeparator,
+               const uint8_t           inAddrBytes[ STATIC_PARAM 4 ],
+               const Boolean           inPrivacy )
+{
+       return( _AppendIPAddress( inDB, inSeparator, inAddrBytes, 4, inPrivacy && !_IPv4AddressIsWhitelisted( inAddrBytes ) ) );
+}
+
+//===========================================================================================================================
+
+static OSStatus
+       _AppendIPv6Address(
+               DataBuffer * const      inDB,
+               const char * const      inSeparator,
+               const uint8_t           inAddrBytes[ STATIC_PARAM 16 ],
+               Boolean                         inPrivacy )
+{
+       return( _AppendIPAddress( inDB, inSeparator, inAddrBytes, 16, inPrivacy && !_IPv6AddressIsWhitelisted( inAddrBytes ) ) );
+}
+
+//===========================================================================================================================
+
+static OSStatus
+       _AppendIPAddress(
+               DataBuffer *    inDB,
+               const char *    inSeparator,
+               const uint8_t * inAddrPtr,
+               int                             inAddrLen,
+               Boolean                 inPrivacy )
+{
+       OSStatus                                err;
+       const char * const              sep = inSeparator ? inSeparator : "";
+       
+       require_action_quiet( ( inAddrLen == 4 ) || ( inAddrLen == 16 ), exit, err = kSizeErr );
+       
+       if( inPrivacy )
+       {
+               int                     n;
+               char            tmpBuf[ ( 16 * 2 ) + 1 ];
+               
+               n = _SNPrintF( tmpBuf, sizeof( tmpBuf ), "%.4H", inAddrPtr, (int) inAddrLen, (int) inAddrLen );
+               require_action_quiet( n >= 0, exit, err = n );
+               
+               err = _DataBuffer_AppendF( inDB, "%s%~s", sep, tmpBuf );
+               require_noerr_quiet( err, exit );
+       }
+       else
+       {
+               err = _DataBuffer_AppendF( inDB, "%s%.*a", sep, inAddrLen, inAddrPtr );
+               require_noerr_quiet( err, exit );
+       }
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+
+static void *  _GetCULibHandle( void )
+{
+       static dispatch_once_t          sOnce   = 0;
+       static void *                           sHandle = NULL;
+       
+       dispatch_once( &sOnce,
+       ^{
+               sHandle = dlopen( "/System/Library/PrivateFrameworks/CoreUtils.framework/CoreUtils", RTLD_NOW );
+       } );
+       return( sHandle );
+}
+
+//===========================================================================================================================
+
+static Boolean _MemIsAllZeros( const uint8_t * const inMemPtr, const size_t inMemLen )
+{
+       require_return_value( inMemLen > 0, false );
+       
+       // The memcmp() call below compares two subregions of length inMemLen - 1. The first subregion starts at
+       // inMemPtr, and the second subregion starts at inMemPtr + 1. The memcmp() call will return zero if for all
+       // i in {0, 1, ..., inMemLen - 2}, inMemPtr[i] == inMemPtr[i + 1]. That is, memcmp() will return zero if all
+       // bytes are equal. So if inMemPtr[0] == 0, and the memcmp() call returns zero, then all bytes are equal to zero.
+       
+       return( ( inMemPtr[ 0 ] == 0 ) && ( memcmp( inMemPtr, inMemPtr + 1, inMemLen - 1 ) == 0 ) );
+}
+
+//===========================================================================================================================
+
+static Boolean _IPv4AddressIsWhitelisted( const uint8_t inAddrBytes[ STATIC_PARAM 4 ] )
+{
+       // Whitelist the all-zeros and localhost addresses.
+       
+       switch( ReadBig32( inAddrBytes ) )
+       {
+               case 0:                                                 // 0.0.0.0
+               case UINT32_C( 0x7F000001 ):    // 127.0.0.1
+                       return( true );
+               
+               default:
+                       return( false );
+       }
+}
+
+//===========================================================================================================================
+
+static Boolean _IPv6AddressIsWhitelisted( const uint8_t inAddrBytes[ STATIC_PARAM 16 ] )
+{
+       // Whitelist the all-zeros and localhost addresses, i.e., :: and ::1.
+       
+       if( ( memcmp( inAddrBytes, AllZeros16Bytes, 15 ) == 0 ) && ( ( inAddrBytes[ 15 ] == 0 ) || ( inAddrBytes[ 15 ] == 1 ) ) )
+       {
+               return( true );
+       }
+       else
+       {
+               return( false );
+       }
+}
+
+//===========================================================================================================================
+
+static int
+       _DNSMessagePrintObfuscatedIPAddress(
+               char *                  inBufPtr,
+               size_t                  inBufLen,
+               const uint8_t * inAddrBytes,
+               size_t                  inAddrLen )
+{
+       int                     n;
+       char            tmpBuf[ ( 16 * 2 ) + 1 ];
+       
+       require_return_value( ( inAddrLen == 4 ) || ( inAddrLen == 16 ), kSizeErr );
+       
+       n = _SNPrintF( tmpBuf, sizeof( tmpBuf ), "%.4H", inAddrBytes, (int) inAddrLen, (int) inAddrLen );
+       require_return_value( n >= 0, n );
+       
+       return( _SNPrintF( inBufPtr, inBufLen, "%~s", tmpBuf ) );
+}
index 56ddeee3650bdbc9beebae97ff6bd42a40908445..f1e4a74302c4d77dc0cc26c7181628c7485772ab 100644 (file)
@@ -1,15 +1,15 @@
 /*
-       Copyright (c) 2016-2019 Apple Inc. All rights reserved.
+       Copyright (c) 2016-2020 Apple Inc. All rights reserved.
 */
 
 #ifndef        __DNSMessage_h
 #define        __DNSMessage_h
 
-#include <CoreUtils/CoreUtils.h>
+#include <CoreUtils/CommonServices.h>
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+CU_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
 
 //---------------------------------------------------------------------------------------------------------------------------
 /*!    @group          DNS domain name size limits
@@ -65,7 +65,7 @@ check_compile_time( sizeof( DNSHeader ) == kDNSHeaderLength );
 
 #define DNSFlagsGetOpCode( FLAGS )             ( ( (FLAGS) >> 11 ) & 0x0FU )
 #define DNSFlagsSetOpCode( FLAGS, OPCODE ) \
-       do { (FLAGS) = ( (FLAGS) & ~0x7800U ) | ( ( (OPCODE) & 0x0FU ) << 11 ); } while( 0 )
+               do { (FLAGS) = ( (FLAGS) & ~0x7800U ) | ( ( (OPCODE) & 0x0FU ) << 11 ); } while( 0 )
 
 #define kDNSOpCode_Query                       0       // QUERY (standard query)
 #define kDNSOpCode_InverseQuery                1       // IQUERY (inverse query)
@@ -75,39 +75,56 @@ check_compile_time( sizeof( DNSHeader ) == kDNSHeaderLength );
 
 // RCODE (bits 3-0), Response Code
 
-#define DNSFlagsGetRCode( FLAGS )              ( (FLAGS) & 0x0FU )
+#define DNSFlagsGetRCode( FLAGS )                              ( (FLAGS) & 0x0FU )
 #define DNSFlagsSetRCode( FLAGS, RCODE ) \
-       do { (FLAGS) = ( (FLAGS) & ~0x000FU ) | ( (RCODE) & 0x0FU ); } while( 0 )
+       do { (FLAGS) = ( (FLAGS) & ~0x000FU ) | ( ( (unsigned int)(RCODE) ) & 0x0FU ); } while( 0 )
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @group          Multicast DNS Constants
+*/
 
-#define kDNSRCode_NoError                              0
-#define kDNSRCode_FormatError                  1
-#define kDNSRCode_ServerFailure                        2
-#define kDNSRCode_NXDomain                             3
-#define kDNSRCode_NotImplemented               4
-#define kDNSRCode_Refused                              5
+#define kMDNSClassUnicastResponseBit           ( 1U << 15 ) // See <https://tools.ietf.org/html/rfc6762#section-18.12>.
+#define kMDNSClassCacheFlushBit                                ( 1U << 15 ) // See <https://tools.ietf.org/html/rfc6762#section-18.13>.
 
 //---------------------------------------------------------------------------------------------------------------------------
 /*!    @group          Misc. DNS message data structures
 */
 
-#define dns_fixed_fields_define_accessors( TYPE, FIELD, BIT_SIZE )     \
-       STATIC_INLINE uint ## BIT_SIZE ##_t                                                             \
-               dns_fixed_fields_ ## TYPE ## _get_ ## FIELD (                           \
-                       const dns_fixed_fields_ ## TYPE *       inFields )                      \
-       {                                                                                                                               \
-               return ReadBig ## BIT_SIZE ( inFields->FIELD );                         \
-       }                                                                                                                               \
-                                                                                                                                       \
-       STATIC_INLINE void                                                                                              \
-               dns_fixed_fields_ ## TYPE ## _set_ ## FIELD (                           \
-                       dns_fixed_fields_ ## TYPE *     inFields,                                       \
-                       uint ## BIT_SIZE ## _t          inValue )                                       \
-       {                                                                                                                               \
-               WriteBig ## BIT_SIZE ( inFields->FIELD, inValue );                      \
-       }                                                                                                                               \
-       check_compile_time( ( sizeof_field( dns_fixed_fields_ ## TYPE, FIELD ) * 8 ) == (BIT_SIZE) )
-
-// DNS question fixed-length fields (see <https://tools.ietf.org/html/rfc1035#section-4.1.2>)
+#define _DNSMessageGet8( PTR )                 Read8( PTR )
+#define _DNSMessageGet16( PTR )                        ReadBig16( PTR )
+#define _DNSMessageGet32( PTR )                        ReadBig32( PTR )
+#define _DNSMessageSet8( PTR, X )              Write8( PTR, X )
+#define _DNSMessageSet16( PTR, X )             WriteBig16( PTR, X )
+#define _DNSMessageSet32( PTR, X )             WriteBig32( PTR, X )
+
+#define dns_fields_define_accessors( PREFIX, TYPE, FIELD, BIT_SIZE )   \
+       STATIC_INLINE uint ## BIT_SIZE ## _t                                                            \
+               dns_ ## PREFIX ## _ ## TYPE ## _get_ ## FIELD (                                 \
+                       const dns_ ## PREFIX ## _ ## TYPE *     inFields )                              \
+       {                                                                                                                                       \
+               return _DNSMessageGet ## BIT_SIZE ( inFields->FIELD );                  \
+       }                                                                                                                                       \
+                                                                                                                                               \
+       STATIC_INLINE void                                                                                                      \
+               dns_ ## PREFIX ## _ ## TYPE ## _set_ ## FIELD (                                 \
+                       dns_ ## PREFIX ## _ ## TYPE *   inFields,                                       \
+                       uint ## BIT_SIZE ## _t                  inValue )                                       \
+       {                                                                                                                                       \
+               _DNSMessageSet ## BIT_SIZE ( inFields->FIELD, inValue );                \
+       }                                                                                                                                       \
+       check_compile_time( ( sizeof_field( dns_ ## PREFIX ## _ ## TYPE, FIELD ) * 8 ) == (BIT_SIZE) )
+
+#define dns_fixed_fields_define_accessors( TYPE, FIELD, BIT_SIZE ) \
+       dns_fields_define_accessors( fixed_fields, TYPE, FIELD, BIT_SIZE )
+
+#define dns_dnskey_fields_define_accessors( TYPE, FIELD, BIT_SIZE ) \
+       dns_fields_define_accessors( dnskey, TYPE, FIELD, BIT_SIZE )
+
+#define dns_ds_fields_define_accessors( TYPE, FIELD, BIT_SIZE ) \
+       dns_fields_define_accessors( ds, TYPE, FIELD, BIT_SIZE )
+
+// DNS question fixed-length fields
+// See <https://tools.ietf.org/html/rfc1035#section-4.1.2>
 
 typedef struct
 {
@@ -131,7 +148,8 @@ STATIC_INLINE void
        dns_fixed_fields_question_set_class( inFields, inQClass );
 }
 
-// DNS resource record fixed-length fields (see <https://tools.ietf.org/html/rfc1035#section-4.1.3>)
+// DNS resource record fixed-length fields
+// See <https://tools.ietf.org/html/rfc1035#section-4.1.3>
 
 typedef struct
 {
@@ -163,7 +181,8 @@ STATIC_INLINE void
        dns_fixed_fields_record_set_rdlength( inFields, inRDLength );
 }
 
-// DNS SRV record data fixed-length fields (see <https://tools.ietf.org/html/rfc2782>)
+// DNS SRV record data fixed-length fields
+// See <https://tools.ietf.org/html/rfc2782>
 
 typedef struct
 {
@@ -191,7 +210,8 @@ STATIC_INLINE void
        dns_fixed_fields_srv_set_port( inFields, inPort );
 }
 
-// DNS SOA record data fixed-length fields (see <https://tools.ietf.org/html/rfc1035#section-3.3.13>)
+// DNS SOA record data fixed-length fields
+// See <https://tools.ietf.org/html/rfc1035#section-3.3.13>
 
 typedef struct
 {
@@ -227,6 +247,375 @@ STATIC_INLINE void
        dns_fixed_fields_soa_set_minimum( inFields, inMinimum );
 }
 
+// OPT pseudo-resource record fixed-length fields without RDATA
+// See <https://tools.ietf.org/html/rfc6891#section-6.1.2>
+
+typedef struct
+{
+       uint8_t         name[ 1 ];
+       uint8_t         type[ 2 ];
+       uint8_t         udp_payload_size[ 2 ];
+       uint8_t         extended_rcode[ 1 ];
+       uint8_t         version[ 1 ];
+       uint8_t         extended_flags[ 2 ];
+       uint8_t         rdlen[ 2 ];
+       
+}      dns_fixed_fields_opt;
+
+check_compile_time( sizeof( dns_fixed_fields_opt ) == 11 );
+
+#define kDNSExtendedFlag_DNSSECOK              ( 1U << 15 ) // <https://tools.ietf.org/html/rfc3225#section-3>
+
+dns_fixed_fields_define_accessors( opt, name,              8 );
+dns_fixed_fields_define_accessors( opt, type,             16 );
+dns_fixed_fields_define_accessors( opt, udp_payload_size, 16 );
+dns_fixed_fields_define_accessors( opt, extended_rcode,    8 );
+dns_fixed_fields_define_accessors( opt, version,           8 );
+dns_fixed_fields_define_accessors( opt, extended_flags,   16 );
+dns_fixed_fields_define_accessors( opt, rdlen,            16 );
+
+// OPT pseudo-resource record fixed-length fields with OPTION-CODE and OPTION-LENGTH
+// See <https://tools.ietf.org/html/rfc6891#section-6.1.2>
+
+typedef struct
+{
+       uint8_t         name[ 1 ];
+       uint8_t         type[ 2 ];
+       uint8_t         udp_payload_size[ 2 ];
+       uint8_t         extended_rcode[ 1 ];
+       uint8_t         version[ 1 ];
+       uint8_t         extended_flags[ 2 ];
+       uint8_t         rdlen[ 2 ];
+       uint8_t         option_code[ 2 ];
+       uint8_t         option_length[ 2 ];
+       
+}      dns_fixed_fields_opt1;
+
+check_compile_time( sizeof( dns_fixed_fields_opt1 ) == 15 );
+
+dns_fixed_fields_define_accessors( opt1, name,              8 );
+dns_fixed_fields_define_accessors( opt1, type,             16 );
+dns_fixed_fields_define_accessors( opt1, udp_payload_size, 16 );
+dns_fixed_fields_define_accessors( opt1, extended_rcode,    8 );
+dns_fixed_fields_define_accessors( opt1, version,           8 );
+dns_fixed_fields_define_accessors( opt1, extended_flags,   16 );
+dns_fixed_fields_define_accessors( opt1, rdlen,            16 );
+dns_fixed_fields_define_accessors( opt1, option_code,      16 );
+dns_fixed_fields_define_accessors( opt1, option_length,    16 );
+
+// OPT pseudo-resource record RDATA option fixed-length fields
+// See <https://tools.ietf.org/html/rfc6891#section-6.1.2>
+
+typedef struct
+{
+       uint8_t         code[ 2 ];
+       uint8_t         length[ 2 ];
+       
+}      dns_fixed_fields_option;
+
+check_compile_time( sizeof( dns_fixed_fields_option ) == 4 );
+
+dns_fixed_fields_define_accessors( option, code,   16 );
+dns_fixed_fields_define_accessors( option, length, 16 );
+
+// DNS DNSKEY record data fixed-length fields
+// See <https://tools.ietf.org/html/rfc4034#section-2.1>
+
+typedef struct
+{
+       uint8_t         flags[ 2 ];
+       uint8_t         protocol[ 1 ];
+       uint8_t         algorithm[ 1 ];
+       
+}      dns_fixed_fields_dnskey;
+
+check_compile_time( sizeof( dns_fixed_fields_dnskey ) == 4 );
+
+dns_fixed_fields_define_accessors( dnskey, flags,     16 );
+dns_fixed_fields_define_accessors( dnskey, protocol,   8 );
+dns_fixed_fields_define_accessors( dnskey, algorithm,  8 );
+
+#define kDNSKeyFlag_ZoneKey            ( 1U << ( 15 -  7 ) )   // MSB bit 7  <https://tools.ietf.org/html/rfc4034#section-2.1.1>
+#define kDNSKeyFlag_SEP                        ( 1U << ( 15 - 15 ) )   // MSB bit 15 <https://tools.ietf.org/html/rfc4034#section-2.1.1>
+
+#define kDNSKeyProtocol_DNSSEC         3       // Protocol value must be 3. <https://tools.ietf.org/html/rfc4034#section-2.1.2>
+
+// DNSSEC Algoritm Numbers
+// See <https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml#dns-sec-alg-numbers-1>
+
+#define kDNSSECAlgorithm_RSASHA1                        5      // RSA/SHA-1
+#define kDNSSECAlgorithm_RSASHA256                      8      // RSA/SHA-256
+#define kDNSSECAlgorithm_RSASHA512                     10      // RSA/SHA-512
+#define kDNSSECAlgorithm_ECDSAP256SHA256       13      // ECDSA P-256 curve/SHA-256
+#define kDNSSECAlgorithm_ECDSAP384SHA384       14      // ECDSA P-384 curve/SHA-384
+#define kDNSSECAlgorithm_Ed25519                       15      // Ed25519
+
+// DNS RRSIG record data fixed-length fields
+// See <https://tools.ietf.org/html/rfc4034#section-3.1>
+
+typedef struct
+{
+       uint8_t         type_covered[ 2 ];
+       uint8_t         algorithm[ 1 ];
+       uint8_t         labels[ 1 ];
+       uint8_t         original_ttl[ 4 ];
+       uint8_t         signature_expiration[ 4 ];
+       uint8_t         signature_inception[ 4 ];
+       uint8_t         key_tag[ 2 ];
+       
+}      dns_fixed_fields_rrsig;
+
+check_compile_time( sizeof( dns_fixed_fields_rrsig ) == 18 );
+
+dns_fixed_fields_define_accessors( rrsig, type_covered,         16 );
+dns_fixed_fields_define_accessors( rrsig, algorithm,             8 );
+dns_fixed_fields_define_accessors( rrsig, labels,                8 );
+dns_fixed_fields_define_accessors( rrsig, original_ttl,         32 );
+dns_fixed_fields_define_accessors( rrsig, signature_expiration, 32 );
+dns_fixed_fields_define_accessors( rrsig, signature_inception,  32 );
+dns_fixed_fields_define_accessors( rrsig, key_tag,              16 );
+
+// DNS DS record data fixed-length fields
+// See <https://tools.ietf.org/html/rfc4034#section-5.1>
+
+typedef struct
+{
+       uint8_t         key_tag[ 2 ];
+       uint8_t         algorithm[ 1 ];
+       uint8_t         digest_type[ 1 ];
+       
+}      dns_fixed_fields_ds;
+
+check_compile_time( sizeof( dns_fixed_fields_ds ) == 4 );
+
+dns_fixed_fields_define_accessors( ds, key_tag,     16 );
+dns_fixed_fields_define_accessors( ds, algorithm,    8 );
+dns_fixed_fields_define_accessors( ds, digest_type,  8 );
+
+#define kDSDigestType_SHA1                     1       // SHA-1   <https://tools.ietf.org/html/rfc4034#appendix-A.2>
+#define kDSDigestType_SHA256           2       // SHA-256 <https://tools.ietf.org/html/rfc4509#section-5>
+
+// DNS DS record data
+// See <https://tools.ietf.org/html/rfc4509#section-2.2>
+
+typedef struct
+{
+       uint8_t         key_tag[ 2 ];
+       uint8_t         algorithm[ 1 ];
+       uint8_t         digest_type[ 1 ];
+       uint8_t         digest[ 32 ];
+       
+}      dns_ds_sha256;
+
+check_compile_time( sizeof( dns_ds_sha256 ) == 36 );
+
+dns_ds_fields_define_accessors( sha256, key_tag,     16 );
+dns_ds_fields_define_accessors( sha256, algorithm,    8 );
+dns_ds_fields_define_accessors( sha256, digest_type,  8 );
+
+// DNS NSEC3 record data fixed-length fields
+// See <https://tools.ietf.org/html/rfc5155#section-3.2>
+
+typedef struct
+{
+       uint8_t         hash_alg[ 1 ];
+       uint8_t         flags[ 1 ];
+       uint8_t         iterations[ 2 ];
+       
+}      dns_fixed_fields_nsec3;
+
+check_compile_time( sizeof( dns_fixed_fields_nsec3 ) == 4 );
+
+dns_fixed_fields_define_accessors( nsec3, hash_alg,    8 );
+dns_fixed_fields_define_accessors( nsec3, flags,       8 );
+dns_fixed_fields_define_accessors( nsec3, iterations, 16 );
+
+// DNS SVCB record data fixed-length fields
+// See <https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-00#section-2.2>
+
+typedef struct
+{
+       uint8_t         priority[ 2 ];
+       
+}      dns_fixed_fields_svcb;
+
+check_compile_time( sizeof( dns_fixed_fields_svcb ) == 2 );
+
+dns_fixed_fields_define_accessors( svcb, priority, 16 );
+
+typedef struct
+{
+       uint8_t         key[ 2 ];
+       uint8_t         value_length[ 2 ];
+       
+}      dns_fixed_fields_svcb_param;
+
+check_compile_time( sizeof( dns_fixed_fields_svcb_param ) == 4 );
+
+dns_fixed_fields_define_accessors( svcb_param, key,          16 );
+dns_fixed_fields_define_accessors( svcb_param, value_length, 16 );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @group          DNS record types
+*/
+// This code was autogenerated on 2020-06-30 by dns-rr-func-autogen version 1.3
+// Data source URL: https://www.iana.org/assignments/dns-parameters/dns-parameters-4.csv
+// Overrides: none
+
+typedef enum
+{
+       kDNSRecordType_A          = 1,
+       kDNSRecordType_NS         = 2,
+       kDNSRecordType_MD         = 3,
+       kDNSRecordType_MF         = 4,
+       kDNSRecordType_CNAME      = 5,
+       kDNSRecordType_SOA        = 6,
+       kDNSRecordType_MB         = 7,
+       kDNSRecordType_MG         = 8,
+       kDNSRecordType_MR         = 9,
+       kDNSRecordType_NULL       = 10,
+       kDNSRecordType_WKS        = 11,
+       kDNSRecordType_PTR        = 12,
+       kDNSRecordType_HINFO      = 13,
+       kDNSRecordType_MINFO      = 14,
+       kDNSRecordType_MX         = 15,
+       kDNSRecordType_TXT        = 16,
+       kDNSRecordType_RP         = 17,
+       kDNSRecordType_AFSDB      = 18,
+       kDNSRecordType_X25        = 19,
+       kDNSRecordType_ISDN       = 20,
+       kDNSRecordType_RT         = 21,
+       kDNSRecordType_NSAP       = 22,
+       kDNSRecordType_NSAP_PTR   = 23,
+       kDNSRecordType_SIG        = 24,
+       kDNSRecordType_KEY        = 25,
+       kDNSRecordType_PX         = 26,
+       kDNSRecordType_GPOS       = 27,
+       kDNSRecordType_AAAA       = 28,
+       kDNSRecordType_LOC        = 29,
+       kDNSRecordType_NXT        = 30,
+       kDNSRecordType_EID        = 31,
+       kDNSRecordType_NIMLOC     = 32,
+       kDNSRecordType_SRV        = 33,
+       kDNSRecordType_ATMA       = 34,
+       kDNSRecordType_NAPTR      = 35,
+       kDNSRecordType_KX         = 36,
+       kDNSRecordType_CERT       = 37,
+       kDNSRecordType_A6         = 38,
+       kDNSRecordType_DNAME      = 39,
+       kDNSRecordType_SINK       = 40,
+       kDNSRecordType_OPT        = 41,
+       kDNSRecordType_APL        = 42,
+       kDNSRecordType_DS         = 43,
+       kDNSRecordType_SSHFP      = 44,
+       kDNSRecordType_IPSECKEY   = 45,
+       kDNSRecordType_RRSIG      = 46,
+       kDNSRecordType_NSEC       = 47,
+       kDNSRecordType_DNSKEY     = 48,
+       kDNSRecordType_DHCID      = 49,
+       kDNSRecordType_NSEC3      = 50,
+       kDNSRecordType_NSEC3PARAM = 51,
+       kDNSRecordType_TLSA       = 52,
+       kDNSRecordType_SMIMEA     = 53,
+       kDNSRecordType_HIP        = 55,
+       kDNSRecordType_NINFO      = 56,
+       kDNSRecordType_RKEY       = 57,
+       kDNSRecordType_TALINK     = 58,
+       kDNSRecordType_CDS        = 59,
+       kDNSRecordType_CDNSKEY    = 60,
+       kDNSRecordType_OPENPGPKEY = 61,
+       kDNSRecordType_CSYNC      = 62,
+       kDNSRecordType_ZONEMD     = 63,
+       kDNSRecordType_SVCB       = 64,
+       kDNSRecordType_HTTPS      = 65,
+       kDNSRecordType_SPF        = 99,
+       kDNSRecordType_UINFO      = 100,
+       kDNSRecordType_UID        = 101,
+       kDNSRecordType_GID        = 102,
+       kDNSRecordType_UNSPEC     = 103,
+       kDNSRecordType_NID        = 104,
+       kDNSRecordType_L32        = 105,
+       kDNSRecordType_L64        = 106,
+       kDNSRecordType_LP         = 107,
+       kDNSRecordType_EUI48      = 108,
+       kDNSRecordType_EUI64      = 109,
+       kDNSRecordType_TKEY       = 249,
+       kDNSRecordType_TSIG       = 250,
+       kDNSRecordType_IXFR       = 251,
+       kDNSRecordType_AXFR       = 252,
+       kDNSRecordType_MAILB      = 253,
+       kDNSRecordType_MAILA      = 254,
+       kDNSRecordType_ANY        = 255,
+       kDNSRecordType_URI        = 256,
+       kDNSRecordType_CAA        = 257,
+       kDNSRecordType_AVC        = 258,
+       kDNSRecordType_DOA        = 259,
+       kDNSRecordType_AMTRELAY   = 260,
+       kDNSRecordType_TA         = 32768,
+       kDNSRecordType_DLV        = 32769,
+       kDNSRecordType_Reserved   = 65535,
+
+}      DNSRecordType;
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @group          DNS RCODEs
+*/
+// This code was autogenerated on 2020-06-15 by dns-rcode-func-autogen version 1.0
+// Data source URL: https://www.iana.org/assignments/dns-parameters/dns-parameters-6.csv
+
+typedef enum
+{
+       kDNSRCode_NoError   = 0,
+       kDNSRCode_FormErr   = 1,
+       kDNSRCode_ServFail  = 2,
+       kDNSRCode_NXDomain  = 3,
+       kDNSRCode_NotImp    = 4,
+       kDNSRCode_Refused   = 5,
+       kDNSRCode_YXDomain  = 6,
+       kDNSRCode_YXRRSet   = 7,
+       kDNSRCode_NXRRSet   = 8,
+       kDNSRCode_NotAuth   = 9,
+       kDNSRCode_NotZone   = 10,
+       kDNSRCode_DSOTYPENI = 11
+       
+}      DNSRCode;
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @group          DNS classes
+*/
+
+typedef enum
+{
+       kDNSClassType_IN = 1    // See <https://tools.ietf.org/html/rfc1035#section-3.2.4>.
+       
+}      DNSClassType;
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @group          DNS EDNS0 Option Codes
+*/
+typedef enum
+{
+       kDNSEDNS0OptionCode_Padding = 12        // <https://tools.ietf.org/html/rfc7830#section-3>
+       
+}      DNSEDNS0OptionCode;
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @group          DNS EDNS0 Option Codes
+       @discussion     See <https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-00#section-12.1.2>.
+*/
+typedef enum
+{
+       kDNSSVCParamKey_Mandatory               = 0,
+       kDNSSVCParamKey_ALPN                    = 1,
+       kDNSSVCParamKey_NoDefaultALPN   = 2,
+       kDNSSVCParamKey_Port                    = 3,
+       kDNSSVCParamKey_IPv4Hint                = 4,
+       kDNSSVCParamKey_ECHConfig               = 5,
+       kDNSSVCParamKey_IPv6Hint                = 6,
+       kDNSSVCParamKey_DOHURI                  = 32768 // XXX: Apple Internal
+       
+}      DNSSVCParamKey;
+
 //---------------------------------------------------------------------------------------------------------------------------
 /*!    @brief          Extracts a domain name from a DNS message.
        
@@ -238,11 +627,11 @@ STATIC_INLINE void
 */
 OSStatus
        DNSMessageExtractDomainName(
-               const uint8_t *         inMsgPtr,
-               size_t                          inMsgLen,
-               const uint8_t *         inPtr,
-               uint8_t                         outName[ kDomainNameLengthMax ],
-               const uint8_t **        outPtr );
+               const uint8_t *                                                 inMsgPtr,
+               size_t                                                                  inMsgLen,
+               const uint8_t *                                                 inPtr,
+               uint8_t                                                                 outName[ _Nullable kDomainNameLengthMax ],
+               const uint8_t * _Nullable * _Nullable   outPtr );
 
 //---------------------------------------------------------------------------------------------------------------------------
 /*!    @brief          Extracts a domain name from a DNS message as a C string.
@@ -255,11 +644,11 @@ OSStatus
 */
 OSStatus
        DNSMessageExtractDomainNameString(
-               const void *            inMsgPtr,
-               size_t                          inMsgLen,
-               const void *            inPtr,
-               char                            outName[ kDNSServiceMaxDomainName ],
-               const uint8_t **        outPtr );
+               const void *                                                    inMsgPtr,
+               size_t                                                                  inMsgLen,
+               const void *                                                    inPtr,
+               char                                                                    outName[ _Nullable kDNSServiceMaxDomainName ],
+               const uint8_t * _Nullable * _Nullable   outPtr );
 
 //---------------------------------------------------------------------------------------------------------------------------
 /*!    @brief          Extracts a question from a DNS message.
@@ -274,13 +663,13 @@ OSStatus
 */
 OSStatus
        DNSMessageExtractQuestion(
-               const uint8_t *         inMsgPtr,
-               size_t                          inMsgLen,
-               const uint8_t *         inPtr,
-               uint8_t                         outName[ kDomainNameLengthMax ],
-               uint16_t *                      outType,
-               uint16_t *                      outClass,
-               const uint8_t **        outPtr );
+               const uint8_t *                                                 inMsgPtr,
+               size_t                                                                  inMsgLen,
+               const uint8_t *                                                 inPtr,
+               uint8_t                                                                 outName[ _Nullable kDomainNameLengthMax ],
+               uint16_t * _Nullable                                    outType,
+               uint16_t * _Nullable                                    outClass,
+               const uint8_t * _Nullable * _Nullable   outPtr );
 
 //---------------------------------------------------------------------------------------------------------------------------
 /*!    @brief          Extracts a resource record from a DNS message.
@@ -298,25 +687,44 @@ OSStatus
 */
 OSStatus
        DNSMessageExtractRecord(
-               const uint8_t *         inMsgPtr,
-               size_t                          inMsgLen,
-               const uint8_t *         inPtr,
-               uint8_t                         outName[ kDomainNameLengthMax ],
-               uint16_t *                      outType,
-               uint16_t *                      outClass,
-               uint32_t *                      outTTL,
-               const uint8_t **        outRDataPtr,
-               size_t *                        outRDataLen,
-               const uint8_t **        outPtr );
+               const uint8_t *                                                 inMsgPtr,
+               size_t                                                                  inMsgLen,
+               const uint8_t *                                                 inPtr,
+               uint8_t                                                                 outName[ _Nullable kDomainNameLengthMax ],
+               uint16_t * _Nullable                                    outType,
+               uint16_t * _Nullable                                    outClass,
+               uint32_t * _Nullable                                    outTTL,
+               const uint8_t * _Nullable * _Nullable   outRDataPtr,
+               size_t * _Nullable                                              outRDataLen,
+               const uint8_t * _Nullable * _Nullable   outPtr );
 
 //---------------------------------------------------------------------------------------------------------------------------
-/*!    @brief          Returns pointer to the start of the answer section, i.e., the end of the question section, of a DNS message.
+/*!    @brief          Gets a DNS message's answer section, i.e., the end of the message's question section.
        
        @param          inMsgPtr                Pointer to the beginning of the DNS message.
        @param          inMsgLen                Length of the DNS message.
        @param          outPtr                  Gets set to point to the start of the answer section. (Optional)
 */
-OSStatus       DNSMessageGetAnswerSection( const uint8_t *inMsgPtr, size_t inMsgLen, const uint8_t **outPtr );
+OSStatus
+       DNSMessageGetAnswerSection(
+               const uint8_t *                                                 inMsgPtr,
+               size_t                                                                  inMsgLen,
+               const uint8_t * _Nullable * _Nullable   outPtr );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Gets a DNS message's OPT record if it exists.
+       
+       @param          inMsgPtr                Pointer to the beginning of the DNS message.
+       @param          inMsgLen                Length of the DNS message.
+       @param          outOptPtr               Gets set to point to the start of the OPT record. (Optional)
+       @param          outOptLen               Gets set to point to the length of the OPT record. (Optional)
+*/
+OSStatus
+       DNSMessageGetOptRecord(
+               const uint8_t *                                                 inMsgPtr,
+               size_t                                                                  inMsgLen,
+               const uint8_t * _Nullable * _Nullable   outOptPtr,
+               size_t * _Nullable                                              outOptLen );
 
 //---------------------------------------------------------------------------------------------------------------------------
 /*!    @brief          Writes a DNS message compression label pointer.
@@ -332,9 +740,12 @@ STATIC_INLINE void DNSMessageWriteLabelPointer( uint8_t inLabelPtr[ STATIC_PARAM
        inLabelPtr[ 1 ] = (uint8_t)(     inOffset        & 0xFF          );
 }
 
-#define kDNSCompressionOffsetMax               0x3FFF
+#define kDNSCompressionOffsetMax                       0x3FFF
+#define kDNSCompressionPointerLength           2
 
 //---------------------------------------------------------------------------------------------------------------------------
+#define kDNSQueryMessageMaxLen         ( kDNSHeaderLength + kDomainNameLengthMax + sizeof( dns_fixed_fields_question ) )
+
 /*!    @brief          Writes a single-question DNS query message.
        
        @param          inMsgID                 The query message's ID.
@@ -344,11 +755,7 @@ STATIC_INLINE void DNSMessageWriteLabelPointer( uint8_t inLabelPtr[ STATIC_PARAM
        @param          inQClass                The question's QCLASS.
        @param          outMsg                  Buffer to write DNS query message.
        @param          outLen                  Gets set to the length of the DNS query message.
-       
-       @discussion     The duplicate domain name must be freed with free() when no longer needed.
 */
-#define kDNSQueryMessageMaxLen         ( kDNSHeaderLength + kDomainNameLengthMax + sizeof( dns_fixed_fields_question ) )
-
 OSStatus
        DNSMessageWriteQuery(
                uint16_t                inMsgID,
@@ -359,18 +766,56 @@ OSStatus
                uint8_t                 outMsg[ STATIC_PARAM kDNSQueryMessageMaxLen ],
                size_t *                outLen );
 
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Creates a collapsed version of a DNS message.
+       
+       @param          inMsgPtr                Pointer to the start of the DNS message.
+       @param          inMsgLen                Length of the DNS message.
+       @param          outMsgLen               Pointer of variable to set to the length of the collapsed DNS message.
+       @param          outError                Pointer of variable to set to the error encountered by this function, if any.
+       
+       @result         A dynamically allocated collapsed version of the DNS message.
+       
+       @discussion This function creates a copy of a DNS message, except that
+       
+       1. All records not in the Authority and Additional sections are removed.
+       2. The CNAME chain, if any, from the QNAME to the non-CNAME records is collapsed, i.e., all CNAME records are removed.
+       3. All records that are not direct or indirect answers to the question are also removed.
+       
+       Note: Collapsing a DNS message is a non-standard operation and should be used with caution.
+*/
+uint8_t * _Nullable
+       DNSMessageCollapse(
+               const uint8_t *                 inMsgPtr,
+               size_t                                  inMsgLen,
+               size_t * _Nullable              outMsgLen,
+               OSStatus * _Nullable    outError );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Appends one domain name to another.
+       
+       @param          inName                  Pointer to the target domain name.
+       @param          inOtherName             Pointer to the domain name to append to the target domain name.
+       @param          outEnd                  Gets set to point to the new end of the domain name if the append succeeded. (Optional)
+*/
+OSStatus
+       DomainNameAppendDomainName(
+               uint8_t                                                 inName[ STATIC_PARAM kDomainNameLengthMax ],
+               const uint8_t *                                 inOtherName,
+               uint8_t * _Nullable * _Nullable outEnd );
+
 //---------------------------------------------------------------------------------------------------------------------------
 /*!    @brief          Appends a C string representing a textual sequence of labels to a domain name.
        
        @param          inName                  Pointer to the domain name.
-       @param          inString                Pointer to textual sequence of labels as a C string. (Optional)
+       @param          inString                Pointer to textual sequence of labels as a C string.
        @param          outEnd                  Gets set to point to the new end of the domain name if the append succeeded. (Optional)
 */
 OSStatus
        DomainNameAppendString(
-               uint8_t                 inName[ STATIC_PARAM kDomainNameLengthMax ],
-               const char *    inString,
-               uint8_t **              outEnd );
+               uint8_t                                                 inName[ STATIC_PARAM kDomainNameLengthMax ],
+               const char *                                    inString,
+               uint8_t * _Nullable * _Nullable outEnd );
 
 //---------------------------------------------------------------------------------------------------------------------------
 /*!    @brief          Creates a duplicate domain name.
@@ -378,11 +823,16 @@ OSStatus
        @param          inName                  The domain name to duplicate.
        @param          inLower                 If true, uppercase letters in the duplicate are converted to lowercase.
        @param          outNamePtr              Gets set to point to a dynamically allocated duplicate.
-       @param          outNameLen              Gets set to the length of the duplicate.
+       @param          outNameLen              Gets set to the length of the duplicate. (Optional)
        
        @discussion     The duplicate domain name must be freed with free() when no longer needed.
 */
-OSStatus       DomainNameDupEx( const uint8_t *inName, Boolean inLower, uint8_t **outNamePtr, size_t *outNameLen );
+OSStatus
+       DomainNameDupEx(
+               const uint8_t *                                 inName,
+               Boolean                                                 inLower,
+               uint8_t * _Nullable * _Nonnull  outNamePtr,
+               size_t * _Nullable                              outNameLen );
 
 #define DomainNameDup( IN_NAME, OUT_NAME, OUT_LEN )                            DomainNameDupEx( IN_NAME, false, OUT_NAME, OUT_LEN )
 #define DomainNameDupLower( IN_NAME, OUT_NAME, OUT_LEN )               DomainNameDupEx( IN_NAME, true, OUT_NAME, OUT_LEN )
@@ -403,14 +853,12 @@ Boolean   DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 );
        @param          outName                 Buffer to write the domain name in label format.
        @param          inString                Textual representation of a domain name as a C string.
        @param          outEnd                  Gets set to point to the new end of the domain name if the append succeeded. (Optional)
-       
-       @discussion     The duplicate domain name must be freed with free() when no longer needed.
 */
 OSStatus
        DomainNameFromString(
-               uint8_t                 outName[ STATIC_PARAM kDomainNameLengthMax ],
-               const char *    inString,
-               uint8_t **              outEnd );
+               uint8_t                                                 outName[ STATIC_PARAM kDomainNameLengthMax ],
+               const char *                                    inString,
+               uint8_t * _Nullable * _Nullable outEnd );
 
 //---------------------------------------------------------------------------------------------------------------------------
 /*!    @brief          Gets the next label in a domain name label sequence.
@@ -426,18 +874,20 @@ STATIC_INLINE const uint8_t *     DomainNameGetNextLabel( const uint8_t *inLabel )
 }
 
 //---------------------------------------------------------------------------------------------------------------------------
-/*!    @brief          Computes the length of a domain name in label format.
+/*!    @brief          Returns the length of a domain name.
        
-       @param          inName          The domain name.
+       @param          inName          The domain name in label format.
 */
-STATIC_INLINE size_t   DomainNameLength( const uint8_t *inName )
-{
-       const uint8_t *         label;
-       int                                     len;
+size_t DomainNameLength( const uint8_t *inName );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Returns the number of labels that make up a domain name.
        
-       for( label = inName; ( len = *label ) != 0; label = &label[ 1 + len ] ) {}
-       return( (size_t)( label - inName ) + 1 );
-}
+       @param          inName          The uncompressed domain name in label format.
+       
+       @result         Returns -1 if the domain name is malformed. Otherwise, returns the number of labels, not counting the root.
+*/
+int    DomainNameLabelCount( const uint8_t *inName );
 
 //---------------------------------------------------------------------------------------------------------------------------
 /*!    @brief          Converts a domain name in label format to its textual representation as a C string.
@@ -449,13 +899,195 @@ STATIC_INLINE size_t     DomainNameLength( const uint8_t *inName )
 */
 OSStatus
        DomainNameToString(
-               const uint8_t *         inName,
-               const uint8_t *         inLimit,
-               char                            outString[ STATIC_PARAM kDNSServiceMaxDomainName ],
-               const uint8_t **        outPtr );
+               const uint8_t *                                                 inName,
+               const uint8_t * _Nullable                               inLimit,
+               char                                                                    outString[ STATIC_PARAM kDNSServiceMaxDomainName ],
+               const uint8_t * _Nullable * _Nullable   outPtr );
 
-#ifdef __cplusplus
-}
-#endif
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Compares two domain name labels for case-insensitive equality.
+       
+       @param          inLabel1        Pointer to the first label.
+       @param          inLabel2        Pointer to the second label.
+       
+       @result         If the label are equal, returns true. Otherwise, returns false.
+*/
+Boolean        DomainLabelEqual( const uint8_t *inLabel1, const uint8_t *inLabel2 );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          For a resource record type's numeric value, returns the resource record type's mnemonic as a C string.
+       
+       @param          inValue         A resource record type's numeric value.
+       
+       @result         The resource record type's mnemonic as a C string if the numeric value is recognized, otherwise, NULL.
+*/
+const char * _Nullable DNSRecordTypeValueToString( int inValue );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          For a resource record type's mnemonic, returns the resource record type's numeric value.
+       
+       @param          inString        A resource record type's mnemonic as a C string.
+       
+       @result         The resource record type's numeric value if the mnemonic is recognized, otherwise, 0.
+*/
+uint16_t       DNSRecordTypeStringToValue( const char *inString );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          For an RCODE value, returns the corresponding RCODE mnemonic as a C string.
+       
+       @param          inValue         An RCODE value.
+       
+       @result         The mnemonic as a C string if the RCODE value is recognized. Otherwise, NULL.
+*/
+const char * _Nullable DNSRCodeToString( int inValue );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          For an RCODE mnemonic, returns the corresponding RCODE value.
+       
+       @param          inString        An RCODE mnemonic as a C string.
+       
+       @result         If the mnemonic is recognized, the corresponding RCODE value (between 0 and 15, inclusive). Otherwise, -1.
+*/
+int    DNSRCodeFromString( const char * const inString );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @typedef        DNSMessageToStringFlags
+       
+       @brief          Formatting options for DNSMessageToString().
+*/
+typedef uint32_t               DNSMessageToStringFlags;
+
+#define kDNSMessageToStringFlag_Null           0
+#define kDNSMessageToStringFlag_MDNS           ( 1U << 0 ) // Treat the message as an mDNS message as opposed to DNS.
+#define kDNSMessageToStringFlag_RawRData       ( 1U << 1 ) // Print record data as a hex string, i.e., no formatting.
+#define kDNSMessageToStringFlag_OneLine                ( 1U << 2 ) // Format the string as a single line.
+#define kDNSMessageToStringFlag_Privacy                ( 1U << 3 ) // Obfuscate or redact items such as domain names and IP addresses.
+#define kDNSMessageToStringFlag_HeaderOnly     ( 1U << 4 ) // Limit printing to just the message header.
+#define kDNSMessageToStringFlag_BodyOnly       ( 1U << 5 ) // Limit printing to just the message body.
+
+#define kDNSMessageToStringFlags_None          kDNSMessageToStringFlag_Null
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Creates a textual representation of a DNS message as a C string.
+       
+       @param          inMsgPtr        Pointer to the beginning of the DNS message.
+       @param          inMsgLen        Length of the DNS message.
+       @param          inFlags         Flags that specify formatting options.
+       @param          outString       Gets set to point to the dynamically allocated C string.
+       
+       @discussion     The created string must be freed with free() when no longer needed.
+*/
+OSStatus
+       DNSMessageToString(
+               const uint8_t *                         inMsgPtr,
+               size_t                                          inMsgLen,
+               DNSMessageToStringFlags         inFlags,
+               char * _Nullable * _Nonnull     outString );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Creates a textual representation of a DNS resource record's data as a C string.
+       
+       @param          inRDataPtr              Pointer to the beginning of record data.
+       @param          inRDataLen              Length of the record data.
+       @param          inRecordType    The record's numeric type.
+       @param          inMsgPtr                Pointer to the beginning of the DNS message containing the resource record. (Optional)
+       @param          inMsgLen                Length of the DNS message containing the resource record.
+       @param          inPrivacy               If true, sensitive items, such as domain names and IP addresses, are obfuscated or redacted.
+       @param          outString               Gets set to point to the dynamically allocated C string.
+       
+       @discussion     The created string must be freed with free() when no longer needed.
+*/
+OSStatus
+       DNSRecordDataToStringEx(
+               const void *                                    inRDataPtr,
+               size_t                                                  inRDataLen,
+               int                                                             inRecordType,
+               const void * _Nullable                  inMsgPtr,
+               size_t                                                  inMsgLen,
+               Boolean                                                 inPrivacy,
+               char * _Nullable * _Nonnull             outString );
+
+#define DNSRecordDataToString(IN_RDATA_PTR, IN_RDATA_LEN, IN_RECORD_TYPE, OUT_STRING) \
+       DNSRecordDataToStringEx(IN_RDATA_PTR, IN_RDATA_LEN, IN_RECORD_TYPE, NULL, 0, false, OUT_STRING)
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Computes a DNSKEY record data's DNSSEC key tag.
+       
+       @param          inRDataPtr              Pointer to the beginning of the DNSKEY record data.
+       @param          inRDataLen              Length of the DNSKEY record data.
+       
+       @discussion     Uses calculation described by <https://tools.ietf.org/html/rfc4034#appendix-B>.
+*/
+uint16_t       DNSComputeDNSKeyTag( const void *inRDataPtr, size_t inRDataLen );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Writes an obfuscated version of a C string to a buffer as a C string.
+       
+       @param          inBufPtr                Pointer to the beginning of the buffer to write the obfuscated version of the string.
+       @param          inBufLen                Length of the buffer.
+       @param          inString                The string to obfuscate.
+       
+       @result
+               If the value returned is non-negative, then the value is the number of non-NUL characters that would have been
+               written if the size of the buffer were unlimited. If the value returned is negative, then the function failed.
+               In this case, the value returned is an error code.
+       
+       @discussion
+               This function is useful for obfuscating domain name strings using the same type of obfuscation used by
+               DNSMessageToString().
+               
+               If the returned value is non-negative, then, unless inBufLen is 0, the output string will be NUL-terminated.
+               If inBufLen is too small, then the end of the output string will be truncated. If inBufLen is not greater than
+               a non-negative return value, then the output string was truncated.
+*/
+int    DNSMessagePrintObfuscatedString( char *inBufPtr, size_t inBufLen, const char *inString );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Writes an obfuscated version of an IPv4 address to a buffer as a C string.
+       
+       @param          inBufPtr                Pointer to the beginning of the buffer to write the obfuscated version of the string.
+       @param          inBufLen                Length of the buffer.
+       @param          inAddr                  IPv4 address in host byte order.
+       
+       @result
+               If the value returned is non-negative, then the value is the number of non-NUL characters that would have been
+               written if the size of the buffer were unlimited. If the value returned is negative, then the function failed.
+               In this case, the value returned is an error code.
+       
+       @discussion
+               This function is useful for obfuscating an IPv4 addresses using the same type of obfuscation used by
+               DNSMessageToString().
+               
+               If the returned value is non-negative, then, unless inBufLen is 0, the output string will be NUL-terminated.
+               If inBufLen is too small, then the end of the output string will be truncated. If inBufLen is not greater than
+               a non-negative return value, then the output string was truncated.
+*/
+int    DNSMessagePrintObfuscatedIPv4Address( char *inBufPtr, size_t inBufLen, const uint32_t inAddr );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Writes an obfuscated version of an IPv6 address to a buffer as a C string.
+       
+       @param          inBufPtr                Pointer to the beginning of the buffer to write the obfuscated version of the string.
+       @param          inBufLen                Length of the buffer.
+       @param          inAddr                  IPv6 address as an array of 16 bytes.
+       
+       @result
+               If the value returned is non-negative, then the value is the number of non-NUL characters that would have been
+               written if the size of the buffer were unlimited. If the value returned is negative, then the function failed.
+               In this case, the value returned is an error code.
+       
+       @discussion
+               This function is useful for obfuscating an IPv6 address using the same type of obfuscation used by
+               DNSMessageToString().
+               
+               If the returned value is non-negative, then, unless inBufLen is 0, the output string will be NUL-terminated.
+               If inBufLen is too small, then the end of the output string will be truncated. If inBufLen is not greater than
+               a non-negative return value, then the output string was truncated.
+*/
+int    DNSMessagePrintObfuscatedIPv6Address( char *inBufPtr, size_t inBufLen, const uint8_t inAddr[ STATIC_PARAM 16 ] );
+
+__END_DECLS
+
+CU_ASSUME_NONNULL_END
 
 #endif // __DNSMessage_h
diff --git a/Clients/dnssdutil/DNSServerDNSSEC.c b/Clients/dnssdutil/DNSServerDNSSEC.c
new file mode 100644 (file)
index 0000000..afdd1a2
--- /dev/null
@@ -0,0 +1,3676 @@
+/*
+       Copyright (c) 2020 Apple Inc. All rights reserved.
+*/
+
+#include "DNSServerDNSSEC.h"
+
+#include "DNSMessage.h"
+
+#include <Security/SecKeyPriv.h>
+
+//===========================================================================================================================
+// MARK: - DNS Key Infos -
+
+// The first member of each algorithm-specific DNS Key info data structure must be of type dns_fixed_fields_dnskey, and
+// must be immediately followed by its public key member variable. This is so that together they can be used as DNSKEY
+// record data. See <https://tools.ietf.org/html/rfc4034#section-2.1>.
+
+#define _DNSKeyInfoCompileTimeChecks( ALG_NAME )                                                                                                                               \
+       check_compile_time( offsetof( DNSKey ## ALG_NAME ## Info, fixedFields ) == 0 );                                                         \
+       check_compile_time( offsetof( DNSKey ## ALG_NAME ## Info, pubKey ) == sizeof( dns_fixed_fields_dnskey ) );      \
+       check_compile_time( kDNSServerSignatureLengthMax >= k ## ALG_NAME ## _SignatureBytes )
+
+//===========================================================================================================================
+// MARK: - RSA/SHA-1 DNS Key Info
+// See <https://tools.ietf.org/html/rfc3110>.
+
+#define kRSASHA1_PublicKeyBytes                260
+#define kRSASHA1_SecretKeyBytes                1190
+#define kRSASHA1_SignatureBytes                256
+
+typedef struct
+{
+       dns_fixed_fields_dnskey         fixedFields;                                            // DNSKEY RDATA fixed fields.
+       uint8_t                                         pubKey[ kRSASHA1_PublicKeyBytes ];      // Public key.
+       uint8_t                                         secKey[ kRSASHA1_SecretKeyBytes ];      // Secret key.
+       
+}      DNSKeyRSASHA1Info;
+
+_DNSKeyInfoCompileTimeChecks( RSASHA1 );
+
+//===========================================================================================================================
+// MARK: - RSA/SHA-256 DNS Key Info
+// See <https://tools.ietf.org/html/rfc5702>.
+
+#define kRSASHA256_PublicKeyBytes              260
+#define kRSASHA256_SecretKeyBytes              1190
+#define kRSASHA256_SignatureBytes              256
+
+typedef struct
+{
+       dns_fixed_fields_dnskey         fixedFields;                                                    // DNSKEY RDATA fixed fields.
+       uint8_t                                         pubKey[ kRSASHA256_PublicKeyBytes ];    // Public key.
+       uint8_t                                         secKey[ kRSASHA256_SecretKeyBytes ];    // Secret key.
+       
+}      DNSKeyRSASHA256Info;
+
+_DNSKeyInfoCompileTimeChecks( RSASHA256 );
+
+//===========================================================================================================================
+// MARK: - RSA/SHA-512 DNS Key Info
+// See <https://tools.ietf.org/html/rfc5702>.
+
+#define kRSASHA512_PublicKeyBytes              260
+#define kRSASHA512_SecretKeyBytes              1190
+#define kRSASHA512_SignatureBytes              256
+
+typedef struct
+{
+       dns_fixed_fields_dnskey         fixedFields;                                                    // DNSKEY RDATA fixed fields.
+       uint8_t                                         pubKey[ kRSASHA512_PublicKeyBytes ];    // Public key.
+       uint8_t                                         secKey[ kRSASHA512_SecretKeyBytes ];    // Secret key.
+       
+}      DNSKeyRSASHA512Info;
+
+_DNSKeyInfoCompileTimeChecks( RSASHA512 );
+
+//===========================================================================================================================
+// MARK: - ECDSA Curve P-256 with SHA-256 DNS Key Info
+// See <https://tools.ietf.org/html/rfc6605>.
+
+#define kECDSAP256SHA256_PublicKeyBytes                64
+#define kECDSAP256SHA256_SecretKeyBytes                96
+#define kECDSAP256SHA256_SignatureBytes                64
+
+typedef struct
+{
+       dns_fixed_fields_dnskey         fixedFields;                                                            // DNSKEY RDATA fixed fields.
+       uint8_t                                         pubKey[ kECDSAP256SHA256_PublicKeyBytes ];      // Public key.
+       uint8_t                                         secKey[ kECDSAP256SHA256_SecretKeyBytes ];      // Secret key.
+       
+}      DNSKeyECDSAP256SHA256Info;
+
+_DNSKeyInfoCompileTimeChecks( ECDSAP256SHA256 );
+
+//===========================================================================================================================
+// MARK: - ECDSA Curve P-384 with SHA-384 DNS Key Info
+// See <https://tools.ietf.org/html/rfc6605>.
+
+#define kECDSAP384SHA384_PublicKeyBytes                64
+#define kECDSAP384SHA384_SecretKeyBytes                96
+#define kECDSAP384SHA384_SignatureBytes                64
+
+typedef struct
+{
+       dns_fixed_fields_dnskey         fixedFields;                                                            // DNSKEY RDATA fixed fields.
+       uint8_t                                         pubKey[ kECDSAP384SHA384_PublicKeyBytes ];      // Public key.
+       uint8_t                                         secKey[ kECDSAP384SHA384_SecretKeyBytes ];      // Secret key.
+       
+}      DNSKeyECDSAP384SHA384Info;
+
+_DNSKeyInfoCompileTimeChecks( ECDSAP384SHA384 );
+
+//===========================================================================================================================
+// MARK: - Ed25519 DNS Key Info
+// See <https://tools.ietf.org/html/rfc8080>.
+
+typedef struct
+{
+       dns_fixed_fields_dnskey         fixedFields;                                            // DNSKEY RDATA fixed fields.
+       uint8_t                                         pubKey[ kEd25519_PublicKeyBytes ];      // Public key.
+       uint8_t                                         secKey[ kEd25519_SecretKeyBytes ];      // Secret key.
+       
+}      DNSKeyEd25519Info;
+
+_DNSKeyInfoCompileTimeChecks( Ed25519 );
+
+//===========================================================================================================================
+// MARK: - DNS Key Info Union
+
+#define _DNSKeyInfoUnionMember( ALG_NAME )             DNSKey ## ALG_NAME ## Info ALG_NAME
+
+union DNSKeyInfo
+{
+       dns_fixed_fields_dnskey         fixedFields;
+       _DNSKeyInfoUnionMember( RSASHA1 );
+       _DNSKeyInfoUnionMember( RSASHA256 );
+       _DNSKeyInfoUnionMember( RSASHA512 );
+       _DNSKeyInfoUnionMember( ECDSAP256SHA256 );
+       _DNSKeyInfoUnionMember( ECDSAP384SHA384 );
+       _DNSKeyInfoUnionMember( Ed25519 );
+};
+
+//===========================================================================================================================
+// MARK: - DNS Keys -
+
+#define _DNSKeyInfoFixedFieldsInitKSK( ALGORITHM )     \
+       {                                                                                               \
+               .flags          = { 0x01, 0x01 },                               \
+               .protocol       = { kDNSKeyProtocol_DNSSEC },   \
+               .algorithm      = { (ALGORITHM) }                               \
+       }
+
+#define _DNSKeyInfoFixedFieldsInitZSK( ALGORITHM )     \
+       {                                                                                               \
+               .flags          = { 0x01, 0x00 },                               \
+               .protocol       = { kDNSKeyProtocol_DNSSEC },   \
+               .algorithm      = { (ALGORITHM) }                               \
+       }
+
+// The Zone Label argument is an integer used to index into a DNS Key sets array, so the number of DNSKEY sets should be
+// one more than the maximum allowable Zone Label argument.
+
+#define _DNSKeySetsCompileTimeChecks( ALG_NAME ) \
+       check_compile_time( countof( kDNSKey ## ALG_NAME ## Sets ) == ( kZoneLabelIndexArgMax + 1 ) )
+
+//===========================================================================================================================
+// MARK: - RSA/SHA1 DNS Keys
+
+typedef struct
+{
+       DNSKeyRSASHA1Info               ksk;    // Key-Signing Key
+       DNSKeyRSASHA1Info               zsk;    // Zone-Signing Key
+       
+}      DNSKeyRSASHA1Set;
+
+static const DNSKeyRSASHA1Set          kDNSKeyRSASHA1Sets[] =
+{
+       // DNSSEC Zone 0
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_RSASHA1 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xbe, 0x7b, 0xba, 0xa1, 0x0c, 0xdf, 0x40, 0xf7, 0xcc, 0x73, 0xae, 0x5a,
+                               0xba, 0xeb, 0x34, 0x18, 0x50, 0xbc, 0x4b, 0x74, 0x14, 0x8c, 0xf9, 0xc1, 0x6c, 0x7d, 0xb0, 0x0d,
+                               0xa7, 0x93, 0x13, 0x56, 0x40, 0xef, 0x0b, 0x35, 0x31, 0xd9, 0x6f, 0x11, 0x96, 0xec, 0x2a, 0x65,
+                               0x50, 0xd5, 0xeb, 0x51, 0xa4, 0x95, 0xfb, 0x0f, 0x9f, 0xc5, 0x71, 0x22, 0x00, 0xc2, 0x59, 0xc6,
+                               0x97, 0x8d, 0xe0, 0x5e, 0x5b, 0x49, 0xdc, 0xc9, 0xfb, 0x36, 0xe1, 0x88, 0xb9, 0x65, 0xf3, 0xff,
+                               0xa0, 0x4e, 0x26, 0xe5, 0x00, 0x41, 0x99, 0x04, 0xa4, 0x64, 0x93, 0x77, 0x57, 0xee, 0x57, 0x55,
+                               0xd7, 0xba, 0x2f, 0x8c, 0x7a, 0x39, 0x66, 0x93, 0x38, 0x5f, 0x9b, 0x3b, 0xb1, 0xe3, 0xab, 0xa2,
+                               0x62, 0xd2, 0x88, 0x34, 0xcf, 0x12, 0x39, 0x5a, 0x3b, 0xf1, 0x72, 0x8d, 0xbe, 0x43, 0x2e, 0xb1,
+                               0xb0, 0x27, 0x81, 0x32, 0x3f, 0x3e, 0xfa, 0x73, 0x47, 0x1e, 0x32, 0x41, 0x69, 0x91, 0xc7, 0x9a,
+                               0x69, 0x27, 0xda, 0x3d, 0x9a, 0x45, 0x5c, 0x83, 0xf4, 0xea, 0x34, 0xb8, 0xcc, 0xf6, 0xb0, 0x37,
+                               0x1f, 0x7f, 0x9e, 0x5a, 0x57, 0x0f, 0xb0, 0x02, 0x35, 0x15, 0x67, 0xfb, 0x9d, 0x3f, 0x08, 0x9c,
+                               0x3a, 0xcb, 0x81, 0x2c, 0x03, 0x2d, 0x44, 0x83, 0xd2, 0xff, 0x75, 0x4e, 0xb0, 0xec, 0x23, 0x0b,
+                               0x8c, 0x7b, 0x52, 0x42, 0x75, 0x94, 0x66, 0xdf, 0x90, 0x7e, 0xa1, 0x3f, 0x33, 0x5c, 0x8f, 0xe4,
+                               0xb9, 0x7c, 0xc9, 0x08, 0xef, 0xbb, 0x9a, 0x6e, 0x91, 0x8d, 0x80, 0xda, 0xc5, 0x37, 0x2c, 0xab,
+                               0x26, 0xc9, 0xd6, 0xc4, 0x69, 0x52, 0x9e, 0x46, 0xc9, 0xce, 0xf2, 0x0b, 0xea, 0x71, 0x28, 0x1f,
+                               0xb3, 0x61, 0x62, 0x5c, 0x7e, 0x56, 0x8b, 0x3f, 0x1c, 0xb0, 0x3c, 0x92, 0x28, 0x87, 0xa1, 0x5b,
+                               0xf2, 0x9a, 0x31, 0x21
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbe, 0x7b, 0xba, 0xa1,
+                               0x0c, 0xdf, 0x40, 0xf7, 0xcc, 0x73, 0xae, 0x5a, 0xba, 0xeb, 0x34, 0x18, 0x50, 0xbc, 0x4b, 0x74,
+                               0x14, 0x8c, 0xf9, 0xc1, 0x6c, 0x7d, 0xb0, 0x0d, 0xa7, 0x93, 0x13, 0x56, 0x40, 0xef, 0x0b, 0x35,
+                               0x31, 0xd9, 0x6f, 0x11, 0x96, 0xec, 0x2a, 0x65, 0x50, 0xd5, 0xeb, 0x51, 0xa4, 0x95, 0xfb, 0x0f,
+                               0x9f, 0xc5, 0x71, 0x22, 0x00, 0xc2, 0x59, 0xc6, 0x97, 0x8d, 0xe0, 0x5e, 0x5b, 0x49, 0xdc, 0xc9,
+                               0xfb, 0x36, 0xe1, 0x88, 0xb9, 0x65, 0xf3, 0xff, 0xa0, 0x4e, 0x26, 0xe5, 0x00, 0x41, 0x99, 0x04,
+                               0xa4, 0x64, 0x93, 0x77, 0x57, 0xee, 0x57, 0x55, 0xd7, 0xba, 0x2f, 0x8c, 0x7a, 0x39, 0x66, 0x93,
+                               0x38, 0x5f, 0x9b, 0x3b, 0xb1, 0xe3, 0xab, 0xa2, 0x62, 0xd2, 0x88, 0x34, 0xcf, 0x12, 0x39, 0x5a,
+                               0x3b, 0xf1, 0x72, 0x8d, 0xbe, 0x43, 0x2e, 0xb1, 0xb0, 0x27, 0x81, 0x32, 0x3f, 0x3e, 0xfa, 0x73,
+                               0x47, 0x1e, 0x32, 0x41, 0x69, 0x91, 0xc7, 0x9a, 0x69, 0x27, 0xda, 0x3d, 0x9a, 0x45, 0x5c, 0x83,
+                               0xf4, 0xea, 0x34, 0xb8, 0xcc, 0xf6, 0xb0, 0x37, 0x1f, 0x7f, 0x9e, 0x5a, 0x57, 0x0f, 0xb0, 0x02,
+                               0x35, 0x15, 0x67, 0xfb, 0x9d, 0x3f, 0x08, 0x9c, 0x3a, 0xcb, 0x81, 0x2c, 0x03, 0x2d, 0x44, 0x83,
+                               0xd2, 0xff, 0x75, 0x4e, 0xb0, 0xec, 0x23, 0x0b, 0x8c, 0x7b, 0x52, 0x42, 0x75, 0x94, 0x66, 0xdf,
+                               0x90, 0x7e, 0xa1, 0x3f, 0x33, 0x5c, 0x8f, 0xe4, 0xb9, 0x7c, 0xc9, 0x08, 0xef, 0xbb, 0x9a, 0x6e,
+                               0x91, 0x8d, 0x80, 0xda, 0xc5, 0x37, 0x2c, 0xab, 0x26, 0xc9, 0xd6, 0xc4, 0x69, 0x52, 0x9e, 0x46,
+                               0xc9, 0xce, 0xf2, 0x0b, 0xea, 0x71, 0x28, 0x1f, 0xb3, 0x61, 0x62, 0x5c, 0x7e, 0x56, 0x8b, 0x3f,
+                               0x1c, 0xb0, 0x3c, 0x92, 0x28, 0x87, 0xa1, 0x5b, 0xf2, 0x9a, 0x31, 0x21, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x13, 0x91, 0xa8, 0xda, 0x8b, 0x1f, 0x7e, 0x7b, 0x01, 0x52, 0x9b,
+                               0x1a, 0xc5, 0x71, 0x8c, 0xc5, 0xb8, 0xb9, 0xa2, 0x1e, 0x22, 0xa4, 0x59, 0xb3, 0x96, 0x33, 0xe8,
+                               0x11, 0x27, 0x0c, 0xe9, 0x8e, 0x26, 0xc2, 0x9d, 0x74, 0xa6, 0xbd, 0x39, 0xf7, 0x65, 0x24, 0x0d,
+                               0xb6, 0x4b, 0x3d, 0x31, 0x87, 0xe4, 0x30, 0x3c, 0xfd, 0x88, 0x6c, 0xf8, 0xd2, 0x9a, 0x4b, 0x07,
+                               0xd2, 0x6a, 0xac, 0x7a, 0x87, 0x47, 0x0f, 0xf4, 0xb2, 0x52, 0xc5, 0x7b, 0x1b, 0x18, 0x42, 0x3c,
+                               0x9f, 0x1f, 0x8c, 0x17, 0xdb, 0xd6, 0x90, 0x66, 0xaa, 0x29, 0x97, 0x64, 0x3c, 0xfd, 0x02, 0xc6,
+                               0x8f, 0x27, 0x9d, 0x55, 0x98, 0x34, 0x25, 0xa4, 0x7f, 0x98, 0x69, 0x88, 0xb6, 0xf3, 0xc4, 0xba,
+                               0x29, 0x1c, 0x72, 0x43, 0x92, 0xfa, 0xa3, 0xaf, 0xd2, 0x5b, 0x83, 0xd2, 0x6b, 0x1f, 0x1c, 0xf8,
+                               0xca, 0xec, 0x18, 0x20, 0x37, 0x68, 0x92, 0x07, 0xa0, 0xf3, 0x3b, 0x61, 0xb8, 0xa0, 0x8c, 0x5f,
+                               0xd9, 0x14, 0x28, 0x4d, 0xa3, 0x83, 0xf2, 0x83, 0x13, 0xde, 0x03, 0x4c, 0xa8, 0xab, 0xa9, 0x1f,
+                               0x66, 0x4c, 0xf6, 0xb2, 0x0d, 0x90, 0xae, 0x08, 0xa7, 0x8c, 0x2d, 0xe9, 0x2b, 0xce, 0x9c, 0x2d,
+                               0x56, 0x0e, 0x7b, 0x7b, 0x14, 0x20, 0x8b, 0x2b, 0x2f, 0x1a, 0x26, 0x63, 0xa4, 0xd4, 0x0c, 0x86,
+                               0xac, 0x5d, 0x0f, 0x8e, 0x80, 0x27, 0x10, 0x42, 0xd6, 0x38, 0x63, 0x0e, 0x59, 0xa2, 0x61, 0xf1,
+                               0xde, 0xda, 0x3f, 0x77, 0x44, 0x13, 0xd6, 0x1a, 0x01, 0x59, 0xb7, 0xc3, 0xda, 0x52, 0xf4, 0x46,
+                               0x69, 0xed, 0x4f, 0xc2, 0xe7, 0x69, 0x0a, 0x02, 0xff, 0x2f, 0x80, 0x63, 0x0a, 0xf5, 0xf8, 0xe4,
+                               0xb9, 0x43, 0x26, 0x5b, 0xf8, 0xc2, 0xa6, 0xe4, 0xd2, 0x46, 0x12, 0x7b, 0x7e, 0xe1, 0xe4, 0xc4,
+                               0x74, 0xfc, 0x8c, 0xe8, 0xbf, 0x02, 0x81, 0x81, 0x00, 0xf1, 0x40, 0x2b, 0x43, 0xa2, 0x49, 0x60,
+                               0x1a, 0xca, 0x88, 0xa2, 0x7a, 0xb0, 0xf7, 0xb4, 0xc1, 0x1d, 0x0a, 0xc5, 0xd3, 0x6a, 0xb0, 0xe0,
+                               0xd3, 0xce, 0x99, 0x84, 0x91, 0xea, 0x9b, 0x48, 0xf1, 0x22, 0x37, 0x29, 0xfd, 0xa0, 0xf7, 0xc1,
+                               0x94, 0x5f, 0xe2, 0x24, 0xa3, 0x1d, 0x0a, 0x5f, 0xea, 0x06, 0x3b, 0xaf, 0x42, 0xc3, 0x7d, 0x1d,
+                               0x22, 0xaf, 0xb4, 0x64, 0x50, 0x35, 0xac, 0xed, 0x65, 0xac, 0x6f, 0x60, 0xf0, 0xa2, 0xa5, 0x20,
+                               0xab, 0xff, 0xb3, 0x93, 0xb0, 0x51, 0x6d, 0xb5, 0x1b, 0x2a, 0x17, 0x06, 0x07, 0x1f, 0xa2, 0x57,
+                               0x4c, 0xf0, 0x02, 0x13, 0xfc, 0x76, 0x8f, 0xae, 0x7e, 0x0e, 0x66, 0xc2, 0xc7, 0x39, 0xed, 0x7e,
+                               0x0d, 0x74, 0xaa, 0xbc, 0xfb, 0xb4, 0x25, 0x1c, 0x72, 0xd1, 0x65, 0x9f, 0x91, 0xe3, 0xfd, 0xd1,
+                               0x38, 0xea, 0x89, 0x8c, 0x32, 0x7a, 0xfd, 0x64, 0x7f, 0x02, 0x81, 0x81, 0x00, 0xca, 0x20, 0xff,
+                               0x2b, 0xe2, 0x8a, 0xc7, 0xab, 0x40, 0xbe, 0xf2, 0xa3, 0x52, 0x69, 0x8e, 0x40, 0x79, 0x39, 0x07,
+                               0x5c, 0x90, 0x43, 0x60, 0xcf, 0x87, 0xc5, 0xdd, 0x0e, 0x34, 0x77, 0xad, 0x50, 0xd1, 0x98, 0xbd,
+                               0x92, 0x7a, 0x9f, 0x8c, 0x1d, 0x72, 0x33, 0xc5, 0x66, 0x7e, 0xc6, 0x28, 0xc4, 0xfb, 0xe9, 0x83,
+                               0x79, 0x7f, 0xb1, 0xfb, 0x60, 0x30, 0x97, 0xd8, 0x59, 0xbe, 0xc0, 0x1d, 0x39, 0xe8, 0x33, 0x20,
+                               0x84, 0xa7, 0x58, 0x5c, 0xaf, 0xe4, 0xc9, 0x15, 0xed, 0xaa, 0xce, 0xe5, 0x27, 0x29, 0xce, 0x83,
+                               0x12, 0xec, 0xc5, 0x8a, 0xe6, 0xb7, 0xd3, 0x27, 0xb8, 0x0f, 0x09, 0x28, 0x2f, 0x98, 0x2d, 0xc1,
+                               0x6e, 0xbf, 0xf0, 0x42, 0xdf, 0xe2, 0x09, 0x90, 0xbb, 0x94, 0x3a, 0x93, 0x93, 0x67, 0x62, 0xb1,
+                               0x10, 0x92, 0x82, 0x63, 0x49, 0x5f, 0x36, 0x56, 0x23, 0xab, 0x23, 0x1a, 0x5f, 0x02, 0x81, 0x80,
+                               0x2a, 0xa8, 0x31, 0xfe, 0x2f, 0x7f, 0xd1, 0xe2, 0x6a, 0xd5, 0x66, 0x05, 0x53, 0xad, 0x38, 0xe4,
+                               0xbc, 0x81, 0xdf, 0x20, 0xd6, 0xc8, 0x97, 0xb8, 0x5b, 0xdb, 0x81, 0x39, 0x5b, 0xc6, 0x41, 0x4b,
+                               0x81, 0xc5, 0x47, 0x43, 0x75, 0x66, 0xd9, 0x6f, 0xa6, 0xd4, 0x91, 0xad, 0xd5, 0xc0, 0xb3, 0xdc,
+                               0xe9, 0x65, 0x3c, 0x44, 0x0d, 0xd4, 0xdd, 0x85, 0xf8, 0x93, 0x68, 0xf9, 0x55, 0xc4, 0x51, 0xe5,
+                               0x9c, 0x6b, 0xc5, 0x34, 0x47, 0x4d, 0xf3, 0x4c, 0xf6, 0x55, 0x86, 0x53, 0xb2, 0xd2, 0x37, 0x27,
+                               0xaa, 0x75, 0x8f, 0xb7, 0x4a, 0xbc, 0xa5, 0xbb, 0x10, 0xe5, 0x0d, 0x2b, 0xa2, 0xbb, 0x1d, 0x1b,
+                               0x64, 0xe8, 0x7e, 0xa0, 0x43, 0x79, 0xde, 0xc3, 0xfa, 0x51, 0x06, 0x41, 0x6b, 0x44, 0xf4, 0x96,
+                               0xc8, 0xb7, 0xb3, 0x53, 0x54, 0x80, 0xd8, 0xb1, 0xbb, 0xa6, 0xbe, 0x87, 0x5e, 0x7f, 0xd7, 0x79,
+                               0x02, 0x81, 0x80, 0x3c, 0xf2, 0x60, 0x1e, 0xaa, 0x6b, 0x70, 0x33, 0x4a, 0x0d, 0x89, 0x7a, 0x07,
+                               0x92, 0x7e, 0x6b, 0x20, 0x62, 0x3a, 0xbf, 0x05, 0x5c, 0xdb, 0xa7, 0x17, 0xe8, 0x68, 0x74, 0x2d,
+                               0x0b, 0xdc, 0xfd, 0x9e, 0x85, 0x70, 0xbb, 0xe7, 0x2f, 0x8e, 0x7f, 0x1e, 0x7d, 0x4f, 0xcb, 0x4c,
+                               0xf4, 0x91, 0x3c, 0x7c, 0x3c, 0xf8, 0x00, 0xbd, 0xa9, 0x3c, 0x03, 0xd0, 0x03, 0x29, 0x20, 0x3e,
+                               0x6d, 0x0d, 0x22, 0xf5, 0xc9, 0xdc, 0xc7, 0x42, 0xf2, 0x58, 0xd8, 0x4c, 0xa2, 0xf9, 0x72, 0xb5,
+                               0x6b, 0x37, 0x1b, 0x6c, 0xf6, 0xb0, 0xfd, 0x16, 0xd9, 0xa5, 0xbd, 0x55, 0xb6, 0x5c, 0xa6, 0x9a,
+                               0x1a, 0x07, 0xbc, 0xfa, 0x24, 0xe9, 0xcb, 0x6c, 0x22, 0x94, 0xf7, 0x8e, 0xdf, 0x95, 0x7d, 0x89,
+                               0xff, 0x73, 0x9a, 0x21, 0xb5, 0x57, 0x70, 0x88, 0xcc, 0xba, 0x05, 0x58, 0xa9, 0x38, 0xa5, 0xfe,
+                               0xa7, 0x51, 0xff, 0x02, 0x81, 0x80, 0x14, 0x3f, 0x61, 0x7b, 0x6b, 0x32, 0xed, 0xcd, 0x65, 0x10,
+                               0x54, 0x4e, 0xa3, 0x6b, 0xda, 0x4e, 0x48, 0x20, 0xeb, 0xc5, 0x41, 0x86, 0x39, 0x36, 0x6e, 0xc5,
+                               0x1a, 0xf0, 0x17, 0x07, 0xac, 0xfb, 0xb0, 0x73, 0xb7, 0x15, 0x7f, 0x1c, 0xd2, 0xb8, 0xf2, 0x55,
+                               0x96, 0x70, 0xa1, 0xf7, 0xe3, 0x48, 0x39, 0x41, 0x9c, 0x5d, 0xe8, 0x50, 0x16, 0xed, 0xec, 0x1e,
+                               0xc3, 0xfd, 0x5d, 0x72, 0xd0, 0x42, 0x44, 0x35, 0xda, 0x1b, 0x07, 0x02, 0x19, 0x0b, 0xc4, 0xb2,
+                               0xc4, 0x0e, 0x08, 0x7d, 0x6c, 0x17, 0x8e, 0x75, 0x41, 0x8f, 0x56, 0xb8, 0x5e, 0x28, 0xf9, 0x30,
+                               0x80, 0x45, 0x5f, 0x2b, 0x5d, 0xe6, 0x67, 0x30, 0xac, 0x51, 0xf9, 0x64, 0x14, 0x6d, 0x51, 0xd0,
+                               0x4a, 0x05, 0xb5, 0xf3, 0xee, 0x7e, 0xa0, 0x77, 0x40, 0x0e, 0xe2, 0xbb, 0x5d, 0x4c, 0x0c, 0x92,
+                               0xb1, 0x91, 0x35, 0x1c, 0x49, 0xa1
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_RSASHA1 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xa2, 0xa2, 0x7c, 0x04, 0x40, 0x00, 0x36, 0x2f, 0x5d, 0xd4, 0x23, 0x87,
+                               0x8b, 0x4d, 0xd2, 0xb3, 0x0f, 0xfd, 0xbd, 0xc0, 0x0f, 0xcb, 0xf8, 0xce, 0xa1, 0x5d, 0x90, 0xd3,
+                               0x6d, 0x4c, 0xce, 0xbb, 0xe0, 0xde, 0x11, 0xd9, 0xbb, 0x08, 0x86, 0x24, 0x48, 0xd8, 0x9d, 0x33,
+                               0xff, 0xb0, 0x0e, 0xfb, 0x8d, 0xe9, 0xb8, 0x21, 0x1a, 0x2e, 0xb4, 0xd1, 0xd1, 0xcf, 0xc2, 0xb3,
+                               0x7d, 0xce, 0x82, 0xd2, 0xf9, 0x32, 0x45, 0x13, 0x1a, 0x84, 0xce, 0xa1, 0xef, 0x70, 0x9b, 0x74,
+                               0x3f, 0xb3, 0x92, 0x0e, 0xed, 0xe7, 0x7d, 0x3d, 0x0c, 0xf9, 0xb5, 0x94, 0x11, 0x8f, 0x06, 0xd1,
+                               0x0f, 0xdf, 0xf5, 0x27, 0x9d, 0xf7, 0xa1, 0x8f, 0xc9, 0x3f, 0x34, 0x09, 0x87, 0x4e, 0x2f, 0xe8,
+                               0xcf, 0x8e, 0x40, 0x2b, 0x55, 0xde, 0xe3, 0xbb, 0x7e, 0xb7, 0x97, 0xdf, 0xfe, 0xa5, 0x30, 0x21,
+                               0xc3, 0x84, 0xe3, 0x25, 0xb7, 0x5a, 0xf9, 0x24, 0xe8, 0xbe, 0x48, 0x97, 0xbc, 0xaa, 0x8a, 0x1a,
+                               0x9e, 0xea, 0xb3, 0x2d, 0x3c, 0x40, 0xf7, 0x31, 0xe2, 0xf6, 0x02, 0xf4, 0x72, 0x05, 0x70, 0x34,
+                               0x45, 0xf3, 0xcc, 0x7f, 0xf9, 0x76, 0x06, 0x10, 0xfc, 0x8c, 0x56, 0x54, 0x68, 0x76, 0x82, 0x7b,
+                               0x20, 0xfc, 0x70, 0x7d, 0xb5, 0x98, 0x5d, 0x33, 0x69, 0xf3, 0xea, 0x64, 0x44, 0x7e, 0xe7, 0x09,
+                               0x77, 0x61, 0xe0, 0xbf, 0x82, 0x87, 0xa1, 0x4f, 0x9e, 0x96, 0x90, 0xb7, 0xc7, 0x0b, 0xf4, 0x20,
+                               0x0d, 0x37, 0x27, 0x82, 0x4d, 0x84, 0xb3, 0x3d, 0x44, 0xe8, 0x49, 0x05, 0xeb, 0xd2, 0x07, 0xbf,
+                               0xd7, 0xcc, 0x90, 0x62, 0x3d, 0x42, 0x16, 0x62, 0x78, 0xe4, 0xdc, 0x77, 0x41, 0xc4, 0xa6, 0xc0,
+                               0xa3, 0x63, 0xef, 0x7d, 0xce, 0xd1, 0x71, 0x8d, 0xb1, 0x96, 0xdc, 0xa0, 0x7d, 0x86, 0xb2, 0x59,
+                               0x19, 0xcb, 0x9e, 0x4b
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa2, 0xa2, 0x7c, 0x04,
+                               0x40, 0x00, 0x36, 0x2f, 0x5d, 0xd4, 0x23, 0x87, 0x8b, 0x4d, 0xd2, 0xb3, 0x0f, 0xfd, 0xbd, 0xc0,
+                               0x0f, 0xcb, 0xf8, 0xce, 0xa1, 0x5d, 0x90, 0xd3, 0x6d, 0x4c, 0xce, 0xbb, 0xe0, 0xde, 0x11, 0xd9,
+                               0xbb, 0x08, 0x86, 0x24, 0x48, 0xd8, 0x9d, 0x33, 0xff, 0xb0, 0x0e, 0xfb, 0x8d, 0xe9, 0xb8, 0x21,
+                               0x1a, 0x2e, 0xb4, 0xd1, 0xd1, 0xcf, 0xc2, 0xb3, 0x7d, 0xce, 0x82, 0xd2, 0xf9, 0x32, 0x45, 0x13,
+                               0x1a, 0x84, 0xce, 0xa1, 0xef, 0x70, 0x9b, 0x74, 0x3f, 0xb3, 0x92, 0x0e, 0xed, 0xe7, 0x7d, 0x3d,
+                               0x0c, 0xf9, 0xb5, 0x94, 0x11, 0x8f, 0x06, 0xd1, 0x0f, 0xdf, 0xf5, 0x27, 0x9d, 0xf7, 0xa1, 0x8f,
+                               0xc9, 0x3f, 0x34, 0x09, 0x87, 0x4e, 0x2f, 0xe8, 0xcf, 0x8e, 0x40, 0x2b, 0x55, 0xde, 0xe3, 0xbb,
+                               0x7e, 0xb7, 0x97, 0xdf, 0xfe, 0xa5, 0x30, 0x21, 0xc3, 0x84, 0xe3, 0x25, 0xb7, 0x5a, 0xf9, 0x24,
+                               0xe8, 0xbe, 0x48, 0x97, 0xbc, 0xaa, 0x8a, 0x1a, 0x9e, 0xea, 0xb3, 0x2d, 0x3c, 0x40, 0xf7, 0x31,
+                               0xe2, 0xf6, 0x02, 0xf4, 0x72, 0x05, 0x70, 0x34, 0x45, 0xf3, 0xcc, 0x7f, 0xf9, 0x76, 0x06, 0x10,
+                               0xfc, 0x8c, 0x56, 0x54, 0x68, 0x76, 0x82, 0x7b, 0x20, 0xfc, 0x70, 0x7d, 0xb5, 0x98, 0x5d, 0x33,
+                               0x69, 0xf3, 0xea, 0x64, 0x44, 0x7e, 0xe7, 0x09, 0x77, 0x61, 0xe0, 0xbf, 0x82, 0x87, 0xa1, 0x4f,
+                               0x9e, 0x96, 0x90, 0xb7, 0xc7, 0x0b, 0xf4, 0x20, 0x0d, 0x37, 0x27, 0x82, 0x4d, 0x84, 0xb3, 0x3d,
+                               0x44, 0xe8, 0x49, 0x05, 0xeb, 0xd2, 0x07, 0xbf, 0xd7, 0xcc, 0x90, 0x62, 0x3d, 0x42, 0x16, 0x62,
+                               0x78, 0xe4, 0xdc, 0x77, 0x41, 0xc4, 0xa6, 0xc0, 0xa3, 0x63, 0xef, 0x7d, 0xce, 0xd1, 0x71, 0x8d,
+                               0xb1, 0x96, 0xdc, 0xa0, 0x7d, 0x86, 0xb2, 0x59, 0x19, 0xcb, 0x9e, 0x4b, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x29, 0x53, 0x09, 0x19, 0x19, 0x5c, 0x14, 0x68, 0x97, 0xba, 0x5a,
+                               0x26, 0xcc, 0x56, 0x43, 0x75, 0xfd, 0x57, 0xb7, 0xb9, 0xd0, 0x29, 0xbf, 0x78, 0x5d, 0x97, 0x45,
+                               0x3f, 0xfb, 0xd3, 0x5a, 0xea, 0x71, 0x5a, 0x6c, 0x05, 0xbb, 0x99, 0x10, 0x60, 0xb2, 0xe3, 0xdb,
+                               0x9a, 0x05, 0x5b, 0xaa, 0x8a, 0xef, 0xc5, 0xd5, 0xe4, 0x0f, 0x5f, 0x93, 0xd1, 0x86, 0xb2, 0xf8,
+                               0x61, 0x9c, 0x53, 0x77, 0x79, 0x7d, 0x19, 0x71, 0x64, 0xc7, 0xc5, 0xbc, 0x2d, 0x78, 0x86, 0x4f,
+                               0xf1, 0xcb, 0x67, 0xe8, 0xf2, 0x83, 0x2f, 0xe1, 0x6a, 0x3c, 0x5c, 0x4c, 0xef, 0xa9, 0x40, 0x82,
+                               0xf6, 0x67, 0x46, 0xbc, 0x67, 0xcb, 0x23, 0x4b, 0x79, 0x9a, 0x24, 0xc5, 0xa7, 0xb0, 0x2a, 0x45,
+                               0x51, 0x2e, 0x40, 0x12, 0x7d, 0x10, 0x6e, 0x38, 0xc6, 0x81, 0x0d, 0xcc, 0x78, 0xfd, 0x9e, 0xe2,
+                               0xaa, 0x89, 0xa0, 0x40, 0x09, 0x02, 0x8d, 0x9a, 0x8a, 0x77, 0x7a, 0xb9, 0xef, 0xdb, 0xa2, 0x8e,
+                               0xbb, 0x97, 0xfc, 0xb0, 0x0b, 0x92, 0x96, 0xc7, 0xa0, 0x70, 0x92, 0xbf, 0xbf, 0x99, 0x9a, 0x90,
+                               0x9f, 0x11, 0xa6, 0x60, 0xb3, 0x1f, 0x7d, 0xb8, 0x15, 0x42, 0x67, 0x70, 0xd2, 0xc1, 0x0c, 0xdf,
+                               0x6d, 0xc1, 0xad, 0x61, 0x76, 0x6d, 0x32, 0x60, 0xa2, 0xb6, 0x6e, 0x06, 0xde, 0xd5, 0x56, 0x6e,
+                               0x27, 0x1d, 0x49, 0xda, 0x6f, 0x67, 0xfc, 0x4c, 0xf0, 0xa2, 0x13, 0xc4, 0x76, 0x18, 0x80, 0x30,
+                               0xb3, 0x6d, 0xcd, 0x71, 0xfc, 0x16, 0xb1, 0x85, 0xf6, 0xe5, 0xdf, 0x01, 0xc1, 0x6a, 0xe8, 0x19,
+                               0x32, 0x9e, 0x6d, 0xff, 0xd0, 0x1e, 0xaa, 0xcd, 0x2e, 0x6c, 0xc1, 0xc0, 0x67, 0x52, 0xdc, 0xf7,
+                               0x2d, 0x56, 0xbf, 0x12, 0xe3, 0xf9, 0x85, 0xfd, 0x4d, 0x71, 0x2d, 0x55, 0x5c, 0xa4, 0xc7, 0xa3,
+                               0x70, 0xc4, 0xc3, 0xdb, 0x65, 0x02, 0x81, 0x81, 0x00, 0xe3, 0x7c, 0x82, 0x4d, 0x5d, 0xcd, 0x1e,
+                               0x71, 0x6e, 0x9f, 0xeb, 0x57, 0x93, 0x25, 0x8a, 0xd0, 0xe0, 0xee, 0x3a, 0xb7, 0xd7, 0xd1, 0x1c,
+                               0x68, 0xc8, 0x65, 0x3c, 0x2c, 0x79, 0x94, 0x36, 0x41, 0x51, 0xae, 0x17, 0x4b, 0xc5, 0x83, 0x00,
+                               0x13, 0x96, 0xd7, 0x8e, 0x17, 0xe1, 0x86, 0x3f, 0x9d, 0x4f, 0xea, 0x10, 0x84, 0xa6, 0x63, 0xc6,
+                               0x8d, 0x09, 0x11, 0xaa, 0x32, 0x92, 0xa6, 0xed, 0x4c, 0x8f, 0xf8, 0x5c, 0xee, 0x6b, 0x32, 0xf3,
+                               0x8e, 0xb8, 0xc0, 0x65, 0xd9, 0x41, 0xed, 0x7b, 0x0d, 0x1e, 0xdb, 0x17, 0xe4, 0xea, 0x20, 0x15,
+                               0xc8, 0x35, 0xfd, 0x3b, 0xc0, 0x66, 0x7c, 0x8c, 0xc6, 0x02, 0xed, 0x74, 0x64, 0x30, 0xfa, 0x8d,
+                               0xe5, 0x29, 0x84, 0x8a, 0x6b, 0x6a, 0x93, 0x38, 0x4c, 0x56, 0xda, 0x5b, 0xb7, 0xe7, 0x44, 0x45,
+                               0x4b, 0x7f, 0xef, 0xae, 0xcd, 0xa7, 0xc5, 0x8a, 0xcd, 0x02, 0x81, 0x81, 0x00, 0xb7, 0x05, 0x0a,
+                               0x9f, 0x46, 0xe9, 0xcb, 0x67, 0x74, 0x6d, 0xe8, 0xbe, 0x11, 0xc4, 0xdd, 0x75, 0x8d, 0x16, 0x8c,
+                               0x20, 0x04, 0x09, 0xf4, 0x4e, 0x88, 0xab, 0xcc, 0x7b, 0xad, 0x24, 0xd1, 0x66, 0x79, 0xf4, 0xd0,
+                               0x2f, 0x9c, 0xb7, 0xf2, 0xcb, 0x4d, 0x26, 0x35, 0x0b, 0x79, 0x47, 0x15, 0xf0, 0x2e, 0xd1, 0x40,
+                               0xb6, 0x9e, 0x59, 0x4e, 0xae, 0x11, 0x4a, 0xf2, 0xbb, 0x9d, 0x68, 0x4d, 0x4a, 0xfc, 0x86, 0xa0,
+                               0xeb, 0x3c, 0x38, 0xf3, 0xfb, 0x5c, 0x27, 0xc3, 0xa0, 0x7e, 0xf2, 0x73, 0x3d, 0x19, 0x3d, 0x0a,
+                               0x2f, 0xa8, 0x2d, 0xe0, 0x2c, 0x81, 0x5c, 0xda, 0xc6, 0x93, 0x60, 0xa0, 0xef, 0xb8, 0x23, 0x26,
+                               0x32, 0xbc, 0x1a, 0x29, 0x2a, 0xb6, 0xe5, 0x62, 0xca, 0x69, 0x78, 0xee, 0xeb, 0x7b, 0xea, 0xa6,
+                               0xb2, 0x53, 0x31, 0x46, 0xf0, 0x5b, 0x37, 0x19, 0x0f, 0x8c, 0xfa, 0x7d, 0x77, 0x02, 0x81, 0x80,
+                               0x7c, 0x16, 0xbd, 0xe9, 0x3f, 0xe2, 0x94, 0xd9, 0xa1, 0x53, 0x80, 0x87, 0xbb, 0xcf, 0x7d, 0xc2,
+                               0x64, 0xff, 0xee, 0x6d, 0xbc, 0x3e, 0xe7, 0xf5, 0x4c, 0x62, 0xd4, 0x89, 0x1a, 0x24, 0xbb, 0xd7,
+                               0xb3, 0x9b, 0x78, 0x3f, 0xaf, 0xd4, 0xbe, 0xa4, 0xf0, 0xe2, 0x52, 0x33, 0x3a, 0xa5, 0x05, 0x6c,
+                               0x65, 0x5c, 0x33, 0x78, 0x78, 0x5e, 0xf2, 0xbe, 0x9a, 0x14, 0xd5, 0xb7, 0x52, 0x81, 0xd5, 0xed,
+                               0xae, 0x96, 0xf6, 0xf2, 0x62, 0xed, 0x5f, 0x28, 0x1a, 0x25, 0x71, 0x29, 0x2a, 0xaa, 0x45, 0x36,
+                               0x29, 0xff, 0xaf, 0x30, 0x02, 0x7a, 0x50, 0x07, 0x6f, 0x53, 0xdc, 0x55, 0x1e, 0x52, 0x90, 0x63,
+                               0xae, 0xd3, 0x22, 0x6b, 0x48, 0xc0, 0x53, 0xc8, 0x8e, 0x76, 0x2b, 0x1f, 0x5e, 0xfa, 0x41, 0x7a,
+                               0x7a, 0xe2, 0x0f, 0xa7, 0xe8, 0x72, 0x38, 0x2e, 0x6f, 0xf2, 0x29, 0x73, 0x2b, 0x32, 0x39, 0x7d,
+                               0x02, 0x81, 0x80, 0x1d, 0x1f, 0x14, 0x3d, 0x9b, 0xce, 0xff, 0xaa, 0x8a, 0x80, 0x22, 0x94, 0x7b,
+                               0xc7, 0x53, 0x65, 0xac, 0xf9, 0x75, 0x7a, 0x72, 0xaa, 0x12, 0xd1, 0x9c, 0x35, 0x99, 0xe7, 0xe3,
+                               0xf9, 0x03, 0xc8, 0xc6, 0x87, 0x09, 0xc9, 0x49, 0xaa, 0x8b, 0x5b, 0x85, 0xff, 0x1a, 0x59, 0xa5,
+                               0x06, 0x86, 0x9b, 0x1d, 0x17, 0xf6, 0xb0, 0x18, 0x2a, 0x25, 0xb6, 0xd4, 0xd1, 0x94, 0x25, 0xfe,
+                               0x39, 0xe6, 0x72, 0x94, 0x13, 0xe7, 0xef, 0x06, 0x3a, 0x19, 0xb8, 0x59, 0x45, 0x8a, 0x7f, 0x33,
+                               0x33, 0xe8, 0xda, 0x43, 0xf5, 0xce, 0x75, 0x1e, 0xd1, 0x8d, 0xe6, 0x06, 0xff, 0x7b, 0x60, 0x35,
+                               0x7b, 0x1e, 0xa0, 0x86, 0x30, 0x31, 0x97, 0xc9, 0x0e, 0x70, 0x96, 0x77, 0x5b, 0xb0, 0x88, 0x7c,
+                               0x97, 0xdd, 0x8c, 0x62, 0xbf, 0x47, 0x9f, 0x00, 0x55, 0xca, 0xef, 0xdb, 0xd3, 0xbc, 0x8d, 0x18,
+                               0xe5, 0x92, 0xa9, 0x02, 0x81, 0x80, 0x26, 0x9a, 0x9b, 0x49, 0x63, 0xac, 0x76, 0x83, 0xc5, 0x06,
+                               0xa0, 0x5a, 0x5c, 0x5b, 0xd8, 0x1e, 0x07, 0x72, 0xa9, 0xa2, 0xeb, 0x6d, 0xd6, 0x29, 0xea, 0xb4,
+                               0x4e, 0x0e, 0x77, 0x7b, 0x4f, 0xe4, 0x93, 0x5b, 0xb8, 0x14, 0x1f, 0x69, 0x82, 0x1e, 0x6d, 0x1d,
+                               0x4c, 0x77, 0xe4, 0xe8, 0xa0, 0x10, 0x82, 0xf2, 0x24, 0xa4, 0x09, 0x86, 0x70, 0xea, 0x40, 0x7b,
+                               0xcf, 0xbc, 0x2a, 0xf4, 0x06, 0xde, 0x2c, 0xe6, 0x07, 0x01, 0xa4, 0x08, 0xe5, 0xe2, 0xd5, 0x54,
+                               0x8c, 0x15, 0x68, 0xce, 0x04, 0xa6, 0xf6, 0x9b, 0x90, 0xd9, 0xd2, 0x53, 0x5c, 0xba, 0xba, 0x2a,
+                               0x86, 0xe1, 0xa8, 0x80, 0x14, 0xc6, 0x9f, 0xe0, 0xcf, 0x1a, 0x89, 0xef, 0x20, 0xe4, 0x6b, 0xc9,
+                               0xec, 0x66, 0xeb, 0xa2, 0xf9, 0xae, 0xd6, 0x20, 0xd7, 0x7a, 0x44, 0xc7, 0x75, 0x7d, 0x43, 0x29,
+                               0x4f, 0x8d, 0x07, 0xc9, 0x46, 0x1c
+                       }
+               }
+       },
+       // DNSSEC Zone 1
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_RSASHA1 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xd3, 0x65, 0xb1, 0x2d, 0x81, 0x6a, 0x8d, 0x12, 0x4a, 0x78, 0x96, 0x04,
+                               0x7e, 0x89, 0xd4, 0x0f, 0xee, 0xf6, 0x06, 0xe0, 0x76, 0xe4, 0x6f, 0x86, 0x60, 0x23, 0x87, 0xd0,
+                               0x42, 0x9f, 0x9d, 0x80, 0xe2, 0xa6, 0x70, 0x63, 0xdf, 0x46, 0x30, 0x70, 0x57, 0x50, 0x02, 0x20,
+                               0x5d, 0x34, 0x43, 0x21, 0x02, 0x5b, 0x85, 0x21, 0xc8, 0x22, 0xb1, 0x03, 0x9d, 0x63, 0x10, 0x0f,
+                               0x0c, 0x54, 0x93, 0x36, 0x46, 0xca, 0x52, 0x38, 0x2a, 0xe4, 0xf7, 0xa1, 0xe8, 0x2a, 0xbb, 0x0f,
+                               0xb2, 0x17, 0x25, 0xb4, 0x46, 0x84, 0x95, 0x21, 0xbe, 0xbe, 0xdc, 0x6a, 0xa1, 0x67, 0x30, 0x62,
+                               0xed, 0x4c, 0x4c, 0xdb, 0x34, 0x10, 0x39, 0xf6, 0x88, 0xdc, 0xa4, 0x5c, 0x53, 0x18, 0x59, 0xa2,
+                               0x19, 0x36, 0xc0, 0xbe, 0x24, 0x10, 0xd9, 0x37, 0xf9, 0xdb, 0xf8, 0xde, 0xaa, 0x2f, 0x28, 0x3d,
+                               0xfc, 0x3c, 0x18, 0xd5, 0xc0, 0xcb, 0xc9, 0x3d, 0x20, 0x6a, 0x3e, 0xeb, 0x9b, 0xe8, 0xdc, 0x70,
+                               0x6f, 0xc7, 0x4d, 0x13, 0xa3, 0xac, 0xe9, 0x3a, 0x1f, 0xe4, 0x64, 0x16, 0x75, 0xa1, 0x56, 0xe4,
+                               0x43, 0x9c, 0xf7, 0xc2, 0x45, 0xea, 0xaa, 0x83, 0x3d, 0x0c, 0x50, 0x75, 0xd8, 0xfa, 0x24, 0x3b,
+                               0xdb, 0x95, 0x0a, 0xa9, 0x25, 0x58, 0x63, 0x44, 0x4a, 0x21, 0xcc, 0x6a, 0xbb, 0xa6, 0xa8, 0x38,
+                               0xf5, 0xd7, 0xd3, 0xd5, 0x4c, 0x60, 0xd2, 0x8d, 0xf8, 0x5b, 0xd3, 0xb1, 0x38, 0x1e, 0xaf, 0xab,
+                               0x78, 0x1d, 0x07, 0xda, 0x91, 0x4c, 0xf0, 0x7b, 0xf5, 0x75, 0xe4, 0xb3, 0x3b, 0xca, 0x8a, 0xc5,
+                               0x6f, 0x29, 0xc9, 0x16, 0xdd, 0xeb, 0x50, 0x8b, 0xbb, 0x02, 0x6d, 0x01, 0xfa, 0xde, 0x55, 0x53,
+                               0xb0, 0x59, 0xa2, 0x63, 0x70, 0x99, 0x0a, 0xd7, 0xb6, 0x96, 0x30, 0xd0, 0x6a, 0x4e, 0x15, 0xa0,
+                               0x39, 0x90, 0x7b, 0x4d
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd3, 0x65, 0xb1, 0x2d,
+                               0x81, 0x6a, 0x8d, 0x12, 0x4a, 0x78, 0x96, 0x04, 0x7e, 0x89, 0xd4, 0x0f, 0xee, 0xf6, 0x06, 0xe0,
+                               0x76, 0xe4, 0x6f, 0x86, 0x60, 0x23, 0x87, 0xd0, 0x42, 0x9f, 0x9d, 0x80, 0xe2, 0xa6, 0x70, 0x63,
+                               0xdf, 0x46, 0x30, 0x70, 0x57, 0x50, 0x02, 0x20, 0x5d, 0x34, 0x43, 0x21, 0x02, 0x5b, 0x85, 0x21,
+                               0xc8, 0x22, 0xb1, 0x03, 0x9d, 0x63, 0x10, 0x0f, 0x0c, 0x54, 0x93, 0x36, 0x46, 0xca, 0x52, 0x38,
+                               0x2a, 0xe4, 0xf7, 0xa1, 0xe8, 0x2a, 0xbb, 0x0f, 0xb2, 0x17, 0x25, 0xb4, 0x46, 0x84, 0x95, 0x21,
+                               0xbe, 0xbe, 0xdc, 0x6a, 0xa1, 0x67, 0x30, 0x62, 0xed, 0x4c, 0x4c, 0xdb, 0x34, 0x10, 0x39, 0xf6,
+                               0x88, 0xdc, 0xa4, 0x5c, 0x53, 0x18, 0x59, 0xa2, 0x19, 0x36, 0xc0, 0xbe, 0x24, 0x10, 0xd9, 0x37,
+                               0xf9, 0xdb, 0xf8, 0xde, 0xaa, 0x2f, 0x28, 0x3d, 0xfc, 0x3c, 0x18, 0xd5, 0xc0, 0xcb, 0xc9, 0x3d,
+                               0x20, 0x6a, 0x3e, 0xeb, 0x9b, 0xe8, 0xdc, 0x70, 0x6f, 0xc7, 0x4d, 0x13, 0xa3, 0xac, 0xe9, 0x3a,
+                               0x1f, 0xe4, 0x64, 0x16, 0x75, 0xa1, 0x56, 0xe4, 0x43, 0x9c, 0xf7, 0xc2, 0x45, 0xea, 0xaa, 0x83,
+                               0x3d, 0x0c, 0x50, 0x75, 0xd8, 0xfa, 0x24, 0x3b, 0xdb, 0x95, 0x0a, 0xa9, 0x25, 0x58, 0x63, 0x44,
+                               0x4a, 0x21, 0xcc, 0x6a, 0xbb, 0xa6, 0xa8, 0x38, 0xf5, 0xd7, 0xd3, 0xd5, 0x4c, 0x60, 0xd2, 0x8d,
+                               0xf8, 0x5b, 0xd3, 0xb1, 0x38, 0x1e, 0xaf, 0xab, 0x78, 0x1d, 0x07, 0xda, 0x91, 0x4c, 0xf0, 0x7b,
+                               0xf5, 0x75, 0xe4, 0xb3, 0x3b, 0xca, 0x8a, 0xc5, 0x6f, 0x29, 0xc9, 0x16, 0xdd, 0xeb, 0x50, 0x8b,
+                               0xbb, 0x02, 0x6d, 0x01, 0xfa, 0xde, 0x55, 0x53, 0xb0, 0x59, 0xa2, 0x63, 0x70, 0x99, 0x0a, 0xd7,
+                               0xb6, 0x96, 0x30, 0xd0, 0x6a, 0x4e, 0x15, 0xa0, 0x39, 0x90, 0x7b, 0x4d, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x81, 0xff, 0x3c, 0xe6, 0x4e, 0xe4, 0x8d, 0x23, 0xe5, 0x00, 0x1f, 0xf4, 0x1d, 0x43,
+                               0xae, 0x30, 0x86, 0xa7, 0x10, 0x70, 0x50, 0x0a, 0xda, 0x35, 0x74, 0xeb, 0x81, 0x46, 0xbb, 0x19,
+                               0x84, 0x17, 0xf7, 0x87, 0xe9, 0xc3, 0x89, 0x1d, 0x3c, 0x75, 0x3b, 0xff, 0x21, 0x28, 0x2b, 0x74,
+                               0xa7, 0x65, 0x26, 0xf1, 0x9b, 0x7c, 0x42, 0x1e, 0x39, 0x49, 0xc5, 0x35, 0x05, 0x62, 0x44, 0x7e,
+                               0x11, 0x4f, 0x4c, 0x19, 0x96, 0xcb, 0x32, 0x64, 0x7b, 0xf6, 0xfa, 0x5f, 0xea, 0x42, 0x65, 0x21,
+                               0x60, 0x6c, 0xca, 0xaf, 0xe4, 0xa0, 0xcd, 0x55, 0xab, 0x5d, 0xd0, 0xa1, 0xe7, 0x5d, 0x92, 0xd2,
+                               0xed, 0x09, 0xce, 0x1a, 0x58, 0x25, 0x54, 0x8d, 0x71, 0x60, 0x1e, 0xf9, 0x79, 0xf6, 0xc9, 0xdb,
+                               0xa8, 0xe7, 0xdd, 0x1e, 0xe9, 0xc4, 0xf0, 0xce, 0xef, 0x6c, 0x2f, 0x85, 0xf6, 0x01, 0x1f, 0xd6,
+                               0xbb, 0x93, 0x94, 0x93, 0x79, 0x72, 0xaa, 0x48, 0x17, 0x2e, 0xa7, 0x44, 0xa9, 0xa8, 0x02, 0xb7,
+                               0xd1, 0x04, 0x76, 0x4e, 0x74, 0x27, 0x5d, 0x8a, 0xe3, 0xaa, 0x1d, 0x8c, 0xd4, 0xc0, 0xd6, 0xd1,
+                               0x6c, 0x65, 0xee, 0xd0, 0xba, 0x1a, 0x39, 0x97, 0x61, 0x45, 0xca, 0xfb, 0xdc, 0xc8, 0xa9, 0x43,
+                               0x9f, 0x0e, 0xf8, 0xcb, 0x68, 0xd9, 0x35, 0x3e, 0x4f, 0x82, 0xac, 0x96, 0xd1, 0x20, 0xb7, 0x82,
+                               0x00, 0xda, 0x7c, 0x22, 0xd8, 0x5c, 0x55, 0x41, 0x16, 0xda, 0x71, 0xf0, 0x1b, 0xf5, 0x97, 0x4f,
+                               0x45, 0x72, 0x9c, 0xad, 0xbe, 0x28, 0x9c, 0xf3, 0xe2, 0x50, 0x85, 0x07, 0x1c, 0x5d, 0xee, 0xd6,
+                               0x4b, 0xcc, 0x2c, 0xaa, 0xa3, 0xfe, 0xb9, 0x4d, 0xcc, 0x23, 0xa3, 0x94, 0x5e, 0x98, 0x35, 0xf3,
+                               0xa7, 0x65, 0x3b, 0x54, 0xcb, 0x81, 0x56, 0xc7, 0x8a, 0x11, 0xcb, 0xf3, 0x37, 0x9f, 0xf9, 0xa7,
+                               0xc9, 0x4c, 0x71, 0x02, 0x81, 0x81, 0x00, 0xf6, 0xb8, 0x8d, 0x85, 0xec, 0xdc, 0x21, 0x33, 0xef,
+                               0x72, 0xe3, 0x81, 0xa8, 0xe8, 0x38, 0xe3, 0x61, 0x56, 0xe2, 0x47, 0x73, 0x86, 0xff, 0x7e, 0x8a,
+                               0xe0, 0xdc, 0x18, 0x16, 0x8c, 0x0c, 0x01, 0x31, 0xb5, 0x85, 0xc7, 0xab, 0xef, 0x7d, 0x77, 0x92,
+                               0x4f, 0x78, 0x61, 0xcd, 0x50, 0x62, 0x1f, 0xc2, 0x31, 0x70, 0x70, 0xba, 0xd2, 0x12, 0x2c, 0xb8,
+                               0xa5, 0xb2, 0x8b, 0x66, 0x55, 0x2b, 0x2c, 0x50, 0xf6, 0x75, 0xc8, 0xad, 0xaf, 0x54, 0xf9, 0x92,
+                               0x56, 0xc4, 0x45, 0x19, 0x8f, 0xe3, 0x43, 0x0c, 0xcf, 0x94, 0xbe, 0x75, 0x1b, 0x6c, 0x0b, 0x0d,
+                               0xcd, 0xaf, 0x65, 0x6d, 0x21, 0xce, 0xf0, 0xba, 0x4d, 0x40, 0x71, 0x94, 0x43, 0x64, 0x09, 0x9d,
+                               0xb6, 0x65, 0x47, 0x98, 0xa7, 0x2b, 0xc1, 0xd5, 0x9e, 0xca, 0x2e, 0x3f, 0x41, 0xd2, 0xea, 0x9e,
+                               0x9e, 0x07, 0xe1, 0xe7, 0x56, 0xfb, 0x9d, 0x02, 0x81, 0x81, 0x00, 0xdb, 0x59, 0x0a, 0x52, 0x84,
+                               0x62, 0xbf, 0x4c, 0xb4, 0x88, 0x76, 0x54, 0xe8, 0xc4, 0x13, 0xee, 0x99, 0x40, 0xf5, 0xc7, 0x44,
+                               0x60, 0xd9, 0x00, 0x3d, 0x01, 0xd7, 0x67, 0x77, 0xd0, 0x55, 0xc0, 0x86, 0x2c, 0xa1, 0xe3, 0xfd,
+                               0x31, 0x0d, 0x1c, 0xfa, 0x3d, 0x3f, 0x35, 0xed, 0xe8, 0x59, 0x35, 0x9f, 0x66, 0xc1, 0xe9, 0x30,
+                               0xb5, 0x50, 0x06, 0x80, 0xf2, 0xf5, 0x35, 0x0a, 0x59, 0xe9, 0xc7, 0xf1, 0xe1, 0x7f, 0xf3, 0x8d,
+                               0xfd, 0x14, 0xb3, 0xfc, 0xea, 0xcd, 0xd4, 0x30, 0x88, 0x57, 0x5a, 0x68, 0x6a, 0xc4, 0x5b, 0x73,
+                               0x46, 0xd8, 0xa9, 0x76, 0x61, 0x5e, 0x92, 0x69, 0xb3, 0x09, 0x06, 0xac, 0x2b, 0x1d, 0xd1, 0x2e,
+                               0xc0, 0xfd, 0xcc, 0x13, 0xa8, 0x17, 0xce, 0x14, 0xc1, 0x9c, 0xe5, 0x33, 0x68, 0xdd, 0x9c, 0xdf,
+                               0x1f, 0x21, 0xf3, 0xa1, 0xc5, 0xf2, 0xbc, 0x8b, 0xa8, 0xa7, 0x71, 0x02, 0x81, 0x81, 0x00, 0x92,
+                               0x2a, 0x1f, 0xdd, 0xd9, 0xc7, 0x47, 0xfc, 0x66, 0xbd, 0x5f, 0xbf, 0x2e, 0xfb, 0xf4, 0xc7, 0xf0,
+                               0xa0, 0xf2, 0x89, 0x76, 0x0d, 0xe2, 0x4b, 0x6b, 0xa7, 0x6f, 0x7c, 0xed, 0xce, 0xa6, 0x46, 0x06,
+                               0xd7, 0x0d, 0x9c, 0x8f, 0x65, 0xe0, 0xa4, 0xf8, 0x0f, 0x10, 0xb8, 0x90, 0x54, 0x30, 0xed, 0xb3,
+                               0xb4, 0x6a, 0x72, 0xbe, 0x9b, 0x39, 0x9d, 0x38, 0xff, 0x21, 0x59, 0xa8, 0x94, 0x88, 0x71, 0x46,
+                               0xbd, 0xdc, 0x65, 0xb9, 0x50, 0x08, 0x3d, 0x9a, 0xce, 0xc0, 0x94, 0x57, 0x62, 0x81, 0x36, 0xf5,
+                               0xdd, 0xfc, 0xb7, 0x20, 0xd5, 0xd0, 0x1a, 0x74, 0x61, 0x08, 0xaa, 0x44, 0x0f, 0x25, 0x74, 0x44,
+                               0x4b, 0x04, 0x04, 0xc5, 0xdb, 0x6e, 0xe6, 0xab, 0x82, 0xa5, 0x59, 0xd7, 0x3c, 0x22, 0x96, 0x41,
+                               0x22, 0x43, 0x91, 0x46, 0x74, 0x8e, 0xa9, 0xe4, 0xb2, 0xa6, 0xe4, 0xd4, 0x93, 0x19, 0x99, 0x02,
+                               0x81, 0x81, 0x00, 0xad, 0x0b, 0x03, 0x9f, 0xb1, 0x9d, 0x0d, 0x79, 0xff, 0xfa, 0xa0, 0x0f, 0xc0,
+                               0x49, 0xdf, 0xc7, 0x9d, 0xd3, 0xa7, 0x91, 0xfa, 0x99, 0xc4, 0xd7, 0xf1, 0x49, 0x20, 0x9c, 0x19,
+                               0xe3, 0x9b, 0xa2, 0xf4, 0xb7, 0x05, 0x48, 0x8d, 0x98, 0x42, 0xd6, 0x17, 0x7c, 0x75, 0xff, 0x9f,
+                               0x9e, 0x6b, 0xb2, 0x67, 0xfc, 0x73, 0x01, 0xfa, 0x51, 0x2f, 0xbe, 0xd1, 0xbf, 0xda, 0x3e, 0x6b,
+                               0xda, 0x1f, 0x1a, 0x83, 0xf1, 0xf8, 0x35, 0x36, 0x50, 0xf0, 0x22, 0x46, 0x67, 0x1e, 0xd6, 0x45,
+                               0x2b, 0x6a, 0x6e, 0x82, 0x6d, 0xa8, 0x56, 0xd5, 0x0c, 0x91, 0x24, 0xaf, 0xa5, 0x85, 0x2c, 0x50,
+                               0x82, 0xd3, 0x9e, 0x15, 0xf9, 0x35, 0xde, 0xae, 0xd9, 0xc5, 0xdf, 0x57, 0xaa, 0x86, 0x8d, 0x27,
+                               0xc6, 0xce, 0x75, 0xeb, 0x26, 0xd1, 0x9e, 0x4e, 0x4f, 0x8b, 0x43, 0x5e, 0xde, 0x80, 0xee, 0xc9,
+                               0xe6, 0xd5, 0xa1, 0x02, 0x81, 0x80, 0x02, 0xec, 0xcf, 0x34, 0xa2, 0x1f, 0x12, 0xc8, 0x27, 0x66,
+                               0x9f, 0x07, 0xb4, 0x55, 0x72, 0x67, 0x0b, 0xc2, 0xba, 0x1c, 0x6e, 0xc9, 0x25, 0x04, 0xed, 0x51,
+                               0x53, 0x21, 0x3c, 0xcd, 0x3c, 0xe7, 0x1f, 0xeb, 0x17, 0x7d, 0x97, 0xde, 0xe4, 0x57, 0xb1, 0x89,
+                               0x3b, 0xf1, 0x01, 0xb9, 0x2a, 0x4f, 0x74, 0xa8, 0x25, 0x0d, 0x92, 0xef, 0xbb, 0x0f, 0xf3, 0x57,
+                               0x70, 0xb3, 0x58, 0x20, 0x75, 0x1e, 0xdb, 0x88, 0x96, 0x67, 0x44, 0x93, 0x27, 0x75, 0xc0, 0xa0,
+                               0x33, 0xde, 0x53, 0xc0, 0x55, 0xc5, 0x52, 0x05, 0x71, 0x99, 0x61, 0xad, 0x77, 0x64, 0xb5, 0x85,
+                               0x3e, 0xfa, 0xf6, 0xfe, 0xcd, 0x41, 0xae, 0x89, 0x48, 0xc3, 0x4f, 0x7a, 0x3e, 0x8e, 0x79, 0x32,
+                               0xe2, 0xee, 0x83, 0x99, 0xb5, 0x73, 0xd9, 0xb6, 0x28, 0xb7, 0x51, 0x12, 0x4e, 0xbe, 0xde, 0x9e,
+                               0xf4, 0x70, 0x65, 0x54, 0xff, 0x4f
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_RSASHA1 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xe5, 0xcd, 0xb9, 0x78, 0x07, 0xfa, 0x23, 0x40, 0xf4, 0x35, 0x7f, 0x00,
+                               0x4e, 0x99, 0xc5, 0xb6, 0xf5, 0xc0, 0x80, 0x37, 0xef, 0x1a, 0xec, 0xeb, 0x1e, 0xc2, 0xa4, 0x53,
+                               0x28, 0x4f, 0x90, 0x43, 0xeb, 0x7b, 0xb7, 0x05, 0xae, 0xef, 0xda, 0x1f, 0x14, 0x74, 0xac, 0xa3,
+                               0x5f, 0xca, 0x83, 0xb3, 0x0c, 0xa1, 0x78, 0x53, 0xc3, 0x76, 0xc3, 0xd3, 0x11, 0xf2, 0xb2, 0xde,
+                               0x83, 0x69, 0xc7, 0xff, 0x54, 0x78, 0xab, 0xf5, 0x31, 0x07, 0xe2, 0xda, 0xa5, 0xff, 0xb1, 0xe0,
+                               0x0a, 0x25, 0x9f, 0xe5, 0x3e, 0x9e, 0x43, 0xf0, 0x76, 0xb5, 0xab, 0x9a, 0x68, 0x71, 0x3f, 0x9c,
+                               0xd5, 0xe4, 0x5c, 0x60, 0x12, 0x9e, 0x6d, 0x9f, 0x44, 0xec, 0xa1, 0xca, 0xf8, 0xcd, 0x2a, 0xe5,
+                               0x17, 0x16, 0x1b, 0xf8, 0xa5, 0xf1, 0xc3, 0x72, 0x6d, 0x7d, 0xa5, 0x77, 0xa5, 0x1b, 0x66, 0xfb,
+                               0x9c, 0xdb, 0xde, 0xb6, 0x25, 0x05, 0x35, 0x14, 0xc4, 0x71, 0x7b, 0x1d, 0x9d, 0x35, 0xb3, 0xa6,
+                               0x53, 0xfb, 0x56, 0xf6, 0xab, 0x92, 0xbc, 0x82, 0x27, 0xfd, 0x1f, 0x87, 0xd9, 0x56, 0xf1, 0xb3,
+                               0xac, 0x88, 0xc4, 0x65, 0x55, 0x7e, 0x4a, 0x38, 0x4d, 0x24, 0xc2, 0xa2, 0xc8, 0x2c, 0x00, 0xb2,
+                               0x92, 0xfb, 0x71, 0x13, 0x20, 0x1d, 0x5e, 0x61, 0x01, 0x22, 0x76, 0x0d, 0x17, 0xbf, 0x13, 0x0f,
+                               0xff, 0x6c, 0xcf, 0xe8, 0x4a, 0x2c, 0x00, 0x30, 0x57, 0x49, 0xb5, 0xd0, 0xe5, 0xef, 0x86, 0x9f,
+                               0x50, 0xfe, 0x0f, 0xc3, 0x56, 0x68, 0x0e, 0xea, 0xb1, 0x39, 0xac, 0x1f, 0x4c, 0xdf, 0xbe, 0x0b,
+                               0xbc, 0xca, 0x0a, 0x33, 0xdb, 0xdb, 0x3e, 0x0f, 0x32, 0x3d, 0xb6, 0x4d, 0x53, 0x20, 0xf2, 0x87,
+                               0x8e, 0x14, 0xbc, 0x8e, 0xf1, 0xed, 0x61, 0x8f, 0x82, 0xca, 0xad, 0x07, 0x52, 0xf4, 0x86, 0x55,
+                               0x2f, 0x33, 0xfe, 0x31
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe5, 0xcd, 0xb9, 0x78,
+                               0x07, 0xfa, 0x23, 0x40, 0xf4, 0x35, 0x7f, 0x00, 0x4e, 0x99, 0xc5, 0xb6, 0xf5, 0xc0, 0x80, 0x37,
+                               0xef, 0x1a, 0xec, 0xeb, 0x1e, 0xc2, 0xa4, 0x53, 0x28, 0x4f, 0x90, 0x43, 0xeb, 0x7b, 0xb7, 0x05,
+                               0xae, 0xef, 0xda, 0x1f, 0x14, 0x74, 0xac, 0xa3, 0x5f, 0xca, 0x83, 0xb3, 0x0c, 0xa1, 0x78, 0x53,
+                               0xc3, 0x76, 0xc3, 0xd3, 0x11, 0xf2, 0xb2, 0xde, 0x83, 0x69, 0xc7, 0xff, 0x54, 0x78, 0xab, 0xf5,
+                               0x31, 0x07, 0xe2, 0xda, 0xa5, 0xff, 0xb1, 0xe0, 0x0a, 0x25, 0x9f, 0xe5, 0x3e, 0x9e, 0x43, 0xf0,
+                               0x76, 0xb5, 0xab, 0x9a, 0x68, 0x71, 0x3f, 0x9c, 0xd5, 0xe4, 0x5c, 0x60, 0x12, 0x9e, 0x6d, 0x9f,
+                               0x44, 0xec, 0xa1, 0xca, 0xf8, 0xcd, 0x2a, 0xe5, 0x17, 0x16, 0x1b, 0xf8, 0xa5, 0xf1, 0xc3, 0x72,
+                               0x6d, 0x7d, 0xa5, 0x77, 0xa5, 0x1b, 0x66, 0xfb, 0x9c, 0xdb, 0xde, 0xb6, 0x25, 0x05, 0x35, 0x14,
+                               0xc4, 0x71, 0x7b, 0x1d, 0x9d, 0x35, 0xb3, 0xa6, 0x53, 0xfb, 0x56, 0xf6, 0xab, 0x92, 0xbc, 0x82,
+                               0x27, 0xfd, 0x1f, 0x87, 0xd9, 0x56, 0xf1, 0xb3, 0xac, 0x88, 0xc4, 0x65, 0x55, 0x7e, 0x4a, 0x38,
+                               0x4d, 0x24, 0xc2, 0xa2, 0xc8, 0x2c, 0x00, 0xb2, 0x92, 0xfb, 0x71, 0x13, 0x20, 0x1d, 0x5e, 0x61,
+                               0x01, 0x22, 0x76, 0x0d, 0x17, 0xbf, 0x13, 0x0f, 0xff, 0x6c, 0xcf, 0xe8, 0x4a, 0x2c, 0x00, 0x30,
+                               0x57, 0x49, 0xb5, 0xd0, 0xe5, 0xef, 0x86, 0x9f, 0x50, 0xfe, 0x0f, 0xc3, 0x56, 0x68, 0x0e, 0xea,
+                               0xb1, 0x39, 0xac, 0x1f, 0x4c, 0xdf, 0xbe, 0x0b, 0xbc, 0xca, 0x0a, 0x33, 0xdb, 0xdb, 0x3e, 0x0f,
+                               0x32, 0x3d, 0xb6, 0x4d, 0x53, 0x20, 0xf2, 0x87, 0x8e, 0x14, 0xbc, 0x8e, 0xf1, 0xed, 0x61, 0x8f,
+                               0x82, 0xca, 0xad, 0x07, 0x52, 0xf4, 0x86, 0x55, 0x2f, 0x33, 0xfe, 0x31, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x08, 0x06, 0x23, 0x3d, 0x66, 0x07, 0x3d, 0x91, 0xe2, 0xb5, 0x0c,
+                               0xd1, 0x30, 0x42, 0xbd, 0x9c, 0xec, 0xd4, 0x78, 0x3a, 0xfb, 0xc0, 0xa9, 0xed, 0xc5, 0x18, 0xdb,
+                               0x6f, 0x57, 0xe4, 0xa7, 0x4f, 0x3d, 0x25, 0x5d, 0xb7, 0xa2, 0x15, 0xc2, 0x92, 0x51, 0x3e, 0xdd,
+                               0xae, 0xfa, 0xaf, 0x9c, 0x8c, 0xf4, 0xef, 0x9d, 0x14, 0x4c, 0xff, 0xe9, 0x9d, 0xdb, 0x6e, 0x3e,
+                               0x56, 0xa1, 0xff, 0x60, 0x8f, 0x5c, 0x1e, 0x0b, 0xe0, 0x62, 0x2c, 0xe4, 0xa6, 0x93, 0xcd, 0xeb,
+                               0x77, 0xbc, 0xd8, 0x4c, 0x23, 0xfa, 0x08, 0x16, 0xab, 0xdf, 0xc9, 0x3f, 0x76, 0x58, 0x6d, 0x3e,
+                               0x8b, 0xb5, 0xb5, 0x8f, 0xe5, 0x7e, 0xe0, 0xef, 0x47, 0x9f, 0x65, 0x7a, 0x7a, 0x16, 0xd9, 0x3b,
+                               0x1a, 0x88, 0x05, 0x7f, 0xf1, 0x32, 0x57, 0x7b, 0x11, 0x3f, 0x72, 0x22, 0xae, 0x9c, 0x77, 0x94,
+                               0xcf, 0x4b, 0xa3, 0xd5, 0xbb, 0xdf, 0x76, 0x47, 0x05, 0x8c, 0x00, 0x12, 0xb9, 0x04, 0x88, 0x29,
+                               0xe2, 0x9b, 0xd0, 0xfd, 0xa8, 0xfb, 0x4d, 0x2b, 0xac, 0x9c, 0x67, 0x8e, 0x56, 0x19, 0xff, 0x69,
+                               0xe7, 0xb3, 0x26, 0x11, 0xc0, 0xc2, 0x10, 0x5c, 0x70, 0xdd, 0x72, 0x07, 0x99, 0x3f, 0xbc, 0x53,
+                               0x4c, 0x5a, 0xda, 0xc3, 0x27, 0x3d, 0xdd, 0x3e, 0x40, 0x91, 0x5f, 0x4a, 0x8c, 0x3d, 0x4d, 0x15,
+                               0x84, 0xc7, 0x5d, 0xfb, 0x8a, 0x40, 0x25, 0xba, 0xce, 0x28, 0x63, 0x24, 0x2d, 0x98, 0x45, 0xa1,
+                               0x9b, 0xf7, 0xed, 0x76, 0xbf, 0x2d, 0xe5, 0x3f, 0x8e, 0x2a, 0xed, 0xd7, 0xaa, 0x15, 0x7e, 0xae,
+                               0xc9, 0x86, 0xa1, 0x02, 0x86, 0x6a, 0x0b, 0x7d, 0x44, 0x41, 0x35, 0x0a, 0x57, 0x26, 0x9b, 0x68,
+                               0x0a, 0x56, 0xbe, 0x26, 0x54, 0xe8, 0x72, 0x54, 0x57, 0x56, 0xad, 0xe7, 0xcb, 0xaa, 0xc6, 0xe8,
+                               0x8f, 0x2c, 0xb0, 0x0a, 0x51, 0x02, 0x81, 0x81, 0x00, 0xfe, 0xc7, 0xda, 0xf5, 0x28, 0x30, 0x06,
+                               0x33, 0x00, 0x85, 0xa7, 0xea, 0x9e, 0x87, 0xc0, 0xdc, 0xd7, 0x1d, 0x31, 0x31, 0xa5, 0x04, 0x0f,
+                               0x84, 0x9e, 0x73, 0x3f, 0x1d, 0x88, 0xfd, 0x64, 0xaf, 0x71, 0xca, 0xaf, 0x9a, 0x78, 0x89, 0xf6,
+                               0xe8, 0x56, 0x45, 0x89, 0xf0, 0x57, 0xa5, 0x71, 0x37, 0x3e, 0x02, 0x34, 0x46, 0x1d, 0xb7, 0xe9,
+                               0x92, 0xaf, 0xb3, 0x58, 0x36, 0xa8, 0xe5, 0xa8, 0x5a, 0x40, 0x86, 0x57, 0x4f, 0x6b, 0x7e, 0x48,
+                               0xe2, 0xba, 0x28, 0xd0, 0xd7, 0xad, 0x60, 0xbd, 0x73, 0x83, 0xaf, 0x3d, 0x60, 0x61, 0x79, 0xa4,
+                               0xfd, 0x20, 0x65, 0xba, 0xdb, 0x18, 0x7a, 0x34, 0x49, 0xc5, 0xf4, 0xe5, 0x41, 0xcd, 0x53, 0x4a,
+                               0x53, 0x19, 0x7a, 0x7e, 0x3d, 0x5d, 0x64, 0xe7, 0x6a, 0xa1, 0x2f, 0x90, 0x74, 0xfd, 0x46, 0x59,
+                               0xec, 0x51, 0x8b, 0xbf, 0x02, 0x44, 0xff, 0x12, 0x1d, 0x02, 0x81, 0x81, 0x00, 0xe6, 0xe7, 0x44,
+                               0xbd, 0x02, 0xf5, 0x90, 0x63, 0xb4, 0x15, 0xfd, 0x2d, 0x97, 0x24, 0x4a, 0x5d, 0xe3, 0xbb, 0x34,
+                               0xac, 0xf2, 0x3a, 0xb9, 0x16, 0xd8, 0x45, 0xd1, 0xef, 0x68, 0x12, 0xb6, 0x64, 0xa7, 0x5a, 0x1f,
+                               0xe7, 0xb7, 0x44, 0xcc, 0x7a, 0xf0, 0x5c, 0xee, 0xaf, 0x0c, 0x1c, 0xe8, 0x1a, 0xb7, 0x47, 0x90,
+                               0x86, 0xb3, 0x40, 0xa4, 0x9e, 0xd1, 0xa7, 0x66, 0xa9, 0x81, 0xc6, 0x42, 0xb6, 0xae, 0x9f, 0x79,
+                               0x88, 0x38, 0xf3, 0xe5, 0xab, 0x68, 0x62, 0xac, 0x34, 0x21, 0xcb, 0x7c, 0x87, 0x04, 0x15, 0x8c,
+                               0x0f, 0x03, 0x0a, 0xcd, 0xc0, 0x61, 0x44, 0xd7, 0x7f, 0xd6, 0x7b, 0x7c, 0x55, 0x0b, 0xba, 0xf1,
+                               0x6c, 0x92, 0x73, 0xaa, 0x1e, 0x96, 0xe3, 0x93, 0x61, 0xa2, 0x08, 0x6d, 0x3e, 0x68, 0x05, 0x83,
+                               0x5a, 0x3f, 0xb8, 0x07, 0xe6, 0x9f, 0x6b, 0x76, 0x94, 0x97, 0xe1, 0xe0, 0x25, 0x02, 0x81, 0x80,
+                               0x19, 0xa9, 0x75, 0xa3, 0x65, 0xc5, 0xd8, 0x8a, 0x00, 0x8e, 0x75, 0xc8, 0x4a, 0xe9, 0x6f, 0x82,
+                               0x8e, 0xe4, 0x30, 0xd5, 0x48, 0x42, 0xad, 0x71, 0x75, 0x28, 0x34, 0x7e, 0x3c, 0x13, 0x11, 0xb6,
+                               0x1e, 0x27, 0x05, 0x22, 0xf0, 0xeb, 0x2c, 0x84, 0x60, 0x54, 0x26, 0xbd, 0x9f, 0x86, 0x59, 0x6d,
+                               0xef, 0xff, 0xf6, 0xd5, 0xed, 0x1e, 0x5e, 0x17, 0x59, 0x3d, 0x1e, 0x30, 0x7b, 0x38, 0x8e, 0x89,
+                               0x70, 0xa0, 0x57, 0x60, 0xf7, 0x79, 0xc3, 0xcb, 0x9a, 0x66, 0x8c, 0x0a, 0x7d, 0x3b, 0x16, 0x39,
+                               0xf1, 0x54, 0x90, 0x41, 0x09, 0xf0, 0x5f, 0xae, 0xe3, 0x39, 0x7b, 0xe5, 0x9f, 0x84, 0x87, 0x89,
+                               0xba, 0xb7, 0x9c, 0xf3, 0xd0, 0xc3, 0x87, 0xe3, 0xf0, 0xd8, 0x06, 0xa9, 0x15, 0xa4, 0x19, 0x2f,
+                               0x03, 0x47, 0x30, 0x2c, 0xa8, 0x6a, 0x12, 0x9a, 0x9b, 0xe8, 0x10, 0x78, 0x21, 0x0c, 0xf3, 0xfd,
+                               0x02, 0x81, 0x80, 0x08, 0xf1, 0x27, 0xde, 0x52, 0x01, 0x04, 0x88, 0x77, 0xee, 0xea, 0x11, 0x1b,
+                               0xeb, 0x51, 0xdd, 0xf4, 0xf5, 0xc9, 0x02, 0x71, 0x97, 0x08, 0x97, 0xf3, 0xe4, 0x30, 0x4f, 0x1f,
+                               0x2e, 0xaa, 0x55, 0x97, 0x8d, 0x8b, 0xf2, 0xc3, 0x4c, 0x2c, 0xc7, 0x0e, 0x0b, 0xeb, 0x4a, 0x68,
+                               0x23, 0xae, 0x71, 0x6a, 0x6b, 0xa1, 0x13, 0x36, 0x59, 0xe8, 0x86, 0x26, 0x04, 0x5f, 0x9b, 0x6c,
+                               0xe5, 0x2a, 0xac, 0x3c, 0x72, 0xfc, 0x97, 0xe3, 0xec, 0xbe, 0x16, 0x37, 0x42, 0xaa, 0xfc, 0x91,
+                               0xda, 0x79, 0x86, 0x19, 0x08, 0x64, 0x96, 0x28, 0x3e, 0x00, 0xdd, 0xd4, 0x66, 0x80, 0x19, 0xf9,
+                               0x4a, 0x6a, 0xf4, 0x38, 0x32, 0x13, 0x2b, 0x6b, 0x38, 0x83, 0x7c, 0x0f, 0xc7, 0xdc, 0x6b, 0x49,
+                               0x85, 0x1e, 0x05, 0xee, 0xad, 0x57, 0xfa, 0xe4, 0xc0, 0xd1, 0xbd, 0x82, 0xaa, 0xfc, 0xba, 0xa0,
+                               0x6f, 0x24, 0xe9, 0x02, 0x81, 0x80, 0x63, 0xd1, 0xac, 0xa7, 0xf1, 0xd3, 0x59, 0x18, 0xc7, 0x6a,
+                               0xb9, 0x07, 0xd3, 0xc1, 0x66, 0xed, 0x87, 0x53, 0x31, 0xbf, 0xfa, 0xf1, 0x91, 0x97, 0x39, 0xf3,
+                               0x8a, 0x8d, 0xe3, 0x2c, 0x35, 0x02, 0x27, 0xa9, 0x58, 0xf3, 0x9f, 0x4f, 0x04, 0x5b, 0xd0, 0xe1,
+                               0x11, 0xa8, 0x41, 0xec, 0x9d, 0x06, 0x51, 0xb5, 0xb8, 0x7e, 0xff, 0x3b, 0xcd, 0xe1, 0x06, 0xf3,
+                               0xd6, 0xa2, 0x2d, 0x22, 0x01, 0x97, 0xb4, 0x82, 0x34, 0x7d, 0x4b, 0x73, 0x55, 0xd5, 0x03, 0xf0,
+                               0xd3, 0x8c, 0xb8, 0x9e, 0xdc, 0x3a, 0x97, 0x2a, 0x2b, 0x51, 0x07, 0x69, 0x7a, 0x59, 0x4b, 0xb4,
+                               0x75, 0xb8, 0x9d, 0x24, 0xb4, 0xed, 0x44, 0xe4, 0xec, 0x66, 0xdc, 0x18, 0x5e, 0xe3, 0x20, 0x5a,
+                               0xde, 0x68, 0x51, 0x8b, 0xe2, 0xae, 0xe2, 0x0c, 0xa5, 0x04, 0xe2, 0xa1, 0xe4, 0xef, 0xfe, 0xff,
+                               0x06, 0x8d, 0x6e, 0xf8, 0x02, 0x2d
+                       }
+               }
+       },
+       // DNSSEC Zone 2
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_RSASHA1 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0x84, 0x17, 0x45, 0x53, 0x09, 0x42, 0x7b, 0x20, 0x4e, 0xda, 0x1a, 0x0e,
+                               0x80, 0xf8, 0xf1, 0xc3, 0x54, 0xc2, 0xcf, 0xad, 0xa9, 0xad, 0x60, 0x88, 0xf0, 0x4b, 0xa0, 0x4d,
+                               0x6c, 0x78, 0xe5, 0x34, 0x1f, 0x28, 0xbc, 0xdc, 0x80, 0x48, 0x7d, 0xfc, 0x70, 0x9c, 0xd0, 0x2f,
+                               0xc0, 0xaa, 0xc2, 0x37, 0xe3, 0x56, 0x0a, 0x53, 0x42, 0x74, 0x5b, 0x4a, 0x5e, 0xfb, 0xe9, 0x9c,
+                               0x25, 0x3c, 0x18, 0xc9, 0xb7, 0x2b, 0xb6, 0xa9, 0xe1, 0xcf, 0x4f, 0xeb, 0x0a, 0xf6, 0x02, 0x85,
+                               0x48, 0xc1, 0xc0, 0x91, 0xe2, 0x86, 0xe7, 0xb2, 0xfe, 0xdc, 0x10, 0x42, 0x37, 0xbb, 0xa6, 0x8d,
+                               0x82, 0x6a, 0xbb, 0xa9, 0x77, 0xa6, 0x1f, 0xa4, 0x9f, 0xd2, 0x9e, 0xdd, 0xe3, 0xa0, 0x07, 0x82,
+                               0xdd, 0xae, 0x32, 0x70, 0xe4, 0x7a, 0x9c, 0xb2, 0x2d, 0x52, 0xd2, 0x0a, 0x58, 0xb3, 0x78, 0x96,
+                               0x83, 0x02, 0x32, 0x84, 0x4f, 0x06, 0x98, 0x3b, 0x26, 0x0c, 0x82, 0x0b, 0x36, 0x64, 0xc2, 0xe4,
+                               0x05, 0xa4, 0x95, 0x47, 0x07, 0xc8, 0xcc, 0x66, 0xd4, 0x64, 0x01, 0x18, 0x27, 0x94, 0x00, 0x1b,
+                               0x32, 0x9b, 0x9e, 0xf6, 0x2f, 0x63, 0xd7, 0x0c, 0x65, 0x73, 0x1d, 0x93, 0x7a, 0xec, 0x8d, 0x15,
+                               0xde, 0x98, 0x5e, 0x2b, 0xbf, 0x42, 0x78, 0xaa, 0xbe, 0xf8, 0xd8, 0x2a, 0x66, 0x2c, 0x15, 0xf1,
+                               0xae, 0x3b, 0xe7, 0x0b, 0x26, 0x25, 0xda, 0x23, 0x22, 0x0d, 0x37, 0x7a, 0x7d, 0x5d, 0xf6, 0x53,
+                               0x70, 0x27, 0xaa, 0x1e, 0x1f, 0x0e, 0x2f, 0x5b, 0xcd, 0xa0, 0xf6, 0x8b, 0xbd, 0x6b, 0x89, 0x82,
+                               0xe5, 0x52, 0x96, 0x1b, 0x5c, 0xac, 0xdb, 0xeb, 0x11, 0x77, 0xc0, 0x67, 0x56, 0x5d, 0x67, 0xd6,
+                               0xa8, 0xad, 0x9a, 0xb4, 0xc2, 0x16, 0x45, 0x6b, 0x48, 0x9f, 0x8c, 0xca, 0x26, 0x12, 0xcc, 0x91,
+                               0x88, 0xbc, 0x61, 0xdf
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0x84, 0x17, 0x45, 0x53,
+                               0x09, 0x42, 0x7b, 0x20, 0x4e, 0xda, 0x1a, 0x0e, 0x80, 0xf8, 0xf1, 0xc3, 0x54, 0xc2, 0xcf, 0xad,
+                               0xa9, 0xad, 0x60, 0x88, 0xf0, 0x4b, 0xa0, 0x4d, 0x6c, 0x78, 0xe5, 0x34, 0x1f, 0x28, 0xbc, 0xdc,
+                               0x80, 0x48, 0x7d, 0xfc, 0x70, 0x9c, 0xd0, 0x2f, 0xc0, 0xaa, 0xc2, 0x37, 0xe3, 0x56, 0x0a, 0x53,
+                               0x42, 0x74, 0x5b, 0x4a, 0x5e, 0xfb, 0xe9, 0x9c, 0x25, 0x3c, 0x18, 0xc9, 0xb7, 0x2b, 0xb6, 0xa9,
+                               0xe1, 0xcf, 0x4f, 0xeb, 0x0a, 0xf6, 0x02, 0x85, 0x48, 0xc1, 0xc0, 0x91, 0xe2, 0x86, 0xe7, 0xb2,
+                               0xfe, 0xdc, 0x10, 0x42, 0x37, 0xbb, 0xa6, 0x8d, 0x82, 0x6a, 0xbb, 0xa9, 0x77, 0xa6, 0x1f, 0xa4,
+                               0x9f, 0xd2, 0x9e, 0xdd, 0xe3, 0xa0, 0x07, 0x82, 0xdd, 0xae, 0x32, 0x70, 0xe4, 0x7a, 0x9c, 0xb2,
+                               0x2d, 0x52, 0xd2, 0x0a, 0x58, 0xb3, 0x78, 0x96, 0x83, 0x02, 0x32, 0x84, 0x4f, 0x06, 0x98, 0x3b,
+                               0x26, 0x0c, 0x82, 0x0b, 0x36, 0x64, 0xc2, 0xe4, 0x05, 0xa4, 0x95, 0x47, 0x07, 0xc8, 0xcc, 0x66,
+                               0xd4, 0x64, 0x01, 0x18, 0x27, 0x94, 0x00, 0x1b, 0x32, 0x9b, 0x9e, 0xf6, 0x2f, 0x63, 0xd7, 0x0c,
+                               0x65, 0x73, 0x1d, 0x93, 0x7a, 0xec, 0x8d, 0x15, 0xde, 0x98, 0x5e, 0x2b, 0xbf, 0x42, 0x78, 0xaa,
+                               0xbe, 0xf8, 0xd8, 0x2a, 0x66, 0x2c, 0x15, 0xf1, 0xae, 0x3b, 0xe7, 0x0b, 0x26, 0x25, 0xda, 0x23,
+                               0x22, 0x0d, 0x37, 0x7a, 0x7d, 0x5d, 0xf6, 0x53, 0x70, 0x27, 0xaa, 0x1e, 0x1f, 0x0e, 0x2f, 0x5b,
+                               0xcd, 0xa0, 0xf6, 0x8b, 0xbd, 0x6b, 0x89, 0x82, 0xe5, 0x52, 0x96, 0x1b, 0x5c, 0xac, 0xdb, 0xeb,
+                               0x11, 0x77, 0xc0, 0x67, 0x56, 0x5d, 0x67, 0xd6, 0xa8, 0xad, 0x9a, 0xb4, 0xc2, 0x16, 0x45, 0x6b,
+                               0x48, 0x9f, 0x8c, 0xca, 0x26, 0x12, 0xcc, 0x91, 0x88, 0xbc, 0x61, 0xdf, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x34, 0x54, 0x2f, 0xbf, 0x8c, 0x9c, 0x8c, 0xc0, 0xa8, 0xdc, 0x58,
+                               0x94, 0x44, 0x5d, 0xdb, 0x11, 0x42, 0x04, 0x07, 0x58, 0x4a, 0x2f, 0x08, 0x5e, 0x57, 0xab, 0xee,
+                               0x2e, 0x9d, 0xf9, 0x6a, 0xfb, 0xf2, 0x18, 0x26, 0x71, 0xc8, 0x54, 0x6f, 0xac, 0xd1, 0x44, 0x1e,
+                               0x69, 0xbc, 0x8f, 0x29, 0x70, 0x10, 0x9c, 0xe6, 0x6c, 0x43, 0x98, 0x6b, 0x0e, 0x14, 0x81, 0x01,
+                               0xf4, 0x3d, 0x00, 0x08, 0xe5, 0xe8, 0xad, 0xe5, 0xa8, 0x79, 0x6f, 0xb2, 0xbe, 0x6f, 0xd2, 0x33,
+                               0xff, 0x6f, 0x6a, 0x6a, 0x8a, 0xa0, 0xce, 0x46, 0xfe, 0x2f, 0x5f, 0x29, 0x05, 0xc2, 0xa4, 0x91,
+                               0x7f, 0xe0, 0x19, 0xbe, 0x8c, 0x8d, 0x7c, 0xff, 0x6e, 0x2d, 0x6e, 0xb8, 0x47, 0x39, 0x45, 0x0d,
+                               0x94, 0xb7, 0x93, 0xaa, 0x21, 0xf5, 0x31, 0xe9, 0xdf, 0xe0, 0x7a, 0xbe, 0x01, 0x21, 0x50, 0xc0,
+                               0x46, 0x08, 0x52, 0x97, 0x86, 0x27, 0x41, 0x1d, 0x88, 0xd9, 0xae, 0xd9, 0xad, 0x4e, 0x37, 0x3b,
+                               0xe9, 0x5b, 0x2b, 0x83, 0x5c, 0xcb, 0xc1, 0x8e, 0x09, 0x0a, 0x6e, 0xb7, 0xe1, 0x4b, 0x1d, 0x31,
+                               0x49, 0x22, 0xb3, 0x05, 0x51, 0x60, 0xa9, 0x78, 0xfd, 0xae, 0xe8, 0xe0, 0xd8, 0xc3, 0xbb, 0xb0,
+                               0xe2, 0xee, 0x9c, 0x40, 0x52, 0x56, 0xed, 0xed, 0x4c, 0x85, 0x79, 0xd2, 0x6b, 0x40, 0xcd, 0x5f,
+                               0x2b, 0x96, 0x35, 0xcf, 0xe2, 0xfc, 0xf5, 0xb1, 0xcc, 0x3e, 0x89, 0xb4, 0xad, 0x08, 0x46, 0x00,
+                               0x78, 0x3e, 0x83, 0x44, 0xd5, 0x76, 0x5f, 0x1b, 0x48, 0xdd, 0xf7, 0x8c, 0x19, 0xf1, 0x79, 0xa0,
+                               0x63, 0x98, 0x66, 0x84, 0x8e, 0x84, 0xa3, 0xfb, 0x0b, 0xaf, 0x88, 0xc7, 0x10, 0xe3, 0x36, 0x86,
+                               0x19, 0xea, 0x4a, 0x04, 0xc2, 0x81, 0x53, 0xb0, 0x9f, 0x44, 0xdb, 0xbc, 0xb6, 0xaf, 0xe2, 0x59,
+                               0x70, 0x97, 0x6d, 0xbf, 0x61, 0x02, 0x81, 0x81, 0x00, 0xb8, 0x00, 0x69, 0x39, 0x0a, 0x0e, 0xc4,
+                               0x1c, 0x46, 0x5a, 0xb9, 0xdb, 0x8a, 0xac, 0xd7, 0xa3, 0x4f, 0xcc, 0x30, 0x18, 0xfb, 0x28, 0x38,
+                               0x3d, 0x2e, 0x7b, 0xe7, 0x30, 0xeb, 0xe3, 0x67, 0x29, 0xbc, 0x93, 0x46, 0x02, 0x04, 0x2f, 0x26,
+                               0x04, 0xe6, 0x21, 0xaf, 0xac, 0x09, 0xea, 0xdf, 0xc6, 0xfd, 0xb1, 0x4c, 0x60, 0xd1, 0xdf, 0x6e,
+                               0x83, 0x3d, 0x5e, 0xa6, 0x6b, 0x56, 0xe9, 0x30, 0xf1, 0x9d, 0xaa, 0xf5, 0x6c, 0xb7, 0x36, 0x8a,
+                               0x68, 0x3f, 0xcc, 0x48, 0xd0, 0x14, 0x52, 0x71, 0x96, 0x7b, 0x0a, 0xbb, 0x99, 0x7d, 0x67, 0xc2,
+                               0xe8, 0xa1, 0xd2, 0x04, 0x0b, 0xc1, 0xdf, 0xb9, 0x5d, 0x29, 0x85, 0x6f, 0x18, 0xf4, 0x7b, 0x23,
+                               0x71, 0x63, 0x4f, 0x58, 0xd8, 0xd3, 0xb6, 0xd4, 0xf4, 0x9e, 0x3c, 0x6a, 0xb9, 0x35, 0x43, 0xfe,
+                               0x8d, 0x91, 0x90, 0x42, 0x5b, 0xcc, 0xc3, 0x6d, 0x4f, 0x02, 0x81, 0x81, 0x00, 0xb7, 0xc6, 0xec,
+                               0x39, 0xbd, 0x05, 0x54, 0xaa, 0x1a, 0xd1, 0x35, 0xb4, 0x2c, 0xc9, 0xa1, 0x19, 0xe1, 0xca, 0x5d,
+                               0x78, 0x18, 0x48, 0x2c, 0xfe, 0xcb, 0x8e, 0x14, 0x92, 0xcb, 0xfd, 0x03, 0x26, 0xb2, 0xef, 0xdd,
+                               0xc0, 0x52, 0x31, 0x8f, 0x79, 0xf2, 0xaf, 0xbe, 0x47, 0x55, 0xbe, 0xe8, 0x50, 0xd3, 0x82, 0x8e,
+                               0xd6, 0x3f, 0xaa, 0x42, 0x86, 0xea, 0x15, 0xe5, 0xfe, 0xcc, 0x9b, 0x27, 0x35, 0x38, 0xd7, 0xab,
+                               0x97, 0xd6, 0x6c, 0x6e, 0x32, 0x07, 0xbd, 0xf1, 0x54, 0x3c, 0xb0, 0x7a, 0x75, 0x2b, 0xa7, 0x89,
+                               0xf6, 0xe5, 0x2f, 0xe2, 0x16, 0xac, 0x61, 0x8b, 0xe1, 0xd6, 0x6d, 0xfd, 0xe4, 0x7e, 0xad, 0x98,
+                               0x81, 0x31, 0xe0, 0x3d, 0x4c, 0xee, 0xe8, 0xda, 0x5d, 0x07, 0x33, 0xc6, 0xe6, 0x26, 0x73, 0xc0,
+                               0xa7, 0xbf, 0x4c, 0xab, 0xc3, 0x82, 0xc7, 0xec, 0x3d, 0x8e, 0x00, 0x3e, 0x71, 0x02, 0x81, 0x80,
+                               0x24, 0x67, 0xf8, 0x69, 0xcf, 0xc2, 0xa2, 0x2c, 0xd2, 0x02, 0xcb, 0x79, 0x53, 0xd3, 0xe5, 0x0a,
+                               0xac, 0x14, 0x43, 0x83, 0xfd, 0xd5, 0xb4, 0x62, 0xee, 0x9a, 0x97, 0x60, 0xc7, 0x9b, 0x83, 0xe2,
+                               0xef, 0x2b, 0xd8, 0x93, 0x37, 0x8e, 0xb8, 0xa0, 0x09, 0x84, 0xaa, 0xc4, 0x88, 0x65, 0xcf, 0x87,
+                               0x08, 0xd5, 0x16, 0xbe, 0xa5, 0x67, 0xe6, 0xee, 0xd2, 0xaa, 0xb2, 0x94, 0xd4, 0x6a, 0x69, 0x43,
+                               0xb5, 0x25, 0x89, 0x4f, 0xcc, 0x64, 0x5f, 0x38, 0x47, 0x3f, 0x3d, 0x3c, 0xe6, 0x78, 0x75, 0x7f,
+                               0x07, 0xb3, 0x54, 0xf1, 0x06, 0xae, 0x0c, 0x13, 0x89, 0x49, 0x0b, 0x81, 0xe4, 0x07, 0x47, 0x1e,
+                               0xd6, 0xc3, 0x4e, 0xfb, 0x0a, 0x0c, 0x7f, 0x33, 0xc5, 0x29, 0x82, 0xf3, 0xcb, 0xd8, 0x06, 0x04,
+                               0xac, 0x76, 0x59, 0xcb, 0x71, 0x6d, 0x95, 0xa5, 0x1f, 0x23, 0x02, 0xee, 0x34, 0xf2, 0x89, 0xe3,
+                               0x02, 0x81, 0x80, 0x04, 0xf8, 0x0f, 0xf3, 0x0f, 0xb5, 0x51, 0x30, 0xb8, 0x40, 0xe7, 0xde, 0xd3,
+                               0x47, 0x30, 0x5f, 0xee, 0xc6, 0xf5, 0xf8, 0x05, 0x50, 0x0e, 0x47, 0x65, 0x61, 0x96, 0x14, 0xd2,
+                               0x07, 0x29, 0x4a, 0xa6, 0x93, 0xed, 0xbf, 0x01, 0x79, 0xed, 0x93, 0x32, 0x88, 0xa5, 0xf0, 0x6f,
+                               0xd5, 0x15, 0x9a, 0xf9, 0xdd, 0x11, 0xd7, 0xa1, 0x29, 0x0d, 0x5b, 0x70, 0x80, 0xdf, 0x13, 0x20,
+                               0x9d, 0x21, 0x56, 0x43, 0x0f, 0x31, 0xc8, 0x8d, 0x37, 0xa9, 0x53, 0x18, 0xfa, 0x7d, 0xc7, 0xf5,
+                               0x4c, 0x20, 0x43, 0x82, 0xc6, 0xa8, 0xe8, 0x50, 0x12, 0x17, 0x5e, 0xdd, 0x92, 0x55, 0xa1, 0xe9,
+                               0x96, 0x57, 0xc3, 0x11, 0xd9, 0xc7, 0xc1, 0xd6, 0x83, 0x65, 0xa4, 0xea, 0xe0, 0xc8, 0xe6, 0xb3,
+                               0x07, 0x27, 0x5d, 0x99, 0xb4, 0x8f, 0x84, 0xf9, 0x9b, 0x2d, 0x19, 0xc0, 0x33, 0x7f, 0xa3, 0xba,
+                               0x6f, 0x2f, 0x41, 0x02, 0x81, 0x80, 0x65, 0xbf, 0x15, 0xf0, 0x83, 0xb3, 0xea, 0x05, 0xd6, 0xe6,
+                               0x0d, 0x46, 0xc4, 0x26, 0xf0, 0x19, 0x64, 0x6d, 0x60, 0x4e, 0x5f, 0xf9, 0x02, 0x49, 0x44, 0x2b,
+                               0x7a, 0x93, 0x34, 0x23, 0x70, 0x1b, 0x15, 0x6e, 0xc6, 0xd3, 0x2e, 0xde, 0xe2, 0xea, 0x6f, 0xf3,
+                               0x3d, 0x2f, 0xc2, 0xc2, 0xad, 0xea, 0x23, 0x90, 0x2f, 0xcb, 0x41, 0xd9, 0x3e, 0x4e, 0x67, 0x84,
+                               0x79, 0x6f, 0x38, 0xea, 0xea, 0x14, 0x3d, 0x70, 0x17, 0x09, 0x2a, 0x78, 0x35, 0xd6, 0xb5, 0x71,
+                               0x64, 0x0d, 0x25, 0xa5, 0xae, 0xf4, 0xdc, 0x37, 0xc5, 0xa8, 0x90, 0xea, 0x3e, 0xd2, 0x09, 0x4b,
+                               0x57, 0x7a, 0x4e, 0xd5, 0x48, 0xbe, 0x0d, 0xfe, 0x9b, 0x45, 0x8d, 0x29, 0x55, 0x94, 0x81, 0xc5,
+                               0x88, 0xfa, 0xf3, 0x42, 0xd0, 0x26, 0x67, 0xa8, 0xc5, 0xf1, 0x49, 0x93, 0x9f, 0x7d, 0x19, 0xd7,
+                               0xe9, 0x23, 0x57, 0xb4, 0x45, 0x4c
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_RSASHA1 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xa5, 0xb9, 0xfb, 0xc3, 0xbe, 0xf4, 0x0b, 0x60, 0x08, 0x16, 0xec, 0x98,
+                               0xed, 0x42, 0x5e, 0xcd, 0xe6, 0xd8, 0xe9, 0x5f, 0xf8, 0xa3, 0x4e, 0xe8, 0x5d, 0x39, 0x65, 0x42,
+                               0xa4, 0x61, 0xbc, 0xa0, 0x54, 0x9d, 0xda, 0x0c, 0xd6, 0xbd, 0x68, 0x6c, 0xef, 0x3c, 0xcf, 0xd0,
+                               0xac, 0xd5, 0xa4, 0x17, 0xb9, 0x49, 0xde, 0x6f, 0x36, 0xb1, 0x04, 0xb5, 0xb7, 0x4d, 0x3c, 0x9d,
+                               0xca, 0xd5, 0xe0, 0xd3, 0x95, 0xe7, 0xe5, 0xf9, 0xb4, 0xb0, 0xa5, 0xa0, 0x1a, 0xd6, 0x5c, 0x6d,
+                               0xf4, 0x3a, 0x69, 0xe6, 0x7e, 0x44, 0x00, 0x1e, 0x27, 0x8f, 0x56, 0xc0, 0x69, 0xcd, 0xac, 0x94,
+                               0x3f, 0x95, 0xa5, 0xd6, 0x15, 0x6e, 0x66, 0x64, 0x35, 0xc6, 0x75, 0xc6, 0xe8, 0x19, 0x96, 0x18,
+                               0xd3, 0xb9, 0xba, 0x0f, 0xfd, 0x01, 0x21, 0x95, 0x27, 0xa6, 0x6b, 0xd7, 0x67, 0x3c, 0x36, 0x46,
+                               0xe0, 0x08, 0x59, 0xa7, 0x97, 0x46, 0x07, 0xfc, 0x38, 0x57, 0xd0, 0xec, 0x53, 0x80, 0xa8, 0x9b,
+                               0x83, 0xbf, 0x4f, 0x10, 0x35, 0x05, 0xe8, 0x63, 0x39, 0x1a, 0x29, 0x19, 0x98, 0xa4, 0x32, 0xa5,
+                               0xf6, 0x23, 0xb0, 0x75, 0xa8, 0x11, 0x9e, 0x26, 0xda, 0x44, 0xe3, 0x36, 0x57, 0x80, 0x8b, 0x11,
+                               0x7c, 0xf9, 0x65, 0xfe, 0x7f, 0x7f, 0x10, 0xbb, 0x16, 0xff, 0xe3, 0x66, 0x78, 0x78, 0x1c, 0xf2,
+                               0xee, 0x22, 0x43, 0x2d, 0x76, 0x56, 0x43, 0xdc, 0x07, 0x1d, 0x08, 0x24, 0x9d, 0x2d, 0x52, 0x1f,
+                               0xdc, 0xfc, 0x08, 0xb8, 0xd0, 0xda, 0xfa, 0x5f, 0x76, 0x48, 0x09, 0xf8, 0x88, 0xa1, 0x1e, 0x3f,
+                               0x5b, 0xcf, 0x94, 0x44, 0x0b, 0x70, 0x30, 0x45, 0x84, 0x3c, 0x8b, 0xdd, 0x4d, 0x2f, 0x44, 0x1f,
+                               0xe4, 0x20, 0xaf, 0x3a, 0x91, 0xb9, 0xd1, 0x4e, 0xb2, 0x96, 0xc3, 0x64, 0xfb, 0x2e, 0x28, 0x3e,
+                               0x0e, 0x36, 0x89, 0xcf
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa5, 0xb9, 0xfb, 0xc3,
+                               0xbe, 0xf4, 0x0b, 0x60, 0x08, 0x16, 0xec, 0x98, 0xed, 0x42, 0x5e, 0xcd, 0xe6, 0xd8, 0xe9, 0x5f,
+                               0xf8, 0xa3, 0x4e, 0xe8, 0x5d, 0x39, 0x65, 0x42, 0xa4, 0x61, 0xbc, 0xa0, 0x54, 0x9d, 0xda, 0x0c,
+                               0xd6, 0xbd, 0x68, 0x6c, 0xef, 0x3c, 0xcf, 0xd0, 0xac, 0xd5, 0xa4, 0x17, 0xb9, 0x49, 0xde, 0x6f,
+                               0x36, 0xb1, 0x04, 0xb5, 0xb7, 0x4d, 0x3c, 0x9d, 0xca, 0xd5, 0xe0, 0xd3, 0x95, 0xe7, 0xe5, 0xf9,
+                               0xb4, 0xb0, 0xa5, 0xa0, 0x1a, 0xd6, 0x5c, 0x6d, 0xf4, 0x3a, 0x69, 0xe6, 0x7e, 0x44, 0x00, 0x1e,
+                               0x27, 0x8f, 0x56, 0xc0, 0x69, 0xcd, 0xac, 0x94, 0x3f, 0x95, 0xa5, 0xd6, 0x15, 0x6e, 0x66, 0x64,
+                               0x35, 0xc6, 0x75, 0xc6, 0xe8, 0x19, 0x96, 0x18, 0xd3, 0xb9, 0xba, 0x0f, 0xfd, 0x01, 0x21, 0x95,
+                               0x27, 0xa6, 0x6b, 0xd7, 0x67, 0x3c, 0x36, 0x46, 0xe0, 0x08, 0x59, 0xa7, 0x97, 0x46, 0x07, 0xfc,
+                               0x38, 0x57, 0xd0, 0xec, 0x53, 0x80, 0xa8, 0x9b, 0x83, 0xbf, 0x4f, 0x10, 0x35, 0x05, 0xe8, 0x63,
+                               0x39, 0x1a, 0x29, 0x19, 0x98, 0xa4, 0x32, 0xa5, 0xf6, 0x23, 0xb0, 0x75, 0xa8, 0x11, 0x9e, 0x26,
+                               0xda, 0x44, 0xe3, 0x36, 0x57, 0x80, 0x8b, 0x11, 0x7c, 0xf9, 0x65, 0xfe, 0x7f, 0x7f, 0x10, 0xbb,
+                               0x16, 0xff, 0xe3, 0x66, 0x78, 0x78, 0x1c, 0xf2, 0xee, 0x22, 0x43, 0x2d, 0x76, 0x56, 0x43, 0xdc,
+                               0x07, 0x1d, 0x08, 0x24, 0x9d, 0x2d, 0x52, 0x1f, 0xdc, 0xfc, 0x08, 0xb8, 0xd0, 0xda, 0xfa, 0x5f,
+                               0x76, 0x48, 0x09, 0xf8, 0x88, 0xa1, 0x1e, 0x3f, 0x5b, 0xcf, 0x94, 0x44, 0x0b, 0x70, 0x30, 0x45,
+                               0x84, 0x3c, 0x8b, 0xdd, 0x4d, 0x2f, 0x44, 0x1f, 0xe4, 0x20, 0xaf, 0x3a, 0x91, 0xb9, 0xd1, 0x4e,
+                               0xb2, 0x96, 0xc3, 0x64, 0xfb, 0x2e, 0x28, 0x3e, 0x0e, 0x36, 0x89, 0xcf, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x3e, 0x8a, 0x2a, 0x57, 0xaa, 0xaa, 0x56, 0xd6, 0x95, 0x06, 0xed,
+                               0x9f, 0x22, 0x3d, 0xdf, 0x1f, 0xed, 0x83, 0x22, 0xc2, 0x9c, 0x46, 0x28, 0x83, 0x68, 0x96, 0xbd,
+                               0xbf, 0x41, 0xe0, 0x3e, 0x39, 0xf4, 0xe4, 0xa2, 0xc2, 0x20, 0x91, 0x56, 0xa4, 0x71, 0x3d, 0xae,
+                               0x20, 0xcd, 0xf0, 0x4b, 0x5d, 0xc6, 0x86, 0xf9, 0x41, 0x57, 0xce, 0xc2, 0x7b, 0xbe, 0xa2, 0x0f,
+                               0x83, 0x6e, 0x65, 0x7a, 0xd3, 0xed, 0xe8, 0x96, 0xb1, 0x3e, 0x3e, 0x41, 0x2a, 0x63, 0xa6, 0x4c,
+                               0x38, 0x14, 0x27, 0xad, 0x0a, 0x15, 0xd1, 0x1c, 0x88, 0x44, 0x9e, 0x83, 0x7a, 0xd6, 0x12, 0x8f,
+                               0x13, 0x5c, 0xd1, 0x60, 0xc5, 0x22, 0xce, 0x48, 0x26, 0x7d, 0xe2, 0x98, 0x54, 0x89, 0x07, 0x0a,
+                               0xcc, 0xf0, 0xad, 0x50, 0x69, 0x33, 0x2d, 0x67, 0x9a, 0x6d, 0x5d, 0x90, 0xa9, 0xb7, 0x64, 0x12,
+                               0x09, 0x7a, 0x06, 0xbc, 0xa9, 0xf5, 0xe6, 0x83, 0xd5, 0xde, 0xee, 0x7c, 0xda, 0xaf, 0xa5, 0xde,
+                               0x93, 0xd7, 0x9a, 0xd5, 0x9f, 0x37, 0xd8, 0xdc, 0x39, 0x63, 0xa1, 0x73, 0xbd, 0x91, 0xeb, 0xc6,
+                               0xff, 0x87, 0xc8, 0x0c, 0xc3, 0xfc, 0x69, 0x4e, 0x80, 0x5b, 0x50, 0x88, 0xd5, 0x1b, 0xc9, 0x00,
+                               0x8f, 0xeb, 0x9d, 0x66, 0xf4, 0xf4, 0x4d, 0x3c, 0x41, 0xe5, 0xbf, 0xdc, 0x9c, 0x0f, 0x35, 0x83,
+                               0x88, 0x2d, 0xd7, 0x81, 0x70, 0x22, 0xfa, 0x6d, 0x77, 0x53, 0x17, 0x13, 0x38, 0x7a, 0xb0, 0x0a,
+                               0x14, 0x15, 0xbf, 0xa2, 0x27, 0xc1, 0xfd, 0x07, 0x24, 0x63, 0x5a, 0xbb, 0x0d, 0x26, 0xad, 0x4d,
+                               0x52, 0x62, 0x84, 0xe2, 0xc2, 0x03, 0xea, 0x61, 0xd0, 0x73, 0x3d, 0x5d, 0x5b, 0x1a, 0x86, 0xc7,
+                               0x92, 0x29, 0x4d, 0x22, 0xf6, 0xf2, 0x59, 0x17, 0xed, 0xf0, 0x83, 0xdb, 0x5c, 0xf2, 0xf4, 0xe6,
+                               0xe1, 0x58, 0xd2, 0xa5, 0xfd, 0x02, 0x81, 0x81, 0x00, 0xcf, 0x1f, 0x3d, 0xb3, 0xec, 0x8b, 0x8e,
+                               0xaa, 0x1f, 0xfb, 0x03, 0xd3, 0x95, 0xb4, 0xbe, 0x61, 0xed, 0xc5, 0x30, 0x2f, 0xc4, 0x20, 0xd8,
+                               0xc6, 0x14, 0x17, 0x55, 0xac, 0x06, 0x0b, 0x02, 0x8c, 0x18, 0xd0, 0xe1, 0x0c, 0xce, 0x14, 0xe4,
+                               0xf1, 0xb0, 0x2a, 0x3f, 0x6e, 0x3b, 0x57, 0x60, 0x7b, 0x22, 0xbd, 0xbe, 0xa7, 0x41, 0x33, 0x84,
+                               0x17, 0xb8, 0xd8, 0x92, 0x9e, 0xe2, 0x1e, 0xf2, 0xa5, 0x03, 0x68, 0x86, 0xe3, 0x42, 0x86, 0x0f,
+                               0xd8, 0xcf, 0x19, 0x51, 0x35, 0x77, 0xa0, 0xcd, 0x20, 0x4c, 0x7b, 0xaf, 0x4b, 0xb6, 0x5a, 0x36,
+                               0xdc, 0xb0, 0x4f, 0xcf, 0x04, 0xf2, 0xba, 0x83, 0x27, 0xdc, 0x7f, 0x7a, 0xd5, 0x24, 0x1c, 0x4e,
+                               0xcc, 0xf3, 0xd8, 0x44, 0xf5, 0xb3, 0x88, 0x42, 0xa6, 0xc7, 0xaa, 0x95, 0xac, 0x63, 0xeb, 0x88,
+                               0xfd, 0x5e, 0xf7, 0xb3, 0x89, 0xad, 0xe3, 0x7f, 0xe3, 0x02, 0x81, 0x81, 0x00, 0xcc, 0xd5, 0xef,
+                               0x49, 0xfd, 0xac, 0x83, 0x95, 0x42, 0x50, 0x34, 0x13, 0x42, 0x34, 0x6e, 0xec, 0xd0, 0xb4, 0x88,
+                               0x6a, 0xdb, 0x9f, 0x77, 0x89, 0x47, 0xb3, 0xd4, 0xbd, 0x20, 0x84, 0xb2, 0xd1, 0x84, 0x64, 0x37,
+                               0xdd, 0x6d, 0x89, 0x43, 0xf0, 0xc0, 0x42, 0xf6, 0x07, 0x76, 0xd8, 0x4d, 0x65, 0xf9, 0x01, 0x0e,
+                               0xad, 0xfc, 0x16, 0xfd, 0x7c, 0x14, 0x03, 0xfa, 0xfe, 0x5f, 0x20, 0x32, 0x56, 0x50, 0x5c, 0x3a,
+                               0x26, 0xaa, 0x18, 0xd1, 0x24, 0x6a, 0x20, 0x85, 0x43, 0xaf, 0xd9, 0xfb, 0xe0, 0x05, 0x80, 0x09,
+                               0xb0, 0x8d, 0xf2, 0x77, 0xd6, 0xaa, 0x6e, 0x6d, 0x66, 0x5f, 0xa0, 0xfd, 0xb9, 0x2e, 0xd6, 0x7b,
+                               0x4e, 0xfb, 0x67, 0xe0, 0xc4, 0x99, 0xaf, 0x15, 0x8e, 0x14, 0x77, 0xdb, 0x49, 0x92, 0xc6, 0x02,
+                               0xd4, 0xe4, 0xe0, 0x77, 0x29, 0xb6, 0x49, 0xf2, 0xe5, 0xf8, 0xc8, 0x1a, 0x25, 0x02, 0x81, 0x80,
+                               0x31, 0x13, 0x0d, 0xe3, 0x9b, 0xa0, 0x55, 0x65, 0x29, 0xbe, 0xa7, 0xe1, 0x72, 0x0f, 0x29, 0x2c,
+                               0xba, 0xd1, 0x85, 0xe1, 0x4b, 0x6f, 0x1c, 0xed, 0x91, 0xc5, 0x15, 0x9f, 0x74, 0xaf, 0x17, 0x23,
+                               0x6a, 0x9d, 0xd3, 0x34, 0xdd, 0x7f, 0x45, 0xdf, 0x9e, 0x05, 0xe4, 0x91, 0x91, 0xad, 0xda, 0x46,
+                               0x52, 0xac, 0xf2, 0x87, 0x5e, 0x83, 0x7e, 0x40, 0xc0, 0xa3, 0x4f, 0xda, 0x25, 0x69, 0x7f, 0xc4,
+                               0x69, 0x52, 0x52, 0xe3, 0x75, 0xd7, 0x9d, 0xd9, 0x98, 0xf8, 0x3e, 0xad, 0x94, 0x72, 0x9d, 0x27,
+                               0x91, 0xf7, 0x9c, 0x49, 0x2c, 0x23, 0xa3, 0xec, 0x16, 0x3a, 0x52, 0xaa, 0xb1, 0x78, 0x4f, 0xab,
+                               0x1a, 0x2b, 0x7a, 0x47, 0x16, 0x0f, 0x3e, 0xfc, 0x80, 0xaa, 0x35, 0x6c, 0xa1, 0xe5, 0x1f, 0x25,
+                               0xb5, 0x19, 0x4d, 0xed, 0x52, 0x9f, 0x74, 0x0d, 0xd4, 0x7a, 0x8c, 0x27, 0x8a, 0xf3, 0xe4, 0x51,
+                               0x02, 0x81, 0x80, 0x53, 0xf0, 0x37, 0x26, 0xb2, 0xf5, 0x46, 0xd6, 0x9b, 0x5e, 0x12, 0x78, 0xf1,
+                               0xe0, 0x5a, 0xb4, 0x60, 0xb2, 0x1a, 0x54, 0xef, 0xba, 0xe1, 0x59, 0x38, 0x30, 0xd1, 0x34, 0xcf,
+                               0x66, 0x91, 0x80, 0x9a, 0x28, 0x17, 0x5c, 0x7d, 0xce, 0x8a, 0x30, 0xd6, 0x18, 0x9a, 0x89, 0x64,
+                               0x82, 0x2e, 0xf5, 0x62, 0x9c, 0xb4, 0x4f, 0x47, 0xc6, 0x84, 0x09, 0x9c, 0x8a, 0x25, 0x08, 0xa8,
+                               0x22, 0xa1, 0x7e, 0x0d, 0x60, 0x3d, 0xd4, 0x2a, 0x50, 0x11, 0x24, 0x42, 0xf9, 0x20, 0xa3, 0x24,
+                               0x24, 0xc7, 0xba, 0x7d, 0x86, 0x82, 0x71, 0xff, 0x39, 0x25, 0x32, 0xaa, 0x94, 0xa5, 0xf0, 0x15,
+                               0xaf, 0xfb, 0x49, 0x2f, 0x90, 0x64, 0xe7, 0x70, 0xce, 0x98, 0xa4, 0xf7, 0xf5, 0x6f, 0x77, 0xd1,
+                               0x9b, 0x6c, 0x19, 0x32, 0x34, 0x42, 0x64, 0xcb, 0x85, 0xd0, 0x4f, 0x1f, 0x8c, 0x1e, 0x25, 0x6f,
+                               0x55, 0x50, 0xa1, 0x02, 0x81, 0x80, 0x35, 0x46, 0x32, 0xc6, 0x88, 0xf4, 0x98, 0xea, 0x25, 0xbe,
+                               0xd8, 0xae, 0x45, 0x82, 0xf5, 0x8f, 0x68, 0x37, 0x76, 0x8f, 0x3e, 0x79, 0x18, 0x8d, 0x89, 0x82,
+                               0xfd, 0x3c, 0x55, 0x6c, 0x50, 0xe9, 0xd4, 0x62, 0xc0, 0x8a, 0xf5, 0x0b, 0x7f, 0x05, 0x30, 0x85,
+                               0x16, 0x4f, 0x44, 0x69, 0x10, 0xef, 0x15, 0x6d, 0xfc, 0x1f, 0xd3, 0xbd, 0x87, 0x27, 0x86, 0xb5,
+                               0x56, 0xbd, 0x51, 0x13, 0xa5, 0x3c, 0xe5, 0xc4, 0x3a, 0x57, 0x4e, 0x4a, 0xfe, 0xfa, 0x7a, 0x1a,
+                               0xad, 0x27, 0x5c, 0x64, 0x1d, 0x7f, 0xd0, 0xf4, 0x10, 0xc5, 0x8d, 0xb2, 0x3e, 0xe2, 0x35, 0xe7,
+                               0x8e, 0xdb, 0x4e, 0x53, 0x9c, 0x4f, 0x4f, 0x19, 0x97, 0x50, 0x9c, 0x90, 0x60, 0xd7, 0x03, 0xfc,
+                               0xa1, 0xea, 0x72, 0x22, 0x43, 0x15, 0x2e, 0x6c, 0x91, 0xfa, 0xaf, 0xe0, 0x32, 0x99, 0xb6, 0xa3,
+                               0x32, 0x51, 0xfa, 0xd4, 0x1f, 0x3c
+                       }
+               }
+       },
+       // DNSSEC Zone 3
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_RSASHA1 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xb7, 0xd1, 0x77, 0xd7, 0x84, 0xf2, 0x21, 0x2b, 0x40, 0x0b, 0x7a, 0x4c,
+                               0x91, 0x95, 0x36, 0xfa, 0x41, 0xc8, 0xc8, 0xe4, 0xba, 0xb2, 0xec, 0x68, 0xdc, 0x12, 0x2c, 0x0b,
+                               0x61, 0xc4, 0x9d, 0x4d, 0xa7, 0x47, 0x05, 0x92, 0x77, 0x44, 0xc9, 0x29, 0xa3, 0x30, 0x95, 0x02,
+                               0x2f, 0xc2, 0xa7, 0xab, 0x63, 0xa2, 0xfc, 0x37, 0x13, 0x80, 0xb9, 0xc9, 0xb7, 0x79, 0xf6, 0xfd,
+                               0x6f, 0xfd, 0x43, 0x4a, 0xd4, 0x4e, 0x58, 0x8f, 0x4d, 0x8b, 0xcc, 0xff, 0x41, 0x7a, 0x93, 0x3d,
+                               0x66, 0x2b, 0x95, 0xf5, 0x59, 0xef, 0x29, 0x22, 0x57, 0x72, 0x2e, 0x7d, 0xa7, 0x67, 0x03, 0x9e,
+                               0x38, 0x05, 0xa8, 0x59, 0x9c, 0x03, 0x9c, 0x33, 0x15, 0x16, 0x12, 0xd4, 0x8c, 0x8e, 0x9f, 0x3f,
+                               0xa3, 0x24, 0x62, 0x2c, 0x19, 0xea, 0xd8, 0x45, 0xae, 0x64, 0x64, 0x1b, 0x14, 0x92, 0xd8, 0x68,
+                               0xb3, 0xf7, 0xe5, 0xb8, 0x01, 0x12, 0x5e, 0x8f, 0xf9, 0x18, 0x6f, 0x29, 0xe2, 0xff, 0xb8, 0x68,
+                               0x2d, 0x68, 0x3c, 0xe6, 0x2e, 0xee, 0xcc, 0x11, 0x90, 0x62, 0x8c, 0x76, 0x08, 0x75, 0xdb, 0x77,
+                               0xe1, 0x4d, 0xdf, 0xbf, 0x6e, 0xa4, 0x11, 0x9f, 0xa4, 0x1b, 0xf4, 0xfa, 0xff, 0xbb, 0x5b, 0xed,
+                               0x4f, 0xb1, 0xdc, 0xef, 0x11, 0x1f, 0x99, 0xc3, 0xa1, 0xa7, 0x6e, 0x06, 0xe6, 0x1e, 0xd1, 0x4e,
+                               0x4e, 0x9a, 0x70, 0x9e, 0xbb, 0xc5, 0xa3, 0xb5, 0x77, 0xdc, 0xa8, 0xc8, 0x9f, 0x5a, 0x61, 0x5f,
+                               0x01, 0x18, 0x39, 0xe0, 0x7f, 0x8a, 0x52, 0x0f, 0x4e, 0x94, 0xb3, 0x03, 0x9a, 0x41, 0x5e, 0x52,
+                               0x9b, 0x30, 0xc4, 0x64, 0x3f, 0xb4, 0x71, 0xec, 0x2a, 0x6c, 0x63, 0x64, 0x8f, 0x16, 0xe3, 0xdf,
+                               0xfc, 0x01, 0x31, 0x8a, 0xfc, 0x08, 0x8d, 0x7c, 0xcd, 0xe9, 0x64, 0x87, 0x19, 0xe1, 0x0e, 0xfa,
+                               0x75, 0xee, 0xe3, 0x87
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xb7, 0xd1, 0x77, 0xd7,
+                               0x84, 0xf2, 0x21, 0x2b, 0x40, 0x0b, 0x7a, 0x4c, 0x91, 0x95, 0x36, 0xfa, 0x41, 0xc8, 0xc8, 0xe4,
+                               0xba, 0xb2, 0xec, 0x68, 0xdc, 0x12, 0x2c, 0x0b, 0x61, 0xc4, 0x9d, 0x4d, 0xa7, 0x47, 0x05, 0x92,
+                               0x77, 0x44, 0xc9, 0x29, 0xa3, 0x30, 0x95, 0x02, 0x2f, 0xc2, 0xa7, 0xab, 0x63, 0xa2, 0xfc, 0x37,
+                               0x13, 0x80, 0xb9, 0xc9, 0xb7, 0x79, 0xf6, 0xfd, 0x6f, 0xfd, 0x43, 0x4a, 0xd4, 0x4e, 0x58, 0x8f,
+                               0x4d, 0x8b, 0xcc, 0xff, 0x41, 0x7a, 0x93, 0x3d, 0x66, 0x2b, 0x95, 0xf5, 0x59, 0xef, 0x29, 0x22,
+                               0x57, 0x72, 0x2e, 0x7d, 0xa7, 0x67, 0x03, 0x9e, 0x38, 0x05, 0xa8, 0x59, 0x9c, 0x03, 0x9c, 0x33,
+                               0x15, 0x16, 0x12, 0xd4, 0x8c, 0x8e, 0x9f, 0x3f, 0xa3, 0x24, 0x62, 0x2c, 0x19, 0xea, 0xd8, 0x45,
+                               0xae, 0x64, 0x64, 0x1b, 0x14, 0x92, 0xd8, 0x68, 0xb3, 0xf7, 0xe5, 0xb8, 0x01, 0x12, 0x5e, 0x8f,
+                               0xf9, 0x18, 0x6f, 0x29, 0xe2, 0xff, 0xb8, 0x68, 0x2d, 0x68, 0x3c, 0xe6, 0x2e, 0xee, 0xcc, 0x11,
+                               0x90, 0x62, 0x8c, 0x76, 0x08, 0x75, 0xdb, 0x77, 0xe1, 0x4d, 0xdf, 0xbf, 0x6e, 0xa4, 0x11, 0x9f,
+                               0xa4, 0x1b, 0xf4, 0xfa, 0xff, 0xbb, 0x5b, 0xed, 0x4f, 0xb1, 0xdc, 0xef, 0x11, 0x1f, 0x99, 0xc3,
+                               0xa1, 0xa7, 0x6e, 0x06, 0xe6, 0x1e, 0xd1, 0x4e, 0x4e, 0x9a, 0x70, 0x9e, 0xbb, 0xc5, 0xa3, 0xb5,
+                               0x77, 0xdc, 0xa8, 0xc8, 0x9f, 0x5a, 0x61, 0x5f, 0x01, 0x18, 0x39, 0xe0, 0x7f, 0x8a, 0x52, 0x0f,
+                               0x4e, 0x94, 0xb3, 0x03, 0x9a, 0x41, 0x5e, 0x52, 0x9b, 0x30, 0xc4, 0x64, 0x3f, 0xb4, 0x71, 0xec,
+                               0x2a, 0x6c, 0x63, 0x64, 0x8f, 0x16, 0xe3, 0xdf, 0xfc, 0x01, 0x31, 0x8a, 0xfc, 0x08, 0x8d, 0x7c,
+                               0xcd, 0xe9, 0x64, 0x87, 0x19, 0xe1, 0x0e, 0xfa, 0x75, 0xee, 0xe3, 0x87, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x12, 0x8c, 0x53, 0x70, 0x6e, 0xdc, 0xd3, 0xd5, 0xfe, 0x67, 0x6a,
+                               0x16, 0xd1, 0xd5, 0xe1, 0xaf, 0x4c, 0xf6, 0x0a, 0xb7, 0x71, 0xd3, 0x5e, 0x21, 0x92, 0x9b, 0x4e,
+                               0xd0, 0x73, 0x34, 0x18, 0xbf, 0x5e, 0x32, 0xbf, 0x70, 0x31, 0x12, 0xc2, 0xcd, 0xad, 0xc6, 0xd8,
+                               0x32, 0x59, 0x49, 0x66, 0x0b, 0xd8, 0x48, 0xf8, 0xf4, 0x41, 0xc6, 0x8a, 0x78, 0xbd, 0x47, 0xd4,
+                               0x51, 0x6c, 0x7c, 0x7c, 0xce, 0xcf, 0x2f, 0x7c, 0xa6, 0x31, 0x27, 0xfb, 0x2e, 0x82, 0x97, 0xf0,
+                               0xc5, 0x90, 0x4a, 0xb3, 0x9e, 0x07, 0x4e, 0x27, 0xb3, 0x6c, 0x69, 0x7c, 0x21, 0x77, 0xc3, 0xe0,
+                               0x4c, 0x95, 0xd0, 0x94, 0x55, 0x3e, 0x75, 0xea, 0xcc, 0x08, 0xc3, 0xaa, 0x70, 0xc2, 0x22, 0x1e,
+                               0x15, 0x98, 0xcb, 0xb2, 0x10, 0x4b, 0x29, 0x34, 0x5b, 0x8b, 0xa6, 0x5b, 0xa7, 0x93, 0xe2, 0xe1,
+                               0x6d, 0x67, 0x43, 0x8b, 0x6d, 0x62, 0x8c, 0xaf, 0x58, 0x30, 0x90, 0x8f, 0x52, 0x3c, 0xf8, 0x9b,
+                               0xd7, 0x19, 0x4a, 0x39, 0xa3, 0x23, 0x24, 0x3c, 0xab, 0xe2, 0x0c, 0x0f, 0x06, 0x86, 0xf9, 0x26,
+                               0x67, 0xd3, 0xbe, 0x05, 0x46, 0xe2, 0x20, 0xd2, 0x13, 0x9e, 0xf9, 0xc5, 0x91, 0x2a, 0x29, 0xf8,
+                               0x51, 0x99, 0xdd, 0x93, 0x72, 0x48, 0xa4, 0x1a, 0xfc, 0x4d, 0x44, 0x0f, 0xad, 0x96, 0xba, 0x73,
+                               0x8a, 0x6f, 0xc3, 0xa1, 0xde, 0xfc, 0xb4, 0x9c, 0xfe, 0xb8, 0xa4, 0x94, 0x53, 0xf5, 0x7f, 0x15,
+                               0xbd, 0x25, 0xa8, 0xec, 0x30, 0x31, 0x31, 0x93, 0xe5, 0x6b, 0xc6, 0xca, 0x1b, 0x6d, 0x8c, 0x02,
+                               0xdf, 0xc5, 0x07, 0xab, 0x58, 0xb0, 0x25, 0x5e, 0x3b, 0x11, 0xb2, 0xd1, 0x51, 0xca, 0x84, 0x2e,
+                               0x68, 0x77, 0x2e, 0x27, 0x77, 0xa7, 0x3e, 0xf6, 0x2e, 0x95, 0xdc, 0x83, 0xe1, 0xd9, 0x6d, 0x4a,
+                               0x5e, 0xc9, 0xc1, 0x2a, 0xa9, 0x02, 0x81, 0x81, 0x00, 0xfa, 0x31, 0xef, 0x94, 0xe0, 0xc0, 0x17,
+                               0xb6, 0xc5, 0x3e, 0xc2, 0x75, 0x98, 0x80, 0x61, 0x04, 0x7c, 0x4f, 0xa6, 0xda, 0x14, 0xab, 0xab,
+                               0x8f, 0xe0, 0x31, 0xd7, 0xb6, 0x89, 0x46, 0x20, 0x87, 0x1d, 0xde, 0x54, 0x31, 0x3c, 0x6c, 0xaa,
+                               0xad, 0x0b, 0x54, 0x17, 0x95, 0x07, 0x9d, 0xa1, 0x24, 0xe7, 0xca, 0xa3, 0x71, 0xa9, 0xc5, 0x63,
+                               0x6d, 0xc7, 0x0a, 0x12, 0xfc, 0xeb, 0xc7, 0x7f, 0x77, 0xdf, 0xbe, 0xc4, 0x14, 0x70, 0xb5, 0x6d,
+                               0xd8, 0x4f, 0x15, 0x9c, 0x1d, 0x06, 0x77, 0x80, 0x1a, 0xb7, 0x41, 0xde, 0x41, 0xa4, 0x12, 0xe3,
+                               0xec, 0x51, 0x12, 0xfd, 0x31, 0x1a, 0x19, 0x63, 0xcf, 0x5a, 0x34, 0xc2, 0xfb, 0xdc, 0x13, 0xa5,
+                               0xf5, 0x82, 0xa7, 0xd4, 0x84, 0x3e, 0x2c, 0xce, 0x09, 0x8d, 0x9b, 0x47, 0x99, 0x84, 0x03, 0x9c,
+                               0x35, 0xa0, 0x2f, 0x4f, 0x72, 0x2d, 0x1a, 0xfd, 0xcb, 0x02, 0x81, 0x81, 0x00, 0xbc, 0x15, 0x47,
+                               0x6c, 0x22, 0xf6, 0x36, 0x48, 0x18, 0xa6, 0x55, 0x9d, 0x86, 0xf4, 0xfc, 0x27, 0x31, 0x94, 0xd1,
+                               0x0d, 0x91, 0xc8, 0x41, 0xd9, 0x58, 0x02, 0x76, 0x12, 0x52, 0xa7, 0xbd, 0xca, 0x97, 0x51, 0x04,
+                               0x37, 0xbd, 0xff, 0x17, 0xb5, 0xfb, 0xb6, 0xf4, 0x9d, 0x91, 0x78, 0x2c, 0xc6, 0x67, 0x2d, 0x91,
+                               0x75, 0xeb, 0xf2, 0x1b, 0xe0, 0x87, 0x54, 0x73, 0xcd, 0x68, 0x02, 0x39, 0x17, 0xbd, 0x58, 0x10,
+                               0x3c, 0xe5, 0x1d, 0x34, 0x7d, 0xe3, 0x86, 0xa6, 0x0c, 0x13, 0xb5, 0xd4, 0x53, 0x06, 0xbb, 0xd9,
+                               0x26, 0x85, 0x9b, 0x53, 0x0c, 0xec, 0xae, 0x9d, 0x5f, 0xe9, 0x3f, 0x50, 0x27, 0x70, 0x8c, 0x7b,
+                               0x79, 0x59, 0x12, 0xfb, 0x1d, 0xd6, 0x82, 0x75, 0x1b, 0x2f, 0xbb, 0xe5, 0xda, 0xe7, 0xaf, 0xc3,
+                               0x54, 0xc0, 0x32, 0x49, 0x5e, 0x64, 0xa1, 0x39, 0xe5, 0x93, 0x1d, 0xf9, 0xb5, 0x02, 0x81, 0x80,
+                               0x23, 0x4b, 0x3a, 0x0d, 0xd9, 0x6a, 0x9f, 0xad, 0xc3, 0xc3, 0x67, 0xb1, 0x29, 0x13, 0x2b, 0x1c,
+                               0x73, 0xe0, 0xd4, 0x9b, 0xbd, 0x00, 0xbe, 0x91, 0xec, 0x41, 0x7b, 0xb9, 0x9f, 0x41, 0xca, 0x42,
+                               0xe5, 0x3e, 0xc0, 0xc7, 0xb5, 0x4b, 0x6b, 0x04, 0x40, 0x2d, 0xdb, 0xa9, 0xc5, 0x4a, 0x42, 0x3a,
+                               0x2f, 0x8c, 0x91, 0x63, 0xee, 0x5c, 0x0e, 0xfb, 0xa4, 0x71, 0x52, 0x5e, 0x65, 0x70, 0x5e, 0x15,
+                               0xed, 0xf5, 0x3e, 0x39, 0xd7, 0xf0, 0x70, 0x0f, 0x6c, 0x90, 0x92, 0xd6, 0x31, 0x5c, 0x58, 0x30,
+                               0xec, 0x9b, 0x19, 0x1c, 0x4f, 0x65, 0xee, 0xcb, 0x1e, 0x60, 0xbc, 0x60, 0xd9, 0xda, 0xad, 0x0e,
+                               0xca, 0x1d, 0xd9, 0x47, 0xa8, 0x33, 0x09, 0x5d, 0x49, 0xd2, 0x1d, 0x13, 0x8e, 0xa5, 0xc9, 0x66,
+                               0xe5, 0x97, 0xfb, 0x10, 0xb7, 0xe3, 0xbe, 0x7c, 0x7e, 0x1d, 0x4e, 0x6a, 0xbf, 0xdc, 0x27, 0xe3,
+                               0x02, 0x81, 0x80, 0x16, 0x5b, 0x39, 0x28, 0x23, 0x67, 0xbc, 0xc0, 0x09, 0x1e, 0x0c, 0x63, 0x33,
+                               0x1d, 0x7f, 0xb3, 0x70, 0xf3, 0x4d, 0x7e, 0x1d, 0x6b, 0xfe, 0x57, 0xc5, 0x5c, 0x55, 0x88, 0x8c,
+                               0x06, 0xa0, 0x4a, 0xaa, 0x7d, 0xd8, 0xd0, 0x7f, 0x67, 0x08, 0xfc, 0x7a, 0xb5, 0x1e, 0x92, 0x74,
+                               0x2e, 0x22, 0xb4, 0x5a, 0xa3, 0x51, 0xfd, 0x00, 0x54, 0xc0, 0xf7, 0x89, 0x5e, 0x82, 0x7d, 0x51,
+                               0xf5, 0xa1, 0xaf, 0xae, 0xb3, 0xff, 0x0a, 0x1d, 0xf9, 0xc0, 0xb9, 0x8e, 0x07, 0xfd, 0x48, 0xc6,
+                               0x37, 0x00, 0xcb, 0xf5, 0xaa, 0xf2, 0x7e, 0xdf, 0xb6, 0xbc, 0xc2, 0x76, 0x87, 0xb2, 0xf1, 0x29,
+                               0x9a, 0x7a, 0xfe, 0x95, 0x24, 0x3d, 0xb1, 0x08, 0xe3, 0x76, 0xb1, 0xef, 0x43, 0x86, 0x83, 0xfa,
+                               0xc2, 0x8c, 0xe9, 0xde, 0x4c, 0x48, 0x1d, 0x94, 0xd2, 0xe5, 0x61, 0xb0, 0xf8, 0x71, 0x17, 0x56,
+                               0xcb, 0x63, 0x59, 0x02, 0x81, 0x80, 0x1d, 0x65, 0x4a, 0xca, 0x46, 0x9c, 0x27, 0x9c, 0xae, 0x8d,
+                               0x07, 0x74, 0xc1, 0xc1, 0xf7, 0x48, 0xe6, 0x57, 0x71, 0xb1, 0x60, 0x14, 0x65, 0xdd, 0x21, 0x3b,
+                               0xd0, 0xe1, 0x3f, 0xd2, 0x72, 0x72, 0x85, 0x6d, 0x07, 0x99, 0x84, 0x81, 0x46, 0x1d, 0x2e, 0x7e,
+                               0x2a, 0x85, 0x76, 0xc6, 0xee, 0x82, 0xef, 0x87, 0x8d, 0xcf, 0x9a, 0xa7, 0x60, 0x92, 0xe9, 0x38,
+                               0x8f, 0xdd, 0x56, 0x84, 0x1f, 0x4b, 0x56, 0x77, 0x8f, 0x2f, 0x2f, 0x1d, 0xb7, 0xdb, 0xef, 0xc9,
+                               0x4f, 0x2a, 0xda, 0x76, 0x0a, 0x14, 0x41, 0x60, 0x5d, 0xb7, 0x3a, 0x1c, 0x48, 0x2c, 0x17, 0xb4,
+                               0xe1, 0x1b, 0x9c, 0x4f, 0xe4, 0x34, 0x89, 0x59, 0xa7, 0x58, 0xc3, 0x90, 0x5e, 0x1c, 0xef, 0x83,
+                               0xca, 0xd6, 0xc9, 0x4c, 0xa4, 0x4a, 0xac, 0xe4, 0x7b, 0x3d, 0xe1, 0x47, 0xa7, 0x65, 0x67, 0xd2,
+                               0x55, 0xa7, 0x30, 0x85, 0xea, 0xf4
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_RSASHA1 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xe0, 0x93, 0xa6, 0x85, 0x5f, 0x32, 0x37, 0x1c, 0xc5, 0x16, 0x25, 0x70,
+                               0xe3, 0x7c, 0x60, 0xd7, 0x5b, 0x6d, 0x64, 0x39, 0xd1, 0x76, 0xdf, 0x06, 0x58, 0x78, 0xa7, 0x5e,
+                               0xa7, 0xbc, 0x53, 0x51, 0x14, 0xf8, 0x01, 0x82, 0x75, 0xf8, 0x1e, 0x17, 0x80, 0xb7, 0xbf, 0xb8,
+                               0x3d, 0xf8, 0xa1, 0x52, 0xcc, 0xc9, 0xc9, 0x07, 0xb7, 0x17, 0x5a, 0xc9, 0x8b, 0xf2, 0x44, 0x2b,
+                               0x2e, 0x18, 0x61, 0x28, 0x2a, 0xf7, 0x04, 0xdb, 0xad, 0xc4, 0xdc, 0x25, 0xf9, 0xb4, 0x86, 0x23,
+                               0x6a, 0xf5, 0x41, 0x6b, 0x76, 0xe6, 0x5a, 0x6f, 0xfe, 0xf3, 0x93, 0x48, 0xf1, 0x01, 0x69, 0x62,
+                               0xc0, 0xda, 0x23, 0x26, 0x7f, 0x1d, 0xf5, 0x3e, 0xbe, 0x7e, 0xb7, 0xb1, 0xeb, 0xd9, 0x3f, 0x20,
+                               0xf1, 0x0e, 0x75, 0xdf, 0x62, 0x77, 0xec, 0x6c, 0xf8, 0x8c, 0x32, 0xb9, 0x2a, 0x5c, 0xfe, 0x93,
+                               0xa0, 0x4d, 0x86, 0x5c, 0x35, 0x97, 0x70, 0x44, 0x1d, 0x8d, 0x59, 0x30, 0xc8, 0xe1, 0x42, 0x74,
+                               0x1b, 0xe1, 0x7f, 0xe1, 0x6d, 0xc3, 0xa0, 0xb7, 0xc3, 0x60, 0x37, 0x9b, 0xce, 0xf5, 0xb3, 0xf5,
+                               0xf8, 0xe2, 0xa3, 0x56, 0x3d, 0x5a, 0x14, 0x09, 0xd5, 0xd1, 0x09, 0x21, 0x93, 0xde, 0x79, 0x4d,
+                               0xad, 0xf3, 0x8a, 0x19, 0x46, 0xaa, 0xe1, 0x3d, 0xf6, 0x11, 0xa0, 0x1d, 0x58, 0x82, 0xf0, 0xcd,
+                               0x54, 0xd4, 0x76, 0x3f, 0x20, 0x85, 0x10, 0x15, 0x19, 0x22, 0x86, 0xb2, 0xc6, 0xc4, 0x48, 0xa5,
+                               0x19, 0x4e, 0x1e, 0xdc, 0x43, 0xba, 0x4d, 0xa6, 0xba, 0xf6, 0x26, 0xee, 0xed, 0xe5, 0xce, 0x4d,
+                               0x5a, 0x25, 0xf6, 0xb5, 0x24, 0x9c, 0x8b, 0xc0, 0xa1, 0xb4, 0x97, 0x56, 0x13, 0x76, 0x2c, 0xed,
+                               0x3b, 0x52, 0x82, 0x1e, 0x92, 0x06, 0xfd, 0x03, 0x32, 0x91, 0xf8, 0x1b, 0xd8, 0xa4, 0xbe, 0x8a,
+                               0x51, 0x7c, 0x21, 0x91
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xe0, 0x93, 0xa6, 0x85,
+                               0x5f, 0x32, 0x37, 0x1c, 0xc5, 0x16, 0x25, 0x70, 0xe3, 0x7c, 0x60, 0xd7, 0x5b, 0x6d, 0x64, 0x39,
+                               0xd1, 0x76, 0xdf, 0x06, 0x58, 0x78, 0xa7, 0x5e, 0xa7, 0xbc, 0x53, 0x51, 0x14, 0xf8, 0x01, 0x82,
+                               0x75, 0xf8, 0x1e, 0x17, 0x80, 0xb7, 0xbf, 0xb8, 0x3d, 0xf8, 0xa1, 0x52, 0xcc, 0xc9, 0xc9, 0x07,
+                               0xb7, 0x17, 0x5a, 0xc9, 0x8b, 0xf2, 0x44, 0x2b, 0x2e, 0x18, 0x61, 0x28, 0x2a, 0xf7, 0x04, 0xdb,
+                               0xad, 0xc4, 0xdc, 0x25, 0xf9, 0xb4, 0x86, 0x23, 0x6a, 0xf5, 0x41, 0x6b, 0x76, 0xe6, 0x5a, 0x6f,
+                               0xfe, 0xf3, 0x93, 0x48, 0xf1, 0x01, 0x69, 0x62, 0xc0, 0xda, 0x23, 0x26, 0x7f, 0x1d, 0xf5, 0x3e,
+                               0xbe, 0x7e, 0xb7, 0xb1, 0xeb, 0xd9, 0x3f, 0x20, 0xf1, 0x0e, 0x75, 0xdf, 0x62, 0x77, 0xec, 0x6c,
+                               0xf8, 0x8c, 0x32, 0xb9, 0x2a, 0x5c, 0xfe, 0x93, 0xa0, 0x4d, 0x86, 0x5c, 0x35, 0x97, 0x70, 0x44,
+                               0x1d, 0x8d, 0x59, 0x30, 0xc8, 0xe1, 0x42, 0x74, 0x1b, 0xe1, 0x7f, 0xe1, 0x6d, 0xc3, 0xa0, 0xb7,
+                               0xc3, 0x60, 0x37, 0x9b, 0xce, 0xf5, 0xb3, 0xf5, 0xf8, 0xe2, 0xa3, 0x56, 0x3d, 0x5a, 0x14, 0x09,
+                               0xd5, 0xd1, 0x09, 0x21, 0x93, 0xde, 0x79, 0x4d, 0xad, 0xf3, 0x8a, 0x19, 0x46, 0xaa, 0xe1, 0x3d,
+                               0xf6, 0x11, 0xa0, 0x1d, 0x58, 0x82, 0xf0, 0xcd, 0x54, 0xd4, 0x76, 0x3f, 0x20, 0x85, 0x10, 0x15,
+                               0x19, 0x22, 0x86, 0xb2, 0xc6, 0xc4, 0x48, 0xa5, 0x19, 0x4e, 0x1e, 0xdc, 0x43, 0xba, 0x4d, 0xa6,
+                               0xba, 0xf6, 0x26, 0xee, 0xed, 0xe5, 0xce, 0x4d, 0x5a, 0x25, 0xf6, 0xb5, 0x24, 0x9c, 0x8b, 0xc0,
+                               0xa1, 0xb4, 0x97, 0x56, 0x13, 0x76, 0x2c, 0xed, 0x3b, 0x52, 0x82, 0x1e, 0x92, 0x06, 0xfd, 0x03,
+                               0x32, 0x91, 0xf8, 0x1b, 0xd8, 0xa4, 0xbe, 0x8a, 0x51, 0x7c, 0x21, 0x91, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x07, 0xa3, 0x25, 0xd0, 0xd7, 0xd3, 0xb0, 0x99, 0x9f, 0x1f, 0x39,
+                               0x6f, 0x3d, 0x21, 0x43, 0xf6, 0xd6, 0xf2, 0x61, 0xab, 0xf0, 0xb2, 0x6a, 0xf3, 0xbf, 0x8a, 0xfc,
+                               0xe0, 0x24, 0x72, 0xac, 0x5f, 0xaf, 0xc7, 0x9e, 0x5f, 0x86, 0x2d, 0x05, 0xb2, 0xf5, 0x6a, 0xea,
+                               0x4f, 0x74, 0xf6, 0x11, 0x60, 0x6c, 0x3b, 0xf9, 0x84, 0xf4, 0x66, 0x4c, 0x2d, 0x59, 0xff, 0xc7,
+                               0x6f, 0x33, 0x7e, 0x5b, 0x41, 0x7f, 0x32, 0x8e, 0xdc, 0xfc, 0xac, 0x1a, 0xf3, 0x84, 0xaa, 0x2d,
+                               0xbc, 0xa3, 0x1c, 0xcb, 0x98, 0x80, 0xbe, 0xe5, 0x38, 0xf5, 0x51, 0xe9, 0xc2, 0x9b, 0x85, 0x99,
+                               0x84, 0xa4, 0xfb, 0xd6, 0x21, 0xaf, 0x45, 0x80, 0xcb, 0x5d, 0x35, 0x98, 0x80, 0x29, 0xbd, 0xe5,
+                               0xde, 0x74, 0x2c, 0x32, 0x78, 0x82, 0x14, 0x03, 0x7b, 0x92, 0x39, 0x4c, 0x59, 0x4d, 0x36, 0x43,
+                               0xe7, 0x17, 0xd6, 0xc3, 0x18, 0xab, 0xdc, 0xea, 0x70, 0xa0, 0x74, 0x70, 0xa8, 0xbd, 0x46, 0xbe,
+                               0x96, 0x1d, 0x8f, 0xd8, 0x91, 0x7c, 0x4d, 0x1c, 0x66, 0x57, 0x93, 0x63, 0x16, 0xb5, 0x8e, 0xb7,
+                               0x7c, 0xed, 0x90, 0x4f, 0xdd, 0xf9, 0x7d, 0x92, 0x4c, 0xea, 0x3d, 0x5d, 0xc0, 0xeb, 0x04, 0x21,
+                               0x88, 0x87, 0xa8, 0x43, 0x12, 0x8c, 0x31, 0xde, 0x17, 0x09, 0x83, 0x2f, 0x4b, 0xa4, 0x9e, 0x75,
+                               0x88, 0x51, 0x61, 0x50, 0xd8, 0xa5, 0x17, 0x77, 0x0d, 0x53, 0x47, 0x89, 0xbf, 0x04, 0x4b, 0x55,
+                               0x5d, 0x32, 0x34, 0x63, 0x05, 0xfd, 0x68, 0x36, 0xe0, 0xbf, 0x75, 0x5b, 0x58, 0x19, 0x28, 0xb1,
+                               0x76, 0x09, 0xfd, 0xde, 0xc2, 0xa3, 0x5a, 0xcd, 0xd9, 0x76, 0xdc, 0x79, 0x6a, 0xf8, 0xae, 0x33,
+                               0x1a, 0x25, 0x20, 0x14, 0x1b, 0x15, 0xf8, 0x0c, 0x8e, 0xc2, 0x4d, 0x95, 0x2d, 0x54, 0x59, 0x0b,
+                               0x10, 0xf7, 0xbe, 0xb8, 0x75, 0x02, 0x81, 0x81, 0x00, 0xf9, 0x0d, 0xab, 0x91, 0xd7, 0x52, 0xe9,
+                               0xf2, 0xbe, 0xd4, 0x57, 0xa7, 0x16, 0x38, 0x85, 0x5b, 0xbb, 0x0e, 0xa9, 0x3b, 0x39, 0x2a, 0x25,
+                               0xbd, 0x85, 0xca, 0x15, 0x3d, 0x41, 0x54, 0x45, 0x95, 0x5b, 0xc4, 0x49, 0xe2, 0x52, 0xf2, 0x6e,
+                               0x8c, 0xf4, 0xd7, 0xcf, 0xfd, 0x4f, 0x77, 0xc4, 0xd3, 0x21, 0x5a, 0x8c, 0x16, 0xd0, 0x3d, 0xd5,
+                               0xb0, 0xeb, 0xf3, 0x22, 0x6a, 0x23, 0x04, 0x70, 0xf5, 0xde, 0x94, 0xc2, 0x76, 0x01, 0xea, 0x97,
+                               0x40, 0xf8, 0x48, 0x7b, 0x59, 0xea, 0x3a, 0xf5, 0x37, 0x6f, 0x6f, 0xf2, 0x13, 0xf9, 0x7c, 0x3c,
+                               0x50, 0x7c, 0x49, 0x20, 0x0e, 0x78, 0xd9, 0x98, 0x60, 0x87, 0xc5, 0xcf, 0x36, 0x58, 0x98, 0x19,
+                               0xc6, 0xff, 0xfe, 0x53, 0xe9, 0x61, 0x55, 0x13, 0x6b, 0xe3, 0x01, 0x59, 0xeb, 0xf0, 0xe8, 0x13,
+                               0x50, 0x0c, 0x00, 0x84, 0x8b, 0xf7, 0xa0, 0x6e, 0xd5, 0x02, 0x81, 0x81, 0x00, 0xe6, 0xd7, 0x35,
+                               0x57, 0xcd, 0xac, 0x1b, 0x50, 0x8a, 0x1f, 0xc5, 0x3a, 0x69, 0x64, 0xd9, 0xa2, 0x2b, 0x96, 0xb4,
+                               0xc1, 0xb7, 0xfc, 0x81, 0x37, 0x6e, 0x5d, 0x9b, 0xd5, 0x57, 0x30, 0x03, 0xb9, 0xc6, 0xac, 0x5a,
+                               0xff, 0x64, 0x0d, 0xd7, 0x82, 0xdf, 0x66, 0x9f, 0x96, 0xee, 0x24, 0x9e, 0xf5, 0xe2, 0x2f, 0xb5,
+                               0x29, 0x07, 0xfe, 0xb6, 0xc5, 0xe9, 0x6f, 0x32, 0x50, 0x98, 0xcc, 0xdd, 0x58, 0xe2, 0xd1, 0xe0,
+                               0x77, 0xf8, 0x7a, 0xcf, 0xa6, 0x68, 0xb4, 0x2e, 0x68, 0x17, 0xb8, 0x31, 0xab, 0x71, 0x1c, 0xa3,
+                               0x6c, 0x95, 0x2f, 0x47, 0x4a, 0x1b, 0x07, 0x93, 0x41, 0x44, 0xd6, 0x51, 0x77, 0x07, 0x8d, 0x84,
+                               0x50, 0x15, 0x5f, 0x6c, 0x5d, 0x4c, 0xef, 0xee, 0xcc, 0xa2, 0x48, 0xde, 0xdf, 0x03, 0x16, 0x00,
+                               0x7f, 0x05, 0xaa, 0x87, 0x1b, 0x65, 0x73, 0xbc, 0x55, 0x26, 0xc0, 0x5d, 0xcd, 0x02, 0x81, 0x80,
+                               0x13, 0xfc, 0x37, 0xd7, 0x55, 0x2b, 0x0f, 0x20, 0xee, 0x95, 0x45, 0x8f, 0x5f, 0xe6, 0x1b, 0x35,
+                               0x78, 0x36, 0x3d, 0xf8, 0x45, 0xa7, 0x0f, 0x2e, 0x3d, 0x2d, 0x31, 0x35, 0x20, 0x27, 0xee, 0x09,
+                               0x85, 0x3a, 0xa4, 0x1f, 0x28, 0x3e, 0xd2, 0x06, 0x37, 0xa3, 0x95, 0xca, 0x22, 0xf5, 0x5e, 0x72,
+                               0xfe, 0xcb, 0x30, 0x50, 0xa8, 0x57, 0x3d, 0xed, 0x9b, 0x91, 0x80, 0x22, 0x7a, 0x3a, 0xe2, 0x01,
+                               0xa8, 0xe1, 0xd1, 0x14, 0xfd, 0x24, 0x61, 0x0c, 0xd2, 0x9f, 0xa6, 0x5e, 0x59, 0xc0, 0x6f, 0x6b,
+                               0x0a, 0x63, 0x36, 0x4e, 0xca, 0x07, 0x61, 0x23, 0xa8, 0x45, 0x89, 0xef, 0xff, 0x5c, 0x9e, 0xb0,
+                               0xa8, 0x54, 0x84, 0x43, 0x3d, 0x2a, 0xbf, 0x6e, 0xcb, 0x9e, 0x12, 0x07, 0xb9, 0x4a, 0xc5, 0x6b,
+                               0x33, 0xe5, 0x28, 0xdd, 0x19, 0x10, 0xd6, 0x73, 0xe6, 0xf2, 0xa7, 0xb0, 0x3f, 0xa7, 0xbd, 0x1d,
+                               0x02, 0x81, 0x80, 0x28, 0x3d, 0x1b, 0x6f, 0x52, 0xca, 0xcd, 0x78, 0x1c, 0x9b, 0xad, 0x25, 0xa1,
+                               0x79, 0x92, 0xf6, 0x51, 0xe2, 0xd3, 0x5f, 0x71, 0x52, 0xf2, 0xb3, 0x56, 0xe9, 0xba, 0x60, 0x55,
+                               0xf2, 0x68, 0xb3, 0xd9, 0x8d, 0xf1, 0xce, 0xd3, 0x02, 0x16, 0xaf, 0x19, 0x82, 0x2a, 0x0f, 0x85,
+                               0x33, 0x9d, 0x01, 0xff, 0x8b, 0x91, 0x68, 0xb5, 0x9c, 0x11, 0x00, 0x2e, 0xd0, 0xd3, 0x54, 0x79,
+                               0x4e, 0x3c, 0xc7, 0x61, 0xc2, 0x73, 0xfe, 0x75, 0xb1, 0xf2, 0xc0, 0x6f, 0x3f, 0xf9, 0x56, 0xeb,
+                               0xcb, 0x8f, 0xdc, 0xf2, 0xba, 0xab, 0x42, 0x22, 0x3f, 0x3b, 0x8e, 0x0f, 0x68, 0x22, 0xa3, 0x1d,
+                               0xfc, 0xbd, 0xf6, 0xa1, 0xba, 0x69, 0x76, 0x0c, 0x43, 0xb6, 0x00, 0x0d, 0x89, 0x45, 0x77, 0x26,
+                               0x52, 0xcd, 0x86, 0xd2, 0x62, 0xe3, 0x73, 0x40, 0xe9, 0x98, 0xe3, 0x8d, 0xab, 0xbf, 0x08, 0x98,
+                               0xd8, 0xaf, 0x81, 0x02, 0x81, 0x80, 0x35, 0xc4, 0xeb, 0x22, 0x1c, 0xa8, 0x64, 0xd2, 0x8e, 0x3f,
+                               0x43, 0x71, 0xfb, 0xbe, 0xa6, 0xc4, 0xb4, 0x35, 0xcb, 0x12, 0x8d, 0x92, 0xba, 0x59, 0x10, 0x9c,
+                               0x0a, 0x49, 0xb3, 0x02, 0x25, 0xe5, 0x0c, 0x9d, 0x8b, 0xe6, 0x88, 0x54, 0x4a, 0x63, 0xe4, 0xd3,
+                               0x15, 0x7e, 0x43, 0xe9, 0x0c, 0xf0, 0xaf, 0x48, 0xfe, 0x04, 0xb7, 0x95, 0x9c, 0xc3, 0xc4, 0x90,
+                               0x9f, 0x56, 0xff, 0xbc, 0x49, 0x25, 0x4f, 0xb9, 0x43, 0xa8, 0xbe, 0x94, 0x8d, 0xb7, 0xeb, 0x5e,
+                               0x84, 0xf9, 0x6c, 0xe8, 0x50, 0xa0, 0xd5, 0x85, 0x50, 0xeb, 0x46, 0x0c, 0x8d, 0xf9, 0xc9, 0x82,
+                               0x1d, 0x6a, 0x3e, 0x5c, 0x1c, 0xfe, 0xc6, 0x7b, 0xee, 0x6e, 0xb2, 0x26, 0x3d, 0x11, 0x3a, 0x16,
+                               0xd0, 0x8f, 0x7b, 0x14, 0x94, 0xd2, 0x3a, 0x7b, 0xbd, 0xd5, 0x3a, 0x33, 0xe8, 0x12, 0xff, 0x97,
+                               0x27, 0xf1, 0xca, 0xe1, 0xf3, 0x90
+                       }
+               }
+       }
+};
+
+_DNSKeySetsCompileTimeChecks( RSASHA1 );
+
+//===========================================================================================================================
+// MARK: - RSA/SHA-256 DNS Keys
+
+typedef struct
+{
+       DNSKeyRSASHA256Info             ksk;    // Key-Signing Key
+       DNSKeyRSASHA256Info             zsk;    // Zone-Signing Key
+       
+}      DNSKeyRSASHA256Set;
+
+static const DNSKeyRSASHA256Set                kDNSKeyRSASHA256Sets[] =
+{
+       // DNSSEC Zone 0
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_RSASHA256 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xdd, 0x64, 0xb2, 0x1e, 0x99, 0xac, 0x71, 0xca, 0x9a, 0x99, 0x14, 0xd5,
+                               0xb8, 0x5e, 0xd3, 0xa6, 0x7f, 0x1f, 0xe6, 0x7f, 0x07, 0xf8, 0x6f, 0xe3, 0x1c, 0xf3, 0x63, 0x5e,
+                               0x06, 0x4a, 0x0e, 0x5b, 0xca, 0xcd, 0xe8, 0xfa, 0xd3, 0x67, 0x42, 0x52, 0x94, 0x54, 0x30, 0x3e,
+                               0x54, 0x55, 0x4d, 0x91, 0xea, 0x0a, 0xbf, 0x5d, 0xfa, 0x7e, 0xd9, 0x27, 0x67, 0xdd, 0x8f, 0x01,
+                               0x44, 0x53, 0x95, 0x3f, 0xe4, 0x62, 0xd0, 0xfe, 0x18, 0x6d, 0xf2, 0x2b, 0xfc, 0x4e, 0x09, 0x28,
+                               0xa0, 0x52, 0xc9, 0xae, 0x3a, 0x4a, 0xc6, 0xc8, 0x08, 0xad, 0x1d, 0xa7, 0x6d, 0x9b, 0xfe, 0xc6,
+                               0x9c, 0xcf, 0x46, 0x8d, 0x0e, 0x20, 0xed, 0xad, 0x40, 0x60, 0x0d, 0x91, 0x0b, 0x48, 0x82, 0x30,
+                               0xa8, 0xcf, 0x92, 0x4e, 0x8b, 0xda, 0x6a, 0x2d, 0xe4, 0x4e, 0x1c, 0x74, 0x44, 0x33, 0x84, 0xc3,
+                               0xa9, 0xb4, 0xea, 0x71, 0x61, 0x6b, 0x4b, 0x0b, 0x0f, 0xcf, 0x58, 0x38, 0xfc, 0x10, 0xd1, 0x37,
+                               0x88, 0x54, 0x99, 0xd4, 0xc2, 0xe9, 0xc9, 0xba, 0x72, 0x23, 0xe0, 0xd4, 0x85, 0xfd, 0x67, 0x75,
+                               0xca, 0x41, 0xe8, 0x69, 0x00, 0x4b, 0x86, 0xd9, 0x74, 0xcf, 0x3f, 0xce, 0x66, 0x13, 0x3a, 0xc0,
+                               0xc5, 0x20, 0x51, 0xd7, 0x9c, 0xbc, 0xbf, 0xc7, 0x56, 0x32, 0x18, 0x4a, 0x98, 0x8b, 0x34, 0x0b,
+                               0x2f, 0x5c, 0x2d, 0xd9, 0x7a, 0x5b, 0xa6, 0xff, 0x59, 0xf2, 0x89, 0xd2, 0x8d, 0xef, 0x0a, 0x35,
+                               0xbf, 0x8f, 0x70, 0xfa, 0x36, 0xac, 0xd2, 0x6e, 0x71, 0xc6, 0x8d, 0x4a, 0xc5, 0x39, 0x4b, 0xb0,
+                               0x5e, 0xad, 0x32, 0x0d, 0x5b, 0x3f, 0x1a, 0x45, 0x0c, 0x43, 0x49, 0x99, 0x89, 0xd0, 0xc1, 0x1c,
+                               0xf9, 0x5f, 0xc2, 0x07, 0xe6, 0x62, 0xf1, 0xad, 0xc2, 0xb2, 0x4d, 0xe7, 0xd2, 0x4e, 0xf0, 0x94,
+                               0xe0, 0x4a, 0xc8, 0x91
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xdd, 0x64, 0xb2, 0x1e,
+                               0x99, 0xac, 0x71, 0xca, 0x9a, 0x99, 0x14, 0xd5, 0xb8, 0x5e, 0xd3, 0xa6, 0x7f, 0x1f, 0xe6, 0x7f,
+                               0x07, 0xf8, 0x6f, 0xe3, 0x1c, 0xf3, 0x63, 0x5e, 0x06, 0x4a, 0x0e, 0x5b, 0xca, 0xcd, 0xe8, 0xfa,
+                               0xd3, 0x67, 0x42, 0x52, 0x94, 0x54, 0x30, 0x3e, 0x54, 0x55, 0x4d, 0x91, 0xea, 0x0a, 0xbf, 0x5d,
+                               0xfa, 0x7e, 0xd9, 0x27, 0x67, 0xdd, 0x8f, 0x01, 0x44, 0x53, 0x95, 0x3f, 0xe4, 0x62, 0xd0, 0xfe,
+                               0x18, 0x6d, 0xf2, 0x2b, 0xfc, 0x4e, 0x09, 0x28, 0xa0, 0x52, 0xc9, 0xae, 0x3a, 0x4a, 0xc6, 0xc8,
+                               0x08, 0xad, 0x1d, 0xa7, 0x6d, 0x9b, 0xfe, 0xc6, 0x9c, 0xcf, 0x46, 0x8d, 0x0e, 0x20, 0xed, 0xad,
+                               0x40, 0x60, 0x0d, 0x91, 0x0b, 0x48, 0x82, 0x30, 0xa8, 0xcf, 0x92, 0x4e, 0x8b, 0xda, 0x6a, 0x2d,
+                               0xe4, 0x4e, 0x1c, 0x74, 0x44, 0x33, 0x84, 0xc3, 0xa9, 0xb4, 0xea, 0x71, 0x61, 0x6b, 0x4b, 0x0b,
+                               0x0f, 0xcf, 0x58, 0x38, 0xfc, 0x10, 0xd1, 0x37, 0x88, 0x54, 0x99, 0xd4, 0xc2, 0xe9, 0xc9, 0xba,
+                               0x72, 0x23, 0xe0, 0xd4, 0x85, 0xfd, 0x67, 0x75, 0xca, 0x41, 0xe8, 0x69, 0x00, 0x4b, 0x86, 0xd9,
+                               0x74, 0xcf, 0x3f, 0xce, 0x66, 0x13, 0x3a, 0xc0, 0xc5, 0x20, 0x51, 0xd7, 0x9c, 0xbc, 0xbf, 0xc7,
+                               0x56, 0x32, 0x18, 0x4a, 0x98, 0x8b, 0x34, 0x0b, 0x2f, 0x5c, 0x2d, 0xd9, 0x7a, 0x5b, 0xa6, 0xff,
+                               0x59, 0xf2, 0x89, 0xd2, 0x8d, 0xef, 0x0a, 0x35, 0xbf, 0x8f, 0x70, 0xfa, 0x36, 0xac, 0xd2, 0x6e,
+                               0x71, 0xc6, 0x8d, 0x4a, 0xc5, 0x39, 0x4b, 0xb0, 0x5e, 0xad, 0x32, 0x0d, 0x5b, 0x3f, 0x1a, 0x45,
+                               0x0c, 0x43, 0x49, 0x99, 0x89, 0xd0, 0xc1, 0x1c, 0xf9, 0x5f, 0xc2, 0x07, 0xe6, 0x62, 0xf1, 0xad,
+                               0xc2, 0xb2, 0x4d, 0xe7, 0xd2, 0x4e, 0xf0, 0x94, 0xe0, 0x4a, 0xc8, 0x91, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x02, 0xeb, 0x68, 0x46, 0xeb, 0x87, 0x60, 0xe3, 0xc7, 0x58, 0xa3,
+                               0xf3, 0xf2, 0x8f, 0xbb, 0x92, 0x4b, 0x44, 0x60, 0x2d, 0x86, 0x2d, 0x22, 0x4a, 0x98, 0x2f, 0xe4,
+                               0x7c, 0x51, 0xb0, 0x69, 0xd2, 0x8f, 0x90, 0xda, 0x14, 0xee, 0xb3, 0x42, 0xa6, 0x64, 0xa2, 0xdc,
+                               0x95, 0x01, 0xd7, 0x33, 0x23, 0xec, 0xb6, 0x72, 0x12, 0x1a, 0xf2, 0xcf, 0xc9, 0xcb, 0x7e, 0x9a,
+                               0x3b, 0x6d, 0xcb, 0x7d, 0x61, 0x1f, 0x91, 0xa7, 0x08, 0x66, 0xc9, 0x92, 0x63, 0x3e, 0x9b, 0x47,
+                               0xd7, 0x23, 0x90, 0x1b, 0xed, 0x42, 0x83, 0x08, 0x6e, 0x8c, 0x60, 0xbe, 0x0e, 0x9e, 0x6d, 0x2b,
+                               0x75, 0xb2, 0x8d, 0x30, 0x34, 0xbd, 0x7e, 0x35, 0x2c, 0x2e, 0xf8, 0x65, 0x4d, 0x67, 0x9c, 0xef,
+                               0xec, 0x94, 0xd9, 0x51, 0xe8, 0x5b, 0xc3, 0x48, 0x59, 0xad, 0x14, 0x53, 0x9f, 0x3b, 0xe4, 0x03,
+                               0x08, 0xf2, 0x84, 0xba, 0x7f, 0xb8, 0x7a, 0xc7, 0x7e, 0xa0, 0x81, 0x53, 0xe8, 0x6a, 0x91, 0x27,
+                               0x0f, 0x36, 0x11, 0x22, 0x45, 0x85, 0xc6, 0x79, 0x35, 0xae, 0x0b, 0x52, 0x86, 0x56, 0xd3, 0xe3,
+                               0x6d, 0x16, 0xa5, 0xb3, 0xf7, 0x90, 0xd7, 0x60, 0x99, 0x79, 0x9d, 0x94, 0xcd, 0x89, 0xe2, 0xb7,
+                               0x2c, 0xfc, 0xfa, 0x0d, 0x38, 0x8d, 0x40, 0x0d, 0x78, 0x5c, 0xb4, 0x90, 0x4a, 0x22, 0xc0, 0x5f,
+                               0xd7, 0xe2, 0xb3, 0x09, 0xd8, 0x49, 0x12, 0x78, 0x19, 0x0a, 0xd9, 0x1c, 0x65, 0xb6, 0xd1, 0x7a,
+                               0x96, 0xf4, 0x61, 0x59, 0x48, 0x6e, 0xbf, 0x7a, 0x81, 0xb6, 0x46, 0x17, 0x56, 0x0d, 0x6f, 0x79,
+                               0xd1, 0x6e, 0x3d, 0xb6, 0x2c, 0x49, 0x92, 0xaa, 0x61, 0x33, 0x7b, 0x88, 0x1b, 0x7d, 0x16, 0x89,
+                               0xef, 0x6f, 0xab, 0xc6, 0x73, 0x7f, 0x08, 0x5b, 0xe6, 0x38, 0xe3, 0x50, 0xd1, 0xf5, 0x8b, 0x64,
+                               0x44, 0xd7, 0xae, 0xe6, 0x7d, 0x02, 0x81, 0x81, 0x00, 0xfd, 0x96, 0x66, 0x13, 0x8b, 0x27, 0x88,
+                               0x67, 0x0c, 0x96, 0x49, 0x3b, 0x12, 0x3e, 0x5d, 0xe7, 0x6a, 0xc0, 0x04, 0xdd, 0x96, 0xca, 0x15,
+                               0x29, 0xf0, 0xee, 0x43, 0x35, 0xf2, 0xc9, 0xe3, 0x29, 0x2a, 0xea, 0xc2, 0x5e, 0xa2, 0xa0, 0x9b,
+                               0xfa, 0x27, 0x99, 0xa0, 0xa6, 0x68, 0x4c, 0x5a, 0x48, 0x0e, 0x4e, 0xd5, 0xc8, 0xfa, 0xc9, 0x44,
+                               0x9b, 0x00, 0x6d, 0xaa, 0x1f, 0xc8, 0x7a, 0xc6, 0x80, 0xba, 0xa8, 0xf3, 0x56, 0x5a, 0x17, 0x75,
+                               0x23, 0x5d, 0xec, 0xca, 0x97, 0xaf, 0x2b, 0x1b, 0x14, 0x1a, 0x9c, 0xb6, 0x0f, 0x74, 0x22, 0xf8,
+                               0xfe, 0x27, 0xe2, 0x59, 0x9a, 0x0b, 0x47, 0x32, 0x72, 0x82, 0x58, 0x03, 0xdd, 0xe5, 0xa5, 0x55,
+                               0xac, 0xfb, 0x02, 0x3a, 0xf9, 0x56, 0x7d, 0xb8, 0x5d, 0xc6, 0x99, 0x6b, 0x03, 0x99, 0x3b, 0xac,
+                               0xc9, 0x6a, 0x90, 0xac, 0x4f, 0x92, 0xe9, 0xfe, 0xed, 0x02, 0x81, 0x81, 0x00, 0xdf, 0x7f, 0xe3,
+                               0xbc, 0x59, 0xa4, 0xf3, 0x12, 0x25, 0x84, 0x44, 0xc9, 0x66, 0xcd, 0xc8, 0xcd, 0x8f, 0xda, 0x17,
+                               0xc1, 0x9a, 0xb3, 0xbf, 0xc5, 0x23, 0x06, 0x54, 0x05, 0x2b, 0x94, 0x92, 0x2a, 0x7d, 0x32, 0xb9,
+                               0x6a, 0x64, 0x45, 0x78, 0xea, 0xc6, 0x36, 0x58, 0x0f, 0xcd, 0x4d, 0x5a, 0xd5, 0xb1, 0x59, 0xed,
+                               0xb8, 0xb2, 0x12, 0xb4, 0x86, 0x55, 0xaf, 0xe9, 0xaa, 0xaa, 0xcf, 0x2c, 0x73, 0xf3, 0x62, 0xac,
+                               0xb2, 0x1f, 0xba, 0x20, 0xf4, 0x6f, 0xdb, 0xea, 0x9f, 0xec, 0x9b, 0xd3, 0xa1, 0x87, 0x18, 0x67,
+                               0x41, 0x82, 0x40, 0x63, 0xe6, 0x7d, 0x57, 0x2a, 0xb0, 0xee, 0x20, 0x57, 0x22, 0xa9, 0x3a, 0x0a,
+                               0x5f, 0x31, 0xa7, 0xec, 0x5d, 0x5b, 0x2f, 0x14, 0xeb, 0x8c, 0x03, 0x1a, 0x64, 0xe3, 0xc5, 0xfd,
+                               0xdd, 0x8b, 0x20, 0xaa, 0xaa, 0x08, 0xda, 0x17, 0x79, 0x3a, 0x33, 0x57, 0xb5, 0x02, 0x81, 0x80,
+                               0x12, 0x04, 0x0d, 0x5c, 0x76, 0x16, 0x68, 0xea, 0x69, 0x4a, 0x84, 0x09, 0x5e, 0x52, 0x6f, 0xf9,
+                               0x70, 0xec, 0x13, 0x6c, 0x6a, 0xba, 0x10, 0xa6, 0xda, 0x27, 0x13, 0x3f, 0x51, 0xf0, 0x65, 0xe3,
+                               0x16, 0xd9, 0x76, 0xd5, 0xa2, 0x58, 0x26, 0xbc, 0xae, 0xf3, 0x8b, 0x26, 0x47, 0x62, 0xa5, 0x47,
+                               0x59, 0x3c, 0xe0, 0x93, 0x56, 0xbd, 0xd5, 0xd1, 0xed, 0x45, 0xdd, 0x40, 0x44, 0xcd, 0xf4, 0x2c,
+                               0x51, 0x16, 0x8f, 0xb3, 0x22, 0xd3, 0x67, 0xcf, 0x0b, 0x6d, 0x37, 0x37, 0x6f, 0x8a, 0x70, 0x72,
+                               0x0d, 0x31, 0xf4, 0xfd, 0x44, 0x12, 0xf7, 0xfd, 0x96, 0x77, 0xce, 0x45, 0xd3, 0x67, 0x4b, 0x7e,
+                               0x37, 0x24, 0x69, 0xa1, 0xea, 0x1e, 0xc4, 0xe7, 0x75, 0x2d, 0xc3, 0x62, 0xd3, 0x72, 0x3b, 0x16,
+                               0xee, 0x75, 0x17, 0xd5, 0x39, 0x9d, 0xb3, 0xb8, 0xdb, 0x89, 0x4b, 0xb4, 0x9b, 0x8b, 0x64, 0x41,
+                               0x02, 0x81, 0x80, 0x4a, 0x82, 0xdf, 0x90, 0xd6, 0xae, 0x18, 0x5f, 0x6f, 0x64, 0x86, 0x6e, 0x42,
+                               0xb8, 0xce, 0x7e, 0x41, 0xbc, 0x2a, 0xf9, 0x5f, 0xb3, 0x17, 0x77, 0x08, 0xb3, 0x3e, 0x65, 0xae,
+                               0xde, 0xcc, 0x50, 0x20, 0x07, 0x3e, 0x2b, 0x8b, 0x1c, 0x62, 0x30, 0x9b, 0x3e, 0x58, 0xe3, 0x83,
+                               0xc5, 0x8b, 0x47, 0xfc, 0xe5, 0x87, 0x4c, 0x71, 0x09, 0xad, 0x67, 0xaf, 0xbf, 0x82, 0xfe, 0x64,
+                               0x0d, 0xc7, 0xbb, 0x2c, 0x64, 0x34, 0x40, 0xfa, 0x34, 0xda, 0x71, 0xc3, 0xad, 0x24, 0xae, 0x86,
+                               0x7a, 0x78, 0xf6, 0xbc, 0x0c, 0x55, 0xb8, 0x50, 0x4d, 0x59, 0x40, 0xe9, 0x7e, 0x9a, 0xfb, 0xfb,
+                               0x97, 0x8a, 0x95, 0x42, 0xa7, 0xd7, 0xf7, 0x5e, 0x9d, 0xa6, 0x3e, 0x85, 0x7c, 0xcf, 0xd3, 0xab,
+                               0x8d, 0x09, 0x4a, 0xa3, 0x69, 0x49, 0xb6, 0x87, 0x80, 0xaf, 0x85, 0x1a, 0x88, 0x63, 0x15, 0x06,
+                               0x63, 0x41, 0x95, 0x02, 0x81, 0x80, 0x58, 0x6e, 0x61, 0xdf, 0x0e, 0x2f, 0x22, 0x91, 0xa4, 0xec,
+                               0x85, 0x6c, 0xec, 0xcc, 0x96, 0xb5, 0xf3, 0xd6, 0x4e, 0xd5, 0x3c, 0x61, 0xb1, 0x37, 0x50, 0x48,
+                               0x9c, 0xff, 0x80, 0x6d, 0x1e, 0x6e, 0xa0, 0xe2, 0x4b, 0xeb, 0x39, 0x05, 0xc0, 0xf3, 0xa8, 0xfc,
+                               0x3b, 0x16, 0x9d, 0xd3, 0x97, 0xa1, 0xb3, 0xdb, 0x73, 0xc5, 0x2a, 0xf8, 0x39, 0x60, 0x49, 0xf7,
+                               0x50, 0x4b, 0x08, 0x6c, 0xc4, 0x24, 0xb0, 0x1b, 0x9f, 0x55, 0x50, 0xc8, 0xb2, 0x62, 0x3e, 0xe2,
+                               0x11, 0x7f, 0x9f, 0xfa, 0x9a, 0x46, 0x41, 0xa1, 0x9c, 0x13, 0xbf, 0x68, 0x7d, 0x40, 0x40, 0x37,
+                               0xbc, 0x44, 0x3c, 0x8f, 0x46, 0x2e, 0x96, 0xb0, 0x0e, 0xbd, 0x66, 0x0e, 0x7d, 0xe9, 0x35, 0x8c,
+                               0x7a, 0xdd, 0xb3, 0x13, 0xd1, 0xd1, 0xff, 0x60, 0x57, 0x94, 0x30, 0xd8, 0x8a, 0x48, 0x24, 0x7d,
+                               0xeb, 0xae, 0xb0, 0xde, 0x53, 0x8a
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_RSASHA256 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xc6, 0x96, 0xd5, 0x2a, 0xef, 0x43, 0x79, 0xc1, 0x8c, 0x5c, 0x75, 0xb7,
+                               0xe5, 0x2a, 0x92, 0x63, 0x28, 0x16, 0x89, 0x1d, 0xb5, 0x71, 0xf5, 0xe7, 0xa8, 0x32, 0xc3, 0x93,
+                               0xb3, 0x93, 0xfc, 0xd4, 0x38, 0x5a, 0xba, 0x11, 0xea, 0x18, 0xc6, 0xe0, 0x01, 0xfb, 0xc6, 0x9b,
+                               0x0e, 0xf1, 0xf7, 0x9e, 0xb6, 0x1a, 0xf3, 0x60, 0xfb, 0x92, 0x73, 0x80, 0xa3, 0xc8, 0xf6, 0x2a,
+                               0x6e, 0x0e, 0x08, 0x85, 0xe6, 0x58, 0x35, 0xaa, 0x49, 0x71, 0x5e, 0xca, 0x77, 0xac, 0x95, 0x1e,
+                               0xbf, 0xd6, 0x0a, 0x1d, 0x1c, 0x6e, 0x12, 0x77, 0xb9, 0x1c, 0xf3, 0xe4, 0xa8, 0xff, 0x06, 0xd9,
+                               0x53, 0x30, 0x41, 0x89, 0x17, 0x8f, 0x25, 0x3a, 0x2d, 0x5a, 0xfc, 0x47, 0x0b, 0x4b, 0xcd, 0xb8,
+                               0x3c, 0x18, 0x0d, 0x27, 0x15, 0x6e, 0x21, 0xae, 0x3f, 0x50, 0x1b, 0x7f, 0x32, 0x0a, 0x08, 0x8d,
+                               0xf2, 0x71, 0xc3, 0xa9, 0xf2, 0x57, 0xb7, 0xf3, 0xdb, 0x12, 0xec, 0xee, 0x85, 0xa7, 0xa9, 0x7f,
+                               0x35, 0x0a, 0xb6, 0xaf, 0xe4, 0xb5, 0x91, 0xe9, 0x77, 0x87, 0xb7, 0x87, 0x05, 0xed, 0x10, 0xbb,
+                               0xe8, 0x5c, 0xb7, 0x1c, 0x3b, 0x11, 0xda, 0x7d, 0xbe, 0xa5, 0xa0, 0x0f, 0x6c, 0xcd, 0x9c, 0x14,
+                               0xac, 0x44, 0xe2, 0xc0, 0x3f, 0x40, 0x10, 0x3e, 0x99, 0xd1, 0x4e, 0xd6, 0x6d, 0x11, 0x73, 0x4a,
+                               0xdd, 0xa1, 0xf5, 0x42, 0xcf, 0x2e, 0x6c, 0x37, 0x4a, 0xe3, 0x81, 0x7c, 0xdf, 0x3e, 0x4e, 0x10,
+                               0x5f, 0x66, 0x64, 0x66, 0x23, 0x5d, 0x8d, 0x16, 0xcb, 0x71, 0x80, 0x4d, 0xb7, 0x73, 0xb9, 0x46,
+                               0xcc, 0x40, 0x5e, 0x3d, 0x1e, 0x4c, 0xae, 0x72, 0xd1, 0x5e, 0xd6, 0x3a, 0xcb, 0x40, 0x8a, 0x15,
+                               0x83, 0x57, 0x20, 0x3d, 0x4a, 0xa0, 0x02, 0x2d, 0x87, 0x2e, 0xec, 0x40, 0x16, 0xff, 0xba, 0x03,
+                               0x62, 0x1d, 0xa7, 0xf5
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc6, 0x96, 0xd5, 0x2a,
+                               0xef, 0x43, 0x79, 0xc1, 0x8c, 0x5c, 0x75, 0xb7, 0xe5, 0x2a, 0x92, 0x63, 0x28, 0x16, 0x89, 0x1d,
+                               0xb5, 0x71, 0xf5, 0xe7, 0xa8, 0x32, 0xc3, 0x93, 0xb3, 0x93, 0xfc, 0xd4, 0x38, 0x5a, 0xba, 0x11,
+                               0xea, 0x18, 0xc6, 0xe0, 0x01, 0xfb, 0xc6, 0x9b, 0x0e, 0xf1, 0xf7, 0x9e, 0xb6, 0x1a, 0xf3, 0x60,
+                               0xfb, 0x92, 0x73, 0x80, 0xa3, 0xc8, 0xf6, 0x2a, 0x6e, 0x0e, 0x08, 0x85, 0xe6, 0x58, 0x35, 0xaa,
+                               0x49, 0x71, 0x5e, 0xca, 0x77, 0xac, 0x95, 0x1e, 0xbf, 0xd6, 0x0a, 0x1d, 0x1c, 0x6e, 0x12, 0x77,
+                               0xb9, 0x1c, 0xf3, 0xe4, 0xa8, 0xff, 0x06, 0xd9, 0x53, 0x30, 0x41, 0x89, 0x17, 0x8f, 0x25, 0x3a,
+                               0x2d, 0x5a, 0xfc, 0x47, 0x0b, 0x4b, 0xcd, 0xb8, 0x3c, 0x18, 0x0d, 0x27, 0x15, 0x6e, 0x21, 0xae,
+                               0x3f, 0x50, 0x1b, 0x7f, 0x32, 0x0a, 0x08, 0x8d, 0xf2, 0x71, 0xc3, 0xa9, 0xf2, 0x57, 0xb7, 0xf3,
+                               0xdb, 0x12, 0xec, 0xee, 0x85, 0xa7, 0xa9, 0x7f, 0x35, 0x0a, 0xb6, 0xaf, 0xe4, 0xb5, 0x91, 0xe9,
+                               0x77, 0x87, 0xb7, 0x87, 0x05, 0xed, 0x10, 0xbb, 0xe8, 0x5c, 0xb7, 0x1c, 0x3b, 0x11, 0xda, 0x7d,
+                               0xbe, 0xa5, 0xa0, 0x0f, 0x6c, 0xcd, 0x9c, 0x14, 0xac, 0x44, 0xe2, 0xc0, 0x3f, 0x40, 0x10, 0x3e,
+                               0x99, 0xd1, 0x4e, 0xd6, 0x6d, 0x11, 0x73, 0x4a, 0xdd, 0xa1, 0xf5, 0x42, 0xcf, 0x2e, 0x6c, 0x37,
+                               0x4a, 0xe3, 0x81, 0x7c, 0xdf, 0x3e, 0x4e, 0x10, 0x5f, 0x66, 0x64, 0x66, 0x23, 0x5d, 0x8d, 0x16,
+                               0xcb, 0x71, 0x80, 0x4d, 0xb7, 0x73, 0xb9, 0x46, 0xcc, 0x40, 0x5e, 0x3d, 0x1e, 0x4c, 0xae, 0x72,
+                               0xd1, 0x5e, 0xd6, 0x3a, 0xcb, 0x40, 0x8a, 0x15, 0x83, 0x57, 0x20, 0x3d, 0x4a, 0xa0, 0x02, 0x2d,
+                               0x87, 0x2e, 0xec, 0x40, 0x16, 0xff, 0xba, 0x03, 0x62, 0x1d, 0xa7, 0xf5, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x18, 0x16, 0xa4, 0xad, 0xcc, 0x0e, 0xfd, 0xbe, 0x5d, 0xf5, 0xbb,
+                               0x2b, 0xbe, 0xc6, 0xfe, 0xb1, 0xbb, 0x63, 0x9c, 0x67, 0x37, 0xa2, 0xa6, 0xa6, 0xde, 0x6e, 0xa5,
+                               0xed, 0x8d, 0x1b, 0x56, 0xd0, 0x31, 0x33, 0xc4, 0x1d, 0x37, 0x2d, 0xa1, 0x35, 0xfb, 0xd7, 0x9b,
+                               0xfc, 0x24, 0x00, 0x72, 0xbe, 0x54, 0x2d, 0x89, 0xb7, 0x0a, 0x5c, 0xf5, 0xd8, 0xb5, 0x45, 0x43,
+                               0xa2, 0xda, 0xed, 0xa5, 0xd8, 0x04, 0x1c, 0x50, 0x0a, 0x23, 0x55, 0x76, 0x9f, 0x8d, 0x8b, 0xa8,
+                               0x1c, 0xee, 0x3d, 0x1a, 0xc5, 0xf4, 0x34, 0x5f, 0xd6, 0x18, 0xe4, 0xab, 0xaa, 0xe1, 0x5a, 0xe9,
+                               0xec, 0x43, 0x9c, 0x77, 0x5e, 0x97, 0x36, 0x7c, 0x58, 0x05, 0xb7, 0x63, 0x21, 0x84, 0xfb, 0xc1,
+                               0x6c, 0xe7, 0x11, 0x2d, 0xcb, 0x3f, 0x5c, 0xb8, 0x06, 0x28, 0x54, 0xe1, 0xb8, 0xf8, 0x8b, 0x62,
+                               0x05, 0x41, 0xd2, 0xcd, 0xde, 0x60, 0xf1, 0xde, 0x11, 0x55, 0x27, 0x8c, 0xdb, 0xf4, 0x93, 0x70,
+                               0x2e, 0x6f, 0xbd, 0x75, 0x7e, 0x75, 0xa9, 0x74, 0xba, 0xfc, 0xc2, 0xbd, 0x4c, 0xe5, 0xec, 0xfb,
+                               0x10, 0x58, 0x7b, 0xe0, 0x81, 0xdb, 0x51, 0x87, 0xd6, 0xfe, 0xba, 0x44, 0xd9, 0x53, 0x48, 0x7b,
+                               0x75, 0x6b, 0xc4, 0xdf, 0x83, 0xac, 0x50, 0xc4, 0x3d, 0x64, 0x00, 0x62, 0x14, 0xa5, 0x88, 0x8e,
+                               0x14, 0xfb, 0x96, 0xe4, 0xa1, 0x6b, 0xee, 0xef, 0x7a, 0x69, 0x9f, 0x6f, 0x7c, 0xc9, 0x05, 0x44,
+                               0xf6, 0x99, 0x0d, 0x33, 0x5b, 0x9a, 0x69, 0x72, 0x98, 0x29, 0x72, 0x54, 0x12, 0xdf, 0xa7, 0x65,
+                               0xd6, 0xae, 0xe1, 0xa7, 0xfa, 0x76, 0x2a, 0xc5, 0xf9, 0xd7, 0xc9, 0xf2, 0x01, 0xac, 0xb0, 0xa3,
+                               0x67, 0xe3, 0x8d, 0x24, 0xf3, 0x8b, 0x33, 0x72, 0x2e, 0x2a, 0x33, 0xeb, 0x5f, 0x81, 0x29, 0x48,
+                               0x16, 0xf1, 0x15, 0x19, 0xf3, 0x02, 0x81, 0x81, 0x00, 0xfa, 0x20, 0x52, 0xcd, 0x71, 0x10, 0x76,
+                               0x95, 0x0e, 0x3f, 0xf2, 0x60, 0xe3, 0x86, 0xce, 0x9b, 0xd3, 0x63, 0xc5, 0x06, 0x6c, 0x2e, 0xc7,
+                               0x2c, 0x85, 0x51, 0xb1, 0x50, 0xf6, 0xa5, 0xc8, 0xe6, 0x1c, 0x9e, 0x1b, 0x2d, 0xfe, 0xa6, 0x05,
+                               0xa1, 0x8f, 0x06, 0x9a, 0xc5, 0x39, 0xee, 0x68, 0xad, 0x41, 0x5d, 0x27, 0xd9, 0x06, 0xb8, 0x0a,
+                               0x04, 0x06, 0x00, 0xbc, 0xf3, 0xca, 0xd6, 0xa8, 0xad, 0x4c, 0x41, 0x61, 0x1a, 0x30, 0xa3, 0xe6,
+                               0xb0, 0x4e, 0x95, 0x9d, 0x8d, 0xd1, 0xf0, 0xe2, 0x47, 0x3c, 0x7e, 0xc1, 0x20, 0xd5, 0x84, 0x0c,
+                               0x17, 0x67, 0x73, 0xc0, 0xb4, 0xb4, 0xf2, 0xf9, 0xac, 0x98, 0x8d, 0xca, 0x62, 0x04, 0x8e, 0x0a,
+                               0xa3, 0x80, 0x63, 0xab, 0x4a, 0x57, 0xfb, 0xd6, 0x9b, 0x0c, 0x11, 0xa2, 0xef, 0x8f, 0xaf, 0x5d,
+                               0x60, 0x01, 0x24, 0xac, 0x40, 0xd3, 0x6c, 0x60, 0x7f, 0x02, 0x81, 0x81, 0x00, 0xcb, 0x40, 0xaf,
+                               0x73, 0xe3, 0x56, 0xd9, 0xbe, 0xa6, 0xb7, 0x16, 0x8e, 0x90, 0xc4, 0x10, 0x66, 0x47, 0x11, 0xcc,
+                               0xbe, 0x69, 0xd3, 0x15, 0x7e, 0xde, 0x63, 0x04, 0x6e, 0xd6, 0xd9, 0x90, 0xbf, 0xfc, 0xa0, 0x4f,
+                               0xc2, 0x2e, 0xa3, 0xf3, 0x42, 0x46, 0x61, 0x3c, 0x26, 0x1b, 0x3c, 0x0b, 0x80, 0xb2, 0x9c, 0xf5,
+                               0xe2, 0x88, 0x1b, 0xcb, 0x4d, 0x45, 0x42, 0xed, 0xf8, 0x57, 0x89, 0x3a, 0x1f, 0x14, 0xf0, 0xb3,
+                               0x8a, 0xaf, 0xe2, 0xca, 0x90, 0xc7, 0x88, 0x15, 0x8d, 0x52, 0xf7, 0xde, 0xbe, 0x61, 0x41, 0xda,
+                               0x93, 0xda, 0x1e, 0xce, 0x09, 0x65, 0xcf, 0x12, 0x0e, 0x94, 0xf2, 0xd7, 0x7a, 0x69, 0x03, 0x45,
+                               0x1b, 0x41, 0xf2, 0x1d, 0xbc, 0xd9, 0xea, 0x70, 0xdd, 0xe9, 0xbd, 0xc8, 0x02, 0x6e, 0x3f, 0xe1,
+                               0x5f, 0x38, 0x6e, 0xf8, 0x91, 0xa7, 0x36, 0x4b, 0x2e, 0x39, 0x39, 0x3d, 0x8b, 0x02, 0x81, 0x80,
+                               0x6b, 0xef, 0xb0, 0xeb, 0xb8, 0xc3, 0xca, 0xf8, 0x4d, 0x9a, 0xe8, 0xc4, 0x48, 0xcb, 0x2e, 0xb3,
+                               0x6d, 0xc8, 0x5b, 0x08, 0x87, 0x7c, 0xb4, 0x34, 0x91, 0x1f, 0x8c, 0xae, 0x0c, 0x91, 0xc4, 0x1d,
+                               0x10, 0xf2, 0x65, 0x76, 0x36, 0xb2, 0x7c, 0x31, 0x98, 0x9f, 0xd8, 0x00, 0x0e, 0x1c, 0xc4, 0x8d,
+                               0x27, 0x6c, 0xc4, 0xba, 0x51, 0xbc, 0xef, 0x8d, 0x86, 0xed, 0xa0, 0x9c, 0x6f, 0xc6, 0xac, 0x29,
+                               0x84, 0x83, 0x9e, 0x1c, 0x88, 0x2a, 0xe2, 0x6c, 0xe8, 0xac, 0x85, 0xbd, 0xe2, 0x7f, 0xd9, 0x95,
+                               0xb6, 0x14, 0x58, 0xd1, 0x0b, 0x72, 0x9d, 0x29, 0x28, 0x5a, 0x7a, 0x29, 0x41, 0x00, 0xad, 0x1f,
+                               0x7f, 0x01, 0x88, 0xf9, 0x3d, 0x4f, 0xf2, 0x65, 0x56, 0x0d, 0x2c, 0xa8, 0xbb, 0x49, 0x10, 0xf5,
+                               0xfd, 0x82, 0x4a, 0xd6, 0xa8, 0x21, 0x9b, 0xe0, 0xc6, 0xfd, 0x85, 0x5b, 0xc0, 0x20, 0xbf, 0x8f,
+                               0x02, 0x81, 0x80, 0x4c, 0x64, 0x6b, 0x8d, 0x6b, 0x0c, 0xe1, 0x9a, 0x3e, 0x9e, 0xe1, 0xe3, 0x83,
+                               0x95, 0xad, 0x74, 0x43, 0x91, 0xf5, 0x1a, 0x3c, 0x3c, 0x12, 0x00, 0x35, 0x25, 0x92, 0x2c, 0xf6,
+                               0xa3, 0x9f, 0x3c, 0x09, 0x7f, 0x57, 0x7a, 0xec, 0x7c, 0xc2, 0x99, 0x4e, 0x4a, 0x8d, 0x88, 0xe5,
+                               0x03, 0x94, 0x6f, 0x32, 0xb2, 0xbb, 0xd8, 0x00, 0xc0, 0x31, 0x91, 0x41, 0x4e, 0xfe, 0x92, 0x30,
+                               0x94, 0x6b, 0x3b, 0xf3, 0x6a, 0xe4, 0x90, 0x63, 0xbb, 0x21, 0xd7, 0x78, 0xa7, 0x35, 0x32, 0x80,
+                               0xd4, 0x98, 0xeb, 0x85, 0x32, 0x73, 0x2c, 0xce, 0xf6, 0x75, 0x0a, 0x23, 0x31, 0xc8, 0xdf, 0xa2,
+                               0x2c, 0x03, 0xc1, 0x06, 0x67, 0x2c, 0x3a, 0x2a, 0x77, 0x0d, 0xa6, 0x45, 0x61, 0x50, 0x3a, 0x97,
+                               0xf7, 0xb4, 0x8c, 0xf5, 0x53, 0x1d, 0x29, 0xe4, 0xd4, 0x10, 0xa8, 0xed, 0xe8, 0x02, 0x29, 0xc5,
+                               0x68, 0x36, 0x31, 0x02, 0x81, 0x80, 0x4f, 0x72, 0x4c, 0x82, 0x45, 0x35, 0x6f, 0xf8, 0xb8, 0x14,
+                               0x8d, 0x8e, 0x61, 0x6f, 0xb1, 0xbc, 0xbe, 0x9d, 0xeb, 0xc9, 0x7c, 0xee, 0x2e, 0x9d, 0x1f, 0xef,
+                               0x79, 0xc7, 0xc4, 0x4a, 0x63, 0x2b, 0x34, 0x51, 0xc7, 0xbc, 0xb0, 0xf8, 0x82, 0xc0, 0x25, 0xb4,
+                               0x67, 0xa3, 0x56, 0xeb, 0x4f, 0x32, 0xa6, 0x61, 0xdf, 0x45, 0xb4, 0x81, 0x20, 0x3c, 0x59, 0x95,
+                               0x8d, 0xfd, 0x4d, 0xdb, 0x6d, 0xb7, 0x58, 0xee, 0xdf, 0x6b, 0xa8, 0xee, 0xfc, 0x28, 0xff, 0xa0,
+                               0x01, 0x38, 0xe1, 0xb8, 0xcc, 0xa3, 0x41, 0x69, 0x36, 0x06, 0x60, 0x8c, 0x82, 0x74, 0xbe, 0x74,
+                               0xe2, 0x72, 0xb5, 0x31, 0xcc, 0x45, 0x31, 0x07, 0xa4, 0x7b, 0xc9, 0xd9, 0xa3, 0x4f, 0x42, 0x30,
+                               0xe1, 0xbb, 0x67, 0x45, 0x82, 0x65, 0xd5, 0x50, 0x0d, 0x2a, 0x87, 0xe0, 0x4e, 0x04, 0x0b, 0xbe,
+                               0xd8, 0x76, 0x3e, 0x55, 0x3b, 0xc3
+                       }
+               }
+       },
+       // DNSSEC Zone 1
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_RSASHA256 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xc4, 0x0c, 0x94, 0xcf, 0xa8, 0x8e, 0x78, 0x76, 0xee, 0xcf, 0xea, 0x6a,
+                               0xb5, 0x2d, 0x7e, 0xea, 0x3e, 0xdc, 0xd4, 0x6c, 0xd2, 0xe5, 0xdc, 0x8e, 0xe3, 0x0e, 0x7f, 0x02,
+                               0x5b, 0xd1, 0xbd, 0xe4, 0x3d, 0xe4, 0x84, 0x4b, 0xc4, 0x63, 0xca, 0x5a, 0x42, 0x81, 0x02, 0xa8,
+                               0x17, 0xd1, 0xb0, 0xa7, 0x43, 0x97, 0x6b, 0x29, 0xa1, 0x4b, 0xd1, 0x55, 0x78, 0x7f, 0xab, 0x85,
+                               0x49, 0xe0, 0x43, 0xf5, 0x18, 0x4c, 0x8b, 0x24, 0x33, 0xd3, 0xc0, 0x90, 0xd1, 0xa8, 0x02, 0xe7,
+                               0x55, 0xcb, 0xad, 0x48, 0x2c, 0x16, 0x44, 0xc1, 0x3e, 0x90, 0xf3, 0xa3, 0x22, 0x26, 0x96, 0x66,
+                               0xcc, 0x8b, 0x39, 0x1f, 0xc2, 0x99, 0x5d, 0x4d, 0x59, 0x6f, 0x3c, 0x7e, 0xb1, 0x50, 0x9c, 0xb4,
+                               0x49, 0xf7, 0xb6, 0x51, 0xdc, 0xbc, 0x2e, 0xff, 0x66, 0x27, 0x31, 0xe5, 0x69, 0x9e, 0x5a, 0x60,
+                               0x07, 0x25, 0xed, 0x6e, 0xc1, 0xe4, 0xa3, 0x6f, 0x3f, 0x0d, 0x66, 0xf1, 0x01, 0x58, 0xdb, 0x10,
+                               0x42, 0x61, 0x8e, 0x7f, 0x69, 0x66, 0xaf, 0x9e, 0xaf, 0x97, 0x6c, 0xab, 0x81, 0x9d, 0xb8, 0xb8,
+                               0x58, 0x84, 0x6c, 0x74, 0x91, 0xe3, 0x57, 0x25, 0xbd, 0xd2, 0x48, 0xc6, 0xe7, 0x94, 0x5e, 0xf7,
+                               0xe0, 0xa6, 0x1c, 0xcd, 0xb7, 0xa7, 0xa9, 0xdc, 0xb1, 0xc4, 0x93, 0x89, 0x48, 0x7a, 0x60, 0x3c,
+                               0xe2, 0xe9, 0xc8, 0x20, 0xc7, 0x59, 0x67, 0x84, 0xd1, 0x1f, 0x09, 0x0e, 0x66, 0x29, 0x3e, 0x43,
+                               0xd2, 0xa3, 0x25, 0xb9, 0xe8, 0x13, 0x01, 0x5b, 0x00, 0xbc, 0xfc, 0x50, 0xc9, 0x0a, 0x5f, 0x76,
+                               0xb7, 0xa8, 0x3f, 0x91, 0xb2, 0xb4, 0xc0, 0xe5, 0x9d, 0xf6, 0x0e, 0xde, 0x51, 0x82, 0x4b, 0xc0,
+                               0x8d, 0xb2, 0x59, 0x22, 0xc4, 0x9a, 0xc7, 0xa6, 0x55, 0xeb, 0x59, 0xb1, 0x84, 0x08, 0x35, 0x77,
+                               0x84, 0x94, 0x9f, 0xb3
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc4, 0x0c, 0x94, 0xcf,
+                               0xa8, 0x8e, 0x78, 0x76, 0xee, 0xcf, 0xea, 0x6a, 0xb5, 0x2d, 0x7e, 0xea, 0x3e, 0xdc, 0xd4, 0x6c,
+                               0xd2, 0xe5, 0xdc, 0x8e, 0xe3, 0x0e, 0x7f, 0x02, 0x5b, 0xd1, 0xbd, 0xe4, 0x3d, 0xe4, 0x84, 0x4b,
+                               0xc4, 0x63, 0xca, 0x5a, 0x42, 0x81, 0x02, 0xa8, 0x17, 0xd1, 0xb0, 0xa7, 0x43, 0x97, 0x6b, 0x29,
+                               0xa1, 0x4b, 0xd1, 0x55, 0x78, 0x7f, 0xab, 0x85, 0x49, 0xe0, 0x43, 0xf5, 0x18, 0x4c, 0x8b, 0x24,
+                               0x33, 0xd3, 0xc0, 0x90, 0xd1, 0xa8, 0x02, 0xe7, 0x55, 0xcb, 0xad, 0x48, 0x2c, 0x16, 0x44, 0xc1,
+                               0x3e, 0x90, 0xf3, 0xa3, 0x22, 0x26, 0x96, 0x66, 0xcc, 0x8b, 0x39, 0x1f, 0xc2, 0x99, 0x5d, 0x4d,
+                               0x59, 0x6f, 0x3c, 0x7e, 0xb1, 0x50, 0x9c, 0xb4, 0x49, 0xf7, 0xb6, 0x51, 0xdc, 0xbc, 0x2e, 0xff,
+                               0x66, 0x27, 0x31, 0xe5, 0x69, 0x9e, 0x5a, 0x60, 0x07, 0x25, 0xed, 0x6e, 0xc1, 0xe4, 0xa3, 0x6f,
+                               0x3f, 0x0d, 0x66, 0xf1, 0x01, 0x58, 0xdb, 0x10, 0x42, 0x61, 0x8e, 0x7f, 0x69, 0x66, 0xaf, 0x9e,
+                               0xaf, 0x97, 0x6c, 0xab, 0x81, 0x9d, 0xb8, 0xb8, 0x58, 0x84, 0x6c, 0x74, 0x91, 0xe3, 0x57, 0x25,
+                               0xbd, 0xd2, 0x48, 0xc6, 0xe7, 0x94, 0x5e, 0xf7, 0xe0, 0xa6, 0x1c, 0xcd, 0xb7, 0xa7, 0xa9, 0xdc,
+                               0xb1, 0xc4, 0x93, 0x89, 0x48, 0x7a, 0x60, 0x3c, 0xe2, 0xe9, 0xc8, 0x20, 0xc7, 0x59, 0x67, 0x84,
+                               0xd1, 0x1f, 0x09, 0x0e, 0x66, 0x29, 0x3e, 0x43, 0xd2, 0xa3, 0x25, 0xb9, 0xe8, 0x13, 0x01, 0x5b,
+                               0x00, 0xbc, 0xfc, 0x50, 0xc9, 0x0a, 0x5f, 0x76, 0xb7, 0xa8, 0x3f, 0x91, 0xb2, 0xb4, 0xc0, 0xe5,
+                               0x9d, 0xf6, 0x0e, 0xde, 0x51, 0x82, 0x4b, 0xc0, 0x8d, 0xb2, 0x59, 0x22, 0xc4, 0x9a, 0xc7, 0xa6,
+                               0x55, 0xeb, 0x59, 0xb1, 0x84, 0x08, 0x35, 0x77, 0x84, 0x94, 0x9f, 0xb3, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x81, 0xff, 0x71, 0xfa, 0x33, 0xdf, 0x31, 0x4a, 0x4d, 0xbe, 0xd7, 0x6d, 0xb4, 0x06,
+                               0x00, 0x4f, 0x48, 0x4f, 0x3c, 0x92, 0x26, 0x43, 0x1e, 0x59, 0x84, 0xb6, 0x38, 0x4b, 0x5f, 0xe1,
+                               0x29, 0xd5, 0xf1, 0x65, 0x95, 0xeb, 0x6f, 0xa9, 0x47, 0xae, 0xbc, 0x76, 0x12, 0x89, 0x43, 0xd7,
+                               0x76, 0x01, 0x9d, 0x87, 0xa0, 0xee, 0xe7, 0x33, 0xcc, 0xbd, 0x1c, 0xcc, 0x14, 0x17, 0x7a, 0xcb,
+                               0x2d, 0xa8, 0x09, 0xda, 0x49, 0xc6, 0x36, 0x74, 0x00, 0x58, 0x46, 0x1b, 0xeb, 0xc7, 0xec, 0x92,
+                               0xe7, 0x23, 0xad, 0x6f, 0xc8, 0x31, 0xd4, 0xc7, 0x2f, 0x18, 0xc1, 0x37, 0x41, 0x23, 0x83, 0x4d,
+                               0x40, 0x9d, 0xb0, 0x6d, 0x9b, 0xdf, 0xe3, 0x33, 0xea, 0x4a, 0xec, 0x0b, 0x66, 0xb5, 0xa1, 0xba,
+                               0x84, 0x9b, 0xa9, 0xe3, 0x98, 0x9b, 0xcf, 0x61, 0xac, 0x82, 0x1d, 0x00, 0x18, 0x92, 0x22, 0x4e,
+                               0x07, 0x2f, 0x30, 0x59, 0xaf, 0x01, 0x27, 0x02, 0x78, 0xdf, 0x2f, 0xff, 0x09, 0x21, 0xb6, 0x6d,
+                               0x5a, 0x56, 0x2e, 0xa4, 0x32, 0x6b, 0xb8, 0xac, 0x71, 0x4b, 0x7a, 0x9c, 0x44, 0x6a, 0x61, 0x8e,
+                               0x18, 0x56, 0x38, 0x70, 0x63, 0x7c, 0xae, 0x4e, 0x40, 0x5e, 0x72, 0x1b, 0xa1, 0xec, 0x22, 0xfe,
+                               0x65, 0x66, 0x15, 0x13, 0x59, 0xd8, 0xfa, 0x75, 0x66, 0x29, 0xb4, 0x0f, 0xda, 0x75, 0xdd, 0x90,
+                               0x0f, 0xbb, 0x29, 0x6a, 0xdf, 0x2a, 0x28, 0xd6, 0x7c, 0xd3, 0xa4, 0x45, 0x62, 0x74, 0x2f, 0x84,
+                               0xe2, 0xbc, 0xa2, 0x70, 0xee, 0xf5, 0x13, 0xbb, 0x03, 0x7c, 0x5b, 0x34, 0xcd, 0x2d, 0xe5, 0x14,
+                               0x6a, 0xc9, 0x07, 0x35, 0x49, 0x56, 0xaf, 0xf9, 0x7c, 0x96, 0xfe, 0x3d, 0x8f, 0xa2, 0xd8, 0x2d,
+                               0xe4, 0x25, 0x7e, 0xd9, 0xfa, 0xa1, 0x73, 0x81, 0x4a, 0xe4, 0xb5, 0x85, 0x8e, 0xcf, 0x75, 0x60,
+                               0x4c, 0xaf, 0xa5, 0x02, 0x81, 0x81, 0x00, 0xef, 0x04, 0x58, 0x13, 0x51, 0x21, 0x8d, 0xb3, 0x56,
+                               0xc7, 0x5b, 0x05, 0xa6, 0x5d, 0x7e, 0xca, 0xe1, 0xbe, 0x08, 0x46, 0xa5, 0x1b, 0x9a, 0x0e, 0x81,
+                               0xab, 0x3c, 0x7a, 0xad, 0xe5, 0xe2, 0x79, 0xfc, 0xfc, 0xda, 0x12, 0xe0, 0xf4, 0x87, 0x66, 0x09,
+                               0x75, 0x22, 0xe5, 0xe8, 0x28, 0x0b, 0x27, 0x19, 0x7c, 0xb8, 0xd0, 0x65, 0x07, 0x9d, 0x8b, 0xf4,
+                               0x5d, 0x8c, 0x5c, 0x79, 0xe5, 0xe0, 0x98, 0x4d, 0x44, 0x11, 0x10, 0x34, 0xae, 0xbc, 0xe4, 0x09,
+                               0x03, 0xb7, 0x9c, 0x51, 0x47, 0x04, 0xe1, 0x9a, 0x5e, 0x71, 0x20, 0xbe, 0x47, 0xf8, 0x19, 0xfd,
+                               0xf4, 0xc5, 0xce, 0x26, 0xd3, 0xb9, 0xe0, 0xed, 0x7a, 0x24, 0x25, 0x94, 0xa6, 0xd0, 0x1e, 0xd3,
+                               0x87, 0xd8, 0x00, 0xbb, 0x36, 0xcd, 0x2a, 0x9c, 0x10, 0xc3, 0x7a, 0x19, 0xbb, 0x7c, 0xec, 0x50,
+                               0x9e, 0xf9, 0x2f, 0x7d, 0x56, 0x14, 0x75, 0x02, 0x81, 0x81, 0x00, 0xd1, 0xfa, 0xa9, 0xef, 0xec,
+                               0xfa, 0x7b, 0x26, 0xeb, 0xff, 0x27, 0xee, 0x67, 0x19, 0xc9, 0xcb, 0x62, 0x92, 0x25, 0x9a, 0xd7,
+                               0x39, 0x29, 0xa3, 0x74, 0xdd, 0x96, 0xf3, 0xbe, 0xdf, 0x5b, 0xa2, 0xf5, 0x59, 0x9c, 0xad, 0x7f,
+                               0x1c, 0x32, 0xa7, 0x8a, 0xa5, 0xcb, 0x8d, 0x2f, 0x25, 0x3c, 0xb2, 0x33, 0x15, 0x72, 0xab, 0xe6,
+                               0xc2, 0x84, 0xa7, 0x3a, 0x8f, 0x68, 0x55, 0x7f, 0xba, 0xfd, 0xec, 0xc1, 0x52, 0xeb, 0x3c, 0x2b,
+                               0x7e, 0x1f, 0x46, 0xb3, 0x0c, 0x76, 0xef, 0x0d, 0x1a, 0x71, 0xf4, 0x47, 0xdc, 0x1b, 0x3a, 0x63,
+                               0xe9, 0xa1, 0x44, 0x91, 0xa8, 0xe6, 0x54, 0x08, 0x9a, 0xfe, 0xad, 0xa6, 0x30, 0x0c, 0xdf, 0xd8,
+                               0x2d, 0x73, 0xc9, 0xb8, 0x19, 0x49, 0xec, 0xff, 0x80, 0xe6, 0xd9, 0x3d, 0xc8, 0xa1, 0x9c, 0x31,
+                               0x2e, 0x6d, 0x1d, 0x09, 0x8f, 0x0c, 0x5f, 0x60, 0xdd, 0xbe, 0x87, 0x02, 0x81, 0x80, 0x3c, 0xf1,
+                               0x38, 0x9b, 0xc4, 0x45, 0xef, 0xe1, 0x58, 0x31, 0x00, 0x6e, 0x52, 0x5a, 0xe8, 0x67, 0x46, 0x63,
+                               0xb3, 0xac, 0x7f, 0x90, 0xa8, 0x19, 0x26, 0xca, 0xc8, 0x62, 0xe7, 0x50, 0x04, 0x0c, 0xe2, 0x8a,
+                               0x7d, 0xf7, 0xee, 0x4f, 0xaa, 0xee, 0x43, 0x5f, 0x10, 0x84, 0xda, 0x4a, 0xcb, 0x7d, 0x2e, 0xac,
+                               0x74, 0x5a, 0xfe, 0x47, 0x90, 0xce, 0x0c, 0x82, 0x85, 0xb5, 0x56, 0x87, 0x5c, 0x5a, 0xb8, 0xe8,
+                               0xb0, 0x09, 0x17, 0xc5, 0xad, 0xf7, 0xde, 0xac, 0x89, 0xf6, 0x5b, 0x6a, 0xe6, 0x3d, 0xb7, 0xa4,
+                               0x78, 0xe7, 0xc6, 0x5c, 0x87, 0x4f, 0xe9, 0x46, 0xad, 0xe0, 0xc0, 0x59, 0x9f, 0xbd, 0x50, 0x0f,
+                               0xa2, 0x83, 0xca, 0x81, 0x35, 0xf3, 0x86, 0x2f, 0xce, 0xcd, 0x70, 0xee, 0xaa, 0x25, 0x41, 0x21,
+                               0xad, 0x15, 0xc6, 0xd5, 0xdd, 0x26, 0x4b, 0xf0, 0x1c, 0xcd, 0x15, 0x02, 0x0b, 0xb9, 0x02, 0x81,
+                               0x81, 0x00, 0xb0, 0x67, 0x97, 0xf0, 0xda, 0xf4, 0x2c, 0x16, 0x2c, 0xd9, 0xb5, 0xa0, 0x51, 0xb9,
+                               0x96, 0x90, 0x69, 0x85, 0x42, 0x56, 0x6d, 0x4d, 0xb6, 0xaf, 0x7d, 0xf4, 0xd3, 0x66, 0x7f, 0x0a,
+                               0x82, 0x9f, 0xca, 0xe3, 0xb7, 0xff, 0x01, 0xf4, 0x9c, 0x91, 0xa8, 0xa7, 0x54, 0xf4, 0x8d, 0x91,
+                               0x09, 0x06, 0xe4, 0x67, 0xb6, 0x68, 0xb8, 0x7a, 0xde, 0x9a, 0xe7, 0x55, 0xa7, 0x5f, 0x7c, 0xe9,
+                               0x89, 0x7e, 0x27, 0x84, 0x13, 0xdf, 0xfe, 0xe1, 0xfc, 0x40, 0x97, 0x17, 0x77, 0xab, 0xa9, 0x24,
+                               0x78, 0x6b, 0xb3, 0x1b, 0x35, 0x3c, 0xbe, 0xc7, 0x7e, 0x16, 0x6f, 0x10, 0xa8, 0x40, 0x1c, 0xe2,
+                               0xa9, 0xe7, 0x14, 0xe0, 0xf5, 0x4f, 0xf0, 0xfb, 0x6e, 0x75, 0x1f, 0x57, 0x0f, 0x86, 0x2d, 0xb9,
+                               0x0b, 0x37, 0xa2, 0xf2, 0x34, 0xe3, 0x55, 0x66, 0x1d, 0x80, 0xf1, 0x90, 0xdd, 0xe2, 0x1b, 0x2c,
+                               0xef, 0x6d, 0x02, 0x81, 0x81, 0x00, 0xb4, 0x55, 0xa9, 0x1b, 0x92, 0x42, 0x09, 0x21, 0x81, 0x24,
+                               0x4a, 0xf9, 0x85, 0x5d, 0x07, 0x87, 0x54, 0xe3, 0xcc, 0x62, 0x1d, 0x28, 0x6d, 0x95, 0x99, 0x23,
+                               0x5d, 0x2f, 0xa7, 0x61, 0x68, 0x43, 0xd7, 0xa0, 0x8f, 0xf8, 0x6b, 0x78, 0xed, 0x29, 0x8e, 0x54,
+                               0xef, 0x55, 0x58, 0x87, 0x39, 0xbf, 0xb1, 0x9d, 0xb7, 0x61, 0x08, 0x35, 0x90, 0xe9, 0x2d, 0x95,
+                               0xbf, 0x6a, 0x6d, 0xab, 0xb7, 0xcb, 0x44, 0x8f, 0xa7, 0x5d, 0x6c, 0x97, 0x69, 0x75, 0xfa, 0x8f,
+                               0xc3, 0x20, 0x5f, 0xea, 0xb8, 0xd8, 0x8a, 0xd1, 0xf6, 0x0a, 0x49, 0xc9, 0xaf, 0xff, 0x79, 0xc7,
+                               0x0a, 0xe0, 0xe1, 0x9a, 0xfa, 0x04, 0x7e, 0x72, 0x32, 0xad, 0x86, 0xbe, 0x2e, 0xe9, 0xc3, 0xc9,
+                               0xdd, 0x5a, 0xd2, 0x0e, 0x7f, 0xb2, 0xef, 0x44, 0x68, 0xc8, 0x27, 0x92, 0xfc, 0xea, 0x43, 0x63,
+                               0x00, 0x6c, 0xbf, 0xc7, 0x5c, 0xa6
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_RSASHA256 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0x8e, 0xa0, 0x5e, 0xea, 0xb3, 0xeb, 0x42, 0xfd, 0x69, 0x9c, 0xcc, 0x18,
+                               0x8a, 0xe0, 0x67, 0x04, 0x6f, 0xdd, 0xb8, 0x8f, 0x63, 0xcb, 0xa9, 0x58, 0x9d, 0x7d, 0xdf, 0x5b,
+                               0x67, 0xcc, 0x0e, 0xde, 0x69, 0xd1, 0x13, 0x0d, 0x78, 0xff, 0x18, 0x1a, 0xe5, 0xd0, 0x6a, 0x14,
+                               0xb3, 0x39, 0x37, 0xd2, 0xd1, 0x8f, 0x85, 0x5d, 0xa9, 0x3c, 0x62, 0x92, 0xe1, 0xc5, 0xe9, 0xe9,
+                               0x9a, 0x42, 0x89, 0xb7, 0x87, 0x8a, 0x56, 0x9a, 0x06, 0x0d, 0x01, 0xd8, 0xb2, 0x85, 0xe3, 0x64,
+                               0x5e, 0x86, 0x66, 0x6f, 0x21, 0x97, 0xdf, 0x6f, 0xaf, 0xe4, 0x7c, 0x4f, 0xa6, 0x5c, 0x2b, 0x91,
+                               0xd1, 0x51, 0x7d, 0x4d, 0x29, 0xc9, 0x6c, 0x6c, 0x9d, 0x7e, 0x02, 0xd7, 0x61, 0xe3, 0xf1, 0x7a,
+                               0x66, 0x3b, 0x77, 0x8a, 0xd3, 0x6c, 0xa6, 0xfc, 0x44, 0xb5, 0x8d, 0xfe, 0x0f, 0xd7, 0x0b, 0x37,
+                               0x0f, 0x2e, 0x39, 0x2c, 0xa9, 0xf7, 0x55, 0xb6, 0x78, 0x17, 0xd1, 0x49, 0x56, 0xac, 0x87, 0x79,
+                               0x9c, 0xa4, 0x48, 0x95, 0xcc, 0x3c, 0x51, 0xee, 0x91, 0x31, 0x09, 0x13, 0x4a, 0xb7, 0x58, 0x71,
+                               0x4d, 0xd9, 0xf7, 0x95, 0x66, 0x6a, 0xbf, 0x31, 0xcf, 0x15, 0x53, 0xfa, 0x6f, 0x7b, 0xa4, 0x88,
+                               0x2e, 0x08, 0x87, 0x40, 0x99, 0xfd, 0x79, 0xdd, 0x82, 0x6f, 0xc0, 0xda, 0x6c, 0x62, 0x07, 0x5e,
+                               0x19, 0xbc, 0x83, 0x05, 0x8b, 0xb3, 0x02, 0x64, 0x64, 0xdc, 0x7d, 0x3d, 0x32, 0x87, 0x5f, 0x25,
+                               0xe1, 0xee, 0x6b, 0x97, 0x27, 0x94, 0x0a, 0xd5, 0xb2, 0xba, 0x43, 0x24, 0x40, 0x26, 0x70, 0x30,
+                               0xf1, 0x77, 0x28, 0x87, 0xba, 0x5c, 0x10, 0x33, 0x6d, 0x10, 0x34, 0xbf, 0xbb, 0x77, 0x31, 0x4d,
+                               0xa7, 0x1c, 0x4b, 0x8f, 0x2f, 0x7c, 0x14, 0x00, 0x0f, 0xb4, 0xc8, 0x29, 0x02, 0x54, 0xeb, 0x2b,
+                               0x0c, 0xf6, 0x85, 0xa7
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0x8e, 0xa0, 0x5e, 0xea,
+                               0xb3, 0xeb, 0x42, 0xfd, 0x69, 0x9c, 0xcc, 0x18, 0x8a, 0xe0, 0x67, 0x04, 0x6f, 0xdd, 0xb8, 0x8f,
+                               0x63, 0xcb, 0xa9, 0x58, 0x9d, 0x7d, 0xdf, 0x5b, 0x67, 0xcc, 0x0e, 0xde, 0x69, 0xd1, 0x13, 0x0d,
+                               0x78, 0xff, 0x18, 0x1a, 0xe5, 0xd0, 0x6a, 0x14, 0xb3, 0x39, 0x37, 0xd2, 0xd1, 0x8f, 0x85, 0x5d,
+                               0xa9, 0x3c, 0x62, 0x92, 0xe1, 0xc5, 0xe9, 0xe9, 0x9a, 0x42, 0x89, 0xb7, 0x87, 0x8a, 0x56, 0x9a,
+                               0x06, 0x0d, 0x01, 0xd8, 0xb2, 0x85, 0xe3, 0x64, 0x5e, 0x86, 0x66, 0x6f, 0x21, 0x97, 0xdf, 0x6f,
+                               0xaf, 0xe4, 0x7c, 0x4f, 0xa6, 0x5c, 0x2b, 0x91, 0xd1, 0x51, 0x7d, 0x4d, 0x29, 0xc9, 0x6c, 0x6c,
+                               0x9d, 0x7e, 0x02, 0xd7, 0x61, 0xe3, 0xf1, 0x7a, 0x66, 0x3b, 0x77, 0x8a, 0xd3, 0x6c, 0xa6, 0xfc,
+                               0x44, 0xb5, 0x8d, 0xfe, 0x0f, 0xd7, 0x0b, 0x37, 0x0f, 0x2e, 0x39, 0x2c, 0xa9, 0xf7, 0x55, 0xb6,
+                               0x78, 0x17, 0xd1, 0x49, 0x56, 0xac, 0x87, 0x79, 0x9c, 0xa4, 0x48, 0x95, 0xcc, 0x3c, 0x51, 0xee,
+                               0x91, 0x31, 0x09, 0x13, 0x4a, 0xb7, 0x58, 0x71, 0x4d, 0xd9, 0xf7, 0x95, 0x66, 0x6a, 0xbf, 0x31,
+                               0xcf, 0x15, 0x53, 0xfa, 0x6f, 0x7b, 0xa4, 0x88, 0x2e, 0x08, 0x87, 0x40, 0x99, 0xfd, 0x79, 0xdd,
+                               0x82, 0x6f, 0xc0, 0xda, 0x6c, 0x62, 0x07, 0x5e, 0x19, 0xbc, 0x83, 0x05, 0x8b, 0xb3, 0x02, 0x64,
+                               0x64, 0xdc, 0x7d, 0x3d, 0x32, 0x87, 0x5f, 0x25, 0xe1, 0xee, 0x6b, 0x97, 0x27, 0x94, 0x0a, 0xd5,
+                               0xb2, 0xba, 0x43, 0x24, 0x40, 0x26, 0x70, 0x30, 0xf1, 0x77, 0x28, 0x87, 0xba, 0x5c, 0x10, 0x33,
+                               0x6d, 0x10, 0x34, 0xbf, 0xbb, 0x77, 0x31, 0x4d, 0xa7, 0x1c, 0x4b, 0x8f, 0x2f, 0x7c, 0x14, 0x00,
+                               0x0f, 0xb4, 0xc8, 0x29, 0x02, 0x54, 0xeb, 0x2b, 0x0c, 0xf6, 0x85, 0xa7, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x12, 0xc4, 0xa7, 0xb8, 0xda, 0xd4, 0x2a, 0x8c, 0x8e, 0x49, 0x43,
+                               0xd2, 0x36, 0xf4, 0x30, 0x9a, 0x34, 0xde, 0x48, 0x5b, 0x16, 0x16, 0xc6, 0xc2, 0x12, 0x77, 0x6d,
+                               0xdc, 0xe9, 0x5b, 0xc0, 0xd9, 0x23, 0xb3, 0x8f, 0x7e, 0x46, 0x5d, 0xcb, 0x1e, 0x3e, 0xff, 0x9d,
+                               0xf5, 0xfb, 0x5f, 0x9c, 0x28, 0xf1, 0xca, 0xa1, 0x12, 0x22, 0x23, 0x00, 0x15, 0xb4, 0x00, 0x07,
+                               0x37, 0xe6, 0x44, 0xbb, 0x3a, 0x9b, 0x6b, 0x68, 0xd9, 0xfb, 0xd1, 0xda, 0x5c, 0x59, 0xa1, 0xa4,
+                               0xba, 0xa0, 0x75, 0xaf, 0x25, 0x10, 0x96, 0x9b, 0xa0, 0x66, 0xd4, 0xfa, 0x46, 0xc0, 0xed, 0x80,
+                               0x0e, 0x40, 0x2d, 0x28, 0xff, 0xee, 0x6f, 0xc3, 0x04, 0xc7, 0xf4, 0xbd, 0x0a, 0xfa, 0xa6, 0xb4,
+                               0x30, 0x17, 0x23, 0xdc, 0x27, 0x38, 0x8b, 0x23, 0xae, 0x2e, 0x9c, 0x75, 0x82, 0xe9, 0x32, 0xb3,
+                               0x17, 0x1b, 0xb8, 0x49, 0x6d, 0x94, 0xe1, 0x1c, 0x13, 0x92, 0xf4, 0x42, 0xab, 0x62, 0x6b, 0x62,
+                               0x74, 0x2e, 0x1c, 0x0a, 0x07, 0x3f, 0x43, 0x01, 0x53, 0x35, 0x7b, 0xc8, 0x22, 0xb7, 0x14, 0x7e,
+                               0x2f, 0x57, 0x4d, 0x98, 0x14, 0x1c, 0x80, 0xbe, 0xc8, 0x2b, 0xd6, 0xd7, 0x22, 0xf0, 0xb1, 0xae,
+                               0xac, 0x81, 0x93, 0x47, 0x51, 0x2d, 0x4f, 0xfc, 0xf2, 0xb1, 0x94, 0xbf, 0x73, 0x5b, 0x2b, 0xf8,
+                               0x67, 0xd8, 0x0b, 0x00, 0x2a, 0x06, 0x3a, 0x37, 0x30, 0x71, 0xe0, 0x20, 0xb8, 0xa3, 0xf1, 0x62,
+                               0x12, 0xea, 0x55, 0x52, 0x7d, 0xcf, 0x93, 0xac, 0xf1, 0x80, 0xfe, 0x79, 0x12, 0x23, 0xa8, 0xd3,
+                               0x92, 0x71, 0x51, 0x22, 0x64, 0xb5, 0x22, 0x9d, 0x8b, 0x38, 0x52, 0x9c, 0x4a, 0x9d, 0x77, 0x3d,
+                               0x45, 0x02, 0x74, 0x4d, 0x6e, 0x2b, 0x70, 0x7f, 0x73, 0x30, 0xc2, 0xa6, 0x8d, 0xe8, 0x7d, 0x13,
+                               0x61, 0x63, 0x03, 0x42, 0x01, 0x02, 0x81, 0x81, 0x00, 0xc4, 0x6b, 0x81, 0xad, 0xa2, 0x14, 0xd8,
+                               0x29, 0x99, 0x34, 0x3f, 0x44, 0xd6, 0xbd, 0x39, 0x7e, 0x48, 0x3a, 0x64, 0xe1, 0x7d, 0x97, 0x68,
+                               0x36, 0x65, 0xc1, 0x8c, 0xa1, 0x38, 0x62, 0x7b, 0x23, 0x89, 0xb5, 0xc1, 0x88, 0xa6, 0xb4, 0xe0,
+                               0x75, 0x98, 0x0f, 0x02, 0x7d, 0x4c, 0x4e, 0xab, 0xc2, 0x3b, 0x16, 0x9c, 0x29, 0xdf, 0xc0, 0xf6,
+                               0xee, 0x1a, 0xc8, 0xfa, 0xc2, 0x01, 0x71, 0xc7, 0x2e, 0x7f, 0x72, 0x64, 0xe3, 0x22, 0xa6, 0x7f,
+                               0xb1, 0xd0, 0x8a, 0x21, 0xe0, 0xd9, 0x1c, 0x92, 0xf1, 0xc5, 0x82, 0xe9, 0x27, 0x20, 0x2a, 0x8e,
+                               0xa4, 0x76, 0x9b, 0x42, 0x58, 0x96, 0x2a, 0x2a, 0x5d, 0xb0, 0x8c, 0xdd, 0x7e, 0xf0, 0xf1, 0xa7,
+                               0x7e, 0xbb, 0xf2, 0x21, 0xe6, 0xbf, 0xc1, 0x30, 0xf3, 0x79, 0x0e, 0x0e, 0x12, 0x19, 0x04, 0x73,
+                               0x1a, 0x4c, 0x3c, 0x52, 0x21, 0xbe, 0x98, 0x2a, 0xb7, 0x02, 0x81, 0x81, 0x00, 0xb9, 0xe3, 0xaa,
+                               0x93, 0x13, 0x85, 0xe4, 0xb6, 0x26, 0x3b, 0xd1, 0x7b, 0x0c, 0xaa, 0x05, 0xf8, 0x5e, 0x2e, 0x8d,
+                               0xe0, 0x3a, 0x93, 0x86, 0x4f, 0x61, 0xa8, 0xb2, 0x86, 0xc7, 0x30, 0xbc, 0x83, 0x4c, 0xa6, 0xe1,
+                               0xd9, 0x49, 0xa7, 0x21, 0x89, 0x5b, 0xcc, 0x97, 0xa5, 0xb5, 0x8f, 0xcc, 0x29, 0xb1, 0x08, 0xc2,
+                               0x13, 0x25, 0x12, 0xf9, 0x2d, 0x8c, 0x09, 0x81, 0xcf, 0x60, 0x66, 0x83, 0x2b, 0xdf, 0xf4, 0xb3,
+                               0x09, 0x4d, 0x1e, 0x2a, 0x67, 0x9f, 0x6f, 0x86, 0x48, 0xf0, 0xd3, 0xd7, 0x20, 0xcf, 0x43, 0x1a,
+                               0xe0, 0xfa, 0x93, 0xae, 0x4e, 0x7d, 0x39, 0xbf, 0x52, 0x46, 0x4b, 0xee, 0xca, 0xd2, 0xf8, 0xea,
+                               0x19, 0xf3, 0x65, 0xa8, 0x68, 0x9a, 0xc6, 0x48, 0xfb, 0x92, 0x89, 0xa7, 0x1f, 0x02, 0xef, 0xd6,
+                               0x53, 0x1f, 0x5c, 0x85, 0xf6, 0x0a, 0xc9, 0x67, 0x36, 0x3c, 0xa8, 0x4c, 0x91, 0x02, 0x81, 0x80,
+                               0x4c, 0x06, 0x28, 0x5d, 0x22, 0x3c, 0xdb, 0x25, 0xce, 0xac, 0x90, 0x48, 0x7e, 0xad, 0x22, 0xd1,
+                               0xa3, 0xf7, 0x26, 0x10, 0xb8, 0xe1, 0x9f, 0x6e, 0x5e, 0x98, 0x39, 0x6c, 0x35, 0x3c, 0xb8, 0xd8,
+                               0x9a, 0x76, 0x84, 0xff, 0xf5, 0x36, 0x51, 0x22, 0x72, 0xc6, 0x6c, 0x7a, 0x3e, 0xcc, 0xbc, 0x62,
+                               0x23, 0x28, 0xaf, 0x2c, 0xed, 0xf3, 0xec, 0x1f, 0x60, 0xb2, 0xa5, 0x8b, 0xed, 0xaf, 0x32, 0x1e,
+                               0xe7, 0x29, 0x49, 0xaf, 0x38, 0x3c, 0x25, 0x74, 0x97, 0x78, 0xbf, 0x80, 0x1d, 0xc8, 0x97, 0x7f,
+                               0x88, 0xa9, 0x10, 0xc1, 0x1a, 0x0c, 0x1e, 0xc5, 0xbd, 0x3a, 0x7e, 0xc5, 0x76, 0xe9, 0xbf, 0x18,
+                               0x20, 0x3b, 0x85, 0x87, 0x9a, 0x8e, 0x8d, 0xfb, 0xd9, 0x96, 0xf0, 0x54, 0x35, 0xa7, 0xfa, 0xe0,
+                               0x61, 0xdc, 0x74, 0xc9, 0xc0, 0x71, 0x1e, 0x73, 0xf1, 0x49, 0xef, 0xa8, 0x2b, 0xf3, 0x0d, 0xa5,
+                               0x02, 0x81, 0x80, 0x07, 0xbc, 0xda, 0x54, 0xd4, 0xf3, 0x35, 0xd9, 0xff, 0x2d, 0x6e, 0x42, 0xd8,
+                               0xfc, 0x1a, 0xd3, 0x83, 0xd8, 0x1c, 0x3f, 0xc1, 0x30, 0x72, 0xde, 0xcb, 0x09, 0xe2, 0xa4, 0x89,
+                               0x96, 0x15, 0xc2, 0xc1, 0x66, 0x10, 0xf0, 0xa2, 0x88, 0x3a, 0x75, 0x25, 0x45, 0x16, 0xea, 0xe6,
+                               0xeb, 0xdd, 0x6b, 0xbe, 0xda, 0xc6, 0x79, 0x55, 0x89, 0xbc, 0xa5, 0x90, 0xb6, 0xb4, 0x98, 0x95,
+                               0x42, 0xaf, 0x23, 0x1c, 0x67, 0x6b, 0x2c, 0x6f, 0xf1, 0x59, 0x82, 0x86, 0xb8, 0x75, 0xb6, 0x83,
+                               0x8f, 0xcd, 0xdc, 0xa7, 0xc3, 0xfb, 0x52, 0x72, 0x1b, 0xc3, 0x9d, 0xb0, 0xc8, 0xbe, 0x96, 0x06,
+                               0x27, 0x13, 0x01, 0x8e, 0x56, 0x9e, 0x28, 0x06, 0x61, 0xac, 0xe8, 0xed, 0xc8, 0x63, 0x31, 0x55,
+                               0xa0, 0x35, 0x3c, 0xad, 0x3a, 0x9e, 0x89, 0x47, 0x3c, 0xea, 0xb6, 0x9b, 0x2c, 0x57, 0x07, 0x82,
+                               0xee, 0xde, 0x81, 0x02, 0x81, 0x80, 0x70, 0xbf, 0x51, 0x43, 0x70, 0x8c, 0x33, 0xde, 0x32, 0x51,
+                               0x5c, 0x69, 0xc5, 0x22, 0xe5, 0x4f, 0x9b, 0x02, 0xaa, 0x11, 0x0c, 0x71, 0x1a, 0x3d, 0xfa, 0xfc,
+                               0x66, 0x40, 0xf8, 0xe9, 0x05, 0xcd, 0x47, 0x44, 0xb1, 0xbf, 0xbd, 0xc2, 0x4e, 0xa5, 0x4f, 0x55,
+                               0x9f, 0xaa, 0x5f, 0xf7, 0xce, 0xe7, 0xa9, 0x74, 0x4e, 0x44, 0xed, 0x32, 0x71, 0x81, 0xb4, 0xae,
+                               0x07, 0x60, 0xcc, 0x6b, 0x7b, 0x89, 0x24, 0x2e, 0x51, 0xa8, 0xf9, 0x2c, 0xbd, 0xc8, 0x91, 0xcd,
+                               0xd6, 0x3a, 0x29, 0x66, 0xf3, 0x64, 0xec, 0xd3, 0x58, 0xa8, 0xc9, 0x2e, 0x9a, 0x1c, 0x41, 0xdf,
+                               0xf5, 0x49, 0xe4, 0x17, 0xe3, 0xaf, 0x48, 0xae, 0x03, 0xb2, 0x4d, 0xaf, 0x04, 0x76, 0x3f, 0x8a,
+                               0x2c, 0xf5, 0x3c, 0xed, 0x67, 0xef, 0x1d, 0xe2, 0x4e, 0xf5, 0x27, 0x86, 0xd1, 0x32, 0x96, 0xbe,
+                               0xf2, 0x12, 0x57, 0x13, 0xe1, 0xe6
+                       }
+               }
+       },
+       // DNSSEC Zone 2
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_RSASHA256 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xa0, 0xee, 0xcd, 0x83, 0x3d, 0xd4, 0x51, 0x95, 0x00, 0x98, 0x86, 0x5d,
+                               0xd8, 0xe6, 0xd1, 0xf4, 0xbb, 0x69, 0x17, 0x9c, 0xaf, 0xea, 0xfc, 0xa6, 0x5b, 0x9f, 0x6f, 0x50,
+                               0x3b, 0x54, 0x73, 0x33, 0x63, 0xc1, 0x26, 0x9e, 0x7b, 0x92, 0xef, 0x34, 0xc2, 0x32, 0xbe, 0x5a,
+                               0xa6, 0x64, 0x51, 0x94, 0x83, 0xb2, 0xa2, 0x2c, 0x5e, 0xa5, 0xdd, 0x05, 0x78, 0xb4, 0x69, 0x1b,
+                               0xde, 0xc0, 0x4b, 0x18, 0x97, 0xe3, 0x66, 0x4a, 0x89, 0xfb, 0x0d, 0x63, 0x1c, 0xeb, 0x1d, 0x45,
+                               0x99, 0xf8, 0x4b, 0xbf, 0xa3, 0x80, 0x96, 0x6c, 0xfa, 0x6d, 0xa9, 0x5d, 0xf4, 0x8e, 0x97, 0x01,
+                               0xa1, 0x0b, 0x34, 0x32, 0x87, 0xee, 0x93, 0xb7, 0x9f, 0x88, 0x23, 0x9f, 0xef, 0x4d, 0xef, 0x96,
+                               0xe2, 0x31, 0xa2, 0xe8, 0x03, 0x67, 0x16, 0x9b, 0x54, 0xe0, 0x1f, 0x29, 0xb6, 0x31, 0x7e, 0x55,
+                               0xa2, 0x0d, 0xfb, 0x52, 0xb5, 0x9a, 0x3f, 0xe9, 0xcd, 0x28, 0xb2, 0xee, 0x72, 0xca, 0xfa, 0x41,
+                               0xd6, 0x50, 0x1b, 0xd0, 0xf5, 0xe6, 0x40, 0x5f, 0x21, 0x50, 0x55, 0xbc, 0x94, 0x6a, 0x1a, 0x07,
+                               0x33, 0x81, 0x9b, 0x83, 0xe8, 0xe2, 0x44, 0xfc, 0x2c, 0xc7, 0x6f, 0x29, 0x3d, 0x67, 0xca, 0x73,
+                               0xcc, 0xe6, 0xeb, 0x8c, 0x3a, 0x8d, 0xe3, 0xfe, 0x43, 0x77, 0x4c, 0xac, 0x07, 0x18, 0x41, 0x0c,
+                               0x9b, 0xe8, 0xf8, 0xc5, 0x6b, 0x3e, 0xd6, 0xe0, 0x18, 0x8f, 0x45, 0x7f, 0x85, 0xab, 0xdc, 0x47,
+                               0xdd, 0xd8, 0x31, 0x26, 0x87, 0xf6, 0x8e, 0x4d, 0xd4, 0x4f, 0xda, 0xbf, 0xe8, 0xc7, 0x2b, 0x05,
+                               0x08, 0xaf, 0x82, 0xea, 0xde, 0x5d, 0xe3, 0x59, 0x09, 0x60, 0xff, 0x1d, 0x35, 0x60, 0xf3, 0xe9,
+                               0x9d, 0x11, 0xf2, 0x0b, 0xc4, 0x34, 0xb5, 0xb9, 0x6f, 0xdc, 0x4b, 0x77, 0x4c, 0x8d, 0x75, 0xed,
+                               0x1b, 0xbf, 0x20, 0x4d
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa0, 0xee, 0xcd, 0x83,
+                               0x3d, 0xd4, 0x51, 0x95, 0x00, 0x98, 0x86, 0x5d, 0xd8, 0xe6, 0xd1, 0xf4, 0xbb, 0x69, 0x17, 0x9c,
+                               0xaf, 0xea, 0xfc, 0xa6, 0x5b, 0x9f, 0x6f, 0x50, 0x3b, 0x54, 0x73, 0x33, 0x63, 0xc1, 0x26, 0x9e,
+                               0x7b, 0x92, 0xef, 0x34, 0xc2, 0x32, 0xbe, 0x5a, 0xa6, 0x64, 0x51, 0x94, 0x83, 0xb2, 0xa2, 0x2c,
+                               0x5e, 0xa5, 0xdd, 0x05, 0x78, 0xb4, 0x69, 0x1b, 0xde, 0xc0, 0x4b, 0x18, 0x97, 0xe3, 0x66, 0x4a,
+                               0x89, 0xfb, 0x0d, 0x63, 0x1c, 0xeb, 0x1d, 0x45, 0x99, 0xf8, 0x4b, 0xbf, 0xa3, 0x80, 0x96, 0x6c,
+                               0xfa, 0x6d, 0xa9, 0x5d, 0xf4, 0x8e, 0x97, 0x01, 0xa1, 0x0b, 0x34, 0x32, 0x87, 0xee, 0x93, 0xb7,
+                               0x9f, 0x88, 0x23, 0x9f, 0xef, 0x4d, 0xef, 0x96, 0xe2, 0x31, 0xa2, 0xe8, 0x03, 0x67, 0x16, 0x9b,
+                               0x54, 0xe0, 0x1f, 0x29, 0xb6, 0x31, 0x7e, 0x55, 0xa2, 0x0d, 0xfb, 0x52, 0xb5, 0x9a, 0x3f, 0xe9,
+                               0xcd, 0x28, 0xb2, 0xee, 0x72, 0xca, 0xfa, 0x41, 0xd6, 0x50, 0x1b, 0xd0, 0xf5, 0xe6, 0x40, 0x5f,
+                               0x21, 0x50, 0x55, 0xbc, 0x94, 0x6a, 0x1a, 0x07, 0x33, 0x81, 0x9b, 0x83, 0xe8, 0xe2, 0x44, 0xfc,
+                               0x2c, 0xc7, 0x6f, 0x29, 0x3d, 0x67, 0xca, 0x73, 0xcc, 0xe6, 0xeb, 0x8c, 0x3a, 0x8d, 0xe3, 0xfe,
+                               0x43, 0x77, 0x4c, 0xac, 0x07, 0x18, 0x41, 0x0c, 0x9b, 0xe8, 0xf8, 0xc5, 0x6b, 0x3e, 0xd6, 0xe0,
+                               0x18, 0x8f, 0x45, 0x7f, 0x85, 0xab, 0xdc, 0x47, 0xdd, 0xd8, 0x31, 0x26, 0x87, 0xf6, 0x8e, 0x4d,
+                               0xd4, 0x4f, 0xda, 0xbf, 0xe8, 0xc7, 0x2b, 0x05, 0x08, 0xaf, 0x82, 0xea, 0xde, 0x5d, 0xe3, 0x59,
+                               0x09, 0x60, 0xff, 0x1d, 0x35, 0x60, 0xf3, 0xe9, 0x9d, 0x11, 0xf2, 0x0b, 0xc4, 0x34, 0xb5, 0xb9,
+                               0x6f, 0xdc, 0x4b, 0x77, 0x4c, 0x8d, 0x75, 0xed, 0x1b, 0xbf, 0x20, 0x4d, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x01, 0xa4, 0x06, 0x64, 0x3d, 0x65, 0x68, 0x49, 0x78, 0xf3, 0x85,
+                               0xe5, 0xbe, 0x10, 0xda, 0xc0, 0x3e, 0xd0, 0xf0, 0x9a, 0x76, 0x00, 0x71, 0x70, 0xb4, 0xb3, 0xed,
+                               0x80, 0x89, 0xd7, 0x74, 0x46, 0x06, 0xe3, 0x5d, 0xb2, 0x27, 0xb9, 0xf7, 0x2f, 0x9e, 0x8c, 0x6c,
+                               0xad, 0x46, 0xd8, 0xa5, 0x3e, 0xcd, 0x01, 0x5b, 0x36, 0x69, 0xa9, 0xb6, 0x2a, 0x66, 0x51, 0x38,
+                               0x74, 0x96, 0x70, 0x95, 0xfd, 0x51, 0x6c, 0xb9, 0xed, 0xc3, 0x73, 0x26, 0x23, 0x04, 0x76, 0x72,
+                               0x77, 0x76, 0xba, 0x37, 0x48, 0xb9, 0x72, 0xcf, 0x94, 0x45, 0xf8, 0x87, 0xb2, 0x31, 0x64, 0xf7,
+                               0x00, 0x16, 0x55, 0xb8, 0xc6, 0xb0, 0xde, 0xdf, 0xd8, 0xa4, 0x0f, 0xac, 0xa0, 0xd4, 0x4f, 0x67,
+                               0xb5, 0x43, 0x76, 0xc9, 0xd2, 0xed, 0xf0, 0xb6, 0xa1, 0x47, 0x23, 0xa9, 0x7b, 0x01, 0x84, 0x62,
+                               0x4d, 0x80, 0x51, 0x16, 0xc5, 0x3c, 0x5a, 0xae, 0x1f, 0xdd, 0x16, 0xae, 0x66, 0xf8, 0x48, 0x87,
+                               0xc6, 0xe2, 0x05, 0x3b, 0x31, 0x15, 0x23, 0xdc, 0x89, 0x2c, 0xb3, 0xcc, 0xfe, 0x6b, 0x24, 0x06,
+                               0x32, 0xdf, 0xfd, 0x00, 0x61, 0xa9, 0xe3, 0x56, 0xc7, 0x06, 0xe5, 0x86, 0x3f, 0x2b, 0x84, 0x32,
+                               0x29, 0x17, 0xda, 0xa9, 0xc0, 0x12, 0x59, 0xc5, 0x02, 0x08, 0xb3, 0x2a, 0x71, 0x29, 0x3f, 0xaf,
+                               0x8b, 0xa9, 0x08, 0xe8, 0x8c, 0x0b, 0x67, 0x94, 0x50, 0x3a, 0x92, 0x23, 0x83, 0x26, 0x4b, 0xcf,
+                               0xe0, 0xa9, 0x46, 0xc6, 0xc1, 0x27, 0xbf, 0xc2, 0x6a, 0x18, 0x71, 0x2d, 0x59, 0xc6, 0xfc, 0x25,
+                               0xe0, 0x04, 0x6b, 0x3e, 0xda, 0x85, 0xec, 0x78, 0x03, 0x31, 0x64, 0x6d, 0xc6, 0x24, 0xdf, 0xbd,
+                               0x55, 0x2e, 0x65, 0x56, 0x5b, 0x1f, 0x6e, 0x09, 0x70, 0x75, 0x5a, 0xcf, 0x2d, 0xcc, 0xa3, 0xe7,
+                               0xa2, 0x1e, 0xe1, 0x85, 0x21, 0x02, 0x81, 0x81, 0x00, 0xe1, 0x8d, 0xf2, 0x86, 0xf3, 0xf2, 0x88,
+                               0xc6, 0xa3, 0x65, 0x67, 0x49, 0x06, 0x52, 0x54, 0x2a, 0xb8, 0xe9, 0x5a, 0x53, 0x5d, 0x65, 0xb8,
+                               0x6e, 0xea, 0x23, 0x1b, 0x6d, 0x26, 0xfd, 0xa1, 0x0f, 0x3f, 0x7e, 0xb0, 0x25, 0xbb, 0x16, 0x89,
+                               0xef, 0x30, 0xab, 0xea, 0x6d, 0x9f, 0x4d, 0x0b, 0x86, 0xcd, 0x5f, 0xa1, 0x89, 0x5d, 0x1f, 0x1f,
+                               0xb4, 0xd7, 0x2a, 0xbc, 0xd2, 0x97, 0x44, 0x42, 0xc6, 0x5a, 0x21, 0x38, 0xbd, 0x30, 0x64, 0xbf,
+                               0x2d, 0x06, 0x10, 0x41, 0xe7, 0xdc, 0xbd, 0x65, 0xc8, 0x46, 0x3c, 0x83, 0xa0, 0x15, 0x1a, 0x64,
+                               0x45, 0xe2, 0xab, 0x57, 0x82, 0x14, 0x04, 0x80, 0x83, 0x85, 0xf2, 0x91, 0x11, 0x96, 0x83, 0x1b,
+                               0x9b, 0x24, 0x20, 0x7d, 0xeb, 0xdf, 0x2c, 0x0f, 0x56, 0xef, 0x1f, 0x46, 0x8f, 0x7f, 0x56, 0xaf,
+                               0x4a, 0xd2, 0xc3, 0x56, 0x5e, 0x65, 0x18, 0xf6, 0x61, 0x02, 0x81, 0x81, 0x00, 0xb6, 0xa7, 0xd9,
+                               0x57, 0x20, 0x65, 0x3c, 0x56, 0x89, 0x96, 0x38, 0x34, 0x53, 0xe6, 0x61, 0xa9, 0xad, 0x91, 0x35,
+                               0x65, 0xd8, 0x67, 0xe8, 0xe5, 0x1f, 0xe2, 0x37, 0x4b, 0xae, 0xf4, 0xfb, 0xa3, 0x3e, 0x26, 0x04,
+                               0xc9, 0x1a, 0xac, 0xb6, 0x6a, 0xf0, 0xf7, 0x59, 0x8e, 0xa8, 0xa1, 0x12, 0xe2, 0x8b, 0x60, 0xb0,
+                               0x2c, 0xd7, 0xfb, 0x68, 0xcc, 0x44, 0x08, 0x7d, 0x09, 0xad, 0xdf, 0x0c, 0xc2, 0x9b, 0x99, 0x0d,
+                               0x82, 0x44, 0x83, 0x82, 0x98, 0x3f, 0xdd, 0x8b, 0xfd, 0x6e, 0x75, 0x04, 0x87, 0x4f, 0xa8, 0x53,
+                               0xb9, 0x2e, 0xa3, 0xfb, 0xd1, 0xc2, 0x31, 0x68, 0xb0, 0x0c, 0xb3, 0x6c, 0xe5, 0xa8, 0x9e, 0x3f,
+                               0xd7, 0x01, 0x3c, 0x65, 0xdb, 0xde, 0x04, 0xd3, 0x53, 0x8e, 0x61, 0x84, 0x7a, 0x28, 0x0d, 0x0a,
+                               0xa0, 0x6a, 0x2d, 0x61, 0xf5, 0x3c, 0x3f, 0x9d, 0xd5, 0xc1, 0x06, 0xd9, 0x6d, 0x02, 0x81, 0x80,
+                               0x61, 0xbe, 0x11, 0x8b, 0x49, 0xcb, 0xa0, 0x59, 0xf4, 0x14, 0xcd, 0x5d, 0x67, 0xcf, 0xe5, 0x89,
+                               0x5d, 0xf8, 0x41, 0x9c, 0x3e, 0xad, 0x05, 0xef, 0x8a, 0xa7, 0x0c, 0x0b, 0x91, 0x09, 0xf7, 0xf1,
+                               0x93, 0xa2, 0x62, 0xd2, 0xf6, 0xf0, 0x1d, 0x75, 0xf9, 0xb1, 0x86, 0x35, 0x5b, 0x03, 0x18, 0x2b,
+                               0xa2, 0x73, 0xfe, 0x9c, 0x78, 0x2e, 0x34, 0x9f, 0x39, 0x5e, 0xdb, 0xd4, 0xa6, 0x98, 0x7e, 0x17,
+                               0x56, 0x97, 0x6e, 0xef, 0x0d, 0x39, 0x9b, 0x5e, 0xf7, 0x71, 0xb4, 0x1d, 0xb6, 0x1a, 0x9d, 0x30,
+                               0xdc, 0xbe, 0xfc, 0x8b, 0x32, 0x99, 0xed, 0x3a, 0x1b, 0xb0, 0x04, 0x8b, 0xea, 0x00, 0xc9, 0x70,
+                               0x35, 0x6b, 0x98, 0x51, 0xf6, 0xd7, 0x79, 0xef, 0x11, 0x08, 0xc3, 0x88, 0xa3, 0x7d, 0x84, 0x68,
+                               0x37, 0xd7, 0x65, 0xcc, 0x69, 0x72, 0x37, 0x24, 0x45, 0x91, 0x40, 0xac, 0x47, 0x43, 0x3a, 0x01,
+                               0x02, 0x81, 0x80, 0x55, 0xa4, 0x0d, 0x7b, 0x94, 0x6c, 0xe3, 0x05, 0x90, 0x2d, 0x26, 0xdd, 0x22,
+                               0x19, 0x7a, 0xe1, 0x1b, 0xc8, 0x61, 0xc9, 0x06, 0xd6, 0x71, 0x94, 0x3c, 0x86, 0xcc, 0x76, 0x67,
+                               0xe9, 0xe5, 0xb2, 0x7c, 0x89, 0x46, 0xe5, 0x23, 0xe4, 0xa7, 0x03, 0x2d, 0x48, 0x4c, 0x6d, 0x5f,
+                               0x13, 0x66, 0xcf, 0x61, 0x9a, 0x4c, 0x00, 0x3a, 0xa4, 0x5e, 0x56, 0xa7, 0xef, 0x72, 0xf0, 0xfe,
+                               0xdc, 0xfe, 0x16, 0xa0, 0xed, 0xfa, 0x2f, 0x82, 0x00, 0x47, 0x53, 0x2b, 0x5e, 0xad, 0x2c, 0x7e,
+                               0x85, 0xcf, 0x41, 0xc2, 0x4f, 0xa2, 0x31, 0xc9, 0x74, 0x89, 0xb5, 0xa7, 0xdf, 0x92, 0xbe, 0x38,
+                               0xb6, 0x47, 0xf0, 0xc6, 0x46, 0xac, 0x98, 0x1a, 0xb4, 0x3e, 0xee, 0x00, 0x03, 0x33, 0x32, 0x92,
+                               0x27, 0x96, 0xfd, 0xcf, 0x99, 0x8d, 0xc9, 0xf8, 0xfe, 0x9e, 0x57, 0x65, 0x90, 0x4e, 0x68, 0xd2,
+                               0xc0, 0x6b, 0x61, 0x02, 0x81, 0x80, 0x60, 0x82, 0x40, 0x89, 0x81, 0x7e, 0xdf, 0x45, 0xa6, 0x18,
+                               0x04, 0xb0, 0xb8, 0x8a, 0xda, 0xb5, 0xaa, 0xf5, 0xd1, 0x45, 0xf9, 0x87, 0x7b, 0xe2, 0xc3, 0x80,
+                               0xc8, 0x6c, 0x2d, 0xf9, 0x5d, 0x29, 0xff, 0xdb, 0xd4, 0xa9, 0x65, 0x4c, 0xa3, 0x3a, 0x63, 0xf5,
+                               0xab, 0xa3, 0xe4, 0x3d, 0x26, 0x33, 0x75, 0x89, 0x4d, 0x5e, 0x75, 0xf2, 0x27, 0x19, 0x35, 0xe0,
+                               0x82, 0x85, 0x15, 0xf8, 0x0c, 0x94, 0x9e, 0x67, 0xb9, 0xbe, 0xab, 0xe6, 0x0e, 0xad, 0xc1, 0xd5,
+                               0x8d, 0x17, 0x9d, 0x01, 0xd6, 0xac, 0xe8, 0xdf, 0x79, 0xdf, 0x56, 0xd2, 0xf9, 0x5a, 0x75, 0x05,
+                               0x85, 0x02, 0x16, 0x97, 0xa4, 0x9d, 0xa9, 0x9c, 0xbc, 0x89, 0x6f, 0x05, 0x1a, 0x6d, 0x25, 0xc8,
+                               0x96, 0xe1, 0x8a, 0x5a, 0x58, 0x6a, 0xa3, 0x7c, 0xfe, 0xf3, 0x56, 0x32, 0x77, 0xf1, 0xc7, 0xce,
+                               0x3d, 0xba, 0x9c, 0xd9, 0xb5, 0x1a
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_RSASHA256 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xd7, 0x0d, 0xf0, 0xf1, 0xd6, 0x32, 0xfa, 0xce, 0x93, 0x1c, 0x28, 0x51,
+                               0xab, 0xa6, 0x7b, 0xec, 0x94, 0x0a, 0xa0, 0x96, 0x9a, 0xfe, 0x8a, 0x4f, 0x0a, 0x12, 0xa2, 0xda,
+                               0x2d, 0xe5, 0x79, 0xa1, 0xe5, 0xd8, 0x67, 0x12, 0xd0, 0x1e, 0xeb, 0xc1, 0x21, 0x48, 0x9b, 0xf6,
+                               0x37, 0xe2, 0x6d, 0x39, 0x02, 0xf5, 0xfa, 0x20, 0x28, 0x19, 0x40, 0x89, 0xf6, 0x55, 0xa2, 0xe5,
+                               0xbd, 0xd7, 0x0c, 0xa5, 0x98, 0xb2, 0x64, 0xdb, 0x29, 0xd9, 0x8e, 0x6b, 0xfa, 0xc3, 0x3c, 0xa9,
+                               0xa8, 0xfb, 0x36, 0x0b, 0x41, 0xe4, 0x42, 0xea, 0x82, 0xcc, 0xbd, 0xff, 0x1a, 0x8a, 0x74, 0x1e,
+                               0xe8, 0xcd, 0xe8, 0x20, 0x5e, 0xc2, 0x7a, 0x17, 0xfb, 0x99, 0x1e, 0xfb, 0xe2, 0x73, 0xff, 0x38,
+                               0x7a, 0x63, 0x95, 0x74, 0xb4, 0xe2, 0x1c, 0xb2, 0xc3, 0x62, 0xac, 0x88, 0x92, 0x8a, 0x5a, 0xb7,
+                               0xe8, 0x83, 0x09, 0xf9, 0xb5, 0xef, 0x74, 0x4e, 0x4f, 0xce, 0x60, 0xf0, 0x14, 0xcf, 0xc3, 0xc7,
+                               0x8f, 0x11, 0x2a, 0x85, 0x5b, 0x9a, 0xe2, 0x4e, 0x14, 0x14, 0xc1, 0x03, 0xf9, 0xc3, 0x00, 0x45,
+                               0xd2, 0x44, 0xa9, 0xd6, 0x4a, 0x9c, 0x10, 0x8f, 0x61, 0x6b, 0x7e, 0x21, 0x47, 0x22, 0x93, 0x72,
+                               0x81, 0x3e, 0x34, 0x81, 0x0a, 0xae, 0xe4, 0x66, 0x4a, 0xea, 0xaa, 0x38, 0x97, 0x35, 0x01, 0x07,
+                               0xab, 0x70, 0x5d, 0x2e, 0x4c, 0x4c, 0x95, 0x41, 0x7c, 0x07, 0x6d, 0x50, 0x60, 0x9c, 0x50, 0xc3,
+                               0x22, 0x8c, 0x46, 0xc3, 0x36, 0x70, 0x42, 0xcf, 0x2b, 0x0b, 0x1a, 0xd8, 0x57, 0x17, 0x36, 0x6f,
+                               0x59, 0xe8, 0xb7, 0x93, 0x1e, 0x16, 0x08, 0xea, 0xca, 0x2d, 0x19, 0x2f, 0x6b, 0x3c, 0xca, 0xd7,
+                               0x0d, 0xd5, 0x0a, 0x07, 0x9d, 0x86, 0xe5, 0xfe, 0xa6, 0x95, 0x24, 0x6e, 0x9d, 0x4d, 0x74, 0x10,
+                               0x89, 0x05, 0xf4, 0xa3
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd7, 0x0d, 0xf0, 0xf1,
+                               0xd6, 0x32, 0xfa, 0xce, 0x93, 0x1c, 0x28, 0x51, 0xab, 0xa6, 0x7b, 0xec, 0x94, 0x0a, 0xa0, 0x96,
+                               0x9a, 0xfe, 0x8a, 0x4f, 0x0a, 0x12, 0xa2, 0xda, 0x2d, 0xe5, 0x79, 0xa1, 0xe5, 0xd8, 0x67, 0x12,
+                               0xd0, 0x1e, 0xeb, 0xc1, 0x21, 0x48, 0x9b, 0xf6, 0x37, 0xe2, 0x6d, 0x39, 0x02, 0xf5, 0xfa, 0x20,
+                               0x28, 0x19, 0x40, 0x89, 0xf6, 0x55, 0xa2, 0xe5, 0xbd, 0xd7, 0x0c, 0xa5, 0x98, 0xb2, 0x64, 0xdb,
+                               0x29, 0xd9, 0x8e, 0x6b, 0xfa, 0xc3, 0x3c, 0xa9, 0xa8, 0xfb, 0x36, 0x0b, 0x41, 0xe4, 0x42, 0xea,
+                               0x82, 0xcc, 0xbd, 0xff, 0x1a, 0x8a, 0x74, 0x1e, 0xe8, 0xcd, 0xe8, 0x20, 0x5e, 0xc2, 0x7a, 0x17,
+                               0xfb, 0x99, 0x1e, 0xfb, 0xe2, 0x73, 0xff, 0x38, 0x7a, 0x63, 0x95, 0x74, 0xb4, 0xe2, 0x1c, 0xb2,
+                               0xc3, 0x62, 0xac, 0x88, 0x92, 0x8a, 0x5a, 0xb7, 0xe8, 0x83, 0x09, 0xf9, 0xb5, 0xef, 0x74, 0x4e,
+                               0x4f, 0xce, 0x60, 0xf0, 0x14, 0xcf, 0xc3, 0xc7, 0x8f, 0x11, 0x2a, 0x85, 0x5b, 0x9a, 0xe2, 0x4e,
+                               0x14, 0x14, 0xc1, 0x03, 0xf9, 0xc3, 0x00, 0x45, 0xd2, 0x44, 0xa9, 0xd6, 0x4a, 0x9c, 0x10, 0x8f,
+                               0x61, 0x6b, 0x7e, 0x21, 0x47, 0x22, 0x93, 0x72, 0x81, 0x3e, 0x34, 0x81, 0x0a, 0xae, 0xe4, 0x66,
+                               0x4a, 0xea, 0xaa, 0x38, 0x97, 0x35, 0x01, 0x07, 0xab, 0x70, 0x5d, 0x2e, 0x4c, 0x4c, 0x95, 0x41,
+                               0x7c, 0x07, 0x6d, 0x50, 0x60, 0x9c, 0x50, 0xc3, 0x22, 0x8c, 0x46, 0xc3, 0x36, 0x70, 0x42, 0xcf,
+                               0x2b, 0x0b, 0x1a, 0xd8, 0x57, 0x17, 0x36, 0x6f, 0x59, 0xe8, 0xb7, 0x93, 0x1e, 0x16, 0x08, 0xea,
+                               0xca, 0x2d, 0x19, 0x2f, 0x6b, 0x3c, 0xca, 0xd7, 0x0d, 0xd5, 0x0a, 0x07, 0x9d, 0x86, 0xe5, 0xfe,
+                               0xa6, 0x95, 0x24, 0x6e, 0x9d, 0x4d, 0x74, 0x10, 0x89, 0x05, 0xf4, 0xa3, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x1c, 0x54, 0x60, 0x7d, 0x3b, 0x33, 0xbe, 0xb3, 0xca, 0x7a, 0xac,
+                               0x6f, 0xb5, 0xd1, 0x04, 0xaf, 0x87, 0x01, 0x04, 0xb9, 0x52, 0x49, 0xb3, 0x26, 0x9e, 0x5e, 0x68,
+                               0xc8, 0x47, 0xc0, 0xdc, 0x63, 0xbb, 0x72, 0x3a, 0x81, 0xd1, 0x46, 0x2c, 0x2b, 0x10, 0xf0, 0xb9,
+                               0x10, 0x42, 0x64, 0x62, 0xf1, 0xb3, 0xb6, 0x26, 0x46, 0x68, 0xd7, 0x35, 0x6d, 0x4c, 0x8a, 0x13,
+                               0x74, 0xcd, 0xac, 0x05, 0x4f, 0xd0, 0x97, 0x0f, 0x8f, 0x8a, 0x2a, 0x78, 0x07, 0x39, 0x78, 0x26,
+                               0xb1, 0xa2, 0xea, 0x6a, 0x40, 0x29, 0x64, 0xa1, 0xd1, 0x60, 0x27, 0x0b, 0x3b, 0x0e, 0x8a, 0xab,
+                               0xfe, 0xb6, 0x64, 0x5e, 0xee, 0x84, 0xb7, 0xd1, 0xd0, 0x54, 0x50, 0x8e, 0x33, 0x03, 0x03, 0x37,
+                               0x87, 0xd5, 0x22, 0x46, 0x8f, 0x4d, 0x3f, 0x76, 0xd0, 0x5b, 0x1f, 0x7b, 0x38, 0x5d, 0x35, 0x8f,
+                               0x50, 0x7d, 0x77, 0x65, 0xaf, 0x70, 0x62, 0x27, 0x1a, 0x8c, 0xc0, 0xc9, 0xa3, 0x45, 0x70, 0xcb,
+                               0x7a, 0xf0, 0x29, 0x73, 0x06, 0x0c, 0x8d, 0x50, 0x48, 0x40, 0x86, 0x0c, 0x35, 0xc3, 0x4e, 0x50,
+                               0x20, 0xce, 0xbc, 0xc9, 0x74, 0x8d, 0x8c, 0xca, 0x0d, 0xc1, 0xbd, 0x34, 0x36, 0xbe, 0x76, 0x4d,
+                               0x68, 0x94, 0x37, 0xef, 0x1c, 0x42, 0x74, 0xa4, 0xe7, 0x39, 0x01, 0x08, 0xe0, 0x1d, 0xe9, 0xf8,
+                               0x3e, 0x0c, 0x6b, 0x91, 0x4b, 0x65, 0xa4, 0x27, 0x1e, 0x5b, 0xef, 0x35, 0xf0, 0x8a, 0xb8, 0x5b,
+                               0x47, 0x53, 0xf3, 0x22, 0xe3, 0x5b, 0x39, 0xb8, 0x1a, 0x61, 0x10, 0x14, 0x42, 0x78, 0xab, 0xf3,
+                               0xfe, 0xc8, 0xcc, 0x02, 0xc7, 0x57, 0xbe, 0xf8, 0xa5, 0xc8, 0x7a, 0xe1, 0x1a, 0x30, 0xfd, 0x82,
+                               0x25, 0x46, 0x50, 0x13, 0x51, 0x0d, 0x41, 0x7d, 0xc0, 0x98, 0xae, 0x39, 0x13, 0x90, 0x55, 0x50,
+                               0x23, 0x09, 0xca, 0xec, 0x21, 0x02, 0x81, 0x81, 0x00, 0xef, 0x38, 0x43, 0x33, 0xaa, 0x87, 0x3d,
+                               0x28, 0x52, 0x3c, 0xfc, 0xaf, 0xc4, 0xf3, 0x12, 0xeb, 0x16, 0x31, 0x1e, 0x1d, 0x30, 0xa5, 0xb4,
+                               0x1a, 0x43, 0x2d, 0xf7, 0xe4, 0x9f, 0x5c, 0x69, 0xf0, 0xe4, 0x99, 0xd6, 0x72, 0x75, 0xe7, 0x26,
+                               0x3e, 0x5f, 0xe8, 0xaf, 0xb0, 0x36, 0xb6, 0x01, 0xbb, 0xec, 0x7f, 0xed, 0x48, 0xbf, 0x51, 0x4e,
+                               0x15, 0x6f, 0xdc, 0x41, 0xcc, 0x17, 0xfd, 0xbd, 0xae, 0x4e, 0x1d, 0x12, 0xb8, 0x3b, 0xd4, 0xba,
+                               0xf3, 0x96, 0x85, 0xba, 0x88, 0x29, 0x25, 0x0a, 0xe4, 0xf7, 0x53, 0x9a, 0x6b, 0xe2, 0xb0, 0x9b,
+                               0xaf, 0x77, 0x13, 0x16, 0x5b, 0xf7, 0x11, 0xf0, 0xed, 0x79, 0xe4, 0xff, 0xad, 0x27, 0x71, 0x12,
+                               0x11, 0xd1, 0x36, 0xf3, 0x31, 0x74, 0xbc, 0x2d, 0x50, 0x66, 0x9a, 0x03, 0x8c, 0xd4, 0x8b, 0x4c,
+                               0xa0, 0x3e, 0x4d, 0x61, 0x62, 0x7d, 0x06, 0x46, 0xdd, 0x02, 0x81, 0x81, 0x00, 0xe6, 0x23, 0xbc,
+                               0x36, 0x7b, 0xd2, 0xd4, 0xbe, 0xa7, 0x87, 0x3e, 0xc7, 0x43, 0x90, 0x0f, 0xa1, 0x8e, 0xf8, 0x63,
+                               0xd5, 0x4c, 0x2d, 0x65, 0xd6, 0x28, 0x2b, 0x35, 0xb6, 0xfc, 0xd0, 0x34, 0xa0, 0xc0, 0xb4, 0xa0,
+                               0xd5, 0x34, 0xe3, 0x69, 0x20, 0x39, 0x6e, 0x70, 0x11, 0x79, 0x72, 0x64, 0xdb, 0x73, 0x00, 0x9b,
+                               0x97, 0x89, 0xda, 0x10, 0xb4, 0xeb, 0xfa, 0xd2, 0xaf, 0x29, 0x5c, 0xc9, 0x99, 0x02, 0xfb, 0x9d,
+                               0x81, 0x36, 0x9f, 0x86, 0x71, 0xcd, 0xa8, 0xd0, 0x33, 0x53, 0xb6, 0x06, 0x7f, 0xb0, 0xf2, 0x18,
+                               0xbf, 0x86, 0xd3, 0xdf, 0xe8, 0xcc, 0x4b, 0x63, 0xe1, 0x08, 0x4b, 0xd1, 0x2e, 0xd6, 0x7e, 0x1e,
+                               0xf6, 0xac, 0x66, 0xbd, 0x63, 0x34, 0x06, 0xef, 0x7b, 0xe9, 0xac, 0x20, 0xa5, 0x53, 0xb6, 0x31,
+                               0xf8, 0x54, 0xc8, 0x1e, 0xd7, 0x11, 0x20, 0x66, 0x68, 0x7b, 0xf2, 0xb1, 0x7f, 0x02, 0x81, 0x80,
+                               0x09, 0xa2, 0xea, 0x92, 0x1a, 0x3c, 0x78, 0x6a, 0x37, 0x65, 0xdd, 0x87, 0xf7, 0x17, 0x13, 0x9c,
+                               0x04, 0xb8, 0xd5, 0xcd, 0xb7, 0x37, 0xf5, 0x99, 0x59, 0x3b, 0x70, 0xd0, 0xf4, 0xda, 0x74, 0x2a,
+                               0x10, 0x0b, 0x62, 0x68, 0x19, 0xf7, 0xc5, 0xc2, 0x9d, 0x7a, 0x72, 0x19, 0x57, 0x1a, 0xdd, 0xd7,
+                               0x04, 0x1f, 0xe3, 0x1f, 0x1b, 0x15, 0xdb, 0x5f, 0x61, 0xb2, 0x2d, 0xb4, 0x5b, 0x03, 0x0d, 0xb1,
+                               0x07, 0xd4, 0x83, 0xfc, 0x87, 0xec, 0x79, 0xdb, 0x76, 0x24, 0xcc, 0xee, 0xcf, 0x39, 0x7e, 0xc6,
+                               0xda, 0x6f, 0xb3, 0xb8, 0xc4, 0xd8, 0xf8, 0x6c, 0x89, 0x0f, 0x6b, 0xc7, 0x28, 0x9c, 0xc0, 0x67,
+                               0xd0, 0x91, 0x38, 0xfb, 0x70, 0xab, 0x4d, 0x5f, 0x0a, 0x5e, 0x7c, 0x4d, 0x71, 0xb6, 0x62, 0xdc,
+                               0xea, 0x45, 0x29, 0xff, 0x72, 0x50, 0x6a, 0x35, 0xca, 0x4c, 0xb3, 0xaf, 0x93, 0xf9, 0xbc, 0xc1,
+                               0x02, 0x81, 0x80, 0x34, 0xdf, 0xc6, 0xf1, 0x39, 0x97, 0xa1, 0xbb, 0x3f, 0xe0, 0x29, 0xc3, 0x3c,
+                               0x39, 0x8f, 0xed, 0xcd, 0x5c, 0x32, 0x0b, 0xa2, 0x59, 0xd7, 0xc8, 0xd1, 0x27, 0xc4, 0x4a, 0x8b,
+                               0x61, 0x06, 0x86, 0x5c, 0x5f, 0xe3, 0x87, 0xb8, 0x83, 0xfa, 0xf6, 0x25, 0x5b, 0xc3, 0x31, 0xb0,
+                               0x96, 0x60, 0xa7, 0xae, 0x8f, 0x58, 0x30, 0x03, 0xc2, 0xef, 0x0d, 0xb2, 0x1d, 0xc9, 0x63, 0xe6,
+                               0xfb, 0xe3, 0x71, 0x5d, 0x27, 0x13, 0xf3, 0xc8, 0xe9, 0xf3, 0xc8, 0x97, 0xb8, 0x8e, 0x45, 0x0c,
+                               0x99, 0x88, 0x7a, 0xb3, 0xdb, 0x9b, 0x65, 0xfd, 0x83, 0x16, 0x31, 0x75, 0x26, 0x69, 0x07, 0x1d,
+                               0x91, 0xd5, 0xb8, 0x8e, 0x1d, 0x21, 0xde, 0x08, 0x93, 0xfe, 0x35, 0x19, 0x91, 0xf2, 0x59, 0xa4,
+                               0xc1, 0x20, 0x2a, 0xfb, 0xc5, 0xa3, 0x4b, 0x6b, 0xa7, 0x22, 0xb6, 0xc4, 0xbf, 0x8d, 0xe7, 0xb9,
+                               0x7a, 0x73, 0xdf, 0x02, 0x81, 0x80, 0x76, 0x65, 0x88, 0x7f, 0xe6, 0xef, 0x64, 0x90, 0xf8, 0x9a,
+                               0x2d, 0x3a, 0xd0, 0xff, 0xb1, 0x50, 0x00, 0x9f, 0x3a, 0xfd, 0xa9, 0x22, 0xea, 0x17, 0x03, 0x9f,
+                               0xfb, 0x78, 0x3d, 0xb2, 0x60, 0xf9, 0x7a, 0x58, 0xcf, 0x21, 0xdc, 0xb7, 0x18, 0x14, 0x9b, 0xb3,
+                               0x54, 0x86, 0x15, 0x0d, 0x47, 0xfc, 0xc8, 0x23, 0x61, 0x47, 0x7b, 0x97, 0xdc, 0x13, 0x01, 0xe5,
+                               0x73, 0xd6, 0x75, 0xf3, 0xf3, 0x28, 0x1d, 0xa6, 0x3b, 0xcc, 0x3f, 0xc0, 0x5d, 0xab, 0xb2, 0xe0,
+                               0x03, 0xd6, 0x11, 0xe7, 0x0b, 0xc8, 0xfe, 0xb0, 0xcd, 0x21, 0x66, 0x23, 0x48, 0x7f, 0xd0, 0x05,
+                               0x56, 0xcf, 0x76, 0x9a, 0xfb, 0x6c, 0x09, 0xb7, 0x84, 0x3c, 0xcc, 0xfb, 0xbc, 0xa1, 0x57, 0xaf,
+                               0x9a, 0x70, 0x4d, 0x25, 0x07, 0xc3, 0xa3, 0xc3, 0x38, 0x93, 0x11, 0x7c, 0x9e, 0xb2, 0xd1, 0x57,
+                               0xb5, 0x3b, 0x64, 0x7e, 0xa1, 0x9a
+                       }
+               }
+       },
+       // DNSSEC Zone 3
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_RSASHA256 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xa9, 0x2a, 0x95, 0x5f, 0x4b, 0x1a, 0x18, 0x95, 0x58, 0xdb, 0xac, 0x74,
+                               0x5a, 0x24, 0x0d, 0x1e, 0x29, 0x5f, 0x0e, 0x46, 0x96, 0x84, 0x39, 0x23, 0x63, 0xcc, 0x8c, 0x06,
+                               0x49, 0xb2, 0x13, 0x58, 0xf3, 0xe1, 0x5e, 0x16, 0xc4, 0xfe, 0x89, 0xac, 0xcd, 0x7b, 0xd7, 0xbe,
+                               0x66, 0x46, 0xba, 0x33, 0x71, 0xbe, 0xd8, 0x5f, 0x4d, 0x97, 0xc6, 0x97, 0xe9, 0x09, 0x18, 0xa0,
+                               0x66, 0xe4, 0x74, 0xff, 0x46, 0xf5, 0xab, 0x1e, 0x89, 0xdf, 0x2c, 0x47, 0x2d, 0x9b, 0xc0, 0x2d,
+                               0x2e, 0x7b, 0xd2, 0xc4, 0x87, 0xc8, 0x5f, 0x75, 0xb9, 0xb8, 0xf1, 0xfa, 0x8a, 0x32, 0x06, 0x40,
+                               0x70, 0x73, 0x4e, 0x34, 0x43, 0x3a, 0x41, 0x4a, 0xff, 0xe4, 0x6c, 0x4d, 0x75, 0xa0, 0x0c, 0x2b,
+                               0x9f, 0x22, 0xcc, 0x27, 0x19, 0xc5, 0x93, 0x55, 0x3a, 0x81, 0xaf, 0x4f, 0x9c, 0xa7, 0x09, 0x1e,
+                               0xe5, 0x04, 0x22, 0x41, 0xb7, 0x1c, 0x64, 0x1c, 0x9a, 0x1f, 0xf1, 0x52, 0x73, 0xb7, 0x38, 0xa2,
+                               0x75, 0xe2, 0x2f, 0x38, 0x71, 0x11, 0x9a, 0x91, 0x8f, 0x62, 0x8e, 0xd3, 0xb3, 0x29, 0x78, 0xe4,
+                               0x99, 0xf7, 0xbd, 0x61, 0x92, 0x6f, 0xcc, 0xe9, 0xf8, 0x09, 0xd0, 0xb4, 0x2f, 0x9e, 0x86, 0x4d,
+                               0xf3, 0x8a, 0xe0, 0x72, 0x8d, 0x87, 0x44, 0xfd, 0x38, 0xf6, 0x9d, 0x58, 0x10, 0xe1, 0x58, 0xf5,
+                               0x2a, 0xf8, 0x50, 0x18, 0xf2, 0x9c, 0x02, 0x42, 0xb4, 0x10, 0x92, 0xf6, 0xac, 0xae, 0x50, 0x6c,
+                               0xac, 0x3f, 0xb9, 0x54, 0xab, 0xa3, 0x1d, 0x20, 0xde, 0x81, 0xe2, 0xec, 0xf2, 0x0b, 0xed, 0x51,
+                               0x55, 0xb6, 0xdc, 0x47, 0x87, 0xac, 0x67, 0x55, 0x5c, 0x02, 0xa8, 0x21, 0x57, 0x53, 0x5c, 0x2a,
+                               0xaf, 0x70, 0x70, 0xa9, 0x52, 0x5c, 0xea, 0x6f, 0xb5, 0x57, 0xaf, 0xc3, 0x11, 0x46, 0xdb, 0xa1,
+                               0x4e, 0x38, 0x54, 0x39
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa9, 0x2a, 0x95, 0x5f,
+                               0x4b, 0x1a, 0x18, 0x95, 0x58, 0xdb, 0xac, 0x74, 0x5a, 0x24, 0x0d, 0x1e, 0x29, 0x5f, 0x0e, 0x46,
+                               0x96, 0x84, 0x39, 0x23, 0x63, 0xcc, 0x8c, 0x06, 0x49, 0xb2, 0x13, 0x58, 0xf3, 0xe1, 0x5e, 0x16,
+                               0xc4, 0xfe, 0x89, 0xac, 0xcd, 0x7b, 0xd7, 0xbe, 0x66, 0x46, 0xba, 0x33, 0x71, 0xbe, 0xd8, 0x5f,
+                               0x4d, 0x97, 0xc6, 0x97, 0xe9, 0x09, 0x18, 0xa0, 0x66, 0xe4, 0x74, 0xff, 0x46, 0xf5, 0xab, 0x1e,
+                               0x89, 0xdf, 0x2c, 0x47, 0x2d, 0x9b, 0xc0, 0x2d, 0x2e, 0x7b, 0xd2, 0xc4, 0x87, 0xc8, 0x5f, 0x75,
+                               0xb9, 0xb8, 0xf1, 0xfa, 0x8a, 0x32, 0x06, 0x40, 0x70, 0x73, 0x4e, 0x34, 0x43, 0x3a, 0x41, 0x4a,
+                               0xff, 0xe4, 0x6c, 0x4d, 0x75, 0xa0, 0x0c, 0x2b, 0x9f, 0x22, 0xcc, 0x27, 0x19, 0xc5, 0x93, 0x55,
+                               0x3a, 0x81, 0xaf, 0x4f, 0x9c, 0xa7, 0x09, 0x1e, 0xe5, 0x04, 0x22, 0x41, 0xb7, 0x1c, 0x64, 0x1c,
+                               0x9a, 0x1f, 0xf1, 0x52, 0x73, 0xb7, 0x38, 0xa2, 0x75, 0xe2, 0x2f, 0x38, 0x71, 0x11, 0x9a, 0x91,
+                               0x8f, 0x62, 0x8e, 0xd3, 0xb3, 0x29, 0x78, 0xe4, 0x99, 0xf7, 0xbd, 0x61, 0x92, 0x6f, 0xcc, 0xe9,
+                               0xf8, 0x09, 0xd0, 0xb4, 0x2f, 0x9e, 0x86, 0x4d, 0xf3, 0x8a, 0xe0, 0x72, 0x8d, 0x87, 0x44, 0xfd,
+                               0x38, 0xf6, 0x9d, 0x58, 0x10, 0xe1, 0x58, 0xf5, 0x2a, 0xf8, 0x50, 0x18, 0xf2, 0x9c, 0x02, 0x42,
+                               0xb4, 0x10, 0x92, 0xf6, 0xac, 0xae, 0x50, 0x6c, 0xac, 0x3f, 0xb9, 0x54, 0xab, 0xa3, 0x1d, 0x20,
+                               0xde, 0x81, 0xe2, 0xec, 0xf2, 0x0b, 0xed, 0x51, 0x55, 0xb6, 0xdc, 0x47, 0x87, 0xac, 0x67, 0x55,
+                               0x5c, 0x02, 0xa8, 0x21, 0x57, 0x53, 0x5c, 0x2a, 0xaf, 0x70, 0x70, 0xa9, 0x52, 0x5c, 0xea, 0x6f,
+                               0xb5, 0x57, 0xaf, 0xc3, 0x11, 0x46, 0xdb, 0xa1, 0x4e, 0x38, 0x54, 0x39, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x1d, 0x63, 0x7d, 0xfe, 0xc7, 0xa3, 0xd9, 0x78, 0x7c, 0xae, 0xf1,
+                               0x37, 0x67, 0x7f, 0x92, 0xdd, 0xd6, 0x0f, 0x23, 0x02, 0x45, 0x5c, 0xe4, 0x36, 0x4f, 0x3c, 0xb6,
+                               0x68, 0xd3, 0x3d, 0xc2, 0x19, 0xb6, 0x88, 0xc5, 0x19, 0x4c, 0x90, 0xf1, 0xe7, 0x18, 0xed, 0xb6,
+                               0x6d, 0xe8, 0x03, 0xca, 0x8e, 0x6c, 0x68, 0xdc, 0xdb, 0x35, 0xb6, 0x33, 0xf4, 0xa9, 0x32, 0x00,
+                               0xda, 0xc0, 0xf5, 0x58, 0x94, 0x0e, 0xc6, 0x58, 0x3b, 0x9e, 0x75, 0x2c, 0x92, 0x93, 0x0c, 0x57,
+                               0x67, 0x22, 0x07, 0xb5, 0xf6, 0x99, 0xa6, 0x48, 0x5b, 0x41, 0xf0, 0x2c, 0xed, 0xbb, 0x3b, 0x74,
+                               0x95, 0x96, 0x53, 0x99, 0xd6, 0xff, 0x84, 0xeb, 0xd9, 0xf5, 0x39, 0xc0, 0x4c, 0xf6, 0xad, 0xde,
+                               0xdf, 0x67, 0x2e, 0x9a, 0xec, 0x02, 0x10, 0xae, 0x87, 0x64, 0x7a, 0x2a, 0x34, 0x39, 0x01, 0x55,
+                               0xd6, 0xcd, 0xaf, 0x99, 0xa9, 0xab, 0x97, 0x7d, 0x0c, 0x1a, 0x32, 0x61, 0x82, 0xcd, 0x60, 0xe0,
+                               0x6b, 0x80, 0x55, 0x50, 0x61, 0x4b, 0x87, 0x16, 0x50, 0x7f, 0x7b, 0x37, 0x92, 0xda, 0xe9, 0x4e,
+                               0xbe, 0x3a, 0xed, 0x64, 0xae, 0xf6, 0x3c, 0xb9, 0x19, 0x3e, 0x25, 0xc4, 0x91, 0x3a, 0x90, 0xbc,
+                               0x8d, 0x2c, 0x12, 0xad, 0xdb, 0xb1, 0x5f, 0x7a, 0x60, 0xe2, 0xd8, 0x13, 0x61, 0x79, 0x45, 0xdf,
+                               0xad, 0x4d, 0x4f, 0x3f, 0xf0, 0xd8, 0xfd, 0xd5, 0xc3, 0x29, 0x71, 0x2a, 0xf8, 0x6a, 0xf6, 0x55,
+                               0x71, 0x3a, 0xa6, 0xb2, 0xf9, 0x62, 0xd7, 0x45, 0x01, 0x00, 0x39, 0x15, 0xbd, 0x5d, 0x4f, 0x18,
+                               0x69, 0x99, 0x67, 0xa9, 0x60, 0xa3, 0x4d, 0x43, 0x68, 0x13, 0x4d, 0xca, 0x52, 0xb0, 0x66, 0x3d,
+                               0xf6, 0xd7, 0x6d, 0x13, 0x7d, 0x17, 0xab, 0x74, 0xac, 0x63, 0x2b, 0xa5, 0xdd, 0xce, 0xe4, 0xaa,
+                               0xfc, 0x98, 0x3c, 0xa8, 0x0f, 0x02, 0x81, 0x81, 0x00, 0xdb, 0x55, 0xe8, 0xb7, 0x5e, 0x3e, 0xab,
+                               0x3a, 0xff, 0x73, 0xdc, 0xb3, 0xc6, 0x79, 0x93, 0x1f, 0xb1, 0xfe, 0x8c, 0xc8, 0x1b, 0x45, 0x5c,
+                               0xcb, 0x3c, 0x00, 0x51, 0x00, 0xd5, 0x3b, 0x91, 0x57, 0x0b, 0x14, 0xad, 0x1e, 0x5c, 0x0a, 0x43,
+                               0x46, 0x4f, 0x42, 0x92, 0x9a, 0x9c, 0xd0, 0x0d, 0x1e, 0x13, 0x60, 0xed, 0xee, 0x1f, 0x99, 0x6d,
+                               0x3b, 0xc1, 0xb9, 0xc9, 0x28, 0xb4, 0x1a, 0x3b, 0xdd, 0x2d, 0x91, 0xa3, 0xea, 0x8c, 0xb5, 0xd6,
+                               0xd6, 0x4d, 0x83, 0x48, 0x45, 0xcb, 0xdb, 0xc6, 0xff, 0xef, 0x5c, 0xbd, 0x5b, 0x82, 0xde, 0x70,
+                               0x0d, 0xac, 0x35, 0x19, 0xd7, 0xdd, 0xf2, 0x6c, 0xaf, 0x29, 0xb4, 0xf2, 0x8e, 0xe8, 0xc6, 0xf4,
+                               0x9a, 0x10, 0x3b, 0x01, 0xd3, 0xa0, 0xde, 0x95, 0x6a, 0xbe, 0xa8, 0x5b, 0xb8, 0x53, 0xea, 0xde,
+                               0x8b, 0xd1, 0x8f, 0xe1, 0x57, 0x44, 0x65, 0x6a, 0x3f, 0x02, 0x81, 0x81, 0x00, 0xc5, 0x71, 0xc4,
+                               0x83, 0x86, 0xcf, 0x8d, 0x18, 0x24, 0x75, 0xf4, 0x4d, 0x8d, 0xc9, 0x6f, 0x7c, 0x07, 0xa2, 0xa5,
+                               0x1b, 0x78, 0xa3, 0xcf, 0x72, 0x75, 0x78, 0x83, 0x21, 0xc7, 0x0f, 0x37, 0x88, 0xb9, 0xe2, 0xe1,
+                               0x27, 0x54, 0x90, 0x62, 0x11, 0x45, 0x20, 0x15, 0x6e, 0x01, 0x19, 0x99, 0xa8, 0xd5, 0xae, 0x69,
+                               0x7c, 0xff, 0xdd, 0xff, 0x79, 0x98, 0x4d, 0x02, 0xdc, 0x42, 0x99, 0xbe, 0x06, 0x58, 0xf3, 0xc6,
+                               0xe3, 0x20, 0x97, 0x21, 0x7f, 0x39, 0xa2, 0xa9, 0xfe, 0xdf, 0xc3, 0x30, 0x6b, 0xb2, 0x4f, 0x87,
+                               0x64, 0xea, 0x72, 0xc8, 0x05, 0xe3, 0xa6, 0x32, 0xa5, 0xe3, 0xfb, 0x02, 0xe2, 0xcf, 0x5c, 0x00,
+                               0xd3, 0x34, 0x9a, 0xe0, 0x57, 0x4a, 0x4f, 0xde, 0xeb, 0x61, 0x45, 0x3d, 0xd8, 0xf4, 0x21, 0x49,
+                               0x35, 0x52, 0x33, 0xbd, 0x29, 0x11, 0x00, 0xd4, 0xde, 0x63, 0x3d, 0x73, 0x87, 0x02, 0x81, 0x80,
+                               0x19, 0x7d, 0xfd, 0xa3, 0x3a, 0x47, 0xec, 0x5c, 0xda, 0x0d, 0xdc, 0x4b, 0xe4, 0xb8, 0x82, 0x99,
+                               0xff, 0x75, 0x07, 0x65, 0xc6, 0x8d, 0xa4, 0x40, 0x56, 0xc3, 0xee, 0xa6, 0x3b, 0x22, 0x46, 0x3d,
+                               0x28, 0xa3, 0x89, 0x8f, 0x6a, 0xc1, 0x22, 0x9a, 0x8c, 0x61, 0xbf, 0x6a, 0x4a, 0xe9, 0x7f, 0x6c,
+                               0xcc, 0x71, 0xca, 0x95, 0x8f, 0x84, 0xa4, 0x86, 0x7a, 0x78, 0x9e, 0x61, 0xa5, 0xb5, 0x1a, 0xda,
+                               0x15, 0x0e, 0xc5, 0x4f, 0x60, 0x75, 0x4d, 0xf7, 0xf0, 0x60, 0x5e, 0xc9, 0x05, 0xed, 0x90, 0x4e,
+                               0x74, 0xc2, 0x81, 0x9b, 0x9e, 0x35, 0x3e, 0x2e, 0xc8, 0xa2, 0x0f, 0x53, 0xe0, 0x8e, 0xe9, 0x84,
+                               0xde, 0x43, 0x12, 0xa1, 0xfb, 0x7a, 0x0c, 0x39, 0xcf, 0xc3, 0x1b, 0xee, 0x3d, 0xd7, 0x40, 0xe4,
+                               0x7c, 0x2e, 0x53, 0x22, 0xb4, 0x56, 0x2a, 0xf1, 0x73, 0x34, 0xd6, 0xca, 0xee, 0xbf, 0x1e, 0xbf,
+                               0x02, 0x81, 0x80, 0x79, 0xc3, 0x0f, 0x4d, 0x8d, 0x75, 0xb1, 0xad, 0x91, 0xce, 0xac, 0x3a, 0xa7,
+                               0x95, 0xf2, 0x0a, 0xab, 0x2a, 0xc2, 0x67, 0x8e, 0x9e, 0x76, 0xf0, 0x3f, 0x41, 0x72, 0x9d, 0x02,
+                               0x05, 0x80, 0x59, 0xf1, 0x48, 0xd9, 0x51, 0x47, 0xf5, 0x42, 0x85, 0x23, 0xfc, 0x05, 0x3b, 0x31,
+                               0xb2, 0x77, 0xf0, 0x86, 0xd6, 0x68, 0x77, 0x8d, 0xd3, 0x7a, 0x19, 0xa2, 0xb2, 0xdb, 0x70, 0xba,
+                               0x21, 0xd5, 0xee, 0x54, 0x2d, 0x02, 0x1a, 0x4f, 0x9e, 0xe4, 0x29, 0x71, 0x81, 0xbf, 0x14, 0x4f,
+                               0x2a, 0x30, 0x52, 0x3a, 0x44, 0x91, 0x5a, 0xca, 0xba, 0xbe, 0x5f, 0xe3, 0x64, 0x3b, 0x17, 0xd2,
+                               0xe0, 0x99, 0x87, 0x06, 0x19, 0xd6, 0xe8, 0x69, 0x28, 0x99, 0xf1, 0x3b, 0x02, 0xc6, 0x96, 0xa2,
+                               0x05, 0xf1, 0x7e, 0x3a, 0x0f, 0xcc, 0xf5, 0xf5, 0x1b, 0xb3, 0x2c, 0x00, 0x30, 0x63, 0x16, 0x42,
+                               0x5d, 0x43, 0x41, 0x02, 0x81, 0x80, 0x2c, 0x82, 0xb7, 0x40, 0x91, 0xa6, 0xf8, 0xd2, 0xf1, 0x25,
+                               0x76, 0x23, 0x8d, 0x33, 0xc8, 0x8e, 0xdf, 0x36, 0xb1, 0x5e, 0x58, 0xfb, 0x91, 0x7d, 0x1a, 0x74,
+                               0xb7, 0xb3, 0x8e, 0x8d, 0xce, 0x16, 0xf7, 0xef, 0x45, 0xb5, 0xb2, 0x80, 0xc4, 0x59, 0x64, 0x97,
+                               0xf0, 0x09, 0xfa, 0xe6, 0x5a, 0x6d, 0x10, 0x6e, 0x74, 0x7d, 0xa7, 0x39, 0xc5, 0x18, 0x07, 0x59,
+                               0xb2, 0x6d, 0xf7, 0x55, 0x36, 0x17, 0x1f, 0x3e, 0x36, 0x74, 0x3b, 0x2e, 0x43, 0x1d, 0x5d, 0x5b,
+                               0xa8, 0x04, 0x99, 0x50, 0x1f, 0xf4, 0x06, 0x25, 0x20, 0x49, 0x89, 0x55, 0x94, 0xf7, 0x5f, 0x0e,
+                               0x93, 0x22, 0xd4, 0x2b, 0x76, 0x3d, 0xb0, 0xd6, 0xf7, 0x3d, 0x4c, 0x39, 0x8e, 0x4b, 0x60, 0x55,
+                               0x17, 0xa2, 0xa3, 0x94, 0xe0, 0x0b, 0x1e, 0x32, 0xbb, 0x8a, 0xf2, 0x34, 0x0f, 0x6a, 0x2e, 0xa0,
+                               0x5c, 0x11, 0xae, 0x59, 0x1b, 0xb0
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_RSASHA256 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xc1, 0x61, 0xce, 0x9c, 0xa6, 0x1c, 0xb8, 0xe5, 0xfd, 0xe6, 0x67, 0x34,
+                               0x07, 0x21, 0x8b, 0xfe, 0xe6, 0x84, 0xbc, 0x97, 0x22, 0xaa, 0xcf, 0x90, 0x2e, 0x88, 0x77, 0x66,
+                               0xd6, 0xf7, 0x24, 0xc6, 0xa1, 0x93, 0x04, 0x92, 0x56, 0x89, 0x2a, 0xb0, 0x35, 0xda, 0x06, 0x9a,
+                               0x76, 0x97, 0xf5, 0xb2, 0x98, 0x80, 0x1f, 0x6e, 0x87, 0x29, 0xff, 0xda, 0x67, 0x27, 0xa7, 0x58,
+                               0x09, 0x32, 0x6d, 0xff, 0x37, 0x31, 0xba, 0x92, 0x88, 0xa1, 0x59, 0xfb, 0x69, 0x4f, 0xa2, 0x26,
+                               0x8e, 0xa1, 0xa8, 0x46, 0x83, 0xe8, 0x90, 0x53, 0xfc, 0x91, 0x9c, 0xe2, 0x54, 0xd8, 0x23, 0x08,
+                               0x29, 0x00, 0x4f, 0x10, 0xc3, 0xff, 0x4b, 0xc9, 0xc4, 0xf9, 0xe5, 0x7d, 0xfb, 0xcd, 0x89, 0x6f,
+                               0x13, 0x4b, 0xc2, 0xda, 0xe3, 0x8b, 0xaa, 0x9c, 0xfd, 0x18, 0x8b, 0xc3, 0x5e, 0x32, 0x40, 0xe9,
+                               0x25, 0x3b, 0x0f, 0xdf, 0x6f, 0x26, 0x3e, 0xb8, 0xe8, 0xd3, 0x5a, 0x38, 0x27, 0xfc, 0xde, 0x6b,
+                               0xbf, 0xb8, 0x5b, 0x5c, 0x8f, 0x38, 0x4f, 0x31, 0x8b, 0x69, 0x76, 0x9d, 0x29, 0x20, 0x17, 0xcc,
+                               0x22, 0x1f, 0x99, 0x33, 0x47, 0x0f, 0x6e, 0xf8, 0xd1, 0x47, 0x28, 0xf5, 0x99, 0x28, 0xad, 0x42,
+                               0xab, 0xac, 0xfc, 0x29, 0x27, 0x90, 0xc1, 0xfc, 0xb8, 0x5f, 0x47, 0xc2, 0xa0, 0x69, 0x39, 0x32,
+                               0x75, 0x5c, 0x0a, 0x4c, 0x98, 0x52, 0xeb, 0xfc, 0x53, 0xaf, 0x16, 0x62, 0x93, 0xcc, 0x63, 0x39,
+                               0x5a, 0x2d, 0x1a, 0x3d, 0x60, 0xf4, 0x22, 0x8d, 0x8d, 0x70, 0xc3, 0xab, 0x2c, 0xf2, 0x91, 0xb3,
+                               0xe5, 0xc6, 0x80, 0x43, 0x3e, 0xd2, 0x9e, 0xf1, 0x28, 0xce, 0xec, 0x98, 0xbc, 0x99, 0x2f, 0x98,
+                               0xbb, 0xf3, 0x29, 0xa9, 0x59, 0x48, 0x18, 0x54, 0xa8, 0x6d, 0x2b, 0xb8, 0xf4, 0x56, 0xb3, 0x79,
+                               0x87, 0xa4, 0xd5, 0x2f
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc1, 0x61, 0xce, 0x9c,
+                               0xa6, 0x1c, 0xb8, 0xe5, 0xfd, 0xe6, 0x67, 0x34, 0x07, 0x21, 0x8b, 0xfe, 0xe6, 0x84, 0xbc, 0x97,
+                               0x22, 0xaa, 0xcf, 0x90, 0x2e, 0x88, 0x77, 0x66, 0xd6, 0xf7, 0x24, 0xc6, 0xa1, 0x93, 0x04, 0x92,
+                               0x56, 0x89, 0x2a, 0xb0, 0x35, 0xda, 0x06, 0x9a, 0x76, 0x97, 0xf5, 0xb2, 0x98, 0x80, 0x1f, 0x6e,
+                               0x87, 0x29, 0xff, 0xda, 0x67, 0x27, 0xa7, 0x58, 0x09, 0x32, 0x6d, 0xff, 0x37, 0x31, 0xba, 0x92,
+                               0x88, 0xa1, 0x59, 0xfb, 0x69, 0x4f, 0xa2, 0x26, 0x8e, 0xa1, 0xa8, 0x46, 0x83, 0xe8, 0x90, 0x53,
+                               0xfc, 0x91, 0x9c, 0xe2, 0x54, 0xd8, 0x23, 0x08, 0x29, 0x00, 0x4f, 0x10, 0xc3, 0xff, 0x4b, 0xc9,
+                               0xc4, 0xf9, 0xe5, 0x7d, 0xfb, 0xcd, 0x89, 0x6f, 0x13, 0x4b, 0xc2, 0xda, 0xe3, 0x8b, 0xaa, 0x9c,
+                               0xfd, 0x18, 0x8b, 0xc3, 0x5e, 0x32, 0x40, 0xe9, 0x25, 0x3b, 0x0f, 0xdf, 0x6f, 0x26, 0x3e, 0xb8,
+                               0xe8, 0xd3, 0x5a, 0x38, 0x27, 0xfc, 0xde, 0x6b, 0xbf, 0xb8, 0x5b, 0x5c, 0x8f, 0x38, 0x4f, 0x31,
+                               0x8b, 0x69, 0x76, 0x9d, 0x29, 0x20, 0x17, 0xcc, 0x22, 0x1f, 0x99, 0x33, 0x47, 0x0f, 0x6e, 0xf8,
+                               0xd1, 0x47, 0x28, 0xf5, 0x99, 0x28, 0xad, 0x42, 0xab, 0xac, 0xfc, 0x29, 0x27, 0x90, 0xc1, 0xfc,
+                               0xb8, 0x5f, 0x47, 0xc2, 0xa0, 0x69, 0x39, 0x32, 0x75, 0x5c, 0x0a, 0x4c, 0x98, 0x52, 0xeb, 0xfc,
+                               0x53, 0xaf, 0x16, 0x62, 0x93, 0xcc, 0x63, 0x39, 0x5a, 0x2d, 0x1a, 0x3d, 0x60, 0xf4, 0x22, 0x8d,
+                               0x8d, 0x70, 0xc3, 0xab, 0x2c, 0xf2, 0x91, 0xb3, 0xe5, 0xc6, 0x80, 0x43, 0x3e, 0xd2, 0x9e, 0xf1,
+                               0x28, 0xce, 0xec, 0x98, 0xbc, 0x99, 0x2f, 0x98, 0xbb, 0xf3, 0x29, 0xa9, 0x59, 0x48, 0x18, 0x54,
+                               0xa8, 0x6d, 0x2b, 0xb8, 0xf4, 0x56, 0xb3, 0x79, 0x87, 0xa4, 0xd5, 0x2f, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x4a, 0xc4, 0x60, 0x88, 0x43, 0x57, 0x46, 0x8b, 0xb1, 0xb3, 0x73,
+                               0xc2, 0x73, 0x61, 0x5e, 0x89, 0xb2, 0xa1, 0xae, 0x72, 0xb6, 0xb6, 0x18, 0xe3, 0x33, 0x82, 0x92,
+                               0xbb, 0xa1, 0xb0, 0x53, 0x39, 0x32, 0xfe, 0x2f, 0x1d, 0xcf, 0x34, 0x60, 0x5b, 0x59, 0xae, 0xce,
+                               0xaf, 0xc8, 0xdb, 0x6e, 0xe4, 0xda, 0xbe, 0x6a, 0xa8, 0xa6, 0xbc, 0xaf, 0xcd, 0xc6, 0xe5, 0x57,
+                               0x77, 0x9b, 0xb2, 0xb2, 0x41, 0xa3, 0x1a, 0xe8, 0x7a, 0x40, 0x36, 0x36, 0x1e, 0x94, 0xb4, 0x73,
+                               0xca, 0x79, 0x89, 0xfa, 0x10, 0x2d, 0x6e, 0xec, 0xe2, 0x73, 0xfb, 0xee, 0x8e, 0xb9, 0x64, 0x09,
+                               0x83, 0x5d, 0xaa, 0xdf, 0x66, 0xe9, 0x88, 0x9a, 0xca, 0x4b, 0x8c, 0x9c, 0xd4, 0xad, 0x56, 0xe5,
+                               0xe4, 0x9a, 0xae, 0xc7, 0x8b, 0x2e, 0xbf, 0xf0, 0xba, 0x9a, 0xc6, 0x73, 0x3f, 0x19, 0x7b, 0xcf,
+                               0x89, 0xf4, 0xd9, 0x95, 0x84, 0x4f, 0x16, 0x86, 0xb1, 0x3f, 0xac, 0x6e, 0x6a, 0xfc, 0x5d, 0x29,
+                               0x8d, 0x33, 0xa5, 0x2d, 0x4c, 0x5a, 0xa1, 0x31, 0x81, 0xe9, 0xb6, 0x12, 0x4e, 0x3d, 0x2f, 0xef,
+                               0xfb, 0x99, 0x48, 0x64, 0xc5, 0xb6, 0x6e, 0xaf, 0x39, 0x5f, 0x94, 0x03, 0xcd, 0xd3, 0x4b, 0xbf,
+                               0x58, 0x6d, 0x17, 0x79, 0x72, 0x93, 0x04, 0xbe, 0xbc, 0x1b, 0x82, 0x2a, 0x11, 0x51, 0xe7, 0x05,
+                               0x06, 0x3b, 0xa7, 0xf9, 0xbc, 0x34, 0x75, 0xd7, 0x3f, 0x3a, 0x37, 0x00, 0x68, 0xa9, 0x4c, 0xd0,
+                               0xc6, 0x8d, 0x6c, 0xfb, 0x2a, 0x74, 0x00, 0x33, 0xda, 0x1e, 0x14, 0xe9, 0x73, 0xb5, 0x4e, 0x17,
+                               0x2e, 0x12, 0x42, 0xf4, 0x9f, 0x01, 0x0b, 0x04, 0xae, 0x53, 0xf0, 0x89, 0xd5, 0x9a, 0x8f, 0x0d,
+                               0xfa, 0x0c, 0xfd, 0x48, 0x6a, 0x09, 0xd9, 0xf7, 0x00, 0x1c, 0x57, 0x10, 0x1e, 0x43, 0xcb, 0x93,
+                               0xc7, 0x57, 0x78, 0xff, 0xfd, 0x02, 0x81, 0x81, 0x00, 0xe2, 0x73, 0x4f, 0xb1, 0xf1, 0x1b, 0x13,
+                               0x7f, 0xd2, 0x62, 0xa3, 0x8e, 0xc3, 0x23, 0xf0, 0xe2, 0xfa, 0x07, 0x49, 0x5f, 0x65, 0xac, 0xdf,
+                               0xf4, 0x9f, 0xf4, 0x9e, 0x7a, 0x0e, 0xf8, 0x7e, 0x8f, 0x60, 0xad, 0x02, 0xb6, 0xd0, 0xf3, 0xb8,
+                               0x4d, 0xdb, 0x2a, 0x16, 0xc4, 0xb8, 0x43, 0x98, 0x1b, 0x89, 0x47, 0xd9, 0x91, 0x3c, 0x06, 0x67,
+                               0xf1, 0xc9, 0xd3, 0x33, 0xb9, 0x82, 0x28, 0x9e, 0xa4, 0x9c, 0xf8, 0xc2, 0x89, 0x9a, 0x05, 0xde,
+                               0xfc, 0xc2, 0xbc, 0x60, 0x2c, 0x04, 0x81, 0xe0, 0x26, 0x64, 0xd4, 0x01, 0xac, 0xaa, 0x36, 0x81,
+                               0x0e, 0xc1, 0x97, 0xc7, 0xaa, 0xb9, 0x24, 0x8e, 0xc5, 0x2f, 0x3c, 0xcb, 0x8a, 0x80, 0x69, 0x94,
+                               0x3b, 0xea, 0xf8, 0x7d, 0x64, 0x96, 0xd8, 0x55, 0x12, 0x95, 0x2c, 0x88, 0x15, 0x2e, 0x19, 0x47,
+                               0x4b, 0x2d, 0x00, 0x02, 0x26, 0xa4, 0xa5, 0xd2, 0xdb, 0x02, 0x81, 0x81, 0x00, 0xda, 0x9d, 0xd4,
+                               0x90, 0x6c, 0x4f, 0x18, 0x8f, 0x32, 0x43, 0xd7, 0xe6, 0x0f, 0xf8, 0x11, 0xc6, 0x07, 0x86, 0x1e,
+                               0xe1, 0x7c, 0xa6, 0x4e, 0x44, 0x08, 0xc6, 0xcb, 0x34, 0xea, 0x0e, 0xdc, 0x62, 0x1b, 0x16, 0xbf,
+                               0x91, 0x78, 0x52, 0xa3, 0x56, 0xee, 0x41, 0xba, 0x2e, 0x07, 0xb3, 0x2d, 0xde, 0x85, 0xc6, 0xb8,
+                               0x73, 0x8b, 0xae, 0x5b, 0x51, 0x46, 0x86, 0xac, 0xe3, 0xdc, 0x3a, 0xa7, 0xd0, 0x5a, 0x2e, 0x2e,
+                               0x39, 0xec, 0xe8, 0x3d, 0x2a, 0x3d, 0xb2, 0x8d, 0x2d, 0xea, 0x5a, 0x30, 0x2d, 0xe0, 0x85, 0xa0,
+                               0x6e, 0xe7, 0x68, 0xf6, 0x3f, 0xf2, 0x7b, 0x61, 0xdd, 0x79, 0x39, 0x85, 0xda, 0x72, 0xad, 0x5f,
+                               0xc4, 0x2f, 0xdd, 0xb3, 0xac, 0x83, 0x95, 0x7c, 0x7a, 0x95, 0xb6, 0x34, 0x18, 0xa7, 0xba, 0x4d,
+                               0x2a, 0xbf, 0x68, 0x16, 0xca, 0x3b, 0xe4, 0x0a, 0x70, 0x9c, 0xe2, 0xf5, 0x3d, 0x02, 0x81, 0x80,
+                               0x63, 0x1e, 0x72, 0x0d, 0xc3, 0x29, 0x44, 0xd9, 0xb8, 0x2e, 0xf0, 0xc4, 0x76, 0x69, 0xee, 0xf0,
+                               0x8a, 0xdc, 0x51, 0xa3, 0x6e, 0x0f, 0xc3, 0x5f, 0x81, 0xfc, 0x42, 0xb9, 0xce, 0x7f, 0xba, 0x75,
+                               0xeb, 0xad, 0x0e, 0xf9, 0x12, 0x70, 0xfb, 0x85, 0x28, 0x9f, 0x3d, 0xa4, 0x11, 0xbb, 0x94, 0x82,
+                               0xc9, 0x0e, 0x28, 0x0f, 0x48, 0x24, 0xcd, 0xae, 0xa9, 0xd6, 0xc6, 0x57, 0x36, 0xbf, 0xac, 0xe1,
+                               0x04, 0xcc, 0x65, 0xea, 0x70, 0xfe, 0x8c, 0xe2, 0x3a, 0x22, 0xd6, 0x3d, 0xae, 0x23, 0x63, 0x07,
+                               0xab, 0x2e, 0x99, 0x25, 0x08, 0xc4, 0x1e, 0xad, 0x64, 0xd3, 0x98, 0xd4, 0x03, 0x82, 0x1a, 0xf3,
+                               0xf8, 0x7f, 0x35, 0xe0, 0x83, 0xe0, 0xb2, 0xbf, 0x9f, 0x53, 0xf1, 0x1e, 0xec, 0x5f, 0xf8, 0xac,
+                               0xcf, 0x9a, 0xd4, 0x5d, 0xe0, 0xf5, 0xb3, 0x9d, 0x16, 0x2f, 0x60, 0xc1, 0xa5, 0x63, 0xe7, 0xed,
+                               0x02, 0x81, 0x80, 0x0d, 0xea, 0xb6, 0x39, 0x6b, 0x6b, 0xad, 0x98, 0x90, 0x0e, 0x99, 0x93, 0xdf,
+                               0xb8, 0x5f, 0x09, 0x48, 0x39, 0x55, 0x85, 0xed, 0x35, 0x79, 0x0e, 0x03, 0xb1, 0x04, 0x06, 0x9e,
+                               0x4f, 0xcb, 0xdf, 0xc2, 0xf1, 0xb3, 0xc8, 0x42, 0xec, 0x9f, 0xd1, 0x4c, 0xe1, 0x8a, 0x44, 0x9b,
+                               0xe0, 0xe0, 0x2e, 0xa6, 0x3d, 0x7c, 0x48, 0x7e, 0xbf, 0xde, 0xb8, 0x51, 0xd1, 0x08, 0xf8, 0x88,
+                               0x70, 0x83, 0x76, 0x54, 0x07, 0x54, 0x92, 0x03, 0x6e, 0xd5, 0x55, 0xf7, 0x0f, 0x82, 0xc5, 0x45,
+                               0x81, 0xf0, 0x47, 0x3b, 0x5f, 0xc6, 0x52, 0xc9, 0x10, 0x79, 0x7c, 0xa0, 0xa7, 0x12, 0x17, 0xd3,
+                               0x3b, 0xc2, 0x35, 0xa1, 0xcf, 0x3f, 0xa5, 0x71, 0x96, 0x9f, 0x75, 0x7f, 0xfa, 0xe8, 0x2d, 0xb5,
+                               0x92, 0x1f, 0xc6, 0xe2, 0x06, 0x8e, 0x1b, 0x06, 0x8c, 0x4d, 0xc7, 0x1a, 0x00, 0xee, 0x59, 0x34,
+                               0xe3, 0x8d, 0xd1, 0x02, 0x81, 0x80, 0x44, 0x9b, 0x6e, 0xb6, 0x1e, 0x84, 0x3c, 0xab, 0x43, 0x3e,
+                               0x20, 0x9a, 0xd2, 0x94, 0x39, 0x38, 0x01, 0x25, 0x67, 0x59, 0x45, 0x2d, 0xcd, 0x7a, 0xcd, 0xea,
+                               0x72, 0x2a, 0x32, 0x71, 0x66, 0x82, 0x04, 0x85, 0xc9, 0x06, 0xf0, 0x26, 0xa6, 0x2d, 0x41, 0x49,
+                               0x3e, 0xa3, 0x68, 0x98, 0xf1, 0x05, 0x00, 0xb5, 0xa0, 0x35, 0x01, 0x39, 0xee, 0x29, 0x1d, 0x8a,
+                               0xea, 0x4e, 0x3b, 0x40, 0x78, 0xe9, 0x97, 0x77, 0x5b, 0xc1, 0x8f, 0xac, 0x2c, 0x45, 0x49, 0x33,
+                               0xc4, 0x33, 0xa4, 0x67, 0xbf, 0xe9, 0x6d, 0xb5, 0x1e, 0x36, 0x0f, 0x6a, 0xb4, 0x8c, 0x77, 0xdd,
+                               0x5a, 0x74, 0x66, 0xf5, 0xb3, 0x9b, 0x78, 0x03, 0x02, 0xf7, 0x21, 0xbd, 0xc2, 0x74, 0x56, 0x10,
+                               0xa2, 0x4f, 0x43, 0xbf, 0xa2, 0xd0, 0xa9, 0xcc, 0xf4, 0xd6, 0xa6, 0x96, 0xae, 0xeb, 0x88, 0x3b,
+                               0x53, 0x5d, 0xe1, 0x71, 0x52, 0x0a
+                       }
+               }
+       }
+};
+
+_DNSKeySetsCompileTimeChecks( RSASHA256 );
+
+//===========================================================================================================================
+// MARK: - RSA/SHA-512 DNS Keys
+
+typedef struct
+{
+       DNSKeyRSASHA512Info             ksk;    // Key-Signing Key
+       DNSKeyRSASHA512Info             zsk;    // Zone-Signing Key
+       
+}      DNSKeyRSASHA512Set;
+
+static const DNSKeyRSASHA512Set                kDNSKeyRSASHA512Sets[] =
+{
+       // DNSSEC Zone 0
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_RSASHA512 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0x9d, 0x14, 0x59, 0x1b, 0x69, 0x68, 0x51, 0xfa, 0xea, 0xb2, 0xd6, 0x77,
+                               0x89, 0x6d, 0xbc, 0xb0, 0x12, 0xd7, 0x88, 0x3b, 0x50, 0x70, 0x6f, 0x89, 0x10, 0x8c, 0x13, 0xeb,
+                               0xe1, 0xc3, 0xa2, 0xc9, 0x87, 0xe6, 0x12, 0x00, 0x9a, 0x2c, 0x7e, 0x56, 0xa0, 0xfc, 0xb5, 0xba,
+                               0xb9, 0xcb, 0xac, 0xac, 0x81, 0x1e, 0x63, 0x40, 0x09, 0xce, 0xd3, 0xd1, 0x38, 0xbc, 0xe9, 0xe0,
+                               0x41, 0xea, 0x86, 0xe9, 0xba, 0x4f, 0xd2, 0x95, 0xc5, 0xa4, 0xad, 0x4a, 0x79, 0xf3, 0x40, 0x57,
+                               0xa4, 0xdf, 0xed, 0x7f, 0x6f, 0x2f, 0x88, 0x1b, 0xf5, 0xff, 0x51, 0xa7, 0xa2, 0x2b, 0x5f, 0xbe,
+                               0xc6, 0x02, 0x25, 0xcf, 0x3c, 0x81, 0x67, 0x7a, 0x12, 0x3a, 0x0f, 0x23, 0x14, 0xbe, 0x20, 0x17,
+                               0x75, 0x1a, 0x99, 0x8c, 0x64, 0x68, 0x95, 0x4e, 0x9d, 0xa0, 0x90, 0xac, 0x48, 0xee, 0xf2, 0x5f,
+                               0x22, 0x35, 0x87, 0x58, 0xa4, 0xfa, 0xed, 0x47, 0xb0, 0xb9, 0xbc, 0xef, 0x60, 0x60, 0x4f, 0x55,
+                               0x8a, 0xf8, 0xb1, 0x87, 0xa2, 0x82, 0x20, 0xf7, 0x29, 0xdb, 0x82, 0x9d, 0xf6, 0xe5, 0x10, 0x20,
+                               0xac, 0xc5, 0x88, 0x61, 0x15, 0xf8, 0xf4, 0x3b, 0x29, 0x17, 0xfc, 0xef, 0x91, 0xe0, 0xee, 0x31,
+                               0xdd, 0xd3, 0x44, 0x55, 0x77, 0x7e, 0xef, 0xdd, 0x9c, 0x2a, 0x02, 0xd4, 0x24, 0x98, 0x59, 0x20,
+                               0x2d, 0x96, 0x67, 0x1f, 0x13, 0xd9, 0xff, 0x6e, 0xec, 0x27, 0xee, 0xa3, 0x74, 0x19, 0x4a, 0xed,
+                               0x3f, 0xed, 0xd6, 0xfc, 0x4c, 0xdb, 0x09, 0x5b, 0xd3, 0xf4, 0x51, 0x22, 0x68, 0xa4, 0x93, 0x7a,
+                               0x46, 0x92, 0x4a, 0x75, 0xc1, 0x58, 0x95, 0xf0, 0x46, 0x6e, 0xda, 0x0f, 0x48, 0x2d, 0x0e, 0x0c,
+                               0xca, 0xd6, 0x21, 0x67, 0x5d, 0x9e, 0xa7, 0x3f, 0xa0, 0x94, 0x57, 0x64, 0xa6, 0xd9, 0x0d, 0x48,
+                               0x55, 0x0f, 0x60, 0x5f
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0x9d, 0x14, 0x59, 0x1b,
+                               0x69, 0x68, 0x51, 0xfa, 0xea, 0xb2, 0xd6, 0x77, 0x89, 0x6d, 0xbc, 0xb0, 0x12, 0xd7, 0x88, 0x3b,
+                               0x50, 0x70, 0x6f, 0x89, 0x10, 0x8c, 0x13, 0xeb, 0xe1, 0xc3, 0xa2, 0xc9, 0x87, 0xe6, 0x12, 0x00,
+                               0x9a, 0x2c, 0x7e, 0x56, 0xa0, 0xfc, 0xb5, 0xba, 0xb9, 0xcb, 0xac, 0xac, 0x81, 0x1e, 0x63, 0x40,
+                               0x09, 0xce, 0xd3, 0xd1, 0x38, 0xbc, 0xe9, 0xe0, 0x41, 0xea, 0x86, 0xe9, 0xba, 0x4f, 0xd2, 0x95,
+                               0xc5, 0xa4, 0xad, 0x4a, 0x79, 0xf3, 0x40, 0x57, 0xa4, 0xdf, 0xed, 0x7f, 0x6f, 0x2f, 0x88, 0x1b,
+                               0xf5, 0xff, 0x51, 0xa7, 0xa2, 0x2b, 0x5f, 0xbe, 0xc6, 0x02, 0x25, 0xcf, 0x3c, 0x81, 0x67, 0x7a,
+                               0x12, 0x3a, 0x0f, 0x23, 0x14, 0xbe, 0x20, 0x17, 0x75, 0x1a, 0x99, 0x8c, 0x64, 0x68, 0x95, 0x4e,
+                               0x9d, 0xa0, 0x90, 0xac, 0x48, 0xee, 0xf2, 0x5f, 0x22, 0x35, 0x87, 0x58, 0xa4, 0xfa, 0xed, 0x47,
+                               0xb0, 0xb9, 0xbc, 0xef, 0x60, 0x60, 0x4f, 0x55, 0x8a, 0xf8, 0xb1, 0x87, 0xa2, 0x82, 0x20, 0xf7,
+                               0x29, 0xdb, 0x82, 0x9d, 0xf6, 0xe5, 0x10, 0x20, 0xac, 0xc5, 0x88, 0x61, 0x15, 0xf8, 0xf4, 0x3b,
+                               0x29, 0x17, 0xfc, 0xef, 0x91, 0xe0, 0xee, 0x31, 0xdd, 0xd3, 0x44, 0x55, 0x77, 0x7e, 0xef, 0xdd,
+                               0x9c, 0x2a, 0x02, 0xd4, 0x24, 0x98, 0x59, 0x20, 0x2d, 0x96, 0x67, 0x1f, 0x13, 0xd9, 0xff, 0x6e,
+                               0xec, 0x27, 0xee, 0xa3, 0x74, 0x19, 0x4a, 0xed, 0x3f, 0xed, 0xd6, 0xfc, 0x4c, 0xdb, 0x09, 0x5b,
+                               0xd3, 0xf4, 0x51, 0x22, 0x68, 0xa4, 0x93, 0x7a, 0x46, 0x92, 0x4a, 0x75, 0xc1, 0x58, 0x95, 0xf0,
+                               0x46, 0x6e, 0xda, 0x0f, 0x48, 0x2d, 0x0e, 0x0c, 0xca, 0xd6, 0x21, 0x67, 0x5d, 0x9e, 0xa7, 0x3f,
+                               0xa0, 0x94, 0x57, 0x64, 0xa6, 0xd9, 0x0d, 0x48, 0x55, 0x0f, 0x60, 0x5f, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x02, 0x96, 0xce, 0x29, 0x31, 0x48, 0xb2, 0xaf, 0x72, 0xfa, 0x00,
+                               0x18, 0x4d, 0x31, 0x98, 0x4c, 0x98, 0x71, 0x21, 0xe9, 0x0b, 0xed, 0xb5, 0x9e, 0x42, 0xbe, 0x01,
+                               0xaa, 0x7f, 0xaa, 0x5c, 0xbb, 0x6d, 0x34, 0xe7, 0x28, 0x64, 0xb7, 0x7e, 0x95, 0x9a, 0xf4, 0xcf,
+                               0x62, 0x04, 0xc0, 0x30, 0xad, 0x89, 0xe4, 0x2e, 0x8e, 0x2b, 0xe2, 0x53, 0x4b, 0x04, 0xa7, 0x48,
+                               0x7d, 0x5c, 0xae, 0x1c, 0xb6, 0x47, 0xc0, 0x8d, 0x11, 0x97, 0xcd, 0x0d, 0x84, 0x64, 0x13, 0xf8,
+                               0x59, 0x87, 0xb2, 0x66, 0x86, 0xa3, 0x6b, 0xa2, 0x41, 0x95, 0x81, 0x8a, 0xbb, 0x3b, 0x81, 0xba,
+                               0x2e, 0x80, 0xcf, 0x3d, 0x24, 0x4d, 0x89, 0xe5, 0x62, 0xf5, 0x7c, 0x2c, 0x64, 0x24, 0xbf, 0x9a,
+                               0xd7, 0x5d, 0x79, 0x36, 0x8b, 0x97, 0x69, 0x6d, 0x71, 0xec, 0xba, 0x50, 0x21, 0x0a, 0x67, 0x27,
+                               0xa3, 0x8f, 0x91, 0x02, 0xbf, 0xa0, 0xe8, 0x84, 0x1b, 0x6f, 0xfc, 0xf2, 0x38, 0xf8, 0x35, 0x38,
+                               0x1e, 0x7b, 0xc4, 0x5e, 0xcc, 0x03, 0xa8, 0xab, 0x6e, 0x2f, 0x99, 0xd3, 0xca, 0x58, 0x6e, 0xd5,
+                               0xe9, 0x08, 0xeb, 0x03, 0xc2, 0x59, 0x0d, 0xb7, 0x7f, 0x8a, 0x76, 0x11, 0x3e, 0x92, 0x32, 0xba,
+                               0x8a, 0x83, 0x5e, 0x92, 0x31, 0xa0, 0x4f, 0x50, 0x7b, 0xa7, 0x49, 0x84, 0x05, 0x7d, 0x96, 0x06,
+                               0xe1, 0x52, 0xf5, 0xce, 0x4f, 0x74, 0x16, 0x64, 0x80, 0x7e, 0x7d, 0x39, 0x90, 0x33, 0x64, 0xd9,
+                               0xa5, 0xf2, 0x44, 0x1e, 0x6d, 0x74, 0xd2, 0xbf, 0xc4, 0xcc, 0x59, 0x91, 0x93, 0xcf, 0x17, 0x9a,
+                               0x6a, 0x97, 0x5b, 0xda, 0x89, 0xb2, 0x70, 0xe7, 0xcb, 0xc5, 0x64, 0xd7, 0x5d, 0x23, 0x02, 0x56,
+                               0x35, 0x54, 0x2f, 0x7b, 0xf2, 0xc8, 0x47, 0xac, 0x61, 0xaf, 0x36, 0x84, 0x92, 0x98, 0x9d, 0x97,
+                               0x14, 0xdb, 0xe7, 0x54, 0xf1, 0x02, 0x81, 0x81, 0x00, 0xcb, 0x0c, 0x61, 0xd8, 0xb7, 0xb2, 0xc9,
+                               0x18, 0x32, 0xfd, 0x4a, 0x3e, 0xf9, 0x22, 0xbe, 0xa2, 0x9b, 0x9f, 0xf9, 0xa2, 0x63, 0x9a, 0xe1,
+                               0x64, 0x53, 0x81, 0x3e, 0x1a, 0x97, 0xf9, 0x6a, 0xf1, 0xc3, 0x59, 0x23, 0xe2, 0x74, 0x09, 0x52,
+                               0x16, 0xfe, 0x35, 0xa5, 0x80, 0x26, 0x9e, 0x5d, 0xbc, 0xb4, 0xeb, 0x88, 0x9e, 0xa1, 0x58, 0x6c,
+                               0x7f, 0x17, 0x0f, 0x2f, 0xa6, 0x2e, 0xc1, 0x61, 0x33, 0xbf, 0x9b, 0xd0, 0xbf, 0x60, 0x09, 0x7d,
+                               0xa4, 0xa1, 0x9b, 0x7a, 0x95, 0x95, 0x47, 0x93, 0x8a, 0xad, 0x5d, 0x3c, 0x49, 0x77, 0x67, 0x3e,
+                               0xe8, 0xa3, 0xa4, 0xd0, 0xce, 0x33, 0xa6, 0x55, 0xf3, 0xa7, 0xdf, 0xbe, 0x62, 0x2b, 0xe4, 0x5d,
+                               0x7f, 0x49, 0x40, 0xbe, 0xb8, 0xac, 0x77, 0x20, 0x40, 0x19, 0xfc, 0x4a, 0xae, 0xfc, 0x42, 0xe4,
+                               0x73, 0xb1, 0xb1, 0xf7, 0x3b, 0x2c, 0x2b, 0xb0, 0xab, 0x02, 0x81, 0x81, 0x00, 0xc6, 0x0b, 0x0e,
+                               0xff, 0xd4, 0x3a, 0xb9, 0x59, 0xc9, 0x98, 0xa8, 0xe4, 0x24, 0xfc, 0xc9, 0x5b, 0x5c, 0x3f, 0x6d,
+                               0x46, 0xe3, 0xd8, 0xb5, 0xb9, 0x23, 0x3d, 0xdf, 0xa4, 0x4c, 0x66, 0x1a, 0x29, 0xaa, 0x8e, 0xd4,
+                               0xa1, 0x57, 0x32, 0x8c, 0x77, 0x2c, 0xd2, 0xf9, 0xb8, 0x44, 0x67, 0xc5, 0xbd, 0x81, 0x6b, 0x08,
+                               0x61, 0x1b, 0x9d, 0x59, 0xb0, 0x26, 0x3e, 0x88, 0x29, 0x90, 0x05, 0xc0, 0x94, 0x05, 0xea, 0x4e,
+                               0x8c, 0x4a, 0x75, 0xf4, 0xe5, 0xa1, 0xf3, 0x0f, 0x74, 0xb4, 0x6d, 0xf5, 0x8c, 0x8d, 0xcc, 0xde,
+                               0x15, 0x8b, 0xc1, 0xa5, 0xac, 0xae, 0x8b, 0xa5, 0xb6, 0xa8, 0xf6, 0x24, 0xcd, 0x63, 0x45, 0x04,
+                               0x5e, 0xaf, 0xf6, 0x37, 0x35, 0x89, 0x2f, 0x89, 0x64, 0x16, 0x7d, 0x4e, 0xaa, 0x14, 0x8c, 0xc5,
+                               0x41, 0x5f, 0x8b, 0x2a, 0xbc, 0xf5, 0xbd, 0x91, 0x05, 0x57, 0xb7, 0x17, 0x1d, 0x02, 0x81, 0x80,
+                               0x11, 0x5c, 0xc9, 0xb1, 0x22, 0x64, 0x23, 0x55, 0xf2, 0x66, 0x3e, 0x47, 0x0c, 0x3e, 0xb0, 0x56,
+                               0x6f, 0x40, 0x4a, 0xb4, 0x5c, 0x18, 0x0e, 0x55, 0xe9, 0xde, 0x0f, 0x55, 0x6e, 0xd1, 0x61, 0x17,
+                               0xb3, 0x40, 0x98, 0x14, 0xf1, 0x1f, 0x2a, 0xe9, 0xd4, 0x6a, 0xf9, 0xef, 0xef, 0x5d, 0x73, 0x5e,
+                               0x83, 0x89, 0xf0, 0x70, 0xc0, 0x13, 0x33, 0x93, 0xda, 0x80, 0xed, 0xee, 0x23, 0xe9, 0x5d, 0x4a,
+                               0x73, 0x83, 0xfb, 0x61, 0xa1, 0xf0, 0xad, 0xd1, 0xba, 0x0f, 0xf5, 0x77, 0x7d, 0x00, 0x55, 0xd9,
+                               0x71, 0xe1, 0x9b, 0x1a, 0x1d, 0x1f, 0xb2, 0xfd, 0x69, 0xa0, 0xda, 0x4a, 0x07, 0x98, 0x9d, 0x98,
+                               0xec, 0x2d, 0xf1, 0xb5, 0xab, 0x53, 0x27, 0xbb, 0x8e, 0xa3, 0xfe, 0xab, 0x03, 0xf4, 0x5b, 0xc9,
+                               0x9f, 0x6f, 0x37, 0x63, 0xa7, 0x26, 0x81, 0x2a, 0x73, 0x73, 0x68, 0x54, 0xa2, 0xfe, 0x49, 0x69,
+                               0x02, 0x81, 0x80, 0x65, 0x10, 0x17, 0xa3, 0x6e, 0x06, 0x59, 0xb7, 0xfc, 0x2c, 0xaf, 0x63, 0x5d,
+                               0x8c, 0xb2, 0xa4, 0xb0, 0xba, 0x9d, 0x65, 0x63, 0xb8, 0x9f, 0x63, 0xcf, 0x1c, 0x30, 0x18, 0x83,
+                               0xf4, 0x46, 0xbd, 0xa4, 0x90, 0x26, 0xe9, 0xfe, 0xb9, 0x88, 0xfc, 0xf8, 0x1e, 0xe1, 0xd8, 0xaa,
+                               0xef, 0xd9, 0xf5, 0x0f, 0x2a, 0x33, 0xa8, 0x7a, 0x93, 0x1d, 0xc5, 0xca, 0x7e, 0x76, 0xb5, 0xdf,
+                               0x0d, 0x50, 0x6d, 0x39, 0x67, 0x8c, 0x96, 0x03, 0x19, 0x96, 0xd3, 0xe8, 0x85, 0x45, 0x09, 0xe5,
+                               0x59, 0x87, 0xc9, 0x26, 0x43, 0xb9, 0xc1, 0x7d, 0x04, 0x61, 0x2b, 0x72, 0x17, 0x49, 0x85, 0xa1,
+                               0x4b, 0x9b, 0x14, 0x44, 0xe8, 0x5f, 0x86, 0x82, 0x7d, 0x3f, 0x47, 0x4e, 0xae, 0xb7, 0x8a, 0x1f,
+                               0xff, 0xea, 0x38, 0xd9, 0x2b, 0xcd, 0x2d, 0xc3, 0x38, 0x1d, 0xa3, 0x38, 0x34, 0x78, 0xe1, 0x25,
+                               0x16, 0x05, 0xf1, 0x02, 0x81, 0x80, 0x19, 0xcb, 0xe3, 0x33, 0xd8, 0x26, 0xad, 0x8b, 0x32, 0xfb,
+                               0xdb, 0x2d, 0x8b, 0xb7, 0x99, 0xd0, 0xf6, 0xc3, 0xa1, 0xf0, 0x6e, 0x53, 0x7c, 0xbf, 0xdf, 0xd5,
+                               0xc4, 0x43, 0x81, 0x09, 0xf5, 0x2a, 0x0b, 0xe9, 0xf4, 0xf3, 0xc1, 0x4e, 0xa4, 0xdc, 0x94, 0x11,
+                               0x17, 0xa7, 0x26, 0x43, 0xee, 0xf3, 0xf0, 0xce, 0x24, 0xb8, 0xcb, 0x86, 0x3a, 0x37, 0xb1, 0xc9,
+                               0x3e, 0x96, 0x68, 0x00, 0xad, 0xf0, 0x9a, 0xd6, 0xc3, 0x25, 0xa6, 0x3a, 0x0c, 0xe6, 0x61, 0xc5,
+                               0x05, 0x80, 0xb6, 0xd6, 0xa7, 0x40, 0xd1, 0xb2, 0xad, 0xb5, 0x35, 0xa4, 0xdd, 0x63, 0x32, 0x5d,
+                               0x09, 0x59, 0x9a, 0x80, 0x00, 0xf6, 0x4f, 0xdf, 0x41, 0xa3, 0xc6, 0x3f, 0xe8, 0xe2, 0x67, 0xa1,
+                               0x87, 0x56, 0x26, 0x1b, 0xc9, 0x8c, 0xe9, 0x24, 0x4d, 0xa5, 0x29, 0xce, 0x37, 0x36, 0xa8, 0xd2,
+                               0x7a, 0x1f, 0x01, 0x10, 0x6c, 0x43
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_RSASHA512 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xc8, 0xc4, 0xc6, 0xc6, 0x72, 0xb0, 0xa6, 0x5e, 0x8a, 0xe2, 0x26, 0x35,
+                               0x1a, 0xad, 0x21, 0x9c, 0x0d, 0x6f, 0xe2, 0x91, 0x95, 0xe7, 0x73, 0xf3, 0xc4, 0x43, 0xe2, 0x86,
+                               0x4a, 0x96, 0xc6, 0x2c, 0x0b, 0x1f, 0xa4, 0xf1, 0x36, 0xc3, 0xa9, 0xcd, 0x53, 0xf5, 0x8e, 0x78,
+                               0x62, 0xad, 0x9e, 0x55, 0x4d, 0x6b, 0xb9, 0xaf, 0xf9, 0x22, 0x88, 0x61, 0xc5, 0xac, 0x57, 0x84,
+                               0x6f, 0x85, 0xf8, 0x0e, 0x66, 0xd8, 0x67, 0x75, 0x69, 0x8e, 0x2d, 0xfa, 0x29, 0x2d, 0xe5, 0xde,
+                               0x1c, 0xe0, 0x9c, 0xa0, 0xcf, 0xf5, 0xec, 0xe2, 0xaa, 0x5a, 0x19, 0x70, 0x87, 0xd0, 0xbe, 0x1a,
+                               0x56, 0x63, 0x74, 0x2a, 0x6f, 0x6f, 0x35, 0xc8, 0x83, 0xe5, 0x8b, 0x44, 0x89, 0x86, 0xa2, 0xbb,
+                               0xb1, 0x16, 0x9a, 0x06, 0x35, 0x21, 0x78, 0xd9, 0x07, 0x27, 0x37, 0xbd, 0x24, 0xca, 0xb9, 0xef,
+                               0x5b, 0x71, 0xd0, 0xc0, 0x8b, 0xec, 0xe4, 0x8d, 0xeb, 0x32, 0x7c, 0x41, 0x5c, 0xe0, 0xda, 0xb2,
+                               0x1c, 0x0f, 0xcd, 0x8b, 0xa7, 0xa8, 0x31, 0x07, 0x4e, 0xd1, 0xe2, 0xd4, 0xae, 0x48, 0x84, 0xa0,
+                               0xee, 0x87, 0x25, 0x3c, 0xdb, 0x1f, 0x85, 0x96, 0xd7, 0x1d, 0x4b, 0x51, 0x4c, 0x92, 0xe5, 0x47,
+                               0x9b, 0x67, 0x79, 0x62, 0x0d, 0xe2, 0xa7, 0x01, 0xab, 0x72, 0x0c, 0x38, 0xed, 0xab, 0x88, 0x86,
+                               0xcf, 0x7e, 0x84, 0xd3, 0x1c, 0x09, 0x26, 0xb3, 0x6b, 0x41, 0x4f, 0x09, 0xef, 0xe5, 0xcc, 0xda,
+                               0x9b, 0x09, 0x55, 0xee, 0x3e, 0xe5, 0x89, 0x11, 0x63, 0x1a, 0x2a, 0xe8, 0xfa, 0xf1, 0x44, 0x9b,
+                               0x92, 0x46, 0x2d, 0x3f, 0x88, 0x26, 0xc1, 0x44, 0xda, 0x9f, 0x02, 0x09, 0x2d, 0x7d, 0xea, 0xc2,
+                               0x63, 0xc5, 0xa7, 0x62, 0x27, 0x42, 0x1a, 0x48, 0xde, 0xf2, 0xa9, 0xf2, 0x80, 0x9b, 0xfc, 0x5b,
+                               0x97, 0xef, 0xfb, 0x6f
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc8, 0xc4, 0xc6, 0xc6,
+                               0x72, 0xb0, 0xa6, 0x5e, 0x8a, 0xe2, 0x26, 0x35, 0x1a, 0xad, 0x21, 0x9c, 0x0d, 0x6f, 0xe2, 0x91,
+                               0x95, 0xe7, 0x73, 0xf3, 0xc4, 0x43, 0xe2, 0x86, 0x4a, 0x96, 0xc6, 0x2c, 0x0b, 0x1f, 0xa4, 0xf1,
+                               0x36, 0xc3, 0xa9, 0xcd, 0x53, 0xf5, 0x8e, 0x78, 0x62, 0xad, 0x9e, 0x55, 0x4d, 0x6b, 0xb9, 0xaf,
+                               0xf9, 0x22, 0x88, 0x61, 0xc5, 0xac, 0x57, 0x84, 0x6f, 0x85, 0xf8, 0x0e, 0x66, 0xd8, 0x67, 0x75,
+                               0x69, 0x8e, 0x2d, 0xfa, 0x29, 0x2d, 0xe5, 0xde, 0x1c, 0xe0, 0x9c, 0xa0, 0xcf, 0xf5, 0xec, 0xe2,
+                               0xaa, 0x5a, 0x19, 0x70, 0x87, 0xd0, 0xbe, 0x1a, 0x56, 0x63, 0x74, 0x2a, 0x6f, 0x6f, 0x35, 0xc8,
+                               0x83, 0xe5, 0x8b, 0x44, 0x89, 0x86, 0xa2, 0xbb, 0xb1, 0x16, 0x9a, 0x06, 0x35, 0x21, 0x78, 0xd9,
+                               0x07, 0x27, 0x37, 0xbd, 0x24, 0xca, 0xb9, 0xef, 0x5b, 0x71, 0xd0, 0xc0, 0x8b, 0xec, 0xe4, 0x8d,
+                               0xeb, 0x32, 0x7c, 0x41, 0x5c, 0xe0, 0xda, 0xb2, 0x1c, 0x0f, 0xcd, 0x8b, 0xa7, 0xa8, 0x31, 0x07,
+                               0x4e, 0xd1, 0xe2, 0xd4, 0xae, 0x48, 0x84, 0xa0, 0xee, 0x87, 0x25, 0x3c, 0xdb, 0x1f, 0x85, 0x96,
+                               0xd7, 0x1d, 0x4b, 0x51, 0x4c, 0x92, 0xe5, 0x47, 0x9b, 0x67, 0x79, 0x62, 0x0d, 0xe2, 0xa7, 0x01,
+                               0xab, 0x72, 0x0c, 0x38, 0xed, 0xab, 0x88, 0x86, 0xcf, 0x7e, 0x84, 0xd3, 0x1c, 0x09, 0x26, 0xb3,
+                               0x6b, 0x41, 0x4f, 0x09, 0xef, 0xe5, 0xcc, 0xda, 0x9b, 0x09, 0x55, 0xee, 0x3e, 0xe5, 0x89, 0x11,
+                               0x63, 0x1a, 0x2a, 0xe8, 0xfa, 0xf1, 0x44, 0x9b, 0x92, 0x46, 0x2d, 0x3f, 0x88, 0x26, 0xc1, 0x44,
+                               0xda, 0x9f, 0x02, 0x09, 0x2d, 0x7d, 0xea, 0xc2, 0x63, 0xc5, 0xa7, 0x62, 0x27, 0x42, 0x1a, 0x48,
+                               0xde, 0xf2, 0xa9, 0xf2, 0x80, 0x9b, 0xfc, 0x5b, 0x97, 0xef, 0xfb, 0x6f, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x01, 0x02, 0x32, 0xb9, 0xd7, 0x17, 0x75, 0x7d, 0xf8, 0x4b, 0xc6,
+                               0xe1, 0x6d, 0x59, 0x2e, 0x2d, 0x46, 0xf3, 0x56, 0x4e, 0xcf, 0xec, 0xce, 0xd6, 0xd0, 0x7e, 0xa1,
+                               0x13, 0x98, 0xc3, 0xba, 0xce, 0xcc, 0xb3, 0xa3, 0xb1, 0xeb, 0xb8, 0xf1, 0x72, 0x77, 0x95, 0xcf,
+                               0x62, 0x3f, 0xe8, 0x1f, 0xb6, 0x34, 0x0f, 0x43, 0xd9, 0x5f, 0xf6, 0xdc, 0xe1, 0x66, 0x76, 0x0d,
+                               0xe1, 0xd5, 0x12, 0x87, 0x0d, 0xdb, 0x33, 0x68, 0xe8, 0xd8, 0x52, 0xf0, 0x8f, 0xdb, 0x54, 0x1a,
+                               0x44, 0x16, 0xe2, 0xd9, 0x50, 0x2f, 0x80, 0x99, 0x68, 0x59, 0xf7, 0x55, 0x2c, 0xa6, 0xa5, 0xc0,
+                               0x0b, 0x00, 0x6d, 0xc4, 0x35, 0x76, 0xc4, 0x0c, 0x5b, 0x9e, 0xe2, 0x8a, 0x6c, 0x99, 0xaf, 0x99,
+                               0xc3, 0xc0, 0x7c, 0x7e, 0xe1, 0x85, 0xf8, 0x12, 0xb6, 0x2d, 0xbb, 0xfc, 0xcd, 0x65, 0x7b, 0x8d,
+                               0xc5, 0x3c, 0xe4, 0x77, 0x50, 0xb1, 0xc0, 0x99, 0x75, 0xca, 0xb9, 0x43, 0x23, 0xf5, 0x2b, 0xfa,
+                               0x77, 0x5c, 0x23, 0x2f, 0x50, 0x75, 0x41, 0x2b, 0xd7, 0x35, 0xe2, 0xe4, 0x44, 0x45, 0xf1, 0xc0,
+                               0xce, 0x86, 0x53, 0x26, 0x02, 0x43, 0x47, 0x65, 0x15, 0x45, 0xc4, 0x14, 0xcd, 0xb5, 0xdb, 0x33,
+                               0x4f, 0x4b, 0xcf, 0xda, 0xf0, 0x7f, 0xe8, 0x0c, 0xae, 0xca, 0x49, 0xf5, 0x26, 0x60, 0x69, 0x17,
+                               0xf9, 0x19, 0x5a, 0x7f, 0x92, 0xe8, 0xc6, 0x52, 0x4d, 0x93, 0x24, 0xd6, 0x2e, 0x7a, 0xd4, 0xbc,
+                               0x02, 0x14, 0x40, 0x93, 0xa2, 0xa6, 0xe8, 0xfb, 0x74, 0xbc, 0x17, 0x2b, 0x83, 0x9c, 0xdf, 0x2b,
+                               0xe0, 0x6c, 0xf6, 0x47, 0x1e, 0x3a, 0x75, 0xb6, 0xc0, 0x07, 0x84, 0x44, 0x32, 0x36, 0xbb, 0x5c,
+                               0x3a, 0x4e, 0x55, 0x14, 0x70, 0x8f, 0x08, 0x96, 0x20, 0xc2, 0x37, 0x49, 0xdc, 0x85, 0xdb, 0x26,
+                               0x7e, 0x1c, 0x83, 0xcb, 0xcd, 0x02, 0x81, 0x81, 0x00, 0xff, 0x1d, 0x80, 0x38, 0x5f, 0xc6, 0xd8,
+                               0xbb, 0xf3, 0xae, 0x35, 0x56, 0x04, 0x01, 0xdf, 0x69, 0xf5, 0x05, 0xbe, 0x5a, 0x0f, 0x19, 0x97,
+                               0x84, 0xf8, 0xb2, 0xe5, 0x55, 0xb0, 0xd7, 0x49, 0x28, 0xfa, 0xf0, 0x2a, 0x71, 0xa8, 0x17, 0x14,
+                               0x5a, 0xb1, 0x34, 0x40, 0x42, 0xf7, 0x9d, 0x8e, 0x2a, 0x1d, 0x26, 0x64, 0x57, 0xbd, 0xaf, 0xe4,
+                               0x13, 0xec, 0xa2, 0xde, 0xd2, 0x68, 0xbd, 0xe9, 0xef, 0x77, 0x64, 0x31, 0xf9, 0xd0, 0xb9, 0x18,
+                               0x4a, 0xf2, 0xdd, 0xbe, 0x9d, 0xd6, 0x57, 0xda, 0x1b, 0x66, 0x98, 0xbf, 0x5d, 0xeb, 0x28, 0x5b,
+                               0xdf, 0x13, 0x81, 0x96, 0xd7, 0x8a, 0x75, 0xee, 0xaa, 0x45, 0x14, 0x6a, 0x75, 0x86, 0x99, 0x19,
+                               0xc7, 0x7e, 0x36, 0xf6, 0xb6, 0x52, 0xe5, 0x2c, 0x2a, 0x1c, 0xe7, 0x93, 0x7a, 0xe5, 0xf1, 0x1a,
+                               0xc8, 0x5f, 0x89, 0xf4, 0x9a, 0x68, 0x88, 0x51, 0x03, 0x02, 0x81, 0x81, 0x00, 0xc9, 0x77, 0x06,
+                               0x69, 0x41, 0x67, 0xf5, 0xd2, 0xdf, 0x69, 0x4e, 0x16, 0x57, 0xf6, 0x18, 0xb5, 0x75, 0x8c, 0x0e,
+                               0xcc, 0xc5, 0x68, 0x4b, 0x21, 0x9d, 0xfa, 0x54, 0x32, 0xcd, 0x09, 0xed, 0x61, 0xf3, 0xe3, 0xe8,
+                               0xf8, 0xed, 0x96, 0xdd, 0x86, 0x26, 0x8b, 0x96, 0x9c, 0x24, 0xed, 0x98, 0x50, 0x3e, 0x10, 0x95,
+                               0x28, 0x89, 0xc7, 0x42, 0x31, 0x37, 0x75, 0xe5, 0xc3, 0xa5, 0x9b, 0x85, 0x15, 0xe9, 0xed, 0x63,
+                               0xf2, 0x0b, 0x52, 0x62, 0xfa, 0x05, 0x8c, 0x36, 0x09, 0xc3, 0xd8, 0xae, 0xbb, 0x53, 0xc0, 0x20,
+                               0x84, 0x0d, 0x84, 0xaf, 0xdf, 0x2c, 0xc8, 0x06, 0x33, 0xf8, 0x3b, 0xcc, 0x0b, 0x07, 0x3e, 0x7f,
+                               0x3c, 0x4a, 0x6f, 0xb9, 0x47, 0xfc, 0xb0, 0xdc, 0xed, 0x89, 0xe7, 0x1c, 0xc4, 0x48, 0x2f, 0xca,
+                               0x5d, 0xa1, 0xe1, 0xda, 0x2a, 0x2a, 0xdc, 0xd5, 0x3e, 0x22, 0x48, 0xc2, 0x25, 0x02, 0x81, 0x80,
+                               0x0b, 0xd5, 0x72, 0x7d, 0x2a, 0xf4, 0x2e, 0x59, 0x89, 0x94, 0x2b, 0x25, 0x32, 0x4a, 0x63, 0xf2,
+                               0xa6, 0x4a, 0xfd, 0xe9, 0x6f, 0x89, 0xe0, 0x5d, 0x6a, 0xab, 0xe1, 0xb7, 0x77, 0xdc, 0x84, 0xa8,
+                               0x41, 0xfb, 0xa1, 0xfc, 0x63, 0xaf, 0xae, 0x62, 0x06, 0x96, 0x45, 0xe4, 0xd5, 0x57, 0x99, 0xa0,
+                               0x9a, 0x79, 0x8d, 0x6e, 0x04, 0x7e, 0x84, 0x35, 0xe8, 0x64, 0x25, 0xb1, 0xdc, 0xe9, 0xf2, 0x50,
+                               0x09, 0x59, 0xbc, 0x77, 0xba, 0x16, 0xdb, 0xe9, 0x9e, 0x1a, 0x6b, 0x1d, 0x27, 0x34, 0x2c, 0x09,
+                               0xd9, 0x58, 0x7c, 0x87, 0xfe, 0xc0, 0x80, 0x82, 0x78, 0x85, 0x2a, 0x5a, 0x15, 0x32, 0x23, 0x40,
+                               0x02, 0xb4, 0x4f, 0xbb, 0xe3, 0xe2, 0x76, 0x2f, 0xaa, 0xcb, 0x21, 0xe6, 0x93, 0x31, 0xce, 0x3a,
+                               0xa5, 0xdc, 0x98, 0x1a, 0xbb, 0x4f, 0xd3, 0xce, 0x37, 0xa0, 0x4e, 0x98, 0x5b, 0x00, 0xf7, 0xc1,
+                               0x02, 0x81, 0x80, 0x7b, 0xc0, 0xaa, 0x66, 0x83, 0x6f, 0xd9, 0xb7, 0xe3, 0xd6, 0x6a, 0xca, 0x1c,
+                               0xd4, 0x25, 0xcf, 0x69, 0x5a, 0x54, 0x49, 0x02, 0xc6, 0x12, 0xc8, 0x9e, 0xa3, 0x03, 0x8f, 0x85,
+                               0x15, 0xa1, 0x04, 0xb7, 0x6c, 0xfb, 0x51, 0xd5, 0x98, 0x03, 0x91, 0x81, 0x21, 0xe3, 0xad, 0x1c,
+                               0x9d, 0xfa, 0x1e, 0xe8, 0x51, 0x05, 0x5e, 0xb6, 0x6b, 0x78, 0xc4, 0x84, 0xbd, 0xde, 0x1a, 0x75,
+                               0x22, 0xf7, 0xf8, 0x43, 0x0e, 0xb8, 0x83, 0x18, 0xe3, 0x4d, 0xbb, 0x29, 0x27, 0x3c, 0x96, 0x79,
+                               0x00, 0x24, 0x2d, 0x1b, 0xbf, 0xfb, 0xf9, 0x41, 0xd8, 0x8c, 0xb3, 0xf9, 0x6d, 0x11, 0x2a, 0x61,
+                               0x44, 0x52, 0x0d, 0x40, 0x30, 0xea, 0xb5, 0x5d, 0x40, 0x0d, 0xbf, 0x23, 0x80, 0x7c, 0xe5, 0x48,
+                               0xfd, 0x77, 0x55, 0x36, 0xc6, 0x6c, 0x77, 0x61, 0x96, 0x3b, 0x8e, 0x05, 0x70, 0x4b, 0x68, 0xf4,
+                               0x64, 0x5f, 0x25, 0x02, 0x81, 0x80, 0x59, 0x97, 0x37, 0x4d, 0xf5, 0x4d, 0x46, 0xe2, 0xb4, 0x43,
+                               0x51, 0xa2, 0x8a, 0x0f, 0x02, 0x12, 0xf0, 0xa2, 0x94, 0x91, 0xdd, 0x65, 0x7f, 0xb0, 0x7d, 0xb5,
+                               0x68, 0xf1, 0x80, 0xa6, 0x4c, 0x6a, 0x65, 0x19, 0xcf, 0x4f, 0x90, 0x95, 0x5e, 0x45, 0x06, 0xd6,
+                               0xda, 0x07, 0x54, 0xa7, 0x8e, 0x8e, 0x85, 0x4c, 0x2c, 0x68, 0x72, 0x20, 0xf0, 0x5d, 0xad, 0x38,
+                               0x5b, 0x91, 0x50, 0x5f, 0x7e, 0x7c, 0x94, 0x2f, 0x92, 0x7b, 0x2f, 0x51, 0x5b, 0x71, 0x11, 0xd7,
+                               0x58, 0x13, 0x5c, 0x00, 0xeb, 0xfe, 0x1a, 0xbf, 0x6c, 0xfe, 0x26, 0xb4, 0xac, 0x42, 0x70, 0x52,
+                               0xb4, 0xf7, 0x9c, 0x75, 0x0c, 0x38, 0xf5, 0x50, 0x01, 0x45, 0x9f, 0x5d, 0x5f, 0x7d, 0xa1, 0x65,
+                               0x52, 0xca, 0xed, 0x30, 0x37, 0x4d, 0x74, 0xda, 0xcf, 0xb1, 0x18, 0xdb, 0x6d, 0x68, 0x75, 0xd3,
+                               0xc0, 0x6d, 0x74, 0x6e, 0x58, 0x32
+                       }
+               }
+       },
+       // DNSSEC Zone 1
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_RSASHA512 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xd1, 0x2a, 0x16, 0x41, 0xce, 0xe4, 0xc4, 0xe8, 0x7f, 0x68, 0x4c, 0xb5,
+                               0xe6, 0x99, 0x5a, 0x5c, 0x07, 0x2d, 0x08, 0x9e, 0x27, 0xf5, 0x4a, 0xdc, 0x77, 0x2d, 0x11, 0xbf,
+                               0x4b, 0x88, 0x6e, 0x46, 0x88, 0xb9, 0x0b, 0x42, 0x48, 0xd9, 0xa9, 0x83, 0xb3, 0xef, 0xe5, 0x0e,
+                               0xbb, 0x3f, 0x5c, 0xe5, 0x99, 0x87, 0xef, 0xd6, 0xa0, 0x00, 0x60, 0x23, 0xa1, 0xd2, 0xb6, 0x27,
+                               0x80, 0xe4, 0x66, 0xe6, 0xea, 0x22, 0xfd, 0xe4, 0x47, 0x3a, 0xda, 0xa9, 0x8e, 0x10, 0x5e, 0x5f,
+                               0xdb, 0x1e, 0xa6, 0x42, 0xba, 0x84, 0x10, 0x98, 0x9b, 0xa1, 0xd3, 0x0d, 0xa8, 0xc9, 0xdc, 0xb4,
+                               0xc3, 0x93, 0x18, 0x87, 0xd9, 0x77, 0xf4, 0x2e, 0x89, 0x4b, 0xa3, 0x0a, 0x16, 0x63, 0x0d, 0x51,
+                               0x1a, 0x86, 0xd7, 0x98, 0x6a, 0x8b, 0x11, 0xf3, 0x38, 0x87, 0xa6, 0x87, 0xc7, 0x76, 0xfe, 0x68,
+                               0x2c, 0x53, 0x8b, 0x6d, 0x9c, 0xee, 0xe1, 0xb3, 0xda, 0xff, 0x22, 0xe2, 0x80, 0xe2, 0xae, 0x14,
+                               0xf0, 0x5e, 0xd2, 0x03, 0x53, 0x72, 0xf1, 0x50, 0xad, 0x03, 0xb1, 0x29, 0xd9, 0x0c, 0x7a, 0xfc,
+                               0x33, 0x76, 0x7e, 0x6a, 0xbf, 0x9d, 0x15, 0x6c, 0x09, 0x27, 0xe9, 0xe3, 0x66, 0x86, 0xe9, 0x42,
+                               0x08, 0xd4, 0x8f, 0xfc, 0x52, 0x24, 0xe7, 0x27, 0xb2, 0x1d, 0x59, 0x90, 0x75, 0xf6, 0xfc, 0x54,
+                               0x11, 0x6b, 0xb0, 0xad, 0x60, 0xab, 0xfe, 0x9a, 0xea, 0xf4, 0xf5, 0xf6, 0xd0, 0x36, 0x95, 0x9f,
+                               0xda, 0x74, 0x9a, 0x59, 0x59, 0xd6, 0x8c, 0x74, 0xd3, 0x9a, 0xca, 0x91, 0x03, 0x36, 0x10, 0xf6,
+                               0x3e, 0x5d, 0xf2, 0xab, 0x10, 0xcb, 0xe4, 0x25, 0xdd, 0x39, 0x44, 0x67, 0xf5, 0xaa, 0x72, 0x27,
+                               0x30, 0x1b, 0xc3, 0xe7, 0x1e, 0x21, 0x0c, 0x57, 0xc5, 0xe6, 0x2c, 0xe3, 0xf2, 0x4b, 0x43, 0x65,
+                               0xa0, 0xa3, 0xb5, 0xb7
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd1, 0x2a, 0x16, 0x41,
+                               0xce, 0xe4, 0xc4, 0xe8, 0x7f, 0x68, 0x4c, 0xb5, 0xe6, 0x99, 0x5a, 0x5c, 0x07, 0x2d, 0x08, 0x9e,
+                               0x27, 0xf5, 0x4a, 0xdc, 0x77, 0x2d, 0x11, 0xbf, 0x4b, 0x88, 0x6e, 0x46, 0x88, 0xb9, 0x0b, 0x42,
+                               0x48, 0xd9, 0xa9, 0x83, 0xb3, 0xef, 0xe5, 0x0e, 0xbb, 0x3f, 0x5c, 0xe5, 0x99, 0x87, 0xef, 0xd6,
+                               0xa0, 0x00, 0x60, 0x23, 0xa1, 0xd2, 0xb6, 0x27, 0x80, 0xe4, 0x66, 0xe6, 0xea, 0x22, 0xfd, 0xe4,
+                               0x47, 0x3a, 0xda, 0xa9, 0x8e, 0x10, 0x5e, 0x5f, 0xdb, 0x1e, 0xa6, 0x42, 0xba, 0x84, 0x10, 0x98,
+                               0x9b, 0xa1, 0xd3, 0x0d, 0xa8, 0xc9, 0xdc, 0xb4, 0xc3, 0x93, 0x18, 0x87, 0xd9, 0x77, 0xf4, 0x2e,
+                               0x89, 0x4b, 0xa3, 0x0a, 0x16, 0x63, 0x0d, 0x51, 0x1a, 0x86, 0xd7, 0x98, 0x6a, 0x8b, 0x11, 0xf3,
+                               0x38, 0x87, 0xa6, 0x87, 0xc7, 0x76, 0xfe, 0x68, 0x2c, 0x53, 0x8b, 0x6d, 0x9c, 0xee, 0xe1, 0xb3,
+                               0xda, 0xff, 0x22, 0xe2, 0x80, 0xe2, 0xae, 0x14, 0xf0, 0x5e, 0xd2, 0x03, 0x53, 0x72, 0xf1, 0x50,
+                               0xad, 0x03, 0xb1, 0x29, 0xd9, 0x0c, 0x7a, 0xfc, 0x33, 0x76, 0x7e, 0x6a, 0xbf, 0x9d, 0x15, 0x6c,
+                               0x09, 0x27, 0xe9, 0xe3, 0x66, 0x86, 0xe9, 0x42, 0x08, 0xd4, 0x8f, 0xfc, 0x52, 0x24, 0xe7, 0x27,
+                               0xb2, 0x1d, 0x59, 0x90, 0x75, 0xf6, 0xfc, 0x54, 0x11, 0x6b, 0xb0, 0xad, 0x60, 0xab, 0xfe, 0x9a,
+                               0xea, 0xf4, 0xf5, 0xf6, 0xd0, 0x36, 0x95, 0x9f, 0xda, 0x74, 0x9a, 0x59, 0x59, 0xd6, 0x8c, 0x74,
+                               0xd3, 0x9a, 0xca, 0x91, 0x03, 0x36, 0x10, 0xf6, 0x3e, 0x5d, 0xf2, 0xab, 0x10, 0xcb, 0xe4, 0x25,
+                               0xdd, 0x39, 0x44, 0x67, 0xf5, 0xaa, 0x72, 0x27, 0x30, 0x1b, 0xc3, 0xe7, 0x1e, 0x21, 0x0c, 0x57,
+                               0xc5, 0xe6, 0x2c, 0xe3, 0xf2, 0x4b, 0x43, 0x65, 0xa0, 0xa3, 0xb5, 0xb7, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x19, 0xdc, 0x4e, 0x6d, 0xd5, 0x82, 0xdd, 0xc0, 0xf1, 0xdb, 0xb7,
+                               0x3c, 0x4d, 0x90, 0xb6, 0xa6, 0x26, 0x86, 0xfa, 0xb2, 0x9f, 0x35, 0xc9, 0xe0, 0x87, 0x3a, 0xed,
+                               0xca, 0x4d, 0x30, 0x46, 0xe8, 0xcb, 0xf2, 0x61, 0x02, 0x26, 0xd8, 0x03, 0xdd, 0x92, 0x40, 0xe8,
+                               0xa7, 0xa6, 0x74, 0x0c, 0xec, 0x20, 0x4c, 0x39, 0x2a, 0xe6, 0x93, 0xbf, 0xf9, 0xe2, 0x81, 0xec,
+                               0x5e, 0x91, 0xa6, 0x40, 0xfe, 0x1a, 0x2d, 0xb8, 0x5e, 0xb7, 0x30, 0x7f, 0x7a, 0x33, 0x8e, 0x87,
+                               0x89, 0xea, 0x21, 0x6d, 0xcf, 0x55, 0xed, 0xdf, 0xd0, 0x16, 0x7c, 0x99, 0x5d, 0x18, 0xcf, 0x83,
+                               0x22, 0x60, 0x59, 0xe3, 0xfb, 0x0b, 0xe2, 0xb3, 0xb0, 0x90, 0x98, 0xbc, 0x45, 0x92, 0x2e, 0x03,
+                               0x51, 0x51, 0xea, 0x76, 0x66, 0x7e, 0xff, 0x1c, 0x79, 0x8a, 0x0a, 0xe4, 0xdb, 0xe8, 0x72, 0xa8,
+                               0x68, 0x15, 0xd9, 0x95, 0xcf, 0xd4, 0x22, 0x22, 0x05, 0x00, 0x36, 0x93, 0x84, 0x12, 0x84, 0x11,
+                               0x50, 0xb4, 0x99, 0xf5, 0xad, 0x9d, 0x88, 0x60, 0xf8, 0x9a, 0xef, 0x14, 0xc3, 0x67, 0x32, 0x2a,
+                               0x35, 0x0b, 0x98, 0x58, 0xa5, 0x56, 0x73, 0xd9, 0x20, 0x41, 0x63, 0x43, 0x47, 0x1c, 0x73, 0x57,
+                               0x61, 0xe6, 0xf6, 0x4f, 0x09, 0xf4, 0xd1, 0xb1, 0x1d, 0xc0, 0x6c, 0x35, 0xd3, 0x5d, 0x68, 0xc2,
+                               0x95, 0x23, 0x43, 0x7f, 0x4a, 0x07, 0x14, 0xb5, 0x87, 0x6d, 0x52, 0xaf, 0x83, 0x06, 0x3e, 0x29,
+                               0xd9, 0x70, 0x6f, 0x88, 0x5f, 0xd1, 0xf8, 0xe7, 0x6b, 0x74, 0x3f, 0xb6, 0x92, 0xae, 0xcd, 0xff,
+                               0x5f, 0x60, 0xea, 0x40, 0x84, 0x44, 0x23, 0x39, 0xcb, 0xbc, 0x25, 0x06, 0x29, 0x85, 0xb5, 0xe2,
+                               0xcb, 0x92, 0x2a, 0x1c, 0x88, 0x40, 0x54, 0x2d, 0x47, 0x42, 0x39, 0xcc, 0xba, 0x8e, 0x89, 0x18,
+                               0x0b, 0x93, 0x33, 0x8d, 0x49, 0x02, 0x81, 0x81, 0x00, 0xf5, 0xe0, 0xdd, 0xef, 0x8b, 0x24, 0xdc,
+                               0xaf, 0x00, 0x1b, 0x39, 0x29, 0xb2, 0x1e, 0x9c, 0x46, 0x8e, 0x5d, 0xe4, 0x5f, 0xd9, 0x40, 0x8e,
+                               0x2f, 0xfc, 0x28, 0x7d, 0x54, 0x68, 0x6b, 0x09, 0xd6, 0x28, 0x97, 0x6f, 0xf9, 0xc9, 0x07, 0x97,
+                               0x88, 0x09, 0x27, 0x68, 0x84, 0x8f, 0x5d, 0x35, 0xbe, 0x4e, 0x80, 0x9d, 0xc7, 0x38, 0x4d, 0x43,
+                               0xf1, 0x34, 0xe5, 0xc7, 0xd3, 0xa0, 0x2e, 0x02, 0x81, 0xf9, 0xbf, 0x59, 0x80, 0x12, 0xe1, 0x5a,
+                               0x8a, 0x72, 0x27, 0x36, 0x0e, 0xbc, 0x13, 0xd0, 0xc0, 0x01, 0xc5, 0x3e, 0x7c, 0x91, 0xa9, 0x6a,
+                               0xfe, 0x0a, 0x8e, 0x3f, 0x52, 0xdd, 0xca, 0x7e, 0x80, 0xe4, 0x09, 0xc3, 0x5a, 0xec, 0x0a, 0x6e,
+                               0xe5, 0xa5, 0x58, 0x45, 0xa2, 0x24, 0x5a, 0xdd, 0x02, 0x42, 0x86, 0xbe, 0x28, 0x34, 0x51, 0x0b,
+                               0x20, 0x8f, 0xe7, 0x5a, 0xf2, 0xed, 0x9c, 0xf1, 0x1f, 0x02, 0x81, 0x81, 0x00, 0xd9, 0xc6, 0x51,
+                               0x6e, 0x51, 0x08, 0xf3, 0xc4, 0x9e, 0x37, 0x06, 0x7c, 0x00, 0xf4, 0x16, 0x0d, 0x36, 0x35, 0xa7,
+                               0xc3, 0x06, 0x3a, 0x44, 0x03, 0x61, 0x11, 0x15, 0x69, 0xa4, 0xc8, 0x5f, 0xbb, 0xb1, 0xdd, 0x9f,
+                               0xf7, 0x7c, 0x9b, 0x5e, 0x91, 0x0f, 0xd6, 0x80, 0x53, 0xb5, 0x54, 0x79, 0x0b, 0x0d, 0x71, 0x13,
+                               0x31, 0xf3, 0xdd, 0xd1, 0xe8, 0xa3, 0xac, 0x4c, 0xbb, 0x71, 0x8a, 0xc5, 0xe5, 0x50, 0xab, 0x67,
+                               0xc5, 0x84, 0xd6, 0xc4, 0x8d, 0x4a, 0x6d, 0xd6, 0x0b, 0xc4, 0x85, 0x4b, 0x83, 0x2f, 0x8d, 0x48,
+                               0x4a, 0xe8, 0x3a, 0x0b, 0x7e, 0xda, 0xea, 0x2b, 0xc6, 0x31, 0xf5, 0xd9, 0x8d, 0xe1, 0x59, 0x66,
+                               0xd7, 0xc8, 0x1a, 0x4b, 0x27, 0x15, 0xcd, 0x2d, 0xe3, 0xe0, 0x51, 0x13, 0x0c, 0xff, 0x3a, 0x25,
+                               0x62, 0x64, 0x80, 0x61, 0xa3, 0xdb, 0x17, 0x06, 0xe8, 0xe5, 0x11, 0x30, 0x69, 0x02, 0x81, 0x80,
+                               0x11, 0xad, 0xa1, 0x47, 0x5f, 0xd1, 0xbe, 0xac, 0x36, 0x6d, 0xbe, 0xa6, 0x68, 0xd0, 0x1a, 0x9c,
+                               0x58, 0x10, 0x18, 0x23, 0x6c, 0x13, 0x60, 0x25, 0xc3, 0x13, 0x86, 0x2f, 0x53, 0x72, 0x9e, 0xba,
+                               0x6f, 0x20, 0xb4, 0x44, 0x59, 0x57, 0xda, 0x65, 0x45, 0x83, 0x37, 0xd8, 0x90, 0x16, 0x3b, 0x8e,
+                               0xfb, 0x45, 0xf4, 0x12, 0x26, 0xd2, 0x27, 0x58, 0x68, 0x5d, 0x3d, 0x08, 0xd2, 0x33, 0x7c, 0xaa,
+                               0xab, 0xb2, 0x3e, 0x55, 0x1f, 0x06, 0x64, 0xe4, 0x1f, 0x61, 0x02, 0x59, 0xfc, 0xdb, 0xcf, 0xde,
+                               0x7e, 0x42, 0x18, 0x44, 0x0c, 0x95, 0x09, 0xc4, 0x4a, 0x2e, 0x00, 0x0f, 0x8d, 0x32, 0xc9, 0xf5,
+                               0xaa, 0x6f, 0xa9, 0x44, 0x18, 0x44, 0x87, 0xc4, 0xab, 0x26, 0xb5, 0x9a, 0xfa, 0xd2, 0x2d, 0xa1,
+                               0xc3, 0xf6, 0xbc, 0x25, 0x57, 0xdd, 0x9b, 0xd7, 0x33, 0x81, 0x86, 0xe0, 0x80, 0x85, 0x10, 0x31,
+                               0x02, 0x81, 0x80, 0x75, 0x06, 0x05, 0xe2, 0x22, 0xf7, 0x8d, 0x8e, 0xbd, 0x7a, 0x52, 0x28, 0xb6,
+                               0x96, 0x73, 0x77, 0x1c, 0x07, 0x4f, 0x24, 0x14, 0xa7, 0xa0, 0xbe, 0xb4, 0x1d, 0x6b, 0x05, 0x3f,
+                               0x00, 0x51, 0xb9, 0x59, 0xc5, 0xff, 0x5d, 0x0d, 0x7f, 0xac, 0x1c, 0x85, 0x87, 0x3d, 0xa4, 0x3d,
+                               0xcf, 0xab, 0xcf, 0xcd, 0xa4, 0x52, 0xad, 0x48, 0x1f, 0x8d, 0xd9, 0xd6, 0x82, 0x13, 0x25, 0xb2,
+                               0xc8, 0xe0, 0xb8, 0xee, 0x55, 0x7d, 0x48, 0xfd, 0xae, 0x2f, 0x26, 0xc1, 0xb5, 0x2e, 0x0d, 0x5a,
+                               0xc8, 0x4a, 0x20, 0xaf, 0x9f, 0xdb, 0x16, 0x2d, 0x9e, 0x6b, 0x68, 0xfa, 0x98, 0x55, 0x77, 0x86,
+                               0x87, 0x57, 0x25, 0xcf, 0xcf, 0xab, 0xdd, 0x0e, 0x71, 0x5a, 0xab, 0x21, 0x23, 0x05, 0xa2, 0xeb,
+                               0x7a, 0x85, 0xa3, 0x39, 0x4f, 0x74, 0xa1, 0x5f, 0xe2, 0x41, 0x15, 0xbc, 0xf8, 0xad, 0xc4, 0xde,
+                               0xbb, 0x12, 0xe9, 0x02, 0x81, 0x80, 0x69, 0xf6, 0xb7, 0x3f, 0x90, 0x91, 0x22, 0x94, 0x4e, 0x2d,
+                               0x75, 0xe7, 0xbe, 0x6f, 0x49, 0x92, 0x12, 0x40, 0xe2, 0xdd, 0x0f, 0x78, 0x98, 0xbb, 0xc6, 0x55,
+                               0x77, 0xd8, 0xe3, 0x2c, 0xcf, 0xea, 0x5e, 0xfe, 0xce, 0x20, 0xa6, 0xc2, 0x3a, 0x0a, 0x62, 0x22,
+                               0x2b, 0x45, 0x77, 0x9e, 0x16, 0x9a, 0xf6, 0x68, 0x3a, 0x48, 0x51, 0xe6, 0x98, 0xbc, 0xf7, 0xa1,
+                               0xc4, 0x86, 0xfe, 0xf5, 0xaf, 0x02, 0xa0, 0xbc, 0x9f, 0x5e, 0xed, 0x88, 0xe0, 0xa3, 0x97, 0xde,
+                               0x2e, 0xf1, 0x41, 0xca, 0x9f, 0x59, 0xf2, 0x9b, 0x70, 0x16, 0x3b, 0x2a, 0x10, 0xab, 0x43, 0x74,
+                               0x5b, 0x36, 0x8b, 0xc9, 0x7c, 0x70, 0x9d, 0x80, 0xa1, 0x34, 0x0b, 0x29, 0x30, 0x26, 0xb8, 0xb6,
+                               0xe2, 0xb8, 0xa1, 0x2c, 0x2e, 0xa5, 0x58, 0x09, 0xf2, 0x7d, 0x99, 0xc2, 0xf6, 0xab, 0x19, 0x2b,
+                               0x75, 0x29, 0x51, 0x52, 0x19, 0x78
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_RSASHA512 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0x96, 0x4c, 0x6f, 0xef, 0x79, 0xdf, 0x8b, 0x5e, 0x8e, 0xcf, 0xd1, 0x4a,
+                               0x4c, 0xa0, 0x89, 0x6b, 0x59, 0x8e, 0x5d, 0x88, 0x4a, 0x82, 0x48, 0x2d, 0xaa, 0x21, 0xab, 0x69,
+                               0x61, 0x19, 0x10, 0x55, 0xc1, 0xe3, 0x40, 0xc7, 0x59, 0xd2, 0xa2, 0xcb, 0x8f, 0x64, 0xdc, 0x1a,
+                               0xd7, 0x70, 0xf9, 0x83, 0x06, 0x55, 0x5d, 0xef, 0xc6, 0x3e, 0x3f, 0x2b, 0x56, 0xc3, 0x0b, 0xbd,
+                               0x1a, 0x01, 0xda, 0x4f, 0x91, 0x45, 0x21, 0x09, 0x0a, 0x83, 0xdb, 0xb6, 0x8b, 0x14, 0xd3, 0xbd,
+                               0x49, 0x9a, 0xb5, 0x20, 0xac, 0x41, 0x65, 0x0b, 0x8e, 0x53, 0x8c, 0x3e, 0xe9, 0xe6, 0xed, 0x48,
+                               0xc5, 0x5d, 0x4d, 0x04, 0xc1, 0x24, 0x67, 0xa3, 0xc6, 0x36, 0x69, 0xb5, 0x7d, 0xa9, 0x75, 0x44,
+                               0xf1, 0x0f, 0x7c, 0x29, 0xc3, 0x70, 0xb1, 0x05, 0x70, 0x97, 0x24, 0xa5, 0x8d, 0xa9, 0xfe, 0xdc,
+                               0x7f, 0x9d, 0xb7, 0x01, 0x5c, 0xf5, 0xee, 0x8e, 0xa5, 0x56, 0xfb, 0xf9, 0x05, 0x10, 0x06, 0xd0,
+                               0x5d, 0x0a, 0xb0, 0x14, 0x7d, 0x4e, 0xac, 0x1d, 0xf5, 0x8e, 0xe9, 0x36, 0x90, 0x5d, 0x91, 0xde,
+                               0xd5, 0x30, 0x1a, 0x8d, 0x2c, 0x97, 0xec, 0x31, 0x37, 0xb7, 0x22, 0x7a, 0x55, 0x8e, 0xa2, 0x71,
+                               0x89, 0x03, 0xcf, 0x10, 0xb3, 0x8d, 0xce, 0xce, 0x2f, 0xfb, 0x43, 0x63, 0x74, 0x27, 0x01, 0x71,
+                               0x22, 0x14, 0x99, 0xe5, 0xd2, 0x6d, 0x68, 0x75, 0xa5, 0x7f, 0xb5, 0xfd, 0x44, 0x67, 0x36, 0x7d,
+                               0x25, 0xa7, 0x61, 0x76, 0x9d, 0x26, 0xfa, 0x53, 0x5f, 0xf9, 0x76, 0x1b, 0x40, 0x6e, 0x2e, 0x25,
+                               0xf4, 0xb1, 0xc3, 0x1c, 0xa0, 0x41, 0x6d, 0x11, 0x05, 0x20, 0xac, 0x68, 0x7e, 0x51, 0x35, 0xa0,
+                               0x62, 0x05, 0x17, 0x87, 0xe0, 0xf4, 0xb7, 0xc6, 0xc9, 0x2e, 0xf7, 0xf8, 0x98, 0x1b, 0x74, 0x09,
+                               0x2e, 0xe6, 0x9f, 0x59
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0x96, 0x4c, 0x6f, 0xef,
+                               0x79, 0xdf, 0x8b, 0x5e, 0x8e, 0xcf, 0xd1, 0x4a, 0x4c, 0xa0, 0x89, 0x6b, 0x59, 0x8e, 0x5d, 0x88,
+                               0x4a, 0x82, 0x48, 0x2d, 0xaa, 0x21, 0xab, 0x69, 0x61, 0x19, 0x10, 0x55, 0xc1, 0xe3, 0x40, 0xc7,
+                               0x59, 0xd2, 0xa2, 0xcb, 0x8f, 0x64, 0xdc, 0x1a, 0xd7, 0x70, 0xf9, 0x83, 0x06, 0x55, 0x5d, 0xef,
+                               0xc6, 0x3e, 0x3f, 0x2b, 0x56, 0xc3, 0x0b, 0xbd, 0x1a, 0x01, 0xda, 0x4f, 0x91, 0x45, 0x21, 0x09,
+                               0x0a, 0x83, 0xdb, 0xb6, 0x8b, 0x14, 0xd3, 0xbd, 0x49, 0x9a, 0xb5, 0x20, 0xac, 0x41, 0x65, 0x0b,
+                               0x8e, 0x53, 0x8c, 0x3e, 0xe9, 0xe6, 0xed, 0x48, 0xc5, 0x5d, 0x4d, 0x04, 0xc1, 0x24, 0x67, 0xa3,
+                               0xc6, 0x36, 0x69, 0xb5, 0x7d, 0xa9, 0x75, 0x44, 0xf1, 0x0f, 0x7c, 0x29, 0xc3, 0x70, 0xb1, 0x05,
+                               0x70, 0x97, 0x24, 0xa5, 0x8d, 0xa9, 0xfe, 0xdc, 0x7f, 0x9d, 0xb7, 0x01, 0x5c, 0xf5, 0xee, 0x8e,
+                               0xa5, 0x56, 0xfb, 0xf9, 0x05, 0x10, 0x06, 0xd0, 0x5d, 0x0a, 0xb0, 0x14, 0x7d, 0x4e, 0xac, 0x1d,
+                               0xf5, 0x8e, 0xe9, 0x36, 0x90, 0x5d, 0x91, 0xde, 0xd5, 0x30, 0x1a, 0x8d, 0x2c, 0x97, 0xec, 0x31,
+                               0x37, 0xb7, 0x22, 0x7a, 0x55, 0x8e, 0xa2, 0x71, 0x89, 0x03, 0xcf, 0x10, 0xb3, 0x8d, 0xce, 0xce,
+                               0x2f, 0xfb, 0x43, 0x63, 0x74, 0x27, 0x01, 0x71, 0x22, 0x14, 0x99, 0xe5, 0xd2, 0x6d, 0x68, 0x75,
+                               0xa5, 0x7f, 0xb5, 0xfd, 0x44, 0x67, 0x36, 0x7d, 0x25, 0xa7, 0x61, 0x76, 0x9d, 0x26, 0xfa, 0x53,
+                               0x5f, 0xf9, 0x76, 0x1b, 0x40, 0x6e, 0x2e, 0x25, 0xf4, 0xb1, 0xc3, 0x1c, 0xa0, 0x41, 0x6d, 0x11,
+                               0x05, 0x20, 0xac, 0x68, 0x7e, 0x51, 0x35, 0xa0, 0x62, 0x05, 0x17, 0x87, 0xe0, 0xf4, 0xb7, 0xc6,
+                               0xc9, 0x2e, 0xf7, 0xf8, 0x98, 0x1b, 0x74, 0x09, 0x2e, 0xe6, 0x9f, 0x59, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x08, 0x1a, 0xa5, 0x83, 0x7a, 0x65, 0x13, 0x18, 0xfd, 0x66, 0xd7,
+                               0xc0, 0x63, 0x9a, 0xd9, 0x66, 0x0c, 0x2d, 0x68, 0xbe, 0x2f, 0x54, 0x50, 0x8b, 0x83, 0xcb, 0xae,
+                               0x78, 0x2f, 0x67, 0xe0, 0x51, 0x5b, 0x73, 0xc3, 0x3b, 0xac, 0x8c, 0x76, 0xc2, 0x36, 0xa0, 0x67,
+                               0xdc, 0xdb, 0xa6, 0x88, 0x11, 0xa8, 0x4f, 0x04, 0x92, 0x67, 0xf4, 0x55, 0xc8, 0x22, 0xb4, 0xc0,
+                               0x26, 0x89, 0x67, 0x35, 0xac, 0x0a, 0x27, 0x79, 0xd5, 0x48, 0xb7, 0x9c, 0xea, 0x7e, 0x1b, 0xd8,
+                               0xa1, 0xf1, 0x7c, 0xd1, 0xcc, 0xe9, 0xf4, 0xd9, 0xbb, 0xb2, 0x88, 0x2f, 0x3c, 0xfa, 0x34, 0x3d,
+                               0x31, 0xaa, 0x10, 0xb2, 0x95, 0x31, 0xcb, 0xe2, 0x79, 0xba, 0x77, 0x97, 0x22, 0x15, 0xdd, 0x44,
+                               0x09, 0x3c, 0x5d, 0x4b, 0x22, 0xde, 0x5a, 0xae, 0x74, 0xde, 0x62, 0x0b, 0xf4, 0x10, 0x4c, 0x2d,
+                               0x05, 0x9b, 0x2c, 0xf8, 0x1f, 0xd9, 0xc6, 0x76, 0x7c, 0x6d, 0xa7, 0x32, 0xa3, 0xa0, 0xe3, 0x5f,
+                               0x61, 0xbe, 0x69, 0xc9, 0x17, 0xa1, 0xed, 0xc2, 0x3a, 0x0e, 0x2b, 0xd9, 0x54, 0x60, 0xa0, 0xdc,
+                               0x9f, 0x73, 0x40, 0xbe, 0x39, 0xf1, 0x11, 0x68, 0xaa, 0x02, 0x68, 0x7a, 0xdc, 0x53, 0xf2, 0x5f,
+                               0x37, 0xbb, 0x79, 0x64, 0x06, 0x33, 0xe8, 0x1a, 0xfe, 0x77, 0xdc, 0x19, 0xdd, 0xe1, 0xdb, 0xe6,
+                               0xbb, 0xfc, 0xcc, 0x95, 0x31, 0xbc, 0x44, 0xf9, 0x2c, 0xe8, 0x60, 0x54, 0x66, 0x4c, 0xe2, 0x5a,
+                               0xda, 0xec, 0xa0, 0xfe, 0x38, 0x41, 0x13, 0xd2, 0x4a, 0xf4, 0x17, 0xf2, 0xd1, 0x5c, 0xe2, 0x3b,
+                               0xbe, 0x23, 0x78, 0xdb, 0xf7, 0x7f, 0x9d, 0x50, 0x41, 0x57, 0x28, 0xa0, 0xbe, 0x00, 0x26, 0x1c,
+                               0xeb, 0xf0, 0xa9, 0xc6, 0x5b, 0x58, 0x9a, 0xb6, 0x3f, 0xc7, 0x6f, 0x99, 0xf9, 0x9d, 0xe6, 0x2e,
+                               0x48, 0xdf, 0x11, 0x78, 0xe1, 0x02, 0x81, 0x81, 0x00, 0xcb, 0x27, 0x6b, 0x97, 0x81, 0xdb, 0x84,
+                               0xf3, 0x33, 0x91, 0xf2, 0x18, 0x23, 0xe5, 0x8b, 0x87, 0x40, 0x47, 0xb0, 0x73, 0x2d, 0xf0, 0x0e,
+                               0xa1, 0xbc, 0x46, 0xaf, 0xaa, 0x78, 0x18, 0x23, 0xa1, 0xf7, 0x9e, 0xf9, 0x13, 0x49, 0xd7, 0x39,
+                               0x04, 0x9b, 0xed, 0x00, 0xf9, 0xfd, 0x3f, 0x0d, 0x07, 0x4f, 0xfb, 0xc7, 0xab, 0x13, 0x26, 0xd9,
+                               0xda, 0x85, 0x93, 0x07, 0x68, 0x73, 0x68, 0xfb, 0x37, 0xc2, 0x5b, 0x7c, 0x02, 0x90, 0xd0, 0x0b,
+                               0xac, 0xa2, 0x4d, 0xe6, 0x2b, 0xb8, 0xf3, 0x04, 0xa6, 0x39, 0x80, 0x12, 0x81, 0xdb, 0x0d, 0x94,
+                               0x8b, 0x7f, 0xf8, 0x25, 0xd0, 0x5c, 0x17, 0x3d, 0xc3, 0x50, 0x43, 0x7f, 0x2c, 0x6f, 0xe4, 0x4f,
+                               0x8d, 0x42, 0x86, 0x9a, 0x78, 0x95, 0xdc, 0x7b, 0xe3, 0x28, 0x6f, 0x70, 0x4e, 0x10, 0x7b, 0x84,
+                               0xa8, 0x1d, 0xa6, 0xd5, 0x3b, 0x80, 0x58, 0xa2, 0x91, 0x02, 0x81, 0x81, 0x00, 0xbd, 0x65, 0x3b,
+                               0x29, 0xb1, 0xd3, 0x7f, 0xf2, 0x2a, 0x74, 0x3a, 0x20, 0xf0, 0x2c, 0x67, 0xa8, 0xbf, 0x61, 0x6b,
+                               0x22, 0xd8, 0x6f, 0x3d, 0x61, 0x04, 0x07, 0xb9, 0x7c, 0x82, 0x50, 0xe3, 0x9d, 0xfa, 0x64, 0xdb,
+                               0x53, 0x2b, 0xa6, 0xfc, 0xcc, 0x5d, 0x44, 0xbe, 0xc7, 0xff, 0xe3, 0x4b, 0x18, 0x30, 0xd1, 0xa8,
+                               0xc5, 0xec, 0x73, 0x36, 0x3a, 0x39, 0x15, 0xa5, 0x2c, 0x69, 0xbc, 0x0c, 0x0c, 0x39, 0x90, 0xc2,
+                               0x6c, 0x1f, 0x56, 0x1f, 0x4d, 0x96, 0x8d, 0x06, 0xb1, 0xc6, 0xd4, 0xca, 0xd6, 0xc7, 0x70, 0x41,
+                               0x77, 0x68, 0x21, 0xc4, 0x98, 0xf4, 0xab, 0x16, 0xbd, 0xef, 0xc1, 0x15, 0x73, 0x6c, 0x38, 0xfb,
+                               0x15, 0xb0, 0x34, 0x99, 0xa9, 0xff, 0x58, 0xb7, 0xf2, 0xbe, 0x78, 0x1d, 0x38, 0x2a, 0x74, 0x03,
+                               0x07, 0xcd, 0xbc, 0x91, 0x17, 0x5d, 0x79, 0x71, 0x52, 0xa0, 0xb6, 0x04, 0x49, 0x02, 0x81, 0x80,
+                               0x3f, 0xd2, 0x91, 0x95, 0x96, 0x81, 0x4d, 0x82, 0x4e, 0x28, 0x42, 0xa3, 0x5c, 0xdb, 0xa7, 0x7b,
+                               0x05, 0x31, 0xc0, 0x78, 0x5e, 0xed, 0x34, 0xdb, 0x90, 0xd7, 0xb0, 0x26, 0x60, 0xf6, 0x4c, 0x73,
+                               0x7c, 0xe4, 0x9f, 0xa1, 0x74, 0xa8, 0x3d, 0xcc, 0x79, 0xe3, 0xfa, 0x53, 0x8f, 0x40, 0xf8, 0xa9,
+                               0xdc, 0x71, 0xe0, 0x27, 0x99, 0xab, 0xb1, 0xf3, 0x59, 0xd5, 0x78, 0x15, 0x53, 0x74, 0x9e, 0xbf,
+                               0xc4, 0xba, 0x92, 0xfa, 0x07, 0x5e, 0xa4, 0xdc, 0x46, 0x5d, 0x2c, 0xbb, 0x7d, 0xae, 0x33, 0x3e,
+                               0xb9, 0x7f, 0xc7, 0xd0, 0xad, 0xed, 0xd5, 0x46, 0x02, 0x1d, 0x06, 0x5c, 0x30, 0x22, 0x49, 0x10,
+                               0x0c, 0x2e, 0x38, 0x4c, 0xd0, 0x96, 0x2c, 0x7b, 0xd7, 0x73, 0x8c, 0x21, 0xe8, 0x0a, 0xcd, 0x8c,
+                               0xee, 0xce, 0x1b, 0xbd, 0x1a, 0xa6, 0x83, 0x65, 0x81, 0x73, 0x41, 0x38, 0x21, 0x39, 0x37, 0x51,
+                               0x02, 0x81, 0x80, 0x6f, 0xf9, 0x98, 0xe8, 0x06, 0xcd, 0xef, 0xd8, 0x62, 0x47, 0x43, 0x52, 0xd4,
+                               0x0c, 0xbc, 0xe0, 0xa4, 0xfd, 0xd8, 0xd4, 0xca, 0x37, 0xc2, 0x87, 0x48, 0x55, 0xb4, 0xd1, 0xab,
+                               0x4e, 0x5d, 0xb4, 0xba, 0x24, 0xc5, 0x13, 0x40, 0x3c, 0xc3, 0x3b, 0xa4, 0x6d, 0x0f, 0x8a, 0xb9,
+                               0x7c, 0x4d, 0x9a, 0xa8, 0xca, 0x5c, 0x49, 0x60, 0x89, 0xa1, 0x66, 0xfc, 0xf7, 0x12, 0x27, 0xd6,
+                               0xff, 0xa9, 0xf3, 0xd4, 0x59, 0x04, 0xc9, 0xde, 0x21, 0x11, 0xf2, 0xd2, 0x6e, 0xe0, 0xa5, 0x5c,
+                               0xd3, 0xd4, 0x74, 0xf5, 0x87, 0x25, 0xfd, 0x8d, 0xe0, 0x61, 0x16, 0xb9, 0x99, 0x29, 0xa1, 0xf4,
+                               0x4d, 0x28, 0xfc, 0x8b, 0xe0, 0x88, 0x7a, 0x83, 0x2a, 0x26, 0x2b, 0xbe, 0xdf, 0x5a, 0x2a, 0x37,
+                               0x78, 0x56, 0x76, 0x2d, 0x8b, 0x57, 0x7b, 0x56, 0xa5, 0x04, 0xf7, 0x56, 0x79, 0x85, 0x89, 0x7f,
+                               0xa0, 0xfd, 0x99, 0x02, 0x81, 0x80, 0x1b, 0xbd, 0x2b, 0x67, 0xfc, 0x29, 0x48, 0x49, 0xe4, 0x18,
+                               0x43, 0x9c, 0x88, 0xbd, 0x70, 0xec, 0xf9, 0x44, 0xff, 0xd6, 0xb5, 0x54, 0x0b, 0xfc, 0x4d, 0x94,
+                               0xf9, 0xb1, 0xbd, 0x3e, 0xa3, 0xab, 0x6e, 0x1f, 0xe3, 0x8b, 0xe3, 0x03, 0xe6, 0x31, 0x6c, 0x5a,
+                               0x9f, 0x5f, 0xe6, 0x7a, 0x36, 0x0e, 0x4c, 0xbe, 0x12, 0x77, 0xc2, 0xce, 0x2e, 0xa5, 0x93, 0x3f,
+                               0x80, 0x97, 0xcb, 0x50, 0x1e, 0x5f, 0xad, 0x28, 0xc2, 0x13, 0x8e, 0xe4, 0x57, 0x5a, 0x4e, 0x59,
+                               0x50, 0x92, 0x83, 0x22, 0xf3, 0xa7, 0xbd, 0x31, 0xb8, 0xbb, 0xb8, 0xd3, 0xfb, 0xd0, 0xcc, 0x52,
+                               0x31, 0xfc, 0xff, 0xc6, 0x61, 0x57, 0x93, 0xa1, 0x92, 0x04, 0x4f, 0x3f, 0xf1, 0x8e, 0x21, 0x95,
+                               0x2e, 0x39, 0x22, 0xaa, 0xfd, 0xb3, 0x06, 0x76, 0x96, 0x20, 0x82, 0x39, 0x66, 0x3b, 0x90, 0x46,
+                               0xa4, 0x1f, 0xaa, 0xef, 0x51, 0x26
+                       }
+               }
+       },
+       // DNSSEC Zone 2
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_RSASHA512 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xf8, 0xb4, 0xc8, 0x01, 0x83, 0x7c, 0x91, 0xac, 0x5a, 0x73, 0x06, 0xa7,
+                               0x15, 0xad, 0x47, 0x18, 0xf2, 0x27, 0xd7, 0x79, 0x01, 0x8f, 0x66, 0xe3, 0xe2, 0x7f, 0x16, 0x60,
+                               0xd4, 0xf2, 0x97, 0xd7, 0x3f, 0x9e, 0xd1, 0xf6, 0x81, 0x1f, 0x7e, 0xe1, 0x6e, 0x95, 0xb8, 0x5b,
+                               0x22, 0x9e, 0xac, 0x8f, 0x8d, 0x9d, 0x57, 0xf2, 0xb0, 0xd7, 0x81, 0x81, 0x21, 0x0a, 0xe3, 0x6b,
+                               0x90, 0xb7, 0xb4, 0x88, 0x9c, 0xa2, 0xec, 0x6c, 0xee, 0xd3, 0xa3, 0xfa, 0x7d, 0x17, 0xa4, 0x11,
+                               0x3a, 0x83, 0x49, 0x08, 0x10, 0xbc, 0xc4, 0xee, 0x58, 0x6a, 0x5e, 0x2b, 0x20, 0x7a, 0xc8, 0x9c,
+                               0xac, 0x33, 0xe4, 0x27, 0xd4, 0x12, 0x0a, 0xd1, 0x2c, 0x65, 0x40, 0x8f, 0xb5, 0xa8, 0x4b, 0x21,
+                               0x9d, 0xd3, 0x88, 0xba, 0x5d, 0x73, 0xa5, 0x9e, 0x42, 0x94, 0x05, 0xf2, 0x76, 0xc9, 0x1f, 0xdc,
+                               0x9d, 0x88, 0x79, 0x11, 0x59, 0x64, 0x31, 0xfb, 0xcb, 0x49, 0x05, 0x28, 0x76, 0x37, 0x6a, 0x5b,
+                               0x2d, 0x23, 0xc5, 0xc0, 0x6f, 0xb7, 0x5d, 0xbc, 0x19, 0x4b, 0x23, 0xc1, 0x3b, 0xdd, 0xa9, 0xec,
+                               0xab, 0xb2, 0xc4, 0x14, 0xa1, 0x79, 0xc5, 0x21, 0x99, 0xc2, 0x01, 0x5b, 0xca, 0x2b, 0xbe, 0x2f,
+                               0x56, 0xaa, 0x89, 0xf2, 0x9e, 0x9a, 0x9a, 0xd7, 0x7a, 0x64, 0xe0, 0xf0, 0x0e, 0x9b, 0xb5, 0x02,
+                               0xba, 0x3a, 0xab, 0x41, 0x98, 0x8f, 0xfb, 0xbf, 0x2b, 0xb8, 0x85, 0x61, 0x36, 0x2e, 0xcb, 0xf7,
+                               0xf1, 0x68, 0x78, 0xbe, 0x9a, 0xdb, 0xab, 0xfb, 0x4f, 0xd4, 0x88, 0xdb, 0x16, 0xdc, 0x0d, 0xfe,
+                               0xf7, 0x2a, 0xce, 0x53, 0x7b, 0x63, 0x72, 0x3c, 0x42, 0x35, 0x10, 0x51, 0x92, 0xbf, 0x79, 0x64,
+                               0x7c, 0x35, 0xa9, 0x97, 0x56, 0x90, 0x2e, 0x7b, 0xe1, 0x4d, 0x1b, 0x46, 0x3a, 0x5c, 0x91, 0x07,
+                               0x0f, 0xd8, 0x22, 0x7d
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xf8, 0xb4, 0xc8, 0x01,
+                               0x83, 0x7c, 0x91, 0xac, 0x5a, 0x73, 0x06, 0xa7, 0x15, 0xad, 0x47, 0x18, 0xf2, 0x27, 0xd7, 0x79,
+                               0x01, 0x8f, 0x66, 0xe3, 0xe2, 0x7f, 0x16, 0x60, 0xd4, 0xf2, 0x97, 0xd7, 0x3f, 0x9e, 0xd1, 0xf6,
+                               0x81, 0x1f, 0x7e, 0xe1, 0x6e, 0x95, 0xb8, 0x5b, 0x22, 0x9e, 0xac, 0x8f, 0x8d, 0x9d, 0x57, 0xf2,
+                               0xb0, 0xd7, 0x81, 0x81, 0x21, 0x0a, 0xe3, 0x6b, 0x90, 0xb7, 0xb4, 0x88, 0x9c, 0xa2, 0xec, 0x6c,
+                               0xee, 0xd3, 0xa3, 0xfa, 0x7d, 0x17, 0xa4, 0x11, 0x3a, 0x83, 0x49, 0x08, 0x10, 0xbc, 0xc4, 0xee,
+                               0x58, 0x6a, 0x5e, 0x2b, 0x20, 0x7a, 0xc8, 0x9c, 0xac, 0x33, 0xe4, 0x27, 0xd4, 0x12, 0x0a, 0xd1,
+                               0x2c, 0x65, 0x40, 0x8f, 0xb5, 0xa8, 0x4b, 0x21, 0x9d, 0xd3, 0x88, 0xba, 0x5d, 0x73, 0xa5, 0x9e,
+                               0x42, 0x94, 0x05, 0xf2, 0x76, 0xc9, 0x1f, 0xdc, 0x9d, 0x88, 0x79, 0x11, 0x59, 0x64, 0x31, 0xfb,
+                               0xcb, 0x49, 0x05, 0x28, 0x76, 0x37, 0x6a, 0x5b, 0x2d, 0x23, 0xc5, 0xc0, 0x6f, 0xb7, 0x5d, 0xbc,
+                               0x19, 0x4b, 0x23, 0xc1, 0x3b, 0xdd, 0xa9, 0xec, 0xab, 0xb2, 0xc4, 0x14, 0xa1, 0x79, 0xc5, 0x21,
+                               0x99, 0xc2, 0x01, 0x5b, 0xca, 0x2b, 0xbe, 0x2f, 0x56, 0xaa, 0x89, 0xf2, 0x9e, 0x9a, 0x9a, 0xd7,
+                               0x7a, 0x64, 0xe0, 0xf0, 0x0e, 0x9b, 0xb5, 0x02, 0xba, 0x3a, 0xab, 0x41, 0x98, 0x8f, 0xfb, 0xbf,
+                               0x2b, 0xb8, 0x85, 0x61, 0x36, 0x2e, 0xcb, 0xf7, 0xf1, 0x68, 0x78, 0xbe, 0x9a, 0xdb, 0xab, 0xfb,
+                               0x4f, 0xd4, 0x88, 0xdb, 0x16, 0xdc, 0x0d, 0xfe, 0xf7, 0x2a, 0xce, 0x53, 0x7b, 0x63, 0x72, 0x3c,
+                               0x42, 0x35, 0x10, 0x51, 0x92, 0xbf, 0x79, 0x64, 0x7c, 0x35, 0xa9, 0x97, 0x56, 0x90, 0x2e, 0x7b,
+                               0xe1, 0x4d, 0x1b, 0x46, 0x3a, 0x5c, 0x91, 0x07, 0x0f, 0xd8, 0x22, 0x7d, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x05, 0xfd, 0x04, 0x5b, 0xd4, 0xf8, 0xa8, 0xe1, 0x47, 0x84, 0x41,
+                               0x82, 0xc0, 0x68, 0xdd, 0xaf, 0x62, 0x15, 0x47, 0x80, 0xe8, 0x62, 0xb5, 0x8d, 0x83, 0x24, 0xa0,
+                               0x3b, 0x50, 0xa5, 0x4e, 0xb6, 0xa6, 0x17, 0x82, 0xe2, 0xb6, 0x95, 0x35, 0x8e, 0xe4, 0x04, 0xc2,
+                               0xdd, 0x9e, 0xe5, 0xc7, 0x2d, 0xe5, 0xb1, 0x06, 0x2f, 0x17, 0xc6, 0xf8, 0x9d, 0x4d, 0x58, 0x5f,
+                               0xc5, 0x75, 0x44, 0x97, 0x1a, 0x3a, 0xfb, 0x49, 0x4a, 0x0e, 0x2e, 0x16, 0x62, 0xaf, 0xa4, 0x64,
+                               0x14, 0xee, 0x1d, 0xbf, 0x22, 0x6c, 0x94, 0xbf, 0x01, 0x26, 0x2b, 0xd9, 0x7c, 0x9c, 0x59, 0x98,
+                               0x7e, 0xfd, 0x3e, 0x7a, 0x9d, 0xe2, 0xe3, 0x54, 0x53, 0x59, 0x08, 0x4f, 0x17, 0xa6, 0xbe, 0x18,
+                               0xa1, 0x04, 0x95, 0xd5, 0x52, 0xf7, 0x71, 0x13, 0x69, 0xce, 0x1c, 0x96, 0x50, 0x12, 0x40, 0x21,
+                               0x7e, 0x44, 0x64, 0xc6, 0xd5, 0x71, 0xf4, 0xb9, 0x83, 0x3c, 0x7c, 0x86, 0x94, 0x6d, 0x96, 0xf3,
+                               0x52, 0x9e, 0xa6, 0xe4, 0x1c, 0x89, 0x8b, 0x00, 0xb3, 0x6b, 0x39, 0x93, 0xa7, 0xa4, 0x2d, 0x52,
+                               0xf7, 0xab, 0x4d, 0xe8, 0x98, 0x83, 0x37, 0xb8, 0xb5, 0xe0, 0x47, 0x10, 0xf9, 0xb4, 0x13, 0x78,
+                               0xab, 0xdf, 0x41, 0xe4, 0xfe, 0x67, 0xb9, 0xea, 0x3c, 0x2c, 0x24, 0x5f, 0x10, 0x72, 0xd9, 0x30,
+                               0xad, 0x4f, 0x0e, 0x29, 0xf6, 0xc4, 0x8a, 0xaf, 0xf9, 0x3c, 0xb1, 0x18, 0x16, 0xfb, 0xbe, 0xd7,
+                               0xc1, 0x7d, 0xb1, 0x20, 0x16, 0xb7, 0x00, 0x67, 0x39, 0x8d, 0xfe, 0xb9, 0x99, 0x25, 0xe9, 0xcc,
+                               0x0f, 0xd7, 0x2d, 0xb5, 0xc6, 0xe0, 0x79, 0x11, 0xdb, 0x1f, 0x69, 0xd2, 0x75, 0x41, 0x40, 0x36,
+                               0xf1, 0x9d, 0xec, 0x83, 0x14, 0xb0, 0xdd, 0x8c, 0xdb, 0x59, 0xe1, 0xe7, 0x0b, 0x89, 0xb6, 0x0f,
+                               0x19, 0x26, 0xd3, 0xff, 0x59, 0x02, 0x81, 0x81, 0x00, 0xfe, 0xa2, 0x13, 0xc6, 0x53, 0x64, 0xaa,
+                               0x01, 0x8c, 0xa5, 0x5f, 0xae, 0x27, 0x1c, 0xe9, 0xfd, 0x12, 0x39, 0x55, 0x81, 0x54, 0x6e, 0x60,
+                               0x67, 0xd2, 0x8d, 0xa0, 0xf7, 0xcc, 0x67, 0xe1, 0xa8, 0x5f, 0xeb, 0xfb, 0x76, 0x8b, 0xee, 0x9d,
+                               0xc7, 0xa2, 0x9a, 0xf8, 0xe7, 0x03, 0xa8, 0x7a, 0x0b, 0xc2, 0xe2, 0x5c, 0xf0, 0x38, 0xfc, 0x50,
+                               0x3b, 0x13, 0x31, 0x0d, 0xbe, 0x21, 0x66, 0x74, 0x55, 0x25, 0xd1, 0x56, 0xd9, 0xb5, 0xd5, 0xee,
+                               0xdb, 0xa5, 0xb6, 0xf0, 0x95, 0xa0, 0x2e, 0xbc, 0x26, 0x0a, 0xeb, 0x59, 0xab, 0xc2, 0x01, 0x3e,
+                               0xf4, 0x38, 0x76, 0x6b, 0x85, 0xad, 0x11, 0x38, 0xa6, 0x6f, 0xc8, 0xf4, 0xb2, 0x95, 0x67, 0xd4,
+                               0x93, 0xd2, 0x96, 0xdd, 0xe9, 0xa9, 0x5a, 0xdd, 0x82, 0x7c, 0x89, 0xa4, 0xf0, 0xbe, 0xe9, 0x31,
+                               0x62, 0x52, 0x7d, 0x2f, 0x6e, 0x6a, 0x03, 0xd6, 0x3f, 0x02, 0x81, 0x81, 0x00, 0xfa, 0x0a, 0x8f,
+                               0x20, 0xb3, 0xf4, 0xbb, 0x09, 0xae, 0xf1, 0x9c, 0x1a, 0xb2, 0x12, 0x17, 0x3b, 0x78, 0xd2, 0x5a,
+                               0x3f, 0xf8, 0xf0, 0x69, 0x99, 0xae, 0x97, 0x3b, 0x16, 0x3f, 0x66, 0xcd, 0xe9, 0xda, 0x56, 0x51,
+                               0x08, 0xa9, 0xbd, 0x1f, 0x51, 0x25, 0x1a, 0x8b, 0xa2, 0x89, 0x3b, 0x20, 0x66, 0x94, 0x49, 0x48,
+                               0x45, 0x9c, 0xd1, 0x7e, 0xba, 0x83, 0x19, 0x9d, 0x74, 0x03, 0x03, 0x6b, 0xa8, 0xe0, 0xd3, 0x53,
+                               0x78, 0x02, 0x52, 0xb9, 0x8d, 0x0d, 0xbd, 0x0c, 0x52, 0xd5, 0x2e, 0x6a, 0x88, 0x7c, 0x9d, 0x39,
+                               0x02, 0x75, 0x72, 0x04, 0x29, 0xc9, 0x7a, 0x67, 0x7b, 0x08, 0xf4, 0xc3, 0xfa, 0x8d, 0x12, 0x7c,
+                               0xdb, 0x8e, 0xcc, 0x47, 0xe5, 0x61, 0xd3, 0x91, 0x1f, 0xd5, 0x6d, 0xf0, 0x8c, 0xe9, 0x3c, 0x71,
+                               0x63, 0x1b, 0x0c, 0x7b, 0xaa, 0x07, 0x69, 0x25, 0x5f, 0xe1, 0x04, 0xf0, 0x43, 0x02, 0x81, 0x80,
+                               0x2b, 0x9e, 0xe2, 0x5a, 0x58, 0x50, 0xe7, 0x5a, 0xca, 0x98, 0x4d, 0xf2, 0xcc, 0x9a, 0x84, 0x6f,
+                               0x29, 0x1f, 0x0f, 0x49, 0xcf, 0x87, 0xf5, 0x15, 0xf4, 0x18, 0xf5, 0x19, 0x5e, 0x37, 0xf1, 0x8d,
+                               0x61, 0x99, 0x50, 0x83, 0xb7, 0x67, 0x12, 0x3a, 0x6f, 0xdd, 0xb2, 0x84, 0x08, 0xb9, 0x64, 0xe6,
+                               0xfc, 0xe4, 0xc0, 0x5d, 0x1a, 0xb6, 0x06, 0x9c, 0x88, 0x26, 0x20, 0x1f, 0x91, 0x23, 0x37, 0x0c,
+                               0x9b, 0x24, 0xe8, 0x86, 0xf5, 0x42, 0x87, 0xdf, 0xb2, 0xdc, 0xcb, 0x0c, 0x26, 0xdb, 0xba, 0xb9,
+                               0x9d, 0xa7, 0x41, 0xbe, 0x4d, 0xc6, 0xde, 0xdd, 0x01, 0x8b, 0x2f, 0xd2, 0x3f, 0x9a, 0xb2, 0xa4,
+                               0xb8, 0x83, 0xa9, 0x7f, 0xaa, 0x5e, 0x96, 0x29, 0x18, 0xab, 0x65, 0xaa, 0xb0, 0xab, 0x73, 0x44,
+                               0xf6, 0x46, 0xb2, 0x2d, 0xd7, 0xd6, 0xa4, 0xf2, 0x1e, 0x05, 0x7a, 0x09, 0xa5, 0x39, 0xb1, 0x0b,
+                               0x02, 0x81, 0x80, 0x27, 0x93, 0x66, 0x46, 0x11, 0xd5, 0xa9, 0x89, 0x05, 0xd4, 0x9e, 0x15, 0xa7,
+                               0x9b, 0xaf, 0x6b, 0xad, 0x5a, 0xf6, 0x07, 0xec, 0x19, 0xf9, 0x4c, 0xe6, 0xd2, 0x6e, 0xfa, 0xbd,
+                               0x7c, 0x43, 0x19, 0x41, 0x82, 0xa9, 0xfe, 0xca, 0x06, 0xf2, 0x1f, 0x00, 0xd5, 0x6b, 0xc6, 0x85,
+                               0x1c, 0x03, 0xab, 0xd8, 0xca, 0xa4, 0x92, 0xc8, 0x18, 0x6c, 0x8b, 0x8e, 0xd6, 0x8e, 0x12, 0x5f,
+                               0xab, 0xb9, 0xed, 0x97, 0xf0, 0x3a, 0xe4, 0xe2, 0xd1, 0x3f, 0x27, 0x31, 0x46, 0x7b, 0xdd, 0xdc,
+                               0x7a, 0x25, 0x48, 0x87, 0x18, 0xc7, 0x7e, 0x39, 0x22, 0x9b, 0xc0, 0x15, 0x70, 0xb7, 0x52, 0x44,
+                               0xff, 0xa0, 0xc9, 0x56, 0x6c, 0xd7, 0xb0, 0x54, 0xb0, 0xc6, 0xac, 0x6a, 0xc8, 0x76, 0xce, 0xe8,
+                               0x14, 0x27, 0x9f, 0xd6, 0x06, 0xc8, 0x17, 0x9c, 0x9f, 0x64, 0x2f, 0x6c, 0x4e, 0xdb, 0x28, 0x8a,
+                               0xd0, 0x22, 0x4b, 0x02, 0x81, 0x80, 0x02, 0x77, 0x7c, 0x61, 0x2b, 0x3b, 0x9a, 0x56, 0x14, 0xdd,
+                               0x15, 0x5c, 0x28, 0x62, 0x9d, 0x85, 0x36, 0xba, 0x80, 0x85, 0xc8, 0x58, 0xd2, 0x4a, 0xfd, 0x6e,
+                               0x02, 0x86, 0xda, 0x31, 0xfc, 0x55, 0xb8, 0x33, 0x95, 0x74, 0x05, 0xf0, 0xaf, 0x60, 0x56, 0xc3,
+                               0xcf, 0xbb, 0x10, 0xa6, 0x18, 0xab, 0xd8, 0xb5, 0xed, 0x7d, 0xce, 0x85, 0xf0, 0xf9, 0xa1, 0x50,
+                               0x41, 0xae, 0x00, 0x49, 0xe5, 0x0e, 0xea, 0xda, 0xb7, 0xdc, 0x1a, 0x62, 0x55, 0x77, 0x80, 0x67,
+                               0xd6, 0x5e, 0x9c, 0x3a, 0x77, 0xb6, 0xff, 0x8c, 0x40, 0x9a, 0xa5, 0x92, 0x78, 0xd0, 0xd1, 0xd3,
+                               0xcb, 0x73, 0x13, 0xca, 0x9e, 0xda, 0x80, 0x25, 0x4a, 0xfc, 0xbb, 0xd4, 0xfe, 0x60, 0x6f, 0x7b,
+                               0x4e, 0x1a, 0xb9, 0x9b, 0x73, 0x0f, 0xf6, 0x8d, 0x7d, 0xaa, 0x04, 0xfd, 0x66, 0x48, 0x41, 0x35,
+                               0x05, 0x20, 0xe7, 0x10, 0x14, 0x69
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_RSASHA512 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xd7, 0xb0, 0x48, 0x38, 0x46, 0x6b, 0x83, 0xe1, 0x31, 0x0b, 0x44, 0x53,
+                               0xbe, 0x08, 0xd1, 0x0d, 0x10, 0x3e, 0x67, 0x9c, 0x6c, 0x5f, 0x42, 0xf1, 0x27, 0x7e, 0xe8, 0x54,
+                               0x6f, 0x6e, 0x48, 0x1a, 0x4c, 0x6e, 0x1a, 0xd5, 0xac, 0x4f, 0x42, 0x0a, 0x05, 0x19, 0xab, 0x0d,
+                               0xec, 0x24, 0x37, 0xb5, 0x10, 0xdc, 0x3a, 0xa9, 0x4f, 0x28, 0xb4, 0x41, 0x9b, 0xcb, 0xd7, 0xc9,
+                               0x42, 0x3f, 0x30, 0x21, 0x27, 0x44, 0x64, 0xf7, 0x7d, 0x6a, 0x13, 0xb3, 0x06, 0x00, 0xed, 0x96,
+                               0x5f, 0xa1, 0xc5, 0x6c, 0xca, 0x7a, 0xfc, 0x13, 0x33, 0x17, 0xb3, 0x04, 0x94, 0xba, 0x38, 0x5b,
+                               0x01, 0xce, 0x23, 0xc4, 0x43, 0x8e, 0x46, 0x5f, 0x99, 0x1c, 0xa9, 0xbe, 0x10, 0x8e, 0x30, 0x06,
+                               0x54, 0x4f, 0xb7, 0x03, 0x66, 0x82, 0x64, 0xa9, 0xf8, 0x7e, 0x71, 0x5a, 0x7f, 0x58, 0x06, 0x8e,
+                               0xf8, 0x73, 0xb9, 0x0a, 0xae, 0x47, 0x10, 0x9d, 0x44, 0x4d, 0x72, 0xcb, 0x38, 0x7b, 0x87, 0xb0,
+                               0x34, 0xdb, 0x8c, 0x45, 0x66, 0x0a, 0x08, 0x6b, 0x9f, 0x31, 0xb9, 0x41, 0x5b, 0x08, 0x9a, 0xd4,
+                               0x48, 0xaa, 0xa8, 0xdd, 0xa2, 0xe0, 0x66, 0xf0, 0xcc, 0x35, 0xaa, 0xc5, 0xb1, 0x8a, 0x04, 0x6b,
+                               0xb3, 0xf2, 0x4b, 0xbb, 0x46, 0xad, 0x37, 0x46, 0xac, 0x55, 0xc3, 0xb0, 0xb1, 0x72, 0x2e, 0xb6,
+                               0x9d, 0xa3, 0x12, 0x87, 0xd7, 0x44, 0x60, 0x6c, 0x18, 0x58, 0x43, 0x3c, 0xc5, 0x7b, 0x04, 0xf3,
+                               0xb3, 0x73, 0x33, 0xd8, 0x4b, 0x90, 0x5f, 0xfd, 0x9c, 0xca, 0xe0, 0x5c, 0x60, 0xb1, 0x46, 0x83,
+                               0x7d, 0x42, 0x13, 0xad, 0x27, 0xfd, 0x81, 0xf3, 0x6d, 0xe1, 0x05, 0x51, 0xc6, 0x41, 0xac, 0x8a,
+                               0xe4, 0x31, 0xc5, 0x72, 0x56, 0x9f, 0xed, 0x0f, 0xf0, 0xc9, 0x8a, 0xae, 0x61, 0x3d, 0xc3, 0x97,
+                               0x2e, 0x96, 0xbc, 0x8b
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd7, 0xb0, 0x48, 0x38,
+                               0x46, 0x6b, 0x83, 0xe1, 0x31, 0x0b, 0x44, 0x53, 0xbe, 0x08, 0xd1, 0x0d, 0x10, 0x3e, 0x67, 0x9c,
+                               0x6c, 0x5f, 0x42, 0xf1, 0x27, 0x7e, 0xe8, 0x54, 0x6f, 0x6e, 0x48, 0x1a, 0x4c, 0x6e, 0x1a, 0xd5,
+                               0xac, 0x4f, 0x42, 0x0a, 0x05, 0x19, 0xab, 0x0d, 0xec, 0x24, 0x37, 0xb5, 0x10, 0xdc, 0x3a, 0xa9,
+                               0x4f, 0x28, 0xb4, 0x41, 0x9b, 0xcb, 0xd7, 0xc9, 0x42, 0x3f, 0x30, 0x21, 0x27, 0x44, 0x64, 0xf7,
+                               0x7d, 0x6a, 0x13, 0xb3, 0x06, 0x00, 0xed, 0x96, 0x5f, 0xa1, 0xc5, 0x6c, 0xca, 0x7a, 0xfc, 0x13,
+                               0x33, 0x17, 0xb3, 0x04, 0x94, 0xba, 0x38, 0x5b, 0x01, 0xce, 0x23, 0xc4, 0x43, 0x8e, 0x46, 0x5f,
+                               0x99, 0x1c, 0xa9, 0xbe, 0x10, 0x8e, 0x30, 0x06, 0x54, 0x4f, 0xb7, 0x03, 0x66, 0x82, 0x64, 0xa9,
+                               0xf8, 0x7e, 0x71, 0x5a, 0x7f, 0x58, 0x06, 0x8e, 0xf8, 0x73, 0xb9, 0x0a, 0xae, 0x47, 0x10, 0x9d,
+                               0x44, 0x4d, 0x72, 0xcb, 0x38, 0x7b, 0x87, 0xb0, 0x34, 0xdb, 0x8c, 0x45, 0x66, 0x0a, 0x08, 0x6b,
+                               0x9f, 0x31, 0xb9, 0x41, 0x5b, 0x08, 0x9a, 0xd4, 0x48, 0xaa, 0xa8, 0xdd, 0xa2, 0xe0, 0x66, 0xf0,
+                               0xcc, 0x35, 0xaa, 0xc5, 0xb1, 0x8a, 0x04, 0x6b, 0xb3, 0xf2, 0x4b, 0xbb, 0x46, 0xad, 0x37, 0x46,
+                               0xac, 0x55, 0xc3, 0xb0, 0xb1, 0x72, 0x2e, 0xb6, 0x9d, 0xa3, 0x12, 0x87, 0xd7, 0x44, 0x60, 0x6c,
+                               0x18, 0x58, 0x43, 0x3c, 0xc5, 0x7b, 0x04, 0xf3, 0xb3, 0x73, 0x33, 0xd8, 0x4b, 0x90, 0x5f, 0xfd,
+                               0x9c, 0xca, 0xe0, 0x5c, 0x60, 0xb1, 0x46, 0x83, 0x7d, 0x42, 0x13, 0xad, 0x27, 0xfd, 0x81, 0xf3,
+                               0x6d, 0xe1, 0x05, 0x51, 0xc6, 0x41, 0xac, 0x8a, 0xe4, 0x31, 0xc5, 0x72, 0x56, 0x9f, 0xed, 0x0f,
+                               0xf0, 0xc9, 0x8a, 0xae, 0x61, 0x3d, 0xc3, 0x97, 0x2e, 0x96, 0xbc, 0x8b, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x0c, 0xa9, 0x99, 0x3b, 0xd0, 0x8a, 0x8c, 0x56, 0x02, 0xa2, 0x96,
+                               0x32, 0xd2, 0x63, 0xc3, 0x1e, 0x20, 0xb7, 0x81, 0x2e, 0xb2, 0x40, 0x0f, 0x76, 0x87, 0xea, 0x8f,
+                               0xea, 0x31, 0x16, 0x88, 0xde, 0x1e, 0x61, 0x92, 0x6a, 0xbb, 0xf0, 0x75, 0x40, 0x90, 0x57, 0x16,
+                               0xef, 0x3c, 0xcf, 0x4d, 0x83, 0xf0, 0x46, 0xf5, 0x9c, 0xad, 0xfc, 0x86, 0x44, 0x99, 0xc8, 0x68,
+                               0x55, 0x34, 0xf1, 0xef, 0xb9, 0xea, 0x8b, 0xbd, 0xdf, 0xb7, 0xb9, 0xac, 0x09, 0x65, 0x0f, 0x55,
+                               0xe3, 0x6f, 0x9e, 0xd8, 0x2f, 0xd0, 0x5d, 0x53, 0x02, 0x2f, 0x8c, 0x33, 0x22, 0xd0, 0x36, 0x74,
+                               0x8b, 0x87, 0x3a, 0x1a, 0x68, 0x71, 0xcc, 0xde, 0xfd, 0xca, 0xc7, 0xdf, 0x81, 0x3c, 0x88, 0x94,
+                               0x72, 0x32, 0xad, 0xf1, 0x61, 0x1e, 0x3a, 0x06, 0x7d, 0xe2, 0x0c, 0x4e, 0x4c, 0x51, 0x50, 0x92,
+                               0x25, 0xff, 0x99, 0x0f, 0xa2, 0x60, 0xbe, 0x02, 0x66, 0x8a, 0x74, 0x54, 0x6e, 0x69, 0x23, 0x6b,
+                               0xdd, 0x6c, 0xe4, 0x82, 0x26, 0xa0, 0x5a, 0x05, 0xe3, 0x6a, 0x41, 0x7c, 0xaa, 0xad, 0x6a, 0xb0,
+                               0x83, 0x9e, 0x03, 0x3b, 0x1b, 0xdd, 0x80, 0xfb, 0x14, 0x63, 0x52, 0x0c, 0x2a, 0x6c, 0xdd, 0xd0,
+                               0xc3, 0xe3, 0x28, 0xf8, 0xd5, 0x66, 0xcc, 0x57, 0xad, 0x4e, 0x0e, 0x6d, 0xad, 0xbf, 0x93, 0x24,
+                               0xd0, 0xb8, 0x27, 0xbd, 0x77, 0x39, 0x2d, 0x26, 0xdf, 0xb4, 0xf5, 0xe8, 0xc3, 0xc8, 0x7d, 0xfd,
+                               0x15, 0x46, 0x68, 0xcd, 0x91, 0x59, 0xc5, 0xbe, 0x9b, 0x36, 0x5f, 0x2d, 0xd9, 0x87, 0x05, 0xa3,
+                               0x3a, 0x10, 0x2e, 0x38, 0x12, 0x51, 0x00, 0x19, 0xad, 0x71, 0x88, 0x7d, 0xa9, 0xc9, 0x27, 0x0e,
+                               0x51, 0x6b, 0x6d, 0x0c, 0x4d, 0x16, 0xc1, 0xbf, 0x81, 0x4a, 0xa1, 0x7f, 0x21, 0x27, 0x07, 0x8b,
+                               0x3c, 0xb1, 0xd5, 0x20, 0x31, 0x02, 0x81, 0x81, 0x00, 0xfa, 0x9f, 0x55, 0xde, 0x82, 0xb5, 0xe5,
+                               0xb8, 0x53, 0x40, 0xa0, 0x7c, 0xf3, 0xd9, 0x6f, 0xe8, 0xe4, 0x9d, 0x19, 0xb4, 0xc6, 0x9f, 0x10,
+                               0xaf, 0xa3, 0xed, 0xb2, 0x5b, 0x5b, 0xce, 0x44, 0x0c, 0xc6, 0xda, 0xe3, 0xaa, 0xec, 0x7d, 0xdf,
+                               0xac, 0x4d, 0x9f, 0x8e, 0xcd, 0x27, 0x64, 0xc4, 0x5a, 0xdc, 0xc2, 0x86, 0x1f, 0x1b, 0x73, 0xdf,
+                               0x4f, 0x41, 0xb5, 0x21, 0xdf, 0x21, 0x57, 0x4b, 0x40, 0x05, 0x66, 0x76, 0x65, 0x54, 0xc7, 0x5f,
+                               0x8a, 0xfa, 0x91, 0x00, 0x09, 0xf3, 0x66, 0x46, 0xe4, 0x6f, 0xbd, 0x20, 0xb6, 0x87, 0x27, 0x52,
+                               0x7d, 0x31, 0xac, 0xb1, 0xfe, 0xe6, 0x9c, 0x98, 0x3e, 0xbb, 0xd8, 0xea, 0x8e, 0x7a, 0x2f, 0x53,
+                               0x9c, 0xcb, 0x72, 0x3d, 0x7d, 0x8f, 0xaa, 0xb6, 0x19, 0x45, 0x74, 0xe1, 0x8a, 0xdf, 0x42, 0x1b,
+                               0x0d, 0xd6, 0xae, 0x10, 0xb9, 0xb8, 0xd3, 0xbe, 0x11, 0x02, 0x81, 0x81, 0x00, 0xdc, 0x51, 0x0e,
+                               0x4f, 0xd9, 0x63, 0x31, 0xbc, 0x75, 0xa8, 0xc1, 0x48, 0xd3, 0xf5, 0x7f, 0xca, 0x9f, 0xa8, 0x11,
+                               0x51, 0x44, 0xa2, 0x90, 0xdf, 0x06, 0x7e, 0x04, 0xe5, 0x7a, 0x65, 0x04, 0xd4, 0x8b, 0x74, 0xb0,
+                               0xa8, 0xe3, 0x1d, 0x98, 0x6e, 0xeb, 0xcd, 0x13, 0x21, 0xaa, 0x23, 0x45, 0x25, 0xd1, 0xc3, 0xab,
+                               0x7d, 0x37, 0x68, 0x83, 0x75, 0x47, 0xe3, 0xe1, 0xb3, 0xa8, 0x50, 0x0f, 0xdf, 0xe1, 0x47, 0x45,
+                               0x8e, 0x9c, 0x11, 0x96, 0xf7, 0x01, 0xc9, 0xf1, 0x88, 0x8a, 0x62, 0x29, 0x1f, 0xf3, 0x2c, 0x59,
+                               0x10, 0x42, 0x7d, 0xe9, 0xb6, 0x45, 0xa9, 0xa4, 0x24, 0x04, 0x3f, 0x32, 0xcd, 0xdd, 0x53, 0xd2,
+                               0x13, 0xa9, 0xcf, 0x39, 0x30, 0xc8, 0x95, 0xea, 0x2e, 0x0c, 0xcd, 0x9b, 0x66, 0x09, 0xb1, 0x90,
+                               0x97, 0x30, 0xd0, 0xbd, 0xff, 0x58, 0x72, 0x34, 0x87, 0x7d, 0x6c, 0xe4, 0xdb, 0x02, 0x81, 0x80,
+                               0x32, 0x87, 0x3f, 0x4b, 0xbe, 0x34, 0xa8, 0x1c, 0xf2, 0x83, 0xfc, 0x17, 0x55, 0x1e, 0x88, 0x86,
+                               0x7e, 0xd9, 0x01, 0x9e, 0xc5, 0xd0, 0xba, 0x0f, 0x7f, 0x50, 0x63, 0xfe, 0x31, 0x53, 0x33, 0xbb,
+                               0x13, 0xb8, 0x7b, 0xe0, 0x31, 0x0f, 0xeb, 0xb1, 0x94, 0x70, 0xcf, 0xdc, 0xa2, 0xcd, 0x99, 0x1c,
+                               0xec, 0x97, 0x37, 0x6d, 0x3c, 0x1e, 0xaf, 0x6e, 0x0e, 0x44, 0x53, 0x90, 0xe5, 0xea, 0xf0, 0x9b,
+                               0xf6, 0xba, 0xc2, 0xb2, 0x09, 0x56, 0xac, 0xf7, 0x9b, 0x2e, 0xf2, 0xc0, 0x0e, 0xd7, 0x97, 0x01,
+                               0x65, 0x59, 0xcf, 0x27, 0x9d, 0xb6, 0x1a, 0xd6, 0x6c, 0xd6, 0x71, 0x88, 0x7f, 0xbc, 0x20, 0xa1,
+                               0xbd, 0x43, 0xad, 0x7e, 0x8e, 0x39, 0xce, 0x74, 0xb5, 0x3b, 0xd2, 0x13, 0xe7, 0x8c, 0x36, 0x6c,
+                               0x7f, 0xe9, 0x61, 0xb3, 0x9a, 0xf1, 0x7b, 0xfc, 0xdd, 0x44, 0x78, 0xd0, 0xc8, 0xe7, 0xbf, 0xd1,
+                               0x02, 0x81, 0x80, 0x37, 0x95, 0x23, 0x7d, 0x35, 0xa4, 0xf6, 0xe9, 0x4a, 0xed, 0xd6, 0x45, 0x9f,
+                               0x63, 0xf2, 0x67, 0x96, 0x3b, 0xfa, 0x92, 0x7c, 0x34, 0x5f, 0x44, 0x45, 0x09, 0x0a, 0x07, 0x33,
+                               0x07, 0xc5, 0x5e, 0x32, 0x08, 0xb0, 0x81, 0x0b, 0x3b, 0x6e, 0x37, 0x9d, 0xb9, 0xd1, 0x37, 0xcf,
+                               0x0a, 0xb2, 0xe9, 0x45, 0xb0, 0x8d, 0xd5, 0x3b, 0x63, 0x5c, 0xb6, 0xfb, 0xfa, 0x1b, 0xf9, 0x0d,
+                               0x5f, 0x95, 0x81, 0xaf, 0xfe, 0x21, 0x28, 0x47, 0x6b, 0x90, 0xa0, 0xa4, 0x8c, 0xe4, 0x28, 0xc4,
+                               0x70, 0xdb, 0x5b, 0x3a, 0x28, 0x58, 0x17, 0xe8, 0x0e, 0x88, 0x25, 0xfa, 0x11, 0x40, 0x5d, 0xed,
+                               0xcc, 0x5b, 0xee, 0xe0, 0x05, 0xc3, 0x9c, 0x3e, 0x5d, 0x0f, 0x5a, 0xa5, 0x60, 0x5d, 0x02, 0x5d,
+                               0x2c, 0x8a, 0x26, 0x06, 0x10, 0x0c, 0x20, 0x78, 0xaa, 0x51, 0x24, 0xd8, 0x95, 0x87, 0x77, 0x1f,
+                               0x47, 0x97, 0xbd, 0x02, 0x81, 0x80, 0x25, 0x8b, 0x8f, 0xb6, 0x3e, 0x35, 0x5c, 0x56, 0x81, 0x03,
+                               0x06, 0x67, 0x8f, 0xc7, 0x7a, 0x54, 0xe0, 0x29, 0x84, 0xf5, 0x0e, 0xd1, 0x96, 0x4b, 0xeb, 0xe2,
+                               0x0a, 0x31, 0x8e, 0xea, 0xcf, 0xc9, 0x0d, 0x73, 0x2f, 0x31, 0xfc, 0xbc, 0xd0, 0xec, 0xe2, 0xd2,
+                               0x2c, 0x72, 0x48, 0xbb, 0xb7, 0x61, 0x85, 0xe8, 0xf0, 0xac, 0x3e, 0x11, 0x34, 0xb4, 0xc4, 0xaa,
+                               0xe8, 0x43, 0x83, 0xeb, 0x12, 0xf5, 0x44, 0x32, 0xb8, 0x82, 0x08, 0x53, 0x5e, 0xec, 0xb8, 0x10,
+                               0x63, 0xc5, 0x53, 0xbe, 0x36, 0x3a, 0x28, 0xdf, 0x87, 0x5e, 0x46, 0xa4, 0xe0, 0xbc, 0xd6, 0xa6,
+                               0x16, 0x77, 0x6a, 0xf5, 0xd9, 0x0a, 0x79, 0x8f, 0xaa, 0x1a, 0x9f, 0x7f, 0xea, 0x94, 0xd8, 0x6d,
+                               0x7a, 0xfd, 0x19, 0x46, 0x27, 0x6f, 0x4b, 0xb8, 0xd9, 0x67, 0xa7, 0xce, 0x51, 0x95, 0x3e, 0xe6,
+                               0xce, 0x36, 0x09, 0xfc, 0xd0, 0x71
+                       }
+               }
+       },
+       // DNSSEC Zone 3
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_RSASHA512 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xae, 0x09, 0xe7, 0x83, 0x17, 0xc1, 0x72, 0xd7, 0x86, 0x28, 0xe8, 0x40,
+                               0xb5, 0x1e, 0x5b, 0xb6, 0xd7, 0x6a, 0xa1, 0xb2, 0xb7, 0xe4, 0xcd, 0x53, 0x40, 0xf5, 0x54, 0x58,
+                               0x8a, 0x52, 0xf5, 0xd3, 0x77, 0x16, 0xd0, 0x1d, 0xc1, 0x80, 0x48, 0x79, 0xe6, 0xa4, 0xb8, 0x2b,
+                               0xe0, 0x5e, 0xc9, 0x87, 0x2e, 0x5e, 0x02, 0x80, 0xe7, 0x1c, 0xb8, 0x88, 0xc9, 0xff, 0x2c, 0x4a,
+                               0x33, 0x10, 0x82, 0x56, 0x3d, 0x1f, 0x61, 0x6d, 0x6d, 0x52, 0x68, 0xaf, 0x39, 0x47, 0x6b, 0xaa,
+                               0x1e, 0x0d, 0x95, 0x35, 0x24, 0x48, 0xff, 0x0c, 0x45, 0xd6, 0x2e, 0x06, 0x3a, 0x50, 0xc9, 0x27,
+                               0xfb, 0xf7, 0x3a, 0x01, 0xfd, 0xc8, 0x0e, 0xd7, 0x93, 0xe0, 0x7c, 0x63, 0xc1, 0x63, 0x4e, 0xf2,
+                               0x23, 0x63, 0x6f, 0x07, 0x74, 0xfd, 0x02, 0xbe, 0x9d, 0x1b, 0xad, 0x14, 0x28, 0x80, 0x15, 0x0a,
+                               0x3a, 0x44, 0x5c, 0xa1, 0x29, 0xfc, 0xb0, 0x18, 0x48, 0x2f, 0xa4, 0xcb, 0x56, 0xb5, 0xd1, 0xa3,
+                               0x20, 0x3b, 0xe1, 0xd2, 0x2e, 0x07, 0x18, 0x06, 0xa1, 0x1f, 0x37, 0xa3, 0x05, 0xbc, 0x11, 0x66,
+                               0x20, 0xa6, 0x9b, 0xd6, 0x6c, 0x89, 0xef, 0x5b, 0x72, 0xfd, 0x44, 0x7c, 0x27, 0x09, 0x65, 0x48,
+                               0x1f, 0x9c, 0x79, 0xa1, 0x49, 0x23, 0xbf, 0x93, 0xcb, 0xfb, 0xeb, 0x14, 0x0a, 0x8e, 0xa4, 0xef,
+                               0xec, 0xfb, 0xc3, 0x48, 0xf1, 0xf0, 0xc3, 0x10, 0xe3, 0x80, 0x24, 0xca, 0xa6, 0xca, 0xc2, 0x4a,
+                               0xee, 0x6e, 0xe7, 0x4e, 0xdc, 0x8c, 0x15, 0xe3, 0xfc, 0xed, 0xa2, 0xc6, 0xa7, 0x4b, 0xe7, 0x79,
+                               0xfd, 0x40, 0xc4, 0x60, 0xd2, 0x50, 0x33, 0x73, 0x70, 0x9d, 0x60, 0x8c, 0x28, 0x81, 0x5d, 0x63,
+                               0xf5, 0xe7, 0x16, 0xe9, 0xe4, 0x08, 0xa2, 0xa9, 0xa5, 0xc5, 0x08, 0x6c, 0x24, 0xea, 0x22, 0x83,
+                               0x29, 0x33, 0x7d, 0xe9
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xae, 0x09, 0xe7, 0x83,
+                               0x17, 0xc1, 0x72, 0xd7, 0x86, 0x28, 0xe8, 0x40, 0xb5, 0x1e, 0x5b, 0xb6, 0xd7, 0x6a, 0xa1, 0xb2,
+                               0xb7, 0xe4, 0xcd, 0x53, 0x40, 0xf5, 0x54, 0x58, 0x8a, 0x52, 0xf5, 0xd3, 0x77, 0x16, 0xd0, 0x1d,
+                               0xc1, 0x80, 0x48, 0x79, 0xe6, 0xa4, 0xb8, 0x2b, 0xe0, 0x5e, 0xc9, 0x87, 0x2e, 0x5e, 0x02, 0x80,
+                               0xe7, 0x1c, 0xb8, 0x88, 0xc9, 0xff, 0x2c, 0x4a, 0x33, 0x10, 0x82, 0x56, 0x3d, 0x1f, 0x61, 0x6d,
+                               0x6d, 0x52, 0x68, 0xaf, 0x39, 0x47, 0x6b, 0xaa, 0x1e, 0x0d, 0x95, 0x35, 0x24, 0x48, 0xff, 0x0c,
+                               0x45, 0xd6, 0x2e, 0x06, 0x3a, 0x50, 0xc9, 0x27, 0xfb, 0xf7, 0x3a, 0x01, 0xfd, 0xc8, 0x0e, 0xd7,
+                               0x93, 0xe0, 0x7c, 0x63, 0xc1, 0x63, 0x4e, 0xf2, 0x23, 0x63, 0x6f, 0x07, 0x74, 0xfd, 0x02, 0xbe,
+                               0x9d, 0x1b, 0xad, 0x14, 0x28, 0x80, 0x15, 0x0a, 0x3a, 0x44, 0x5c, 0xa1, 0x29, 0xfc, 0xb0, 0x18,
+                               0x48, 0x2f, 0xa4, 0xcb, 0x56, 0xb5, 0xd1, 0xa3, 0x20, 0x3b, 0xe1, 0xd2, 0x2e, 0x07, 0x18, 0x06,
+                               0xa1, 0x1f, 0x37, 0xa3, 0x05, 0xbc, 0x11, 0x66, 0x20, 0xa6, 0x9b, 0xd6, 0x6c, 0x89, 0xef, 0x5b,
+                               0x72, 0xfd, 0x44, 0x7c, 0x27, 0x09, 0x65, 0x48, 0x1f, 0x9c, 0x79, 0xa1, 0x49, 0x23, 0xbf, 0x93,
+                               0xcb, 0xfb, 0xeb, 0x14, 0x0a, 0x8e, 0xa4, 0xef, 0xec, 0xfb, 0xc3, 0x48, 0xf1, 0xf0, 0xc3, 0x10,
+                               0xe3, 0x80, 0x24, 0xca, 0xa6, 0xca, 0xc2, 0x4a, 0xee, 0x6e, 0xe7, 0x4e, 0xdc, 0x8c, 0x15, 0xe3,
+                               0xfc, 0xed, 0xa2, 0xc6, 0xa7, 0x4b, 0xe7, 0x79, 0xfd, 0x40, 0xc4, 0x60, 0xd2, 0x50, 0x33, 0x73,
+                               0x70, 0x9d, 0x60, 0x8c, 0x28, 0x81, 0x5d, 0x63, 0xf5, 0xe7, 0x16, 0xe9, 0xe4, 0x08, 0xa2, 0xa9,
+                               0xa5, 0xc5, 0x08, 0x6c, 0x24, 0xea, 0x22, 0x83, 0x29, 0x33, 0x7d, 0xe9, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x0f, 0x3c, 0xda, 0xc2, 0x45, 0x90, 0x30, 0x67, 0xe2, 0x5d, 0x2e,
+                               0x88, 0x8b, 0xd3, 0x0e, 0xa0, 0xa1, 0x97, 0xba, 0x74, 0x95, 0x59, 0x39, 0x08, 0xf3, 0x05, 0x07,
+                               0xcf, 0x80, 0xbc, 0xe6, 0xbd, 0x0f, 0x0e, 0x36, 0x46, 0x84, 0xac, 0xa0, 0xab, 0xdb, 0x9f, 0xcc,
+                               0x68, 0x3b, 0x0a, 0xd1, 0x86, 0x3e, 0x6f, 0x08, 0xb5, 0x14, 0x87, 0x15, 0x39, 0x14, 0xc6, 0x7c,
+                               0xb0, 0x3a, 0x6c, 0x55, 0x9d, 0x0b, 0x58, 0xf6, 0x04, 0xc8, 0xaf, 0x90, 0x6e, 0xd7, 0x0b, 0x03,
+                               0xc8, 0x2f, 0x3d, 0x6d, 0x34, 0xeb, 0xea, 0x60, 0x3a, 0x48, 0xdb, 0x09, 0xc8, 0x8d, 0xce, 0x88,
+                               0xee, 0x31, 0x47, 0x62, 0x12, 0xce, 0x8f, 0x77, 0x8d, 0x34, 0x6b, 0x51, 0x38, 0xc2, 0xb3, 0xd9,
+                               0x33, 0xa6, 0x19, 0x8c, 0x9d, 0x10, 0x87, 0x1e, 0x78, 0x65, 0xf5, 0x08, 0x60, 0x5d, 0x73, 0x69,
+                               0x64, 0x2d, 0xd9, 0xd2, 0x6a, 0x3c, 0x87, 0x7e, 0x9b, 0x27, 0x4d, 0x27, 0x8a, 0xab, 0xc5, 0x1f,
+                               0xe1, 0x3f, 0x45, 0x58, 0x69, 0xfe, 0x53, 0x86, 0x71, 0xed, 0x4b, 0x59, 0x0b, 0x60, 0x8b, 0x57,
+                               0x13, 0xef, 0xcc, 0x42, 0x17, 0x40, 0x19, 0x11, 0x68, 0x98, 0x12, 0x9d, 0x87, 0x79, 0xe7, 0xf6,
+                               0x27, 0x91, 0x92, 0x40, 0xdd, 0x23, 0x50, 0x42, 0x88, 0xb1, 0x59, 0x5f, 0x21, 0xc8, 0x51, 0x8f,
+                               0xde, 0x6c, 0x0c, 0x6f, 0x7c, 0xa2, 0xf9, 0x69, 0x28, 0xd6, 0xab, 0xa8, 0x7a, 0x29, 0x48, 0x29,
+                               0xfd, 0x87, 0x84, 0xff, 0xff, 0x3d, 0xad, 0x58, 0xc3, 0xb7, 0x3f, 0x3b, 0x85, 0x98, 0x57, 0x6d,
+                               0x80, 0x61, 0x33, 0x98, 0x4c, 0x33, 0xc0, 0x70, 0x11, 0x9a, 0x89, 0xfe, 0x04, 0xd2, 0x3f, 0x6d,
+                               0xa3, 0xd1, 0x50, 0xee, 0x5c, 0xe1, 0xd4, 0x1f, 0x6d, 0xb9, 0xff, 0x30, 0x09, 0xd0, 0x59, 0x02,
+                               0x83, 0x60, 0x84, 0xbe, 0x29, 0x02, 0x81, 0x81, 0x00, 0xf6, 0x00, 0xf9, 0x0d, 0x70, 0xb7, 0xeb,
+                               0x8d, 0x1d, 0xb8, 0x5d, 0xf1, 0xad, 0x98, 0x91, 0x52, 0xa9, 0x83, 0x2f, 0xc2, 0xf1, 0x5d, 0x36,
+                               0xf0, 0x3f, 0x12, 0xb2, 0xfb, 0x2a, 0x8c, 0xd2, 0x55, 0x05, 0xc3, 0xdb, 0x0d, 0xd9, 0x33, 0x25,
+                               0x82, 0x20, 0x65, 0x08, 0xe3, 0xe8, 0x85, 0x9c, 0x59, 0x15, 0x8e, 0x54, 0xfa, 0xc7, 0x7d, 0x6f,
+                               0x61, 0xeb, 0x19, 0xf9, 0xdc, 0xab, 0x1e, 0x30, 0x60, 0x5f, 0x12, 0x73, 0x33, 0x09, 0xd8, 0xba,
+                               0x7e, 0x6f, 0x05, 0x49, 0x8f, 0x5a, 0xd2, 0x7c, 0x4c, 0xd5, 0x71, 0x3f, 0xb0, 0xdc, 0x84, 0xdd,
+                               0x39, 0x90, 0x55, 0x5c, 0x20, 0xcf, 0x63, 0xf0, 0xbd, 0xb1, 0xef, 0xec, 0xdc, 0x50, 0x7c, 0xfc,
+                               0xbe, 0x7d, 0x84, 0x0b, 0x0d, 0x54, 0xd8, 0x4d, 0x06, 0x88, 0xfc, 0x62, 0xc4, 0x64, 0xba, 0x61,
+                               0x49, 0xf0, 0xed, 0xea, 0x63, 0xf0, 0x1b, 0xae, 0x43, 0x02, 0x81, 0x81, 0x00, 0xb5, 0x1c, 0x52,
+                               0x8a, 0x71, 0xbb, 0x69, 0x4d, 0x10, 0xc1, 0xaf, 0x03, 0xaa, 0x8e, 0xfb, 0xf0, 0x17, 0xad, 0x2f,
+                               0xac, 0x2f, 0xa8, 0x05, 0x92, 0xd9, 0xa2, 0xb1, 0x85, 0xde, 0x9f, 0xe7, 0x82, 0x32, 0x34, 0x08,
+                               0x45, 0xae, 0xb4, 0x5d, 0xe1, 0x27, 0xf3, 0x25, 0xe4, 0xa1, 0x03, 0x4d, 0xbd, 0x15, 0xa1, 0x1c,
+                               0xbf, 0x36, 0x8c, 0x8f, 0x5d, 0x33, 0x62, 0x19, 0xa1, 0xab, 0xeb, 0xdf, 0x10, 0x14, 0xf6, 0x93,
+                               0x94, 0xb0, 0x7f, 0x35, 0xed, 0x8e, 0x4f, 0x46, 0x54, 0xe5, 0x24, 0xb1, 0x7d, 0x17, 0x0c, 0xfc,
+                               0x85, 0x65, 0x57, 0x6d, 0x44, 0x1b, 0x1c, 0x79, 0x55, 0x21, 0xe8, 0x2e, 0x64, 0x93, 0x01, 0xf7,
+                               0xcc, 0x75, 0x2e, 0x32, 0x10, 0xef, 0x6a, 0xe6, 0x95, 0x22, 0xf1, 0x5b, 0x6e, 0x19, 0x6b, 0x4f,
+                               0xf6, 0xad, 0xf3, 0x45, 0xea, 0xf3, 0xe1, 0xad, 0xfa, 0x25, 0x8b, 0xde, 0x63, 0x02, 0x81, 0x80,
+                               0x02, 0x60, 0x46, 0x07, 0x8c, 0x34, 0x2a, 0x91, 0x45, 0x5d, 0x37, 0x83, 0x13, 0x13, 0x2b, 0x2c,
+                               0x2a, 0x38, 0xfb, 0x28, 0x1d, 0xde, 0xb1, 0x9e, 0x26, 0x6d, 0xce, 0xce, 0xc0, 0x3d, 0x75, 0xf6,
+                               0x9e, 0x44, 0xa3, 0x04, 0x02, 0x3a, 0x0d, 0x3d, 0xb1, 0x84, 0x68, 0x4e, 0x93, 0x3b, 0x5f, 0x24,
+                               0x39, 0x22, 0x13, 0xce, 0x0c, 0x49, 0x38, 0xdf, 0x30, 0x3f, 0x22, 0xfb, 0xaf, 0xa7, 0x6d, 0xfe,
+                               0x30, 0x4c, 0xe4, 0x51, 0xf4, 0xe1, 0x64, 0x03, 0xbe, 0x9c, 0xc5, 0x76, 0x1e, 0x01, 0x74, 0x73,
+                               0xe5, 0x87, 0xdf, 0x7e, 0x9c, 0xd2, 0xa7, 0xb4, 0x5b, 0xa1, 0x87, 0x6f, 0x45, 0xb1, 0x88, 0x6c,
+                               0xbc, 0xa0, 0x3d, 0xa0, 0x6f, 0x22, 0x95, 0xea, 0x5d, 0x19, 0xfe, 0x65, 0x55, 0x91, 0x75, 0x44,
+                               0xff, 0xe3, 0xb8, 0x2c, 0x7f, 0x70, 0x91, 0x69, 0xfe, 0x6a, 0xbd, 0xdd, 0xd3, 0x3e, 0xe1, 0x33,
+                               0x02, 0x81, 0x80, 0x3b, 0x7c, 0xa0, 0x07, 0xe5, 0x2f, 0x5f, 0x78, 0xf6, 0xa6, 0x07, 0x6c, 0x36,
+                               0xdb, 0x5c, 0x19, 0x12, 0x4d, 0xca, 0x38, 0xce, 0x36, 0x6c, 0x0e, 0x3d, 0xf9, 0xd8, 0x41, 0x56,
+                               0x4c, 0x43, 0x68, 0x5a, 0x41, 0x08, 0xee, 0x8a, 0x61, 0xa7, 0xec, 0x4a, 0x88, 0xec, 0xcb, 0x59,
+                               0xaf, 0x92, 0x1b, 0x27, 0x3d, 0x92, 0x09, 0xa4, 0x4b, 0x2a, 0xd3, 0xbc, 0x0a, 0xde, 0xcc, 0x61,
+                               0x81, 0xc9, 0xf4, 0xa8, 0x4e, 0x0d, 0x4f, 0x6b, 0x61, 0x7d, 0x3f, 0x17, 0xdd, 0x26, 0x06, 0xc0,
+                               0x74, 0xfa, 0xef, 0x51, 0x60, 0xec, 0x98, 0x8c, 0x1e, 0xd9, 0x3e, 0xe3, 0x80, 0xc8, 0xc1, 0x93,
+                               0xaf, 0x85, 0x7d, 0x77, 0x63, 0xd0, 0x16, 0x91, 0xee, 0xa8, 0x77, 0x33, 0x8c, 0x73, 0x4c, 0x4c,
+                               0xfb, 0xe5, 0xc2, 0x16, 0x0b, 0x4f, 0x6a, 0x80, 0xdc, 0x44, 0xfc, 0xd5, 0x3a, 0x2d, 0x40, 0xda,
+                               0x90, 0x44, 0x6d, 0x02, 0x81, 0x80, 0x6e, 0x45, 0xc7, 0x6f, 0x93, 0xfb, 0x46, 0x8b, 0x81, 0xa8,
+                               0x6b, 0x09, 0x9a, 0xaf, 0x26, 0xe6, 0xcb, 0xfb, 0x6c, 0x5a, 0xc6, 0x4d, 0x29, 0x22, 0x65, 0x47,
+                               0xab, 0x0c, 0xee, 0xf3, 0x8b, 0xed, 0xa4, 0x9d, 0x2a, 0x15, 0xcc, 0x6f, 0x1e, 0x19, 0x1b, 0x0e,
+                               0xda, 0xd3, 0xcc, 0xb3, 0x0d, 0x4f, 0x5c, 0x4a, 0x73, 0xee, 0xaf, 0x22, 0x83, 0x2b, 0x92, 0x0f,
+                               0xfc, 0xf8, 0xa3, 0xac, 0x53, 0xb8, 0x28, 0xfc, 0x96, 0x4e, 0x1d, 0x84, 0xdd, 0xeb, 0x74, 0xac,
+                               0x7b, 0xbe, 0x7e, 0x2e, 0xaf, 0x47, 0x16, 0x50, 0x84, 0xdf, 0x05, 0x7b, 0xeb, 0x4a, 0xb6, 0xea,
+                               0x27, 0x65, 0x5d, 0xef, 0x01, 0x8c, 0x91, 0x70, 0xc6, 0x33, 0xad, 0xe9, 0x55, 0xf4, 0x0c, 0x00,
+                               0xa5, 0xfa, 0xb5, 0x90, 0xde, 0x16, 0x8b, 0x2d, 0x02, 0x56, 0xc0, 0x95, 0xef, 0x68, 0x8e, 0x15,
+                               0x9a, 0x49, 0x53, 0x96, 0x2b, 0x17
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_RSASHA512 ),
+                       .pubKey =
+                       {
+                               0x03, 0x01, 0x00, 0x01, 0xdc, 0xac, 0xa7, 0x8f, 0xd9, 0xca, 0xf4, 0x85, 0x3d, 0x3a, 0xa7, 0x54,
+                               0x5e, 0xda, 0x67, 0x30, 0x3b, 0x1d, 0xf1, 0x90, 0x1c, 0x84, 0xa3, 0xb6, 0xab, 0xb8, 0x06, 0xc6,
+                               0xca, 0x99, 0x5e, 0xa6, 0xb0, 0x8b, 0x75, 0x48, 0x61, 0xce, 0x94, 0xf4, 0x50, 0x88, 0xcb, 0x4d,
+                               0xe7, 0xe9, 0x70, 0x91, 0x47, 0x0a, 0x77, 0xdc, 0x38, 0xc2, 0x73, 0x54, 0xd0, 0x33, 0x27, 0x92,
+                               0xdd, 0x35, 0xab, 0xe6, 0x32, 0xee, 0x7d, 0x5b, 0x4e, 0x9d, 0x6e, 0xfb, 0x00, 0x7e, 0xe6, 0xd0,
+                               0x68, 0x9b, 0xc0, 0x6d, 0x02, 0x4c, 0xc4, 0x20, 0x6f, 0x61, 0x98, 0x24, 0xdb, 0x21, 0x89, 0xb0,
+                               0x13, 0xf2, 0x58, 0x98, 0x82, 0x5c, 0x79, 0xcb, 0x0f, 0x56, 0x83, 0xda, 0x50, 0x21, 0x40, 0xd5,
+                               0x28, 0xcc, 0x92, 0xbc, 0x09, 0x04, 0xba, 0x9d, 0xa2, 0xcb, 0xed, 0x56, 0x91, 0xfd, 0x1b, 0x4b,
+                               0x73, 0x5f, 0x5f, 0xb7, 0x46, 0x25, 0xc3, 0x0c, 0xcb, 0x66, 0xe6, 0x59, 0x0e, 0x0e, 0x39, 0x12,
+                               0x23, 0x22, 0x97, 0x3e, 0x34, 0x69, 0x4d, 0x8b, 0xbd, 0x14, 0x9d, 0x08, 0xef, 0xd9, 0x0a, 0x5d,
+                               0xf1, 0x96, 0x8b, 0xa0, 0xe6, 0x1c, 0x83, 0x38, 0x21, 0xc9, 0xe6, 0xd0, 0x35, 0xd4, 0x73, 0x92,
+                               0xc5, 0x27, 0x98, 0xb3, 0x70, 0x3d, 0x87, 0x93, 0x41, 0xb3, 0xc5, 0xf3, 0x27, 0xd4, 0x29, 0x1e,
+                               0xc8, 0xf6, 0xe1, 0xc1, 0xc9, 0x48, 0xb3, 0x7e, 0xa2, 0x53, 0xe2, 0xfe, 0xd8, 0x48, 0xa6, 0x04,
+                               0x40, 0x71, 0x99, 0x08, 0x96, 0x43, 0x25, 0x01, 0xc4, 0xdd, 0x6f, 0xe2, 0x86, 0xac, 0x73, 0xc2,
+                               0xe3, 0x05, 0x67, 0x21, 0xf7, 0x63, 0x4f, 0x46, 0xf9, 0xd2, 0xf6, 0x80, 0x43, 0x13, 0xe4, 0x05,
+                               0x5c, 0x99, 0x2a, 0xf5, 0xde, 0x1d, 0x87, 0x4f, 0xf3, 0x87, 0x00, 0x36, 0x46, 0x21, 0xe8, 0x12,
+                               0x6a, 0xda, 0x76, 0x29
+                       },
+                       .secKey =
+                       {
+                               0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xdc, 0xac, 0xa7, 0x8f,
+                               0xd9, 0xca, 0xf4, 0x85, 0x3d, 0x3a, 0xa7, 0x54, 0x5e, 0xda, 0x67, 0x30, 0x3b, 0x1d, 0xf1, 0x90,
+                               0x1c, 0x84, 0xa3, 0xb6, 0xab, 0xb8, 0x06, 0xc6, 0xca, 0x99, 0x5e, 0xa6, 0xb0, 0x8b, 0x75, 0x48,
+                               0x61, 0xce, 0x94, 0xf4, 0x50, 0x88, 0xcb, 0x4d, 0xe7, 0xe9, 0x70, 0x91, 0x47, 0x0a, 0x77, 0xdc,
+                               0x38, 0xc2, 0x73, 0x54, 0xd0, 0x33, 0x27, 0x92, 0xdd, 0x35, 0xab, 0xe6, 0x32, 0xee, 0x7d, 0x5b,
+                               0x4e, 0x9d, 0x6e, 0xfb, 0x00, 0x7e, 0xe6, 0xd0, 0x68, 0x9b, 0xc0, 0x6d, 0x02, 0x4c, 0xc4, 0x20,
+                               0x6f, 0x61, 0x98, 0x24, 0xdb, 0x21, 0x89, 0xb0, 0x13, 0xf2, 0x58, 0x98, 0x82, 0x5c, 0x79, 0xcb,
+                               0x0f, 0x56, 0x83, 0xda, 0x50, 0x21, 0x40, 0xd5, 0x28, 0xcc, 0x92, 0xbc, 0x09, 0x04, 0xba, 0x9d,
+                               0xa2, 0xcb, 0xed, 0x56, 0x91, 0xfd, 0x1b, 0x4b, 0x73, 0x5f, 0x5f, 0xb7, 0x46, 0x25, 0xc3, 0x0c,
+                               0xcb, 0x66, 0xe6, 0x59, 0x0e, 0x0e, 0x39, 0x12, 0x23, 0x22, 0x97, 0x3e, 0x34, 0x69, 0x4d, 0x8b,
+                               0xbd, 0x14, 0x9d, 0x08, 0xef, 0xd9, 0x0a, 0x5d, 0xf1, 0x96, 0x8b, 0xa0, 0xe6, 0x1c, 0x83, 0x38,
+                               0x21, 0xc9, 0xe6, 0xd0, 0x35, 0xd4, 0x73, 0x92, 0xc5, 0x27, 0x98, 0xb3, 0x70, 0x3d, 0x87, 0x93,
+                               0x41, 0xb3, 0xc5, 0xf3, 0x27, 0xd4, 0x29, 0x1e, 0xc8, 0xf6, 0xe1, 0xc1, 0xc9, 0x48, 0xb3, 0x7e,
+                               0xa2, 0x53, 0xe2, 0xfe, 0xd8, 0x48, 0xa6, 0x04, 0x40, 0x71, 0x99, 0x08, 0x96, 0x43, 0x25, 0x01,
+                               0xc4, 0xdd, 0x6f, 0xe2, 0x86, 0xac, 0x73, 0xc2, 0xe3, 0x05, 0x67, 0x21, 0xf7, 0x63, 0x4f, 0x46,
+                               0xf9, 0xd2, 0xf6, 0x80, 0x43, 0x13, 0xe4, 0x05, 0x5c, 0x99, 0x2a, 0xf5, 0xde, 0x1d, 0x87, 0x4f,
+                               0xf3, 0x87, 0x00, 0x36, 0x46, 0x21, 0xe8, 0x12, 0x6a, 0xda, 0x76, 0x29, 0x02, 0x03, 0x01, 0x00,
+                               0x01, 0x02, 0x82, 0x01, 0x00, 0x06, 0x43, 0xda, 0x19, 0x7f, 0xca, 0xce, 0xec, 0xa1, 0xf7, 0x82,
+                               0x74, 0xe7, 0x20, 0xfe, 0xfd, 0x3b, 0xee, 0x6e, 0x93, 0x63, 0x75, 0xe2, 0x12, 0x7f, 0x77, 0x50,
+                               0xec, 0xef, 0xa7, 0x51, 0x4c, 0x3c, 0xf6, 0xa6, 0xa0, 0xa4, 0x93, 0x39, 0x80, 0x22, 0xb1, 0xb1,
+                               0x88, 0xef, 0x90, 0xbd, 0x77, 0x08, 0x6b, 0xec, 0x09, 0x7a, 0xec, 0x8d, 0x5a, 0xee, 0xf2, 0xc6,
+                               0xd8, 0xb8, 0xf2, 0x4b, 0x44, 0x34, 0xb5, 0xb7, 0xe6, 0x21, 0xda, 0x1f, 0x22, 0x9d, 0xe9, 0xdc,
+                               0x6a, 0x76, 0x0f, 0xd6, 0xf3, 0x99, 0x55, 0x3d, 0xdb, 0xb8, 0x61, 0xce, 0x50, 0x9c, 0x8e, 0x8c,
+                               0x12, 0xcd, 0x4c, 0x2b, 0xf6, 0xff, 0x7b, 0x79, 0xe3, 0x39, 0x97, 0x20, 0x94, 0xf0, 0x01, 0xb3,
+                               0xae, 0x21, 0x93, 0x86, 0x46, 0x73, 0xa0, 0x98, 0x5e, 0x3d, 0x97, 0x19, 0xd0, 0xc3, 0x82, 0x0a,
+                               0xb6, 0x76, 0x8a, 0xf2, 0x84, 0x0b, 0x69, 0x10, 0xf5, 0x7c, 0xa1, 0xb5, 0xf0, 0xcc, 0xca, 0xda,
+                               0x66, 0xdd, 0xc1, 0x01, 0x70, 0x2b, 0xc7, 0xb1, 0x28, 0xfc, 0xab, 0xea, 0xf4, 0x8a, 0x3c, 0xc8,
+                               0x7d, 0xdf, 0x6d, 0x89, 0x7b, 0x75, 0xad, 0xa0, 0xfc, 0x88, 0x31, 0x43, 0x21, 0xa6, 0x33, 0x13,
+                               0x0f, 0x4a, 0xab, 0x71, 0xd5, 0xca, 0x1a, 0xf3, 0xca, 0xe6, 0x45, 0xec, 0xf5, 0xe2, 0xee, 0x56,
+                               0xc0, 0x62, 0x66, 0x91, 0x14, 0x95, 0xd7, 0xf1, 0x1d, 0xd0, 0x57, 0x8a, 0xa6, 0xb7, 0xc5, 0x90,
+                               0x7d, 0xcb, 0xb5, 0xb2, 0xa0, 0xa8, 0x97, 0xd3, 0x50, 0x20, 0x49, 0x6b, 0xbc, 0x3d, 0x25, 0x02,
+                               0x5d, 0x35, 0x61, 0xd2, 0x47, 0xe1, 0x69, 0xf4, 0xe3, 0x50, 0xf5, 0xbe, 0xb2, 0x5c, 0xe7, 0x64,
+                               0x62, 0x1f, 0x32, 0xce, 0x0e, 0x26, 0xd0, 0xf9, 0x6e, 0x15, 0x84, 0xc9, 0xba, 0x21, 0x16, 0x02,
+                               0x12, 0xf6, 0xef, 0xd4, 0x71, 0x02, 0x81, 0x81, 0x00, 0xef, 0xb8, 0xdc, 0x5b, 0xe0, 0x55, 0x50,
+                               0x3a, 0xeb, 0xcb, 0x96, 0x35, 0x0e, 0x97, 0x45, 0x38, 0x3b, 0xdd, 0xf7, 0x9f, 0xeb, 0x1d, 0x0e,
+                               0x08, 0x6a, 0xa4, 0x6f, 0xf1, 0x87, 0xec, 0x4c, 0x69, 0xaf, 0x51, 0xdf, 0x08, 0xe7, 0xde, 0x07,
+                               0x5b, 0x2c, 0x9c, 0x6f, 0x21, 0xd6, 0x5d, 0x5f, 0x71, 0x8b, 0xc8, 0x9d, 0x1e, 0xdb, 0xcb, 0x4d,
+                               0xf3, 0xbc, 0xfe, 0x58, 0x7c, 0x39, 0x12, 0x42, 0x11, 0xa8, 0x30, 0xc2, 0xf6, 0x87, 0x33, 0xd8,
+                               0x99, 0xfb, 0x7b, 0x70, 0x30, 0x51, 0x95, 0xea, 0x42, 0x09, 0xd8, 0x2d, 0xdf, 0x68, 0xa4, 0xd9,
+                               0xc0, 0xb7, 0x6d, 0x4d, 0x20, 0xd6, 0xff, 0x95, 0x92, 0x03, 0xcf, 0x3a, 0xda, 0x76, 0xcd, 0x30,
+                               0x49, 0xf5, 0xb5, 0x38, 0x08, 0x79, 0xc7, 0x09, 0x95, 0xdf, 0x39, 0x10, 0x02, 0xa4, 0x20, 0xd2,
+                               0x4e, 0xc8, 0x19, 0x0a, 0xf9, 0x50, 0xff, 0xee, 0x91, 0x02, 0x81, 0x81, 0x00, 0xeb, 0xa8, 0xaf,
+                               0x19, 0x20, 0xf9, 0xe0, 0x7b, 0x8e, 0xfc, 0x48, 0xe2, 0x98, 0xc4, 0x53, 0x03, 0x84, 0x0f, 0x72,
+                               0x1a, 0x24, 0x4a, 0xd0, 0xff, 0xa2, 0x47, 0x32, 0x9b, 0xe4, 0x49, 0x23, 0xa5, 0x9b, 0x40, 0xce,
+                               0x92, 0xeb, 0x2e, 0x32, 0x89, 0xc8, 0x92, 0xbc, 0xff, 0xec, 0x92, 0xbd, 0x95, 0xc2, 0xb6, 0xde,
+                               0x78, 0x72, 0xa8, 0xb7, 0x50, 0x6b, 0x8b, 0x33, 0x2b, 0x44, 0xf7, 0x40, 0x9c, 0x60, 0xee, 0x41,
+                               0x80, 0x63, 0xb9, 0x0a, 0x22, 0x7c, 0x9f, 0x7e, 0xaa, 0x67, 0xc3, 0xbf, 0x5f, 0xed, 0xd0, 0xb5,
+                               0x22, 0x09, 0x74, 0x8d, 0xfe, 0x0d, 0x9b, 0xd9, 0x89, 0x55, 0x4d, 0x2f, 0xef, 0xed, 0x45, 0x94,
+                               0x1e, 0xa8, 0x67, 0x5a, 0xb4, 0xde, 0x97, 0xeb, 0xac, 0x0a, 0xae, 0x0c, 0x1f, 0xa3, 0xa7, 0x76,
+                               0x9f, 0x8b, 0x18, 0x7a, 0x1a, 0xe8, 0x40, 0x41, 0x5a, 0x1c, 0x22, 0x8a, 0x19, 0x02, 0x81, 0x80,
+                               0x2f, 0x7a, 0x79, 0x8a, 0x68, 0xdf, 0xfc, 0xc7, 0xee, 0xb3, 0x9f, 0xc8, 0x5a, 0x5f, 0x73, 0x82,
+                               0x33, 0xb9, 0x3e, 0xb6, 0x19, 0xa6, 0xe3, 0x84, 0x9f, 0x3a, 0x7a, 0x41, 0x68, 0x1e, 0x50, 0xf4,
+                               0x0d, 0x99, 0x35, 0x87, 0x5a, 0x05, 0x0b, 0x87, 0xef, 0x49, 0xfc, 0x68, 0xc3, 0x40, 0x33, 0x0b,
+                               0x16, 0x18, 0x61, 0xa6, 0x1e, 0xfa, 0x21, 0x32, 0x49, 0x18, 0x47, 0x06, 0x33, 0x77, 0x7e, 0x46,
+                               0x68, 0xfd, 0x5f, 0x5f, 0xdc, 0x5f, 0x54, 0x63, 0x0a, 0xff, 0xe3, 0xb4, 0x6e, 0x34, 0x36, 0xf5,
+                               0x65, 0x65, 0xbf, 0x90, 0x23, 0xf5, 0xa2, 0x7b, 0x1c, 0xd9, 0x35, 0x11, 0x70, 0x03, 0xa6, 0xe8,
+                               0x08, 0x81, 0x1e, 0xb2, 0xee, 0x1c, 0xad, 0x97, 0xb8, 0x66, 0x6f, 0xf6, 0x9d, 0xc7, 0xe2, 0x0a,
+                               0x20, 0xef, 0xf8, 0x4f, 0xcd, 0x9a, 0x02, 0x8d, 0x3f, 0xec, 0x55, 0xd4, 0x13, 0x4a, 0xdd, 0x41,
+                               0x02, 0x81, 0x80, 0x4e, 0xce, 0x4e, 0x1f, 0xbd, 0x23, 0x52, 0xaa, 0x7f, 0x1a, 0x66, 0xd5, 0x3f,
+                               0xf9, 0x07, 0x39, 0xb9, 0xc3, 0xe0, 0x88, 0x55, 0x72, 0x9f, 0x89, 0x9d, 0x1e, 0xea, 0x11, 0xaf,
+                               0xb9, 0xb5, 0xad, 0xff, 0xc9, 0x20, 0x4b, 0x89, 0x52, 0x1d, 0x9c, 0x6d, 0xf2, 0x84, 0x39, 0xa1,
+                               0x47, 0x41, 0x16, 0xff, 0xd2, 0x5f, 0x7b, 0x2f, 0xfc, 0xb3, 0xb0, 0xb5, 0x06, 0x0b, 0xca, 0x80,
+                               0x79, 0x9e, 0xa5, 0xac, 0xd4, 0x80, 0x1b, 0x0b, 0x3e, 0x29, 0xe1, 0x76, 0x83, 0x6f, 0xbc, 0x54,
+                               0x8e, 0xe5, 0x44, 0x93, 0x26, 0xa4, 0x2c, 0x09, 0xb5, 0x6c, 0x76, 0x74, 0xde, 0x95, 0x54, 0x93,
+                               0xae, 0x08, 0x45, 0xd7, 0xb6, 0xd8, 0xdd, 0x97, 0xbb, 0x58, 0x5d, 0xfb, 0xc2, 0x1a, 0x11, 0xa2,
+                               0x50, 0xa9, 0xc5, 0x3a, 0xd9, 0x19, 0x96, 0x28, 0xd1, 0xba, 0xa3, 0x9c, 0xdf, 0x3f, 0xfb, 0x7e,
+                               0x45, 0x1e, 0xa1, 0x02, 0x81, 0x80, 0x60, 0xfd, 0x65, 0x54, 0x17, 0x04, 0x28, 0x92, 0x73, 0xc8,
+                               0xff, 0x3b, 0xb7, 0x8f, 0x91, 0xed, 0x29, 0x0a, 0xcd, 0xdb, 0x7c, 0xbb, 0x4e, 0xe6, 0xf1, 0xe6,
+                               0xd3, 0xe3, 0xb4, 0x64, 0x09, 0x4d, 0x25, 0x1e, 0x85, 0x74, 0x8c, 0x95, 0xe8, 0x87, 0x39, 0xa0,
+                               0x02, 0x05, 0xf7, 0x31, 0x37, 0xf1, 0x88, 0x58, 0x7e, 0x9d, 0xf6, 0x19, 0x09, 0x8b, 0x5f, 0xee,
+                               0xae, 0x48, 0x9b, 0x7b, 0xa6, 0x3e, 0x6d, 0x32, 0x07, 0x20, 0xda, 0x4e, 0x95, 0x7f, 0xb3, 0x28,
+                               0xaf, 0x7d, 0x46, 0xc7, 0x25, 0x41, 0x38, 0x9c, 0x8d, 0xe6, 0x1f, 0xe5, 0x98, 0xdd, 0x83, 0x08,
+                               0xe0, 0xee, 0x31, 0xea, 0xdd, 0x54, 0x42, 0xa3, 0x5c, 0x35, 0x96, 0x6a, 0x96, 0xc6, 0xb5, 0xae,
+                               0xac, 0x79, 0x2b, 0x55, 0xf1, 0x09, 0x3f, 0xb8, 0x97, 0x78, 0x02, 0xd6, 0x8a, 0xc9, 0xd5, 0xd7,
+                               0xbe, 0x3f, 0x68, 0xb8, 0x3d, 0xce
+                       }
+               }
+       }
+};
+
+_DNSKeySetsCompileTimeChecks( RSASHA512 );
+
+//===========================================================================================================================
+// MARK: - ECDSA Curve P-256 with SHA-256 DNS Keys
+
+typedef struct
+{
+       DNSKeyECDSAP256SHA256Info               ksk;    // Key-Signing Key
+       DNSKeyECDSAP256SHA256Info               zsk;    // Zone-Signing Key
+       
+}      DNSKeyECDSAP256SHA256Set;
+
+static const DNSKeyECDSAP256SHA256Set          kDNSKeyECDSAP256SHA256Sets[] =
+{
+       // DNSSEC Zone 0
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_ECDSAP256SHA256 ),
+                       .pubKey =
+                       {
+                               0x29, 0x03, 0x21, 0x45, 0x5c, 0x5e, 0x50, 0x4b, 0x4a, 0x02, 0x08, 0x48, 0xfb, 0xc5, 0x75, 0xf8, 0xff,
+                               0x74, 0x03, 0x99, 0xe9, 0x47, 0xcb, 0xa7, 0xf2, 0xd0, 0xe9, 0x96, 0x24, 0x60, 0x16, 0x83, 0x9a, 0x50,
+                               0xa2, 0xea, 0x24, 0x43, 0x89, 0x5a, 0x2f, 0x6d, 0x42, 0xca, 0xb1, 0x3a, 0x78, 0xbf, 0xf6, 0xf5, 0xe6,
+                               0xb2, 0x42, 0x4b, 0x5b, 0x15, 0x8c, 0x1e, 0x8d, 0xe2, 0x5a, 0x6d, 0xbb, 0x80
+                       },
+                       .secKey =
+                       {
+                               0x29, 0x03, 0x21, 0x45, 0x5c, 0x5e, 0x50, 0x4b, 0x4a, 0x02, 0x08, 0x48, 0xfb, 0xc5, 0x75, 0xf8, 0xff,
+                               0x74, 0x03, 0x99, 0xe9, 0x47, 0xcb, 0xa7, 0xf2, 0xd0, 0xe9, 0x96, 0x24, 0x60, 0x16, 0x83, 0x9a, 0x50,
+                               0xa2, 0xea, 0x24, 0x43, 0x89, 0x5a, 0x2f, 0x6d, 0x42, 0xca, 0xb1, 0x3a, 0x78, 0xbf, 0xf6, 0xf5, 0xe6,
+                               0xb2, 0x42, 0x4b, 0x5b, 0x15, 0x8c, 0x1e, 0x8d, 0xe2, 0x5a, 0x6d, 0xbb, 0x80, 0xb0, 0xb7, 0x11, 0xa1,
+                               0x52, 0xca, 0xaf, 0x49, 0xe9, 0xb9, 0x89, 0x0f, 0x44, 0x4a, 0xcd, 0xb0, 0x6c, 0xcb, 0xe2, 0x17, 0x45,
+                               0xe6, 0x73, 0x22, 0x6b, 0xce, 0x88, 0xf8, 0x55, 0xcc, 0x5a, 0xbf
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_ECDSAP256SHA256 ),
+                       .pubKey =
+                       {
+                               0x71, 0x00, 0x24, 0xba, 0xbe, 0x13, 0xb1, 0xdb, 0xa6, 0xb9, 0x77, 0xbd, 0xeb, 0x38, 0x03, 0x33, 0x38,
+                               0xab, 0x4b, 0x6b, 0xc2, 0xb2, 0xe4, 0x51, 0xad, 0xa1, 0x1d, 0x1f, 0x83, 0x42, 0xcb, 0x33, 0x0b, 0x8a,
+                               0x3d, 0x54, 0x17, 0x43, 0x56, 0x87, 0xcc, 0xc1, 0xf3, 0x2c, 0x04, 0x7f, 0xaa, 0x4c, 0x18, 0x78, 0x86,
+                               0x10, 0xcf, 0x53, 0x24, 0xee, 0x5d, 0x74, 0x31, 0xe4, 0x4d, 0xc4, 0x84, 0x6a
+                       },
+                       .secKey =
+                       {
+                               0x71, 0x00, 0x24, 0xba, 0xbe, 0x13, 0xb1, 0xdb, 0xa6, 0xb9, 0x77, 0xbd, 0xeb, 0x38, 0x03, 0x33, 0x38,
+                               0xab, 0x4b, 0x6b, 0xc2, 0xb2, 0xe4, 0x51, 0xad, 0xa1, 0x1d, 0x1f, 0x83, 0x42, 0xcb, 0x33, 0x0b, 0x8a,
+                               0x3d, 0x54, 0x17, 0x43, 0x56, 0x87, 0xcc, 0xc1, 0xf3, 0x2c, 0x04, 0x7f, 0xaa, 0x4c, 0x18, 0x78, 0x86,
+                               0x10, 0xcf, 0x53, 0x24, 0xee, 0x5d, 0x74, 0x31, 0xe4, 0x4d, 0xc4, 0x84, 0x6a, 0x46, 0x1e, 0x0b, 0x8e,
+                               0xcc, 0x18, 0x3a, 0xad, 0xfb, 0x7e, 0x07, 0xaf, 0xd6, 0x20, 0x99, 0xbb, 0xaa, 0x6b, 0xb0, 0xb8, 0x89,
+                               0x3c, 0x6e, 0x3b, 0x9c, 0x73, 0x25, 0x12, 0x06, 0x50, 0x37, 0x3a
+                       }
+               }
+       },
+       // DNSSEC Zone 1
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_ECDSAP256SHA256 ),
+                       .pubKey =
+                       {
+                               0xaf, 0x44, 0xb4, 0x8a, 0xc1, 0x8f, 0x0b, 0xd1, 0xfb, 0x90, 0x45, 0x0d, 0x08, 0x32, 0x79, 0xa1, 0xe5,
+                               0x9b, 0x4a, 0xbd, 0x64, 0xb3, 0xce, 0xbb, 0x6d, 0x5f, 0x74, 0xe7, 0x74, 0x85, 0xf2, 0xe8, 0xda, 0x37,
+                               0xbe, 0x58, 0x60, 0x94, 0xed, 0x22, 0xa4, 0xd7, 0x6b, 0xee, 0x7f, 0xd8, 0x1b, 0xcf, 0xc8, 0x26, 0x8f,
+                               0x85, 0x31, 0xcc, 0xdd, 0x90, 0x3a, 0x71, 0xe1, 0xd0, 0x1e, 0x65, 0x69, 0x62
+
+                       },
+                       .secKey =
+                       {
+                               0xaf, 0x44, 0xb4, 0x8a, 0xc1, 0x8f, 0x0b, 0xd1, 0xfb, 0x90, 0x45, 0x0d, 0x08, 0x32, 0x79, 0xa1, 0xe5,
+                               0x9b, 0x4a, 0xbd, 0x64, 0xb3, 0xce, 0xbb, 0x6d, 0x5f, 0x74, 0xe7, 0x74, 0x85, 0xf2, 0xe8, 0xda, 0x37,
+                               0xbe, 0x58, 0x60, 0x94, 0xed, 0x22, 0xa4, 0xd7, 0x6b, 0xee, 0x7f, 0xd8, 0x1b, 0xcf, 0xc8, 0x26, 0x8f,
+                               0x85, 0x31, 0xcc, 0xdd, 0x90, 0x3a, 0x71, 0xe1, 0xd0, 0x1e, 0x65, 0x69, 0x62, 0x5a, 0x72, 0xe9, 0x12,
+                               0xb6, 0x87, 0xf8, 0x22, 0x3c, 0xde, 0x13, 0xa0, 0x2f, 0x81, 0x44, 0x9f, 0x46, 0xaf, 0x41, 0x56, 0x3f,
+                               0x69, 0xfb, 0xfd, 0x0b, 0xae, 0x52, 0xa1, 0xd8, 0xae, 0x23, 0xd7
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_ECDSAP256SHA256 ),
+                       .pubKey =
+                       {
+                               0xd7, 0x3a, 0x08, 0xe9, 0x14, 0x1b, 0xf7, 0xa7, 0xe5, 0x48, 0xb2, 0xf7, 0x5f, 0x7d, 0xe0, 0xb3, 0x42,
+                               0x90, 0xe6, 0x96, 0x41, 0x58, 0x98, 0xfb, 0x89, 0xc7, 0xbb, 0x43, 0xcd, 0x16, 0xc2, 0x41, 0x40, 0xd7,
+                               0x3c, 0x28, 0x23, 0x94, 0x45, 0x96, 0xad, 0xa0, 0x9b, 0x67, 0x1d, 0x53, 0xa3, 0x97, 0x84, 0x6a, 0x48,
+                               0xcc, 0x58, 0xe9, 0x34, 0x5b, 0x2c, 0xb6, 0xcf, 0x4b, 0xe5, 0xbc, 0xb6, 0x34
+                       },
+                       .secKey =
+                       {
+                               0xd7, 0x3a, 0x08, 0xe9, 0x14, 0x1b, 0xf7, 0xa7, 0xe5, 0x48, 0xb2, 0xf7, 0x5f, 0x7d, 0xe0, 0xb3, 0x42,
+                               0x90, 0xe6, 0x96, 0x41, 0x58, 0x98, 0xfb, 0x89, 0xc7, 0xbb, 0x43, 0xcd, 0x16, 0xc2, 0x41, 0x40, 0xd7,
+                               0x3c, 0x28, 0x23, 0x94, 0x45, 0x96, 0xad, 0xa0, 0x9b, 0x67, 0x1d, 0x53, 0xa3, 0x97, 0x84, 0x6a, 0x48,
+                               0xcc, 0x58, 0xe9, 0x34, 0x5b, 0x2c, 0xb6, 0xcf, 0x4b, 0xe5, 0xbc, 0xb6, 0x34, 0xaf, 0x91, 0xcf, 0x9c,
+                               0x8a, 0xa5, 0xad, 0x12, 0xa1, 0xbb, 0x04, 0xeb, 0xb8, 0x2e, 0x8a, 0x2c, 0x96, 0xdf, 0x5c, 0xbc, 0xce,
+                               0x88, 0xab, 0x52, 0x60, 0xaf, 0x27, 0x4e, 0xd9, 0x9d, 0x39, 0x42
+                       }
+               }
+       },
+       // DNSSEC Zone 2
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_ECDSAP256SHA256 ),
+                       .pubKey =
+                       {
+                               0x6a, 0xb9, 0x99, 0x0e, 0x9f, 0x7f, 0xad, 0x0c, 0x77, 0x94, 0xb2, 0x40, 0xa7, 0x40, 0x15, 0xf5, 0xe4,
+                               0x9b, 0x1e, 0x90, 0x58, 0x25, 0xed, 0x26, 0x2f, 0x35, 0x55, 0x3c, 0x0f, 0x09, 0xd2, 0x58, 0x6f, 0x20,
+                               0xa7, 0x77, 0x36, 0x4c, 0xdf, 0x45, 0x25, 0xa4, 0xa8, 0xc4, 0x8a, 0xa0, 0x1c, 0xe1, 0xea, 0x47, 0xb7,
+                               0xb6, 0xa2, 0xf4, 0x49, 0x9a, 0xbc, 0x07, 0xe5, 0x32, 0x15, 0xaf, 0x5d, 0x6f
+                       },
+                       .secKey =
+                       {
+                               0x6a, 0xb9, 0x99, 0x0e, 0x9f, 0x7f, 0xad, 0x0c, 0x77, 0x94, 0xb2, 0x40, 0xa7, 0x40, 0x15, 0xf5, 0xe4,
+                               0x9b, 0x1e, 0x90, 0x58, 0x25, 0xed, 0x26, 0x2f, 0x35, 0x55, 0x3c, 0x0f, 0x09, 0xd2, 0x58, 0x6f, 0x20,
+                               0xa7, 0x77, 0x36, 0x4c, 0xdf, 0x45, 0x25, 0xa4, 0xa8, 0xc4, 0x8a, 0xa0, 0x1c, 0xe1, 0xea, 0x47, 0xb7,
+                               0xb6, 0xa2, 0xf4, 0x49, 0x9a, 0xbc, 0x07, 0xe5, 0x32, 0x15, 0xaf, 0x5d, 0x6f, 0xa9, 0xa7, 0xf2, 0xe4,
+                               0x8a, 0x0b, 0xf9, 0x87, 0x60, 0x96, 0x91, 0x72, 0xdd, 0xeb, 0xb7, 0x65, 0x1a, 0x9e, 0xa4, 0xcc, 0xb4,
+                               0x9c, 0xee, 0xc9, 0xe2, 0x73, 0x1c, 0x22, 0x4c, 0x8f, 0xb4, 0xec
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_ECDSAP256SHA256 ),
+                       .pubKey =
+                       {
+                               0x89, 0xfc, 0xb4, 0xa2, 0x5f, 0x27, 0xbd, 0x2c, 0xa8, 0xf7, 0xb3, 0xfb, 0xa1, 0x24, 0x25, 0x61, 0x28,
+                               0x55, 0xd2, 0x8a, 0xb5, 0x74, 0x24, 0xf4, 0x1b, 0x27, 0x47, 0x23, 0x10, 0x5d, 0x76, 0xd6, 0x40, 0x01,
+                               0xf2, 0x91, 0x74, 0xa5, 0x99, 0x5b, 0x9e, 0xc5, 0xd8, 0x82, 0x63, 0x1f, 0xcd, 0x55, 0xa8, 0xcf, 0x01,
+                               0x0a, 0xf8, 0x97, 0x0b, 0x41, 0x23, 0xee, 0x6b, 0xc4, 0x3e, 0x8e, 0x81, 0x22
+                       },
+                       .secKey =
+                       {
+                               0x89, 0xfc, 0xb4, 0xa2, 0x5f, 0x27, 0xbd, 0x2c, 0xa8, 0xf7, 0xb3, 0xfb, 0xa1, 0x24, 0x25, 0x61, 0x28,
+                               0x55, 0xd2, 0x8a, 0xb5, 0x74, 0x24, 0xf4, 0x1b, 0x27, 0x47, 0x23, 0x10, 0x5d, 0x76, 0xd6, 0x40, 0x01,
+                               0xf2, 0x91, 0x74, 0xa5, 0x99, 0x5b, 0x9e, 0xc5, 0xd8, 0x82, 0x63, 0x1f, 0xcd, 0x55, 0xa8, 0xcf, 0x01,
+                               0x0a, 0xf8, 0x97, 0x0b, 0x41, 0x23, 0xee, 0x6b, 0xc4, 0x3e, 0x8e, 0x81, 0x22, 0x37, 0x9a, 0x95, 0xfe,
+                               0xf1, 0x98, 0x28, 0x2b, 0x01, 0xa9, 0x8a, 0x57, 0x19, 0xea, 0x61, 0xf1, 0x57, 0x21, 0x15, 0x72, 0x3a,
+                               0x67, 0x41, 0x79, 0x8a, 0x1f, 0x12, 0x3b, 0x48, 0xe6, 0xf1, 0x71
+                       }
+               }
+       },
+       // DNSSEC Zone 3
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_ECDSAP256SHA256 ),
+                       .pubKey =
+                       {
+                               0xd4, 0x0d, 0x1c, 0xe7, 0xd7, 0x38, 0xda, 0xda, 0x50, 0x3f, 0xd3, 0x4e, 0x41, 0x6e, 0x72, 0xde, 0x8c,
+                               0x97, 0x57, 0xdc, 0x7c, 0xed, 0x44, 0x09, 0x9c, 0xa6, 0xef, 0x05, 0x74, 0x6a, 0xb6, 0xb4, 0x16, 0xf4,
+                               0x39, 0xc8, 0x6d, 0x3a, 0xe5, 0x26, 0x1c, 0x44, 0x96, 0x96, 0x3f, 0x22, 0x0c, 0xb7, 0xce, 0x57, 0x6f,
+                               0x60, 0x8f, 0x90, 0x64, 0x29, 0x97, 0x60, 0x11, 0xfd, 0xf4, 0x8d, 0x38, 0x03
+                       },
+                       .secKey =
+                       {
+                               0xd4, 0x0d, 0x1c, 0xe7, 0xd7, 0x38, 0xda, 0xda, 0x50, 0x3f, 0xd3, 0x4e, 0x41, 0x6e, 0x72, 0xde, 0x8c,
+                               0x97, 0x57, 0xdc, 0x7c, 0xed, 0x44, 0x09, 0x9c, 0xa6, 0xef, 0x05, 0x74, 0x6a, 0xb6, 0xb4, 0x16, 0xf4,
+                               0x39, 0xc8, 0x6d, 0x3a, 0xe5, 0x26, 0x1c, 0x44, 0x96, 0x96, 0x3f, 0x22, 0x0c, 0xb7, 0xce, 0x57, 0x6f,
+                               0x60, 0x8f, 0x90, 0x64, 0x29, 0x97, 0x60, 0x11, 0xfd, 0xf4, 0x8d, 0x38, 0x03, 0x0a, 0xc0, 0xef, 0x99,
+                               0x50, 0x3c, 0x4b, 0xbb, 0x29, 0xf9, 0xed, 0xac, 0x06, 0xfe, 0x5f, 0x04, 0x92, 0xa8, 0xe2, 0xba, 0x3d,
+                               0x82, 0x8b, 0xa6, 0x03, 0xe4, 0xcb, 0xaf, 0xff, 0xf2, 0x3e, 0xbd
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_ECDSAP256SHA256 ),
+                       .pubKey =
+                       {
+                               0x00, 0x84, 0x50, 0x07, 0x60, 0x06, 0x3b, 0xae, 0x68, 0xe1, 0x57, 0x5e, 0x7b, 0xd2, 0xdd, 0x1f, 0x0f,
+                               0x10, 0xaa, 0x27, 0xd4, 0x93, 0x94, 0x5e, 0xe8, 0xd8, 0x2d, 0x96, 0xe0, 0x61, 0xd2, 0xfb, 0x97, 0xa9,
+                               0x88, 0x0a, 0x6d, 0xca, 0x13, 0x78, 0x95, 0x61, 0x60, 0x09, 0xcd, 0xf1, 0x69, 0xa4, 0xf8, 0xa7, 0xfa,
+                               0x84, 0xef, 0xab, 0xe1, 0xc3, 0x3d, 0x48, 0xbb, 0x57, 0x13, 0x3d, 0x47, 0x46
+                       },
+                       .secKey =
+                       {
+                               0x00, 0x84, 0x50, 0x07, 0x60, 0x06, 0x3b, 0xae, 0x68, 0xe1, 0x57, 0x5e, 0x7b, 0xd2, 0xdd, 0x1f, 0x0f,
+                               0x10, 0xaa, 0x27, 0xd4, 0x93, 0x94, 0x5e, 0xe8, 0xd8, 0x2d, 0x96, 0xe0, 0x61, 0xd2, 0xfb, 0x97, 0xa9,
+                               0x88, 0x0a, 0x6d, 0xca, 0x13, 0x78, 0x95, 0x61, 0x60, 0x09, 0xcd, 0xf1, 0x69, 0xa4, 0xf8, 0xa7, 0xfa,
+                               0x84, 0xef, 0xab, 0xe1, 0xc3, 0x3d, 0x48, 0xbb, 0x57, 0x13, 0x3d, 0x47, 0x46, 0x1f, 0xc8, 0x60, 0x20,
+                               0xcd, 0x95, 0x3a, 0x4a, 0xae, 0xb5, 0xa2, 0xfb, 0x9a, 0x24, 0x66, 0x9d, 0xdc, 0xba, 0xdf, 0x2a, 0x73,
+                               0x53, 0xed, 0xdd, 0xe0, 0x30, 0x7c, 0x5f, 0x1f, 0x27, 0x58, 0xf8
+                       }
+               }
+       }
+};
+
+_DNSKeySetsCompileTimeChecks( ECDSAP256SHA256 );
+
+//===========================================================================================================================
+// MARK: - ECDSA Curve P-384 with SHA-384 DNS Keys
+
+typedef struct
+{
+       DNSKeyECDSAP384SHA384Info               ksk;    // Key-Signing Key
+       DNSKeyECDSAP384SHA384Info               zsk;    // Zone-Signing Key
+       
+}      DNSKeyECDSAP384SHA384Set;
+
+static const DNSKeyECDSAP384SHA384Set          kDNSKeyECDSAP384SHA384Sets[] =
+{
+       // DNSSEC Zone 0
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_ECDSAP384SHA384 ),
+                       .pubKey =
+                       {
+                               0xbe, 0x4a, 0x40, 0xe7, 0xc8, 0x60, 0x51, 0x7f, 0x7c, 0x8a, 0x8f, 0x37, 0x5d, 0xe3, 0xfc, 0x8e, 0x12,
+                               0xe0, 0xa1, 0x42, 0x7f, 0x2f, 0x2d, 0xda, 0x27, 0x60, 0x0f, 0xf0, 0xa1, 0x94, 0x47, 0x1c, 0xa4, 0x4e,
+                               0x37, 0xef, 0x59, 0x6b, 0x7c, 0x3d, 0x47, 0xaa, 0xef, 0x10, 0x63, 0x6a, 0x4f, 0x17, 0xb5, 0xa0, 0x2b,
+                               0xe3, 0xac, 0x90, 0xd6, 0x5e, 0xca, 0xd2, 0x2f, 0x21, 0xe3, 0x5d, 0xae, 0x63
+                       },
+                       .secKey =
+                       {
+                               0xbe, 0x4a, 0x40, 0xe7, 0xc8, 0x60, 0x51, 0x7f, 0x7c, 0x8a, 0x8f, 0x37, 0x5d, 0xe3, 0xfc, 0x8e, 0x12,
+                               0xe0, 0xa1, 0x42, 0x7f, 0x2f, 0x2d, 0xda, 0x27, 0x60, 0x0f, 0xf0, 0xa1, 0x94, 0x47, 0x1c, 0xa4, 0x4e,
+                               0x37, 0xef, 0x59, 0x6b, 0x7c, 0x3d, 0x47, 0xaa, 0xef, 0x10, 0x63, 0x6a, 0x4f, 0x17, 0xb5, 0xa0, 0x2b,
+                               0xe3, 0xac, 0x90, 0xd6, 0x5e, 0xca, 0xd2, 0x2f, 0x21, 0xe3, 0x5d, 0xae, 0x63, 0xe7, 0x91, 0x9c, 0x19,
+                               0x57, 0x44, 0xe4, 0x97, 0x3d, 0xd2, 0xc4, 0xc1, 0x84, 0x7b, 0x2c, 0x4e, 0xcb, 0xe5, 0x36, 0x56, 0xf5,
+                               0x6e, 0x7c, 0x54, 0xd7, 0x49, 0xd7, 0x69, 0x7c, 0x87, 0xd0, 0xce
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_ECDSAP384SHA384 ),
+                       .pubKey =
+                       {
+                               0x8f, 0x51, 0x89, 0xe2, 0x28, 0x01, 0xde, 0x07, 0xf6, 0x2f, 0xb3, 0xbd, 0x42, 0xc5, 0xe0, 0x66, 0x8d,
+                               0x08, 0x09, 0xdb, 0x06, 0x77, 0x45, 0xdd, 0xa8, 0x1d, 0xe2, 0x68, 0xb6, 0x90, 0xa7, 0x8a, 0x9d, 0x06,
+                               0x11, 0x8f, 0x98, 0x05, 0xf1, 0xb0, 0xfa, 0x55, 0xa6, 0x4b, 0xd7, 0x04, 0x63, 0x40, 0xcc, 0x72, 0xe4,
+                               0x5b, 0xb9, 0x5b, 0x2c, 0x68, 0xee, 0x3c, 0x7e, 0x3c, 0xac, 0x67, 0xfc, 0x7e
+                       },
+                       .secKey =
+                       {
+                               0x8f, 0x51, 0x89, 0xe2, 0x28, 0x01, 0xde, 0x07, 0xf6, 0x2f, 0xb3, 0xbd, 0x42, 0xc5, 0xe0, 0x66, 0x8d,
+                               0x08, 0x09, 0xdb, 0x06, 0x77, 0x45, 0xdd, 0xa8, 0x1d, 0xe2, 0x68, 0xb6, 0x90, 0xa7, 0x8a, 0x9d, 0x06,
+                               0x11, 0x8f, 0x98, 0x05, 0xf1, 0xb0, 0xfa, 0x55, 0xa6, 0x4b, 0xd7, 0x04, 0x63, 0x40, 0xcc, 0x72, 0xe4,
+                               0x5b, 0xb9, 0x5b, 0x2c, 0x68, 0xee, 0x3c, 0x7e, 0x3c, 0xac, 0x67, 0xfc, 0x7e, 0xaf, 0xa8, 0xfd, 0x7f,
+                               0x39, 0x1a, 0x72, 0xf6, 0x19, 0x3b, 0xc7, 0x62, 0xcc, 0xf7, 0x16, 0xfd, 0x16, 0x55, 0xc3, 0x57, 0x77,
+                               0xca, 0x88, 0x18, 0x48, 0x79, 0x69, 0xa6, 0xc0, 0x2d, 0xa6, 0x7d
+                       }
+               }
+       },
+       // DNSSEC Zone 1
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_ECDSAP384SHA384 ),
+                       .pubKey =
+                       {
+                               0xf7, 0x8c, 0x7e, 0xca, 0xa6, 0xf7, 0xd7, 0x8c, 0x42, 0x22, 0x53, 0xcf, 0xc2, 0x7e, 0x75, 0xdf, 0xf2,
+                               0x64, 0xbd, 0x58, 0x75, 0x82, 0x4f, 0x80, 0xe8, 0xcd, 0x3b, 0xed, 0xc8, 0x09, 0x94, 0x2a, 0x49, 0x5e,
+                               0x85, 0x67, 0xeb, 0xaf, 0x2e, 0x04, 0x7f, 0x6b, 0x4c, 0x12, 0x16, 0x52, 0xd3, 0x8f, 0x4c, 0x42, 0x85,
+                               0xdd, 0x69, 0x3d, 0x45, 0x74, 0xc3, 0x35, 0xba, 0x3d, 0x6d, 0xb3, 0xea, 0xe9
+                       },
+                       .secKey =
+                       {
+                               0xf7, 0x8c, 0x7e, 0xca, 0xa6, 0xf7, 0xd7, 0x8c, 0x42, 0x22, 0x53, 0xcf, 0xc2, 0x7e, 0x75, 0xdf, 0xf2,
+                               0x64, 0xbd, 0x58, 0x75, 0x82, 0x4f, 0x80, 0xe8, 0xcd, 0x3b, 0xed, 0xc8, 0x09, 0x94, 0x2a, 0x49, 0x5e,
+                               0x85, 0x67, 0xeb, 0xaf, 0x2e, 0x04, 0x7f, 0x6b, 0x4c, 0x12, 0x16, 0x52, 0xd3, 0x8f, 0x4c, 0x42, 0x85,
+                               0xdd, 0x69, 0x3d, 0x45, 0x74, 0xc3, 0x35, 0xba, 0x3d, 0x6d, 0xb3, 0xea, 0xe9, 0x44, 0x2a, 0x21, 0xb1,
+                               0xad, 0x42, 0x72, 0x2c, 0x0f, 0x92, 0xdb, 0x0b, 0x2c, 0x0e, 0x47, 0x11, 0x8b, 0x0b, 0xaa, 0x58, 0x73,
+                               0x06, 0x79, 0xaf, 0xf4, 0x8f, 0x71, 0x0d, 0x7d, 0xbc, 0x95, 0xdf
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_ECDSAP384SHA384 ),
+                       .pubKey =
+                       {
+                               0xf1, 0xa1, 0xe6, 0xaf, 0xa8, 0x29, 0x4f, 0x65, 0xc8, 0xb0, 0xb4, 0x32, 0x59, 0x79, 0x40, 0x57, 0xc6,
+                               0x7a, 0x10, 0xc0, 0x09, 0x44, 0xfc, 0xe4, 0x32, 0xaf, 0xa2, 0x70, 0x39, 0x39, 0x59, 0x83, 0x12, 0x14,
+                               0x6a, 0x0b, 0x83, 0x8b, 0xa9, 0x23, 0x29, 0xa4, 0x5e, 0x98, 0x4f, 0x84, 0xbd, 0xfa, 0x69, 0xd2, 0xb8,
+                               0xa1, 0x44, 0x56, 0xfe, 0xdc, 0x81, 0xa1, 0xe7, 0xeb, 0x15, 0x9b, 0xda, 0x99
+                       },
+                       .secKey =
+                       {
+                               0xf1, 0xa1, 0xe6, 0xaf, 0xa8, 0x29, 0x4f, 0x65, 0xc8, 0xb0, 0xb4, 0x32, 0x59, 0x79, 0x40, 0x57, 0xc6,
+                               0x7a, 0x10, 0xc0, 0x09, 0x44, 0xfc, 0xe4, 0x32, 0xaf, 0xa2, 0x70, 0x39, 0x39, 0x59, 0x83, 0x12, 0x14,
+                               0x6a, 0x0b, 0x83, 0x8b, 0xa9, 0x23, 0x29, 0xa4, 0x5e, 0x98, 0x4f, 0x84, 0xbd, 0xfa, 0x69, 0xd2, 0xb8,
+                               0xa1, 0x44, 0x56, 0xfe, 0xdc, 0x81, 0xa1, 0xe7, 0xeb, 0x15, 0x9b, 0xda, 0x99, 0x12, 0xf6, 0x33, 0x34,
+                               0x17, 0x32, 0xf2, 0x6d, 0x12, 0x25, 0x73, 0x91, 0x57, 0xdf, 0x6a, 0xf6, 0xba, 0x64, 0x73, 0x09, 0xda,
+                               0xea, 0x02, 0x3f, 0x5e, 0x96, 0xa0, 0x0d, 0x2e, 0x72, 0xc8, 0xd8
+                       }
+               }
+       },
+       // DNSSEC Zone 2
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_ECDSAP384SHA384 ),
+                       .pubKey =
+                       {
+                               0x0e, 0x67, 0xd0, 0x3e, 0x50, 0x85, 0x75, 0x90, 0x68, 0x55, 0x02, 0x4d, 0xd8, 0x23, 0x74, 0x55, 0xf6,
+                               0xb6, 0x95, 0xa3, 0x9e, 0x02, 0x2e, 0xbd, 0x61, 0xd8, 0x7e, 0xa2, 0xed, 0xe9, 0xcc, 0x40, 0xc1, 0xf6,
+                               0xa4, 0xb5, 0x9e, 0x14, 0xcf, 0x92, 0xf2, 0xe0, 0x8a, 0xe6, 0x21, 0xa0, 0x48, 0xb7, 0xff, 0x04, 0xeb,
+                               0x53, 0x6d, 0xb6, 0x52, 0xd0, 0x0e, 0x7d, 0x52, 0x5a, 0x1a, 0x72, 0xa8, 0xab
+                       },
+                       .secKey =
+                       {
+                               0x0e, 0x67, 0xd0, 0x3e, 0x50, 0x85, 0x75, 0x90, 0x68, 0x55, 0x02, 0x4d, 0xd8, 0x23, 0x74, 0x55, 0xf6,
+                               0xb6, 0x95, 0xa3, 0x9e, 0x02, 0x2e, 0xbd, 0x61, 0xd8, 0x7e, 0xa2, 0xed, 0xe9, 0xcc, 0x40, 0xc1, 0xf6,
+                               0xa4, 0xb5, 0x9e, 0x14, 0xcf, 0x92, 0xf2, 0xe0, 0x8a, 0xe6, 0x21, 0xa0, 0x48, 0xb7, 0xff, 0x04, 0xeb,
+                               0x53, 0x6d, 0xb6, 0x52, 0xd0, 0x0e, 0x7d, 0x52, 0x5a, 0x1a, 0x72, 0xa8, 0xab, 0x59, 0x0e, 0x96, 0x19,
+                               0x2c, 0x32, 0x81, 0xce, 0x2f, 0x0a, 0x67, 0xa0, 0xb7, 0xa7, 0x98, 0xd2, 0xfc, 0x5f, 0xc5, 0x1a, 0x7b,
+                               0xc9, 0x94, 0x72, 0xe4, 0xcc, 0xf7, 0xc3, 0x7d, 0x01, 0xef, 0xf5
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_ECDSAP384SHA384 ),
+                       .pubKey =
+                       {
+                               0xfa, 0xc6, 0x7e, 0xa5, 0x0f, 0xd0, 0xda, 0x3e, 0xbd, 0x89, 0x96, 0x9b, 0x8f, 0xbf, 0x11, 0x6d, 0x68,
+                               0xaf, 0x2d, 0x6b, 0x90, 0x62, 0x31, 0x74, 0xcf, 0x60, 0xbb, 0xab, 0x28, 0x00, 0xbc, 0x5c, 0x54, 0xb4,
+                               0x2a, 0xe1, 0x25, 0xfd, 0x72, 0x27, 0x80, 0x79, 0xc3, 0x05, 0xd7, 0xb7, 0xb1, 0x43, 0x17, 0x12, 0x58,
+                               0x9e, 0xb0, 0x4e, 0x69, 0x1d, 0x8a, 0x18, 0x96, 0x15, 0x75, 0x18, 0xbd, 0xa6
+                       },
+                       .secKey =
+                       {
+                               0xfa, 0xc6, 0x7e, 0xa5, 0x0f, 0xd0, 0xda, 0x3e, 0xbd, 0x89, 0x96, 0x9b, 0x8f, 0xbf, 0x11, 0x6d, 0x68,
+                               0xaf, 0x2d, 0x6b, 0x90, 0x62, 0x31, 0x74, 0xcf, 0x60, 0xbb, 0xab, 0x28, 0x00, 0xbc, 0x5c, 0x54, 0xb4,
+                               0x2a, 0xe1, 0x25, 0xfd, 0x72, 0x27, 0x80, 0x79, 0xc3, 0x05, 0xd7, 0xb7, 0xb1, 0x43, 0x17, 0x12, 0x58,
+                               0x9e, 0xb0, 0x4e, 0x69, 0x1d, 0x8a, 0x18, 0x96, 0x15, 0x75, 0x18, 0xbd, 0xa6, 0x64, 0x67, 0xa5, 0xb1,
+                               0x41, 0x8c, 0x4e, 0xfb, 0xaf, 0xf5, 0xd8, 0xfc, 0xbc, 0x6a, 0xc3, 0x2a, 0x05, 0xd8, 0x1a, 0x70, 0xe4,
+                               0xd0, 0xa4, 0x4c, 0xc5, 0x26, 0xd2, 0x0c, 0x8b, 0x37, 0xa9, 0xb8
+                       }
+               }
+       },
+       // DNSSEC Zone 3
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_ECDSAP384SHA384 ),
+                       .pubKey =
+                       {
+                               0xec, 0xa5, 0x90, 0x20, 0xea, 0x1d, 0xa8, 0xbd, 0x8c, 0x83, 0xd8, 0x79, 0x2a, 0xd2, 0x1c, 0x91, 0xd5,
+                               0x6a, 0x40, 0xa6, 0x91, 0x55, 0x8e, 0xc6, 0xaf, 0x48, 0xd0, 0xa6, 0x6a, 0x2d, 0xec, 0x11, 0x65, 0x05,
+                               0x2b, 0x71, 0x4a, 0x2d, 0xc7, 0xd9, 0x00, 0x40, 0x8f, 0x34, 0x72, 0x13, 0x1d, 0xc5, 0x2b, 0xff, 0x3e,
+                               0xc1, 0xff, 0x0a, 0xbf, 0x06, 0x37, 0x01, 0x32, 0xcd, 0x6e, 0x3d, 0x31, 0x11
+                       },
+                       .secKey =
+                       {
+                               0xec, 0xa5, 0x90, 0x20, 0xea, 0x1d, 0xa8, 0xbd, 0x8c, 0x83, 0xd8, 0x79, 0x2a, 0xd2, 0x1c, 0x91, 0xd5,
+                               0x6a, 0x40, 0xa6, 0x91, 0x55, 0x8e, 0xc6, 0xaf, 0x48, 0xd0, 0xa6, 0x6a, 0x2d, 0xec, 0x11, 0x65, 0x05,
+                               0x2b, 0x71, 0x4a, 0x2d, 0xc7, 0xd9, 0x00, 0x40, 0x8f, 0x34, 0x72, 0x13, 0x1d, 0xc5, 0x2b, 0xff, 0x3e,
+                               0xc1, 0xff, 0x0a, 0xbf, 0x06, 0x37, 0x01, 0x32, 0xcd, 0x6e, 0x3d, 0x31, 0x11, 0x8a, 0x2d, 0xde, 0x0c,
+                               0xcb, 0xaf, 0xe9, 0x33, 0xc3, 0x3b, 0xcc, 0x30, 0x24, 0xdc, 0xb3, 0xcd, 0xe8, 0xb4, 0x48, 0xef, 0x65,
+                               0xb8, 0x1e, 0xa0, 0x3d, 0x04, 0xf6, 0x75, 0xa2, 0xc2, 0xce, 0x06
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_ECDSAP384SHA384 ),
+                       .pubKey =
+                       {
+                               0x26, 0x82, 0x99, 0xe2, 0x51, 0x15, 0x28, 0x73, 0x5e, 0x3f, 0x41, 0x5b, 0xc9, 0x8e, 0xad, 0x55, 0x93,
+                               0x68, 0x6d, 0x36, 0xf8, 0xe1, 0x6b, 0xd9, 0xd4, 0x83, 0x57, 0xdd, 0x37, 0x78, 0xfd, 0xb4, 0xf6, 0xc2,
+                               0x0d, 0xe8, 0xf8, 0x9f, 0xbf, 0x79, 0xe5, 0x4e, 0xa7, 0x75, 0x33, 0x0d, 0xc4, 0x5b, 0x0a, 0xeb, 0x3e,
+                               0xd7, 0x70, 0x0f, 0x77, 0x86, 0x75, 0xd8, 0x37, 0xe9, 0x06, 0x5e, 0xa6, 0x9c
+                       },
+                       .secKey =
+                       {
+                               0x26, 0x82, 0x99, 0xe2, 0x51, 0x15, 0x28, 0x73, 0x5e, 0x3f, 0x41, 0x5b, 0xc9, 0x8e, 0xad, 0x55, 0x93,
+                               0x68, 0x6d, 0x36, 0xf8, 0xe1, 0x6b, 0xd9, 0xd4, 0x83, 0x57, 0xdd, 0x37, 0x78, 0xfd, 0xb4, 0xf6, 0xc2,
+                               0x0d, 0xe8, 0xf8, 0x9f, 0xbf, 0x79, 0xe5, 0x4e, 0xa7, 0x75, 0x33, 0x0d, 0xc4, 0x5b, 0x0a, 0xeb, 0x3e,
+                               0xd7, 0x70, 0x0f, 0x77, 0x86, 0x75, 0xd8, 0x37, 0xe9, 0x06, 0x5e, 0xa6, 0x9c, 0xcb, 0x7b, 0xa7, 0xdb,
+                               0x80, 0x63, 0x4e, 0xb8, 0x8b, 0x96, 0xd5, 0x5c, 0x36, 0xc7, 0xd5, 0xde, 0x25, 0xa7, 0xf6, 0xf5, 0x52,
+                               0xcc, 0x09, 0x6b, 0x55, 0xde, 0xbf, 0x63, 0xa2, 0x1c, 0x4f, 0xce
+                       }
+               }
+       }
+};
+
+_DNSKeySetsCompileTimeChecks( ECDSAP384SHA384 );
+
+//===========================================================================================================================
+// MARK: - Ed25519 DNS Keys
+
+typedef struct
+{
+       DNSKeyEd25519Info               ksk;    // Key-Signing Key
+       DNSKeyEd25519Info               zsk;    // Zone-Signing Key
+       
+}      DNSKeyEd25519Set;
+
+static const DNSKeyEd25519Set          kDNSKeyEd25519Sets[] =
+{
+       // DNSSEC Zone 0
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_Ed25519 ),
+                       .pubKey =
+                       {
+                               0xCD, 0x87, 0xB7, 0x2E, 0x13, 0xC4, 0x97, 0x6E, 0x94, 0x78, 0x0A, 0xE9, 0x2E, 0x2B, 0x68, 0x98,
+                               0x70, 0xCB, 0x42, 0xF5, 0x79, 0x41, 0x9A, 0x00, 0xB2, 0x21, 0x84, 0xB6, 0xA7, 0x81, 0x48, 0x55
+                       },
+                       .secKey =
+                       {
+                               0x68, 0x41, 0xF5, 0x9D, 0x2F, 0xA7, 0x45, 0xC7, 0xFA, 0xFE, 0x2F, 0xD5, 0xD9, 0xD9, 0x18, 0xB8,
+                               0x3C, 0x47, 0xFD, 0xAE, 0xC4, 0x3A, 0x06, 0x93, 0x9C, 0x90, 0xAA, 0x31, 0xFE, 0x84, 0xC5, 0x88
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_Ed25519 ),
+                       .pubKey =
+                       {
+                               0xDF, 0x14, 0xB0, 0x25, 0x78, 0xFD, 0xC2, 0x57, 0x2A, 0xC0, 0xDB, 0x4F, 0x88, 0xD7, 0x13, 0xE2,
+                               0x5C, 0x1E, 0x28, 0xE6, 0xBC, 0xA6, 0x14, 0x50, 0x39, 0x51, 0xC0, 0x0A, 0x1F, 0x1D, 0x30, 0x64
+                       },
+                       .secKey =
+                       {
+                               0x37, 0x9B, 0x7B, 0x5A, 0x88, 0x55, 0xA8, 0xB9, 0xBE, 0x63, 0x89, 0x16, 0xF8, 0x8E, 0x1D, 0x34,
+                               0x22, 0x09, 0xF1, 0xED, 0xC4, 0x2B, 0xBA, 0xD5, 0xEE, 0x2C, 0xA5, 0x47, 0x34, 0x6C, 0x28, 0xFB
+                       }
+               }
+       },
+       // DNSSEC Zone 1
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_Ed25519 ),
+                       .pubKey =
+                       {
+                               0x67, 0x2E, 0xCD, 0xEE, 0xD5, 0x6D, 0xEE, 0xB5, 0xE4, 0x08, 0x3A, 0x24, 0xFF, 0x36, 0xBF, 0xC9,
+                               0x6A, 0x86, 0x0D, 0xFD, 0x50, 0xE8, 0x23, 0xB3, 0x0F, 0x78, 0x5C, 0x4E, 0xF4, 0x5D, 0x16, 0x2C
+                       },
+                       .secKey =
+                       {
+                               0xB9, 0x6E, 0xD4, 0x87, 0x7F, 0x83, 0x6F, 0xA6, 0x69, 0x03, 0x5C, 0x40, 0xF4, 0x27, 0x80, 0x6F,
+                               0xBD, 0x2E, 0x98, 0xA8, 0xBE, 0xE7, 0x31, 0xF2, 0x43, 0xAD, 0xDF, 0x81, 0x1A, 0x3C, 0xCA, 0x9C
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_Ed25519 ),
+                       .pubKey =
+                       {
+                               0xAC, 0xB3, 0x9C, 0x79, 0x18, 0xC6, 0x74, 0x8F, 0x6B, 0x1F, 0x6A, 0xF5, 0xB0, 0x0B, 0x67, 0xDB,
+                               0x30, 0xF1, 0x2D, 0xD7, 0xB5, 0xD6, 0x1F, 0xA8, 0xAC, 0x6C, 0xBB, 0x7A, 0x23, 0x88, 0x58, 0xF1
+                       },
+                       .secKey =
+                       {
+                               0xBB, 0xDA, 0x61, 0x6F, 0x93, 0x0D, 0xB7, 0x55, 0x6D, 0x10, 0xFA, 0x65, 0x05, 0xB5, 0xEA, 0xFC,
+                               0xA9, 0x11, 0x1F, 0x2C, 0xD6, 0xD7, 0xB4, 0x1D, 0xC8, 0x56, 0xA8, 0xEE, 0x7D, 0xE9, 0x32, 0x22
+                       }
+               }
+       },
+       // DNSSEC Zone 2
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_Ed25519 ),
+                       .pubKey =
+                       {
+                               0xA9, 0xC7, 0x9D, 0x56, 0xDA, 0x01, 0x35, 0xA1, 0xFD, 0x24, 0x17, 0x4A, 0x37, 0xDA, 0x6F, 0x6A,
+                               0x00, 0x5A, 0xAD, 0x14, 0x7D, 0x7A, 0x8E, 0xD6, 0xC6, 0x2F, 0xA5, 0xCB, 0x90, 0x92, 0x23, 0xC4
+                       },
+                       .secKey =
+                       {
+                               0x49, 0x55, 0x36, 0x5F, 0x87, 0x5B, 0xC1, 0x70, 0x02, 0x4B, 0xB7, 0xE7, 0xB7, 0xD0, 0x30, 0x7A,
+                               0x14, 0xB4, 0x4D, 0xC6, 0x91, 0x4A, 0x64, 0x33, 0x5F, 0xA5, 0x6E, 0x45, 0x18, 0xDE, 0x9B, 0x9C
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_Ed25519 ),
+                       .pubKey =
+                       {
+                               0x40, 0x84, 0x91, 0xFF, 0x79, 0x2A, 0x2A, 0x00, 0x9B, 0x81, 0x47, 0xF3, 0xB5, 0xFD, 0xCF, 0x45,
+                               0xDD, 0x40, 0xCD, 0xDC, 0x7E, 0xC3, 0xEA, 0x52, 0x1F, 0x92, 0x47, 0x8F, 0x3B, 0xFC, 0x2B, 0x1E
+                       },
+                       .secKey =
+                       {
+                               0x31, 0xEE, 0x63, 0x02, 0x4D, 0x0F, 0x67, 0x19, 0x8E, 0xE0, 0x81, 0xE3, 0x19, 0xCA, 0x0E, 0x44,
+                               0xBF, 0x47, 0x26, 0x1C, 0x2E, 0x45, 0x98, 0xAC, 0x97, 0x45, 0x57, 0x59, 0x17, 0x45, 0xB4, 0xA5
+                       }
+               }
+       },
+       // DNSSEC Zone 3
+       {
+               .ksk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitKSK( kDNSSECAlgorithm_Ed25519 ),
+                       .pubKey =
+                       {
+                               0x6A, 0xBE, 0x53, 0x33, 0xA7, 0x21, 0xF5, 0xE9, 0x09, 0xC6, 0x63, 0xBB, 0x16, 0x19, 0xD6, 0x5D,
+                               0x7C, 0xA6, 0x90, 0x7B, 0xD6, 0x30, 0xDB, 0x81, 0xFD, 0x95, 0xF0, 0xDB, 0xCF, 0x6B, 0xB1, 0xDA
+                       },
+                       .secKey =
+                       {
+                               0x3C, 0xE0, 0x72, 0x1F, 0x4F, 0x3E, 0xA1, 0xBA, 0x12, 0xF3, 0x6E, 0x8E, 0xAE, 0x67, 0x9F, 0x45,
+                               0x20, 0x69, 0xA6, 0xC3, 0x6E, 0xEC, 0xA8, 0xC0, 0x84, 0x31, 0xA8, 0x12, 0x0A, 0xEC, 0xED, 0x3B
+                       }
+               },
+               .zsk =
+               {
+                       .fixedFields = _DNSKeyInfoFixedFieldsInitZSK( kDNSSECAlgorithm_Ed25519 ),
+                       .pubKey =
+                       {
+                               0x4A, 0xE1, 0x8C, 0xED, 0x15, 0x60, 0x8C, 0x0A, 0x97, 0xD9, 0x77, 0xF2, 0x93, 0x1B, 0xDA, 0x40,
+                               0xAB, 0xE7, 0x9E, 0x0C, 0xE7, 0xF4, 0xA6, 0x68, 0x08, 0x3E, 0x9B, 0xC9, 0x2A, 0x0C, 0x7A, 0xDA
+                       },
+                       .secKey =
+                       {
+                               0x6B, 0x1E, 0x83, 0xC8, 0xFB, 0xF5, 0xF8, 0x98, 0x92, 0x85, 0x7E, 0x5F, 0x07, 0xF8, 0xB5, 0xB1,
+                               0x9D, 0x9C, 0x58, 0x76, 0xCB, 0x20, 0x2E, 0x81, 0xB4, 0x4B, 0x4D, 0xFF, 0xF2, 0x3B, 0xCD, 0x50
+                       }
+               }
+       }
+};
+
+_DNSKeySetsCompileTimeChecks( Ed25519 );
+
+//===========================================================================================================================
+// MARK: - Public Functions -
+
+#define _GetDNSKeyInfoCase( ALG_NAME )                                                                                                                                         \
+       case kDNSSECAlgorithm_ ## ALG_NAME:                                                                                                                                             \
+               if( inIndex < countof( kDNSKey ## ALG_NAME ## Sets ) )                                                                                          \
+               {                                                                                                                                                                                                       \
+                       const DNSKey ## ALG_NAME ## Set * const             _set = &kDNSKey ## ALG_NAME ## Sets[ inIndex ];     \
+                       const DNSKey ## ALG_NAME ## Info * const                _keyInfo = inGetZSK ? &_set->zsk : &_set->ksk;  \
+                                                                                                                                                                                                                       \
+                       return( (DNSKeyInfoRef) _keyInfo );                                                                                                                             \
+               }                                                                                                                                                                                                       \
+               break
+
+DNSKeyInfoRef  GetDNSKeyInfoEx( const uint32_t inAlgorithm, const uint32_t inIndex, const Boolean inGetZSK )
+{
+       switch( inAlgorithm )
+       {
+               _GetDNSKeyInfoCase( RSASHA1 );
+               _GetDNSKeyInfoCase( RSASHA256 );
+               _GetDNSKeyInfoCase( RSASHA512 );
+               _GetDNSKeyInfoCase( ECDSAP256SHA256 );
+               _GetDNSKeyInfoCase( ECDSAP384SHA384 );
+               _GetDNSKeyInfoCase( Ed25519 );
+               default: break;
+       }
+       return( NULL );
+}
+
+//===========================================================================================================================
+
+uint8_t        DNSKeyInfoGetAlgorithm( const DNSKeyInfoRef me )
+{
+       return( dns_fixed_fields_dnskey_get_algorithm( &me->fixedFields ) );
+}
+
+//===========================================================================================================================
+
+const uint8_t *        DNSKeyInfoGetRDataPtr( const DNSKeyInfoRef me )
+{
+       return( (const uint8_t *) &me->fixedFields );
+}
+
+//===========================================================================================================================
+
+#define _DNSKeyInfoGetRDataLenCase( ALG_NAME ) \
+       case kDNSSECAlgorithm_ ## ALG_NAME: return( endof_field( DNSKey ## ALG_NAME ## Info, pubKey ) )
+
+uint16_t       DNSKeyInfoGetRDataLen( const DNSKeyInfoRef me )
+{
+       switch( DNSKeyInfoGetAlgorithm( me ) )
+       {
+               _DNSKeyInfoGetRDataLenCase( RSASHA1 );
+               _DNSKeyInfoGetRDataLenCase( RSASHA256 );
+               _DNSKeyInfoGetRDataLenCase( RSASHA512 );
+               _DNSKeyInfoGetRDataLenCase( ECDSAP256SHA256 );
+               _DNSKeyInfoGetRDataLenCase( ECDSAP384SHA384 );
+               _DNSKeyInfoGetRDataLenCase( Ed25519 );
+               default: return( 0 );
+       }
+}
+
+//===========================================================================================================================
+
+#define _DNSKeyInfoGetPubKeyPtrCase( ALG_NAME ) \
+       case kDNSSECAlgorithm_ ## ALG_NAME: return( me->ALG_NAME.pubKey )
+
+const uint8_t *        DNSKeyInfoGetPubKeyPtr( const DNSKeyInfoRef me )
+{
+       switch( DNSKeyInfoGetAlgorithm( me ) )
+       {
+               _DNSKeyInfoGetPubKeyPtrCase( RSASHA1 );
+               _DNSKeyInfoGetPubKeyPtrCase( RSASHA256 );
+               _DNSKeyInfoGetPubKeyPtrCase( RSASHA512 );
+               _DNSKeyInfoGetPubKeyPtrCase( ECDSAP256SHA256 );
+               _DNSKeyInfoGetPubKeyPtrCase( ECDSAP384SHA384 );
+               _DNSKeyInfoGetPubKeyPtrCase( Ed25519 );
+               default: return( NULL );
+       }
+}
+
+//===========================================================================================================================
+
+#define _DNSKeyInfoGetPubKeyLenCase( ALG_NAME ) \
+       case kDNSSECAlgorithm_ ## ALG_NAME: return( sizeof_field( DNSKey ## ALG_NAME ## Info, pubKey ) )
+
+size_t DNSKeyInfoGetPubKeyLen( const DNSKeyInfoRef me )
+{
+       switch( DNSKeyInfoGetAlgorithm( me ) )
+       {
+               _DNSKeyInfoGetPubKeyLenCase( RSASHA1 );
+               _DNSKeyInfoGetPubKeyLenCase( RSASHA256 );
+               _DNSKeyInfoGetPubKeyLenCase( RSASHA512 );
+               _DNSKeyInfoGetPubKeyLenCase( ECDSAP256SHA256 );
+               _DNSKeyInfoGetPubKeyLenCase( ECDSAP384SHA384 );
+               _DNSKeyInfoGetPubKeyLenCase( Ed25519 );
+               default: return( 0 );
+       }
+}
+
+//===========================================================================================================================
+
+uint16_t       DNSKeyInfoGetKeyTag( const DNSKeyInfoRef inKeyInfo )
+{
+       return( DNSComputeDNSKeyTag( DNSKeyInfoGetRDataPtr( inKeyInfo ), DNSKeyInfoGetRDataLen( inKeyInfo ) ) );
+}
+
+//===========================================================================================================================
+
+#define SHA1_OUTPUT_SIZE               20
+#define SHA256_OUTPUT_SIZE             32
+#define SHA384_OUTPUT_SIZE             48
+
+Boolean
+       DNSKeyInfoSign(
+               DNSKeyInfoRef   dnsKey,
+               const uint8_t * dataToSign,
+               const size_t    dataLen,
+               uint8_t                 outSignature[ STATIC_PARAM kDNSServerSignatureLengthMax ],
+               size_t * const  outSignatureLen )
+{
+       Boolean isSigned = false;
+       CFErrorRef cfError = NULL;
+       uint8_t dnsKeyAlgorithm = DNSKeyInfoGetAlgorithm( dnsKey );
+
+       if( dataLen > LONG_MAX )
+       {
+               return isSigned;
+       }
+
+       switch( dnsKeyAlgorithm )
+       {
+               case kDNSSECAlgorithm_RSASHA1:
+               case kDNSSECAlgorithm_RSASHA256:
+               case kDNSSECAlgorithm_RSASHA512:
+               {
+                       const void *            secKeyRefOpts[]         = { kSecAttrKeyType, kSecAttrKeyClass };
+                       const void *            seckeyRefVals[]         = { kSecAttrKeyTypeRSA, kSecAttrKeyClassPrivate };
+                       SecKeyAlgorithm         algorithm;
+                       const uint8_t *         secKeyBytes                     = NULL;
+                       CFIndex                         secKeyBytesLength;
+                       CFDictionaryRef         secKeyDic                       = NULL;
+                       CFDataRef                       secKeyDataRef           = NULL;
+                       SecKeyRef                       secKeyRef                       = NULL;
+                       CFDataRef                       cfDataToSign            = NULL;
+                       CFDataRef                       sigDataRef                      = NULL;
+
+                       secKeyDic = CFDictionaryCreate(kCFAllocatorDefault, secKeyRefOpts, seckeyRefVals, countof( secKeyRefOpts ),
+                               &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
+                       require_quiet( secKeyDic, rsa_sign_exit );
+
+                       if( dnsKeyAlgorithm == kDNSSECAlgorithm_RSASHA1 )
+                       {
+                               secKeyBytes = dnsKey->RSASHA1.secKey;
+                               secKeyBytesLength = kRSASHA1_SecretKeyBytes;
+                               algorithm = kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA1;
+                       }
+                       else if( dnsKeyAlgorithm == kDNSSECAlgorithm_RSASHA256 )
+                       {
+                               secKeyBytes = dnsKey->RSASHA256.secKey;
+                               secKeyBytesLength = kRSASHA256_SecretKeyBytes;
+                               algorithm = kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA256;
+                       }
+                       else // dnsKeyAlgorithm == kDNSSECAlgorithm_RSASHA512
+                       {
+                               secKeyBytes = dnsKey->RSASHA512.secKey;
+                               secKeyBytesLength = kRSASHA512_SecretKeyBytes;
+                               algorithm = kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA512;
+                       }
+
+                       secKeyDataRef = CFDataCreate( kCFAllocatorDefault, secKeyBytes, secKeyBytesLength);
+                       require_quiet( secKeyDataRef, rsa_sign_exit );
+                       secKeyRef = SecKeyCreateWithData( secKeyDataRef, secKeyDic, &cfError );
+                       require_quiet( secKeyRef, rsa_sign_exit );
+
+                       cfDataToSign = CFDataCreate( kCFAllocatorDefault, dataToSign, ( signed long )dataLen );
+                       require_quiet( cfDataToSign, rsa_sign_exit );
+
+                       sigDataRef = SecKeyCreateSignature( secKeyRef, algorithm, cfDataToSign, &cfError );
+                       require_quiet( sigDataRef != NULL, rsa_sign_exit );
+
+                       memcpy( outSignature, CFDataGetBytePtr( sigDataRef ), (size_t) CFDataGetLength( sigDataRef ) );
+                       if( outSignatureLen != NULL ) *outSignatureLen = (size_t)CFDataGetLength( sigDataRef );
+
+                       isSigned = true;
+               rsa_sign_exit:
+                       if( secKeyDic != NULL )         CFRelease(secKeyDic);
+                       if( secKeyDataRef != NULL )     CFRelease(secKeyDataRef);
+                       if( secKeyRef != NULL )         CFRelease(secKeyRef);
+                       if( cfDataToSign != NULL )      CFRelease(cfDataToSign);
+                       if( sigDataRef != NULL )        CFRelease(sigDataRef);
+                       break;
+               }
+               case kDNSSECAlgorithm_ECDSAP256SHA256:
+               case kDNSSECAlgorithm_ECDSAP384SHA384:
+               {
+                       // prepare kSecAttrKey dictionary to create the SecKeyRef
+                       const void *            secKeyRefOpts[]         = { kSecAttrKeyType, kSecAttrKeyClass };
+                       const void *            seckeyRefVals[]         = { kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyClassPrivate };
+                       uint8_t                         secKeyDataWithPrefix[ 1 + MAX( kECDSAP256SHA256_SecretKeyBytes, kECDSAP384SHA384_SecretKeyBytes ) ]; // 04|data
+                       CFDictionaryRef         secKeyDic                       = NULL;
+                       CFDataRef                       secKeyDataRef           = NULL;
+                       SecKeyRef                       secKeyRef                       = NULL;
+                       uint8_t                         digest[ MAX( SHA256_OUTPUT_SIZE, SHA384_OUTPUT_SIZE ) ];
+                       uint8_t                         digestLen                       = (dnsKeyAlgorithm == kDNSSECAlgorithm_ECDSAP256SHA256) ? SHA256_OUTPUT_SIZE : SHA384_OUTPUT_SIZE;
+                       CFDataRef                       digestToSignRef         = NULL;
+                       CFDataRef                       sigDataRef                      = NULL;
+
+                       // construct private key
+                       secKeyDataWithPrefix[0] = 4;
+                       if( dnsKeyAlgorithm == kDNSSECAlgorithm_ECDSAP256SHA256 )
+                       {
+                               memcpy( secKeyDataWithPrefix + 1, dnsKey->ECDSAP256SHA256.secKey, sizeof( dnsKey->ECDSAP256SHA256.secKey ) );
+                       }
+                       else // dnsKeyAlgorithm == kDNSSECAlgorithm_ECDSAP384SHA384
+                       {
+                               memcpy( secKeyDataWithPrefix + 1, dnsKey->ECDSAP384SHA384.secKey, sizeof( dnsKey->ECDSAP384SHA384.secKey ) );
+                       }
+
+                       // create kSecAttrKey dictionary
+                       secKeyDic = CFDictionaryCreate( kCFAllocatorDefault, secKeyRefOpts, seckeyRefVals,
+                               countof( secKeyRefOpts ), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
+                       require_quiet( secKeyDic != NULL, ecdsa_sign_exit );
+
+                       // create private key
+                       secKeyDataRef = CFDataCreate( kCFAllocatorDefault, secKeyDataWithPrefix, sizeof( secKeyDataWithPrefix ) );
+                       require_quiet(secKeyDataRef != NULL, ecdsa_sign_exit);
+                       secKeyRef = SecKeyCreateWithData( secKeyDataRef, secKeyDic, &cfError );
+                       require_quiet(secKeyRef != NULL, ecdsa_sign_exit );
+
+                       // calculate digest for the data and create CFDataRef
+                       if( dnsKeyAlgorithm == kDNSSECAlgorithm_ECDSAP256SHA256 )
+                       {
+                               CC_SHA256( dataToSign, (uint32_t)dataLen, digest );
+                       }
+                       else // dnsKeyAlgorithm == kDNSSECAlgorithm_ECDSAP384SHA384
+                       {
+                               CC_SHA384( dataToSign, (uint32_t)dataLen, digest );
+                       }
+                       digestToSignRef = CFDataCreate( kCFAllocatorDefault, digest, digestLen );
+                       require_quiet( digestToSignRef != NULL, ecdsa_sign_exit);
+
+                       // Sign the data
+                       sigDataRef = SecKeyCreateSignature( secKeyRef, kSecKeyAlgorithmECDSASignatureRFC4754, digestToSignRef, &cfError );
+                       require_quiet( sigDataRef != NULL, ecdsa_sign_exit );
+
+                       // copy the result
+                       memcpy( outSignature, CFDataGetBytePtr( sigDataRef ), (size_t) CFDataGetLength( sigDataRef ) );
+                       if( outSignatureLen != NULL ) *outSignatureLen = (size_t)CFDataGetLength( sigDataRef );
+
+                       isSigned = true;
+               ecdsa_sign_exit:
+                       if( sigDataRef != NULL )                        CFRelease( sigDataRef );
+                       if( digestToSignRef != NULL )           CFRelease( digestToSignRef );
+                       if( secKeyRef != NULL )                         CFRelease( secKeyRef );
+                       if( secKeyDataRef != NULL )                     CFRelease( secKeyDataRef );
+                       if( secKeyDic != NULL )                         CFRelease( secKeyDic );
+                       break;
+               }
+               case kDNSSECAlgorithm_Ed25519:
+               {
+                       const DNSKeyEd25519Info *key = &dnsKey->Ed25519;
+                       Ed25519_sign( outSignature, dataToSign, dataLen, key->pubKey, key->secKey );
+                       isSigned = true;
+                       if( outSignatureLen != NULL ) *outSignatureLen = kEd25519_SignatureBytes;
+                       break;
+               }
+               default:
+                       isSigned = false;
+                       break;
+       }
+
+       return isSigned;
+}
+
+//===========================================================================================================================
+
+static void
+       _ParseRSAPublicKey(
+               const uint8_t *         inPublicKey,
+               uint16_t                        inKeyLength,
+               const uint8_t **        outModulus,
+               long *                          outModulusLength,
+               const uint8_t **        outExponent,
+               long *                          outExponentLength );
+
+Boolean
+       DNSKeyInfoVerify(
+               DNSKeyInfoRef   inKeyInfo,
+               const uint8_t * inMsgPtr,
+               size_t                  inMsgLen,
+               const uint8_t * inSignaturePtr,
+               size_t                  inSignatureLen )
+{
+       CFErrorRef              cfError                         = NULL;
+       SecKeyRef               pubKeyRef                       = NULL;
+       const uint8_t * dataToSign                      = NULL;
+       size_t                  dataToSignLength        = 0;
+       CFDataRef               dataToSignRef           = NULL;
+       CFDataRef               sigDataRef                      = NULL;
+       Boolean                 isValid                         = false;
+       uint8_t                 dnsKeyAlgorithm;
+       SecKeyAlgorithm verifyAlgorithm;
+
+       dnsKeyAlgorithm = DNSKeyInfoGetAlgorithm( inKeyInfo );
+       switch( dnsKeyAlgorithm )
+       {
+               case kDNSSECAlgorithm_RSASHA1:
+               case kDNSSECAlgorithm_RSASHA256:
+               case kDNSSECAlgorithm_RSASHA512:
+               {
+                       const uint8_t * keyBytes;
+                       uint16_t                keyLength;
+                       if( dnsKeyAlgorithm == kDNSSECAlgorithm_RSASHA1 )
+                       {
+                               verifyAlgorithm = kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA1;
+                               keyBytes                = inKeyInfo->RSASHA1.pubKey;
+                               keyLength               = kRSASHA1_PublicKeyBytes;
+                       }
+                       else if( dnsKeyAlgorithm == kDNSSECAlgorithm_RSASHA256 )
+                       {
+                               verifyAlgorithm = kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA256;
+                               keyBytes                = inKeyInfo->RSASHA256.pubKey;
+                               keyLength               = kRSASHA256_PublicKeyBytes;
+                       }
+                       else // dnsKeyAlgorithm == kDNSSECAlgorithm_RSASHA512
+                       {
+                               verifyAlgorithm = kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA512;
+                               keyBytes                = inKeyInfo->RSASHA512.pubKey;
+                               keyLength               = kRSASHA512_PublicKeyBytes;
+                       }
+                       SecRSAPublicKeyParams rsaKeyParams;
+                       _ParseRSAPublicKey( keyBytes, keyLength, ( const uint8_t ** )&rsaKeyParams.modulus,
+                               &rsaKeyParams.modulusLength, ( const uint8_t ** )&rsaKeyParams.exponent, &rsaKeyParams.exponentLength );
+                       pubKeyRef = SecKeyCreateRSAPublicKey( kCFAllocatorDefault, ( const uint8_t * )&rsaKeyParams,
+                               sizeof( rsaKeyParams ), kSecKeyEncodingRSAPublicParams );
+
+                       dataToSign                      = inMsgPtr;
+                       dataToSignLength        = inMsgLen;
+
+                       break;
+               }
+               case kDNSSECAlgorithm_ECDSAP256SHA256:
+               case kDNSSECAlgorithm_ECDSAP384SHA384:
+               {
+                       const void *            pubKeyRefOpts[]         = { kSecAttrKeyType, kSecAttrKeyClass };
+                       const void *            pubKeyRefVals[]         = { kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyClassPublic };
+                       uint8_t                         pubKeyDataWithPrefix[ 1 + MAX( kECDSAP256SHA256_PublicKeyBytes , kECDSAP384SHA384_PublicKeyBytes ) ]; // 04|data
+                       CFDictionaryRef         pubKeyDic                       = NULL;
+                       CFDataRef                       pubKeyDataRef           = NULL;
+                       uint8_t                         digest[ MAX( SHA256_OUTPUT_SIZE , SHA384_OUTPUT_SIZE ) ];
+
+                       pubKeyDataWithPrefix[ 0 ] = 4;
+                       if( dnsKeyAlgorithm == kDNSSECAlgorithm_ECDSAP256SHA256 )
+                       {
+                               memcpy( pubKeyDataWithPrefix + 1, inKeyInfo->ECDSAP256SHA256.pubKey, sizeof( inKeyInfo->ECDSAP256SHA256.pubKey ) );
+                       }
+                       else
+                       {
+                               memcpy( pubKeyDataWithPrefix + 1, inKeyInfo->ECDSAP384SHA384.pubKey, sizeof( inKeyInfo->ECDSAP384SHA384.pubKey ) );
+                       }
+                       verifyAlgorithm = kSecKeyAlgorithmECDSASignatureRFC4754;
+
+                       pubKeyDic = CFDictionaryCreate( kCFAllocatorDefault, pubKeyRefOpts, pubKeyRefVals,
+                               sizeof( pubKeyRefOpts ) / sizeof( void * ), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
+                       require_quiet( pubKeyDic != NULL, ecdsa_exit );
+
+                       pubKeyDataRef = CFDataCreate( kCFAllocatorDefault, pubKeyDataWithPrefix, sizeof( pubKeyDataWithPrefix ) );
+                       require_quiet( pubKeyDataRef != NULL, ecdsa_exit );
+                       pubKeyRef = SecKeyCreateWithData( pubKeyDataRef, pubKeyDic, &cfError );
+                       require_action_quiet( pubKeyRef != NULL, ecdsa_exit, FPrintF( stderr, "%@", CFErrorCopyDescription( cfError ) ) );
+
+                       if( dnsKeyAlgorithm == kDNSSECAlgorithm_ECDSAP256SHA256 )
+                       {
+                               CC_SHA256( inMsgPtr, (uint32_t)inMsgLen, digest );
+                               dataToSignLength = SHA256_OUTPUT_SIZE;
+                       }
+                       else
+                       {
+                               CC_SHA384( inMsgPtr, (uint32_t)inMsgLen, digest );
+                               dataToSignLength = SHA384_OUTPUT_SIZE;
+                       }
+                       dataToSign = digest;
+
+               ecdsa_exit:
+                       if( pubKeyDataRef != NULL )     CFRelease( pubKeyDataRef );
+                       if( pubKeyDic != NULL )         CFRelease( pubKeyDic );
+                       break;
+               }
+               case kDNSSECAlgorithm_Ed25519:
+               {
+                       require_action_quiet( inSignatureLen == kEd25519_SignatureBytes, exit, isValid = false );
+                       isValid = ( Ed25519_verify( inMsgPtr, inMsgLen, inSignaturePtr, inKeyInfo->Ed25519.pubKey ) == 0 );
+                       goto exit;
+               }
+               default:
+                       isValid = false;
+                       goto exit;
+       }
+
+       require_quiet( pubKeyRef != NULL, exit );
+
+       dataToSignRef = CFDataCreate( kCFAllocatorDefault, dataToSign, ( signed long )dataToSignLength );
+       require_quiet( dataToSignRef != NULL, exit);
+
+       sigDataRef = CFDataCreate( kCFAllocatorDefault, inSignaturePtr, (signed long)inSignatureLen );
+       require_quiet( sigDataRef != NULL, exit );
+
+       isValid = SecKeyVerifySignature( pubKeyRef, verifyAlgorithm, dataToSignRef, sigDataRef, &cfError);
+
+exit:
+       if( pubKeyRef != NULL )         CFRelease( pubKeyRef );
+       if( dataToSignRef != NULL )     CFRelease( dataToSignRef );
+       if( sigDataRef != NULL )        CFRelease( sigDataRef );
+       return isValid;
+}
+
+//===========================================================================================================================
+
+static void
+       _ParseRSAPublicKey(
+       const uint8_t * const   publicKey,
+       const uint16_t                  keyLength,
+       const uint8_t ** const  outModulus,
+       long * const                    outModulusLength,
+       const uint8_t ** const  outExponent,
+       long * const                    outExponentLength )
+{
+       uint8_t exponentLengthFieldLength;
+       if( publicKey[0] != 0 )
+       {
+               *outExponentLength = publicKey[0];
+               exponentLengthFieldLength = 1;
+       }
+       else
+       {
+               *outExponentLength = (long)(((uint32_t)publicKey[1] << 8) | (uint32_t)publicKey[2]);
+               exponentLengthFieldLength = 3;
+       }
+
+       *outExponent            = publicKey + exponentLengthFieldLength;
+       *outModulusLength       = keyLength - ( *outExponentLength + exponentLengthFieldLength );
+       *outModulus                     = publicKey + exponentLengthFieldLength + *outExponentLength;
+}
+
+//===========================================================================================================================
+// Note: The descriptions come from
+// <https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml#dns-sec-alg-numbers-1>.
+
+const char *   DNSKeyInfoGetAlgorithmDescription( const DNSKeyInfoRef me )
+{
+       switch( DNSKeyInfoGetAlgorithm( me ) )
+       {
+               case kDNSSECAlgorithm_RSASHA1:                  return( "RSA/SHA-1" );
+               case kDNSSECAlgorithm_RSASHA256:                return( "RSA/SHA-256" );
+               case kDNSSECAlgorithm_RSASHA512:                return( "RSA/SHA-512" );
+               case kDNSSECAlgorithm_ECDSAP256SHA256:  return( "ECDSA Curve P-256 with SHA-256" );
+               case kDNSSECAlgorithm_ECDSAP384SHA384:  return( "ECDSA Curve P-384 with SHA-384" );
+               case kDNSSECAlgorithm_Ed25519:                  return( "Ed25519" );
+               default:                                                                return( "<UNKNOWN ALGORITHM>" );
+       }
+}
diff --git a/Clients/dnssdutil/DNSServerDNSSEC.h b/Clients/dnssdutil/DNSServerDNSSEC.h
new file mode 100644 (file)
index 0000000..28ea7ae
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+       Copyright (c) 2020 Apple Inc. All rights reserved.
+*/
+
+#ifndef        __DNSServerDNSSEC_h
+#define        __DNSServerDNSSEC_h
+
+#include <CoreUtils/CoreUtils.h>
+
+CU_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief  Zone Label Argument Limits
+*/
+
+#define kZoneLabelIndexArgMin          1
+#define kZoneLabelIndexArgMax          3
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief  Reference to a DNSKeyInfo object.
+*/
+typedef const union DNSKeyInfo *               DNSKeyInfoRef;
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Gets a constant DNSKeyInfo object, which represents a DNSSEC DNS key.
+       
+       @param          inAlgorithm             The desired DNSKeyInfo object's DNSSEC algorithm number.
+       @param          inIndex                 The desired DNSKeyInfo object's index number.
+       @param          inGetZSK                If true, gets a zone-signing key. Otherwise a key-signing key.
+       
+       @result         If a reference to the DNSKeyInfo object if it exists, otherwise, NULL.
+*/
+DNSKeyInfoRef _Nullable        GetDNSKeyInfoEx( uint32_t inAlgorithm, uint32_t inIndex, Boolean inGetZSK );
+#define GetDNSKeyInfoKSK( ALGORITHM, INDEX )           GetDNSKeyInfoEx( ALGORITHM, INDEX, false )
+#define GetDNSKeyInfoZSK( ALGORITHM, INDEX )           GetDNSKeyInfoEx( ALGORITHM, INDEX, true )
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Gets a DNSKeyInfo object's DNSSEC algorithm number.
+       
+       @param          inKeyInfo               The DNSKeyInfo object.
+       
+       @result         The DNSSEC algorithm number.
+       
+       @discussion     See <https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml#dns-sec-alg-numbers-1>.
+*/
+uint8_t        DNSKeyInfoGetAlgorithm( DNSKeyInfoRef inKeyInfo );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Gets a pointer to a DNSKeyInfo object's DNSKEY record data.
+       
+       @param          inKeyInfo               The DNSKeyInfo object.
+       
+       @result         The DNSKEY record data in wire format. See <https://tools.ietf.org/html/rfc4034#section-2.1>.
+       
+       @discussion     Use DNSKeyInfoGetRDataLen() to get the record data's length.
+*/
+const uint8_t *        DNSKeyInfoGetRDataPtr( DNSKeyInfoRef inKeyInfo );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Gets the length of a DNSKeyInfo object's DNSKEY record data.
+       
+       @param          inKeyInfo               The DNSKeyInfo object.
+       
+       @result         The length of the record data.
+*/
+uint16_t       DNSKeyInfoGetRDataLen( DNSKeyInfoRef inKeyInfo );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Gets a pointer to a DNSKeyInfo object's public key.
+       
+       @param          inKeyInfo               The DNSKeyInfo object.
+       
+       @result         A pointer to the public key.
+       
+       @discussion     Use DNSKeyInfoGetPubKeyLen() to get the public key's length.
+*/
+const uint8_t *        _Nullable DNSKeyInfoGetPubKeyPtr( DNSKeyInfoRef inKeyInfo );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Gets the length of a DNSKeyInfo object's public key.
+       
+       @param          inKeyInfo               The DNSKeyInfo object.
+       
+       @result         The length of the public key.
+*/
+size_t DNSKeyInfoGetPubKeyLen( DNSKeyInfoRef inKeyInfo );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Gets the DNSSEC key tag of DNSKeyInfo objects' DNSKEY record data.
+       
+       @param          inKeyInfo               The DNSKeyInfo object.
+       
+       @result         The DNSSEC key tag.
+*/
+uint16_t       DNSKeyInfoGetKeyTag( DNSKeyInfoRef inKeyInfo );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @defined        kDNSServerSignatureLengthMax
+       
+       @discussion     The maximum length of a DNSSEC signature for DNSSEC algorithms currently implemented by the test DNS server.
+*/
+#define kDNSServerSignatureLengthMax           256
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Signs a message using a DNSKeyInfo object's secret key.
+       
+       @param          inKeyInfo                       The DNSKeyInfo object.
+       @param          inMsgPtr                        Pointer to the message to sign.
+       @param          inMsgLen                        Length, in bytes, of the message to sign.
+       @param          outSignature            Buffer to which to write the signature.
+       @param          outSignatureLen         Pointer of variable to get set to the signature's length.
+       
+       @result         Returns true if the message was able to be signed, otherwise, returns false.
+*/
+Boolean
+       DNSKeyInfoSign(
+               DNSKeyInfoRef   inKeyInfo,
+               const uint8_t * inMsgPtr,
+               size_t                  inMsgLen,
+               uint8_t                 outSignature[ STATIC_PARAM kDNSServerSignatureLengthMax ],
+               size_t *                outSignatureLen );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Verifies a signature using a DNSKeyInfo object's public key.
+       
+       @param          inKeyInfo                       The DNSKeyInfo object.
+       @param          inMsgPtr                        Pointer to the message that was signed.
+       @param          inMsgLen                        Length, in bytes, of the message that was signed.
+       @param          inSignaturePtr          Pointer to the supposed signature.
+       @param          inSignatureLen          Length, in bytes, of the supposed signature.
+       
+       @result         Returns true if the signature was verified, otherwise, returns false.
+*/
+Boolean
+       DNSKeyInfoVerify(
+               DNSKeyInfoRef   inKeyInfo,
+               const uint8_t * inMsgPtr,
+               size_t                  inMsgLen,
+               const uint8_t * inSignaturePtr,
+               size_t                  inSignatureLen );
+
+//---------------------------------------------------------------------------------------------------------------------------
+/*!    @brief          Gets a short description of a DNSKeyInfo object's DNSSEC algorithm.
+       
+       @param          inKeyInfo               The DNSKeyInfo object.
+       
+       @result         The description as a UTF-8 C string.
+*/
+const char *   DNSKeyInfoGetAlgorithmDescription( DNSKeyInfoRef inKeyInfo );
+
+__END_DECLS
+
+CU_ASSUME_NONNULL_END
+
+#endif // __DNSServerDNSSEC_h
index c2631640c025b3c9f18fd09451cdc7ce476de6dc..81fbb10375c2288626c13b2fcf123d00bb696796 100644 (file)
@@ -10,6 +10,7 @@
 
 #include <TargetConditionals.h>
 #include <MacTypes.h>
+#include <mach/mach.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -17,7 +18,8 @@ extern "C" {
 
 #define DNSSDUTIL_XCTEST "DNSSDUTIL_XCTEST"
 
-Boolean TestUtilsRunXCTestNamed(const char * classname);
+bool run_xctest_named(const char *classname);
+bool audit_token_for_pid(pid_t pid, const audit_token_t *token);
 
 #ifdef __cplusplus
 }
index 00b3c4cb4815d476c4bac1dc8ea4a5f274f8df8f..35efdbbc5d9609f6804e73d8bb0b7c017ad38cb6 100644 (file)
@@ -7,59 +7,71 @@
 
 #import "TestUtils.h"
 
-#import <Foundation/Foundation.h>
-#import <CoreUtils/CoreUtils.h>
+#import <dlfcn.h>
 #import <XCTest/XCTest.h>
 
 #if TARGET_OS_OSX
-#define XCTest_Framework_Runtime_Path       "/AppleInternal/Developer/Library/Frameworks/XCTest.framework"
+#define XCTest_Framework_Runtime_Path       "/AppleInternal/Developer/Library/Frameworks/XCTest.framework/XCTest"
 #else
-#define XCTest_Framework_Runtime_Path       "/Developer/Library/Frameworks/XCTest.framework"
+#define XCTest_Framework_Runtime_Path       "/Developer/Library/Frameworks/XCTest.framework/XCTest"
 #endif
 
 //===========================================================================================================================
 //    XCTest Utils
 //===========================================================================================================================
-static NSBundle * LoadXCTestFramework()
+static bool _load_xctest_framework()
 {
-    NSBundle * result = nil;
-    Boolean loaded = (NSClassFromString(@"XCTestSuite") != nil);
-
-    if(!result) {
-        result = [NSBundle bundleWithPath: @ XCTest_Framework_Runtime_Path];
-        [result load];
+    bool loaded = (NSClassFromString(@"XCTestSuite") != nil);
+    static void *s_xctest_handle;
+    if (!loaded) {
+        s_xctest_handle = dlopen(XCTest_Framework_Runtime_Path, RTLD_LAZY | RTLD_LOCAL);
         loaded = (NSClassFromString(@"XCTestSuite") != nil);
-        if( !loaded ) {
-            FPrintF( stdout, "Failed to load XCTest framework from: %s\n", XCTest_Framework_Runtime_Path );
+        if (!loaded) {
+            fprintf(stderr, "%s Failed to load XCTest framework from: %s\n", __FUNCTION__, XCTest_Framework_Runtime_Path);
         }
     }
-
-    return( result );
+    return loaded;
 }
 
 //===========================================================================================================================
 //    Main Test Running
 //===========================================================================================================================
 
-Boolean TestUtilsRunXCTestNamed(const char * classname)
+bool run_xctest_named(const char *classname)
 {
-    Boolean     result = false;
-    NSBundle *  xctestFramework = LoadXCTestFramework();
-
-    if(xctestFramework) {
-        NSString *  name = [NSString stringWithUTF8String: classname];
-        NSBundle *  testBundle = [NSBundle bundleWithPath: @"/AppleInternal/XCTests/com.apple.mDNSResponder/Tests.xctest"];
+    bool result = false;
+    if (_load_xctest_framework()) {
+        NSString *name = [NSString stringWithUTF8String:classname];
+        NSBundle *testBundle = [NSBundle bundleWithPath:@"/AppleInternal/XCTests/com.apple.mDNSResponder/Tests.xctest"];
         [testBundle load];
 
-        XCTestSuite *   compiledSuite = [NSClassFromString(@"XCTestSuite") testSuiteForTestCaseWithName: name];
-        if(compiledSuite.tests.count) {
+        XCTestSuite *compiledSuite = [NSClassFromString(@"XCTestSuite") testSuiteForTestCaseWithName: name];
+        if (compiledSuite.tests.count) {
             [compiledSuite runTest];
             XCTestRun *testRun = compiledSuite.testRun;
             result = (testRun.hasSucceeded != NO);
         } else {
-            FPrintF( stdout, "Test class %s not found\n", classname );
+            fprintf(stderr, "%s Test class %s not found\n", __FUNCTION__, classname);
         }
     }
+    return result;
+}
+
+bool audit_token_for_pid(pid_t pid, const audit_token_t *token)
+{
+    kern_return_t err;
+    task_t task;
+    mach_msg_type_number_t info_size = TASK_AUDIT_TOKEN_COUNT;
+
+    err = task_for_pid(mach_task_self(), pid, &task);
+    if (err != KERN_SUCCESS) {
+        return false;
+    }
+
+    err = task_info(task, TASK_AUDIT_TOKEN, (integer_t *) token, &info_size);
+    if (err != KERN_SUCCESS) {
+        return false;
+    }
 
-    return( result );
+    return true;
 }
diff --git a/Clients/dnssdutil/dns-rcode-func-autogen b/Clients/dnssdutil/dns-rcode-func-autogen
new file mode 100755 (executable)
index 0000000..d8b0a6f
--- /dev/null
@@ -0,0 +1,257 @@
+#! /bin/bash
+#
+#      Copyright (c) 2020 Apple Inc. All rights reserved.
+#
+
+declare -r version=1.0
+declare -r script=${BASH_SOURCE[0]}
+declare -r rcodesURL='https://www.iana.org/assignments/dns-parameters/dns-parameters-6.csv'
+
+#============================================================================================================================
+
+PrintHelp()
+{
+       echo ""
+       echo "Usage: $( basename "${script}" ) [options]"
+       echo ""
+       echo "Options:"
+       echo "    -h  Display script usage."
+       echo "    -V  Display version of this script and exit."
+       echo ""
+       echo "This script writes C functions to convert DNS RCODE values to strings and vice versa to stdout"
+       echo "based on the latest DNS RCODE data available at"
+       echo ""
+       echo "    ${rcodesURL}"
+       echo ""
+}
+
+#============================================================================================================================
+
+ErrQuit()
+{
+       echo "error: $*" 1>&2
+       exit 1
+}
+
+#============================================================================================================================
+
+StripLeadingTrailingWhitespace()
+{
+       sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
+}
+
+#============================================================================================================================
+
+GetNamesAndValues()
+{
+       shopt -s nocasematch
+       while IFS=',' read value name others; do
+               name=$( StripLeadingTrailingWhitespace <<< "${name}" )
+               [[ ${name} =~ ^unassigned$ ]] && continue
+               
+               value=$( StripLeadingTrailingWhitespace <<< "${value}" )
+               [[ ${value} =~ ^[0-9]+$ ]] || continue
+               [ "${value}" -le 15 ] || continue
+               
+               echo "${name},${value}"
+       done
+       shopt -u nocasematch
+}
+
+#============================================================================================================================
+
+RCodeMnemonicToEnum()
+{
+       name="${1//[^A-Za-z0-9_]/_}" # Only allow alphanumeric and underscore characters.
+       printf "kDNSRCode_${name}"
+}
+
+#============================================================================================================================
+
+PrintRCodeEnums()
+{
+       local -r inputFile=${1}
+       printf "typedef enum\n"
+       printf "{\n"
+       local sep=""
+       < "${inputFile}" sort --field-separator=, --key=2,2 --numeric-sort --unique |
+       while IFS=',' read name value; do
+               printf "%b" "${sep}"
+               local enum=$( RCodeMnemonicToEnum "${name}" )
+               printf "\t%-20s= %d" "${enum}" "${value}"
+               sep=",\n"
+       done
+       printf "\n"
+       printf "\t\n"
+       printf "}\tDNSRCode;\n"
+}
+
+#============================================================================================================================
+
+PrintValueToStringElseIf()
+{
+       local -r first=${1}
+       local -r last=${2}
+       [ "${first}" -le "${last}" ] || ErrQuit "${first} > ${last}"
+       shift 2
+       local stringArray=( "$@" )
+       
+       if [ "${last}" -ne "${first}" ]; then
+               printf "\telse if( ( inValue >= ${first} ) && ( inValue <= ${last} ) )\n"
+               local -r arrayVarName="sNames_${first}_${last}"
+       else
+               printf "\telse if( inValue == ${first} )\n"
+               local -r arrayVarName="sNames_${first}"
+       fi
+       printf "\t{\n"
+       printf "\t\tstatic const char * const\t\t${arrayVarName}[] =\n"
+       printf "\t\t{\n"
+       local value=${first}
+       for string in "${stringArray[@]}"; do
+               printf "\t\t\t%-15s // %3d\n" "\"${string}\"," "${value}"
+               value=$(( value + 1 ))
+       done
+       local -r stringCount=$(( value - first ))
+       local -r expectedCount=$(( last - first + 1 ))
+       [ "${stringCount}" -eq "${expectedCount}" ] || ErrQuit "${stringCount} != ${expectedCount}"
+       printf "\t\t};\n"
+       printf "\t\tstring = ${arrayVarName}[ inValue - ${first} ];\n"
+       printf "\t}\n"
+}
+
+#============================================================================================================================
+
+PrintValueToStringFunction()
+{
+       local -r inputFile=${1}
+       printf "const char *\tDNSRCodeToString( const int inValue )\n"
+       printf "{\n"
+       printf "\tswitch( inValue )\n"
+       printf "\t{\n"
+       < "${inputFile}" sort --field-separator=, --key=2,2 --numeric-sort --unique |
+       {
+               local stringArray=()
+               while IFS=',' read name value; do
+                   local enum=$( RCodeMnemonicToEnum "${name}" )
+                       printf "\t\t%-28s%s\n" "case ${enum}:" "return( \"${name}\" );"
+               done
+       }
+       printf "\t\t%-28sreturn( NULL );\n" "default:"
+       printf "\t}\n"
+       printf "}\n"
+}
+
+#============================================================================================================================
+
+PrintStringToValueFunction()
+{
+       local -r inputFile=${1}
+       printf "#include <stdlib.h>\n"
+       printf "\n"
+       printf "typedef struct\n"
+       printf "{\n"
+       printf "\tconst char *\t\tname;\n"
+       printf "\tint\t\t\t\t\tvalue;\n"
+       printf "\t\n"
+       printf "}\t_DNSRCodeTableEntry;\n"
+       printf "\n"
+       printf "static int\t_DNSRCodeFromStringCmp( const void *inKey, const void *inElement );\n"
+       printf "\n"
+       printf "int\tDNSRCodeFromString( const char * const inString )\n"
+       printf "{\n"
+       printf "\t// The name-value table is sorted by name in ascending lexicographical order to allow going from name to\n"
+       printf "\t// value in logarithmic time via a binary search.\n"
+       printf "\t\n"
+       printf "\tstatic const _DNSRCodeTableEntry\t\tsTable[] =\n"
+       printf "\t{\n"
+       
+       local sep=""
+       < "${inputFile}" sort --field-separator=, --key=1,1 --ignore-case --unique |
+       while IFS=',' read name value; do
+               printf "%b" "${sep}"
+               local enum=$( RCodeMnemonicToEnum "${name}" )
+               printf "\t\t%-16s%-20s}" "{ \"${name}\"," "${enum}"
+               sep=",\n"
+       done
+       printf "\n"
+       printf "\t};\n"
+       printf "\tconst _DNSRCodeTableEntry *\t\t\tentry;\n"
+       printf "\t\n"
+       printf "\tentry = (_DNSRCodeTableEntry *) bsearch( inString, sTable, sizeof( sTable ) / sizeof( sTable[ 0 ] ),\n"
+       printf "\t\tsizeof( sTable[ 0 ] ), _DNSRCodeFromStringCmp );\n"
+       printf "\treturn( entry ? entry->value : -1 );\n"
+       printf "}\n"
+       printf "\n"
+       printf "static int\t_DNSRCodeFromStringCmp( const void * const inKey, const void * const inElement )\n"
+       printf "{\n"
+       printf "\tconst _DNSRCodeTableEntry * const\t\tentry = (const _DNSRCodeTableEntry *) inElement;\n"
+       printf "\treturn( strcasecmp( (const char *) inKey, entry->name ) );\n"
+       printf "}\n"
+}
+
+#============================================================================================================================
+
+ExitHandler()
+{
+       if [ -d "${tempDir}" ]; then
+               rm -fr "${tempDir}"
+       fi
+}
+
+#============================================================================================================================
+
+PrintAutoGenNote()
+{
+       printf "// This code was autogenerated on $( date -u '+%Y-%m-%d' ) by $( basename ${script} ) version ${version}\n"
+       printf "// Data source URL: ${rcodesURL}\n"
+       printf "\n"
+}
+
+#============================================================================================================================
+
+main()
+{
+       while getopts ":hO:V" option; do
+               case "${option}" in
+                       h)
+                               PrintHelp
+                               exit 0
+                               ;;
+                       V)
+                               echo "$( basename "${script}" ) version ${version}"
+                               exit 0
+                               ;;
+                       :)
+                               ErrQuit "option '${OPTARG}' requires an argument."
+                               ;;
+                       *)
+                               ErrQuit "unknown option '${OPTARG}'."
+                               ;;
+               esac
+       done
+       
+       [ "${OPTIND}" -gt "$#" ] || ErrQuit "unexpected argument \"${!OPTIND}\"."
+       
+       trap ExitHandler EXIT
+       tempDir=$( mktemp -d ) || ErrQuit "Failed to make temporary directory."
+       declare -r originalRCodesFile="${tempDir}/rcodesOriginal.csv"
+       curl --output "${originalRCodesFile}" "${rcodesURL}" || ErrQuit "Failed to download CSV file."
+       
+       declare -r rcodesFile="${tempDir}/rcodes.csv"
+       < "${originalRCodesFile}" GetNamesAndValues > "${rcodesFile}"
+       
+       declare -r tempFile="${tempDir}/temp.csv"
+       < "${rcodesFile}" sort --field-separator=, --key=2,2 --unique --numeric-sort > "${tempFile}"
+       < "${tempFile}" sort --field-separator=, --key=1,1 --unique --ignore-case > "${rcodesFile}"
+       
+       PrintAutoGenNote
+       PrintRCodeEnums "${rcodesFile}"
+       printf "\n"
+       PrintAutoGenNote
+       PrintValueToStringFunction "${rcodesFile}"
+       printf "\n"
+       PrintAutoGenNote
+       PrintStringToValueFunction "${rcodesFile}"
+}
+
+main "$@"
diff --git a/Clients/dnssdutil/dns-rr-func-autogen b/Clients/dnssdutil/dns-rr-func-autogen
new file mode 100755 (executable)
index 0000000..0c72d41
--- /dev/null
@@ -0,0 +1,287 @@
+#! /bin/bash
+#
+#      Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+#
+
+declare -r version=1.3
+declare -r script=${BASH_SOURCE[0]}
+declare -r recordTypesURL='https://www.iana.org/assignments/dns-parameters/dns-parameters-4.csv'
+
+#============================================================================================================================
+
+PrintHelp()
+{
+       echo ""
+       echo "Usage: $( basename "${script}" ) [options]"
+       echo ""
+       echo "Options:"
+       echo "    -O '<name>,<value>' Specifies a record name-value pair override. Can be used more than once."
+       echo "    -h                  Display script usage."
+       echo "    -V                  Display version of this script and exit."
+       echo ""
+       echo "This script writes C functions to convert DNS resource record type values to strings and vice versa to stdout"
+       echo "based on the latest DNS resource record type data available at"
+       echo ""
+       echo "    ${recordTypesURL}"
+       echo ""
+}
+
+#============================================================================================================================
+
+ErrQuit()
+{
+       echo "error: $*" 1>&2
+       exit 1
+}
+
+#============================================================================================================================
+
+StripLeadingTrailingWhitespace()
+{
+       sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
+}
+
+#============================================================================================================================
+
+GetNamesAndValues()
+{
+       local -r isOverride=${1}
+       shopt -s nocasematch
+       while IFS=',' read name value others; do
+               name=$( StripLeadingTrailingWhitespace <<< "${name}" )
+               [[ ${name} =~ ^unassigned$ ]] && continue
+               
+               value=$( StripLeadingTrailingWhitespace <<< "${value}" )
+               [[ ${value} =~ ^[0-9]+$ ]] || continue
+               [ "${value}" -le 65535 ] || continue
+
+               if [ "${value}" -eq 255 ]; then
+                       name=ANY
+               fi
+               echo "${name},${value},${isOverride}"
+       done
+       shopt -u nocasematch
+}
+
+#============================================================================================================================
+
+PrintRecordTypesEnum()
+{
+       local -r inputFile=${1}
+       printf "typedef enum\n"
+       printf "{\n"
+       < "${inputFile}" sort --field-separator=, --key=2,2 --numeric-sort --unique |
+       while IFS=',' read name value override; do
+               name="${name//[^A-Za-z0-9_]/_}" # Only allow alphanumeric and underscore characters.
+               printf "\tkDNSRecordType_%-10s = %d," "${name}" "${value}"
+               if [ "${override}" -ne 0 ]; then
+                       printf " // OVERRIDE"
+               fi
+               printf "\n"
+       done
+       printf "\t\n"
+       printf "}\tDNSRecordType;\n"
+}
+
+#============================================================================================================================
+
+PrintValueToStringElseIf()
+{
+       local -r first=${1}
+       local -r last=${2}
+       [ "${first}" -le "${last}" ] || ErrQuit "${first} > ${last}"
+       shift 2
+       local stringArray=( "$@" )
+       
+       if [ "${last}" -ne "${first}" ]; then
+               printf "\telse if( ( inValue >= ${first} ) && ( inValue <= ${last} ) )\n"
+               local -r arrayVarName="sNames_${first}_${last}"
+       else
+               printf "\telse if( inValue == ${first} )\n"
+               local -r arrayVarName="sNames_${first}"
+       fi
+       printf "\t{\n"
+       printf "\t\tstatic const char * const\t\t${arrayVarName}[] =\n"
+       printf "\t\t{\n"
+       local value=${first}
+       for string in "${stringArray[@]}"; do
+               printf "\t\t\t%-15s // %3d\n" "\"${string}\"," "${value}"
+               value=$(( value + 1 ))
+       done
+       local -r stringCount=$(( value - first ))
+       local -r expectedCount=$(( last - first + 1 ))
+       [ "${stringCount}" -eq "${expectedCount}" ] || ErrQuit "${stringCount} != ${expectedCount}"
+       printf "\t\t};\n"
+       printf "\t\tstring = ${arrayVarName}[ inValue - ${first} ];\n"
+       printf "\t}\n"
+}
+
+#============================================================================================================================
+
+PrintValueToStringFunction()
+{
+       local -r inputFile=${1}
+       printf "const char *\tDNSRecordTypeValueToString( int inValue )\n"
+       printf "{\n"
+       printf "\tconst char *\t\tstring;\n"
+       printf "\t\n"
+       printf "\tif( 0 ) {}\n"
+       < "${inputFile}" sort --field-separator=, --key=2,2 --numeric-sort --unique |
+       {
+               local first=-1
+               local last=-1
+               local next=-1
+               local stringArray=()
+               while IFS=',' read name value override; do
+                       if [ "${value}" -ne "${next}" ]; then
+                               [ "${first}" -ge 0 ] && PrintValueToStringElseIf "${first}" "${last}" "${stringArray[@]}"
+                               first=${value}
+                               stringArray=()
+                       fi
+                       stringArray+=( "${name}" )
+                       last=${value}
+                       next=$(( value + 1 ))
+               done
+               [ "${first}" -ge 0 ] && PrintValueToStringElseIf "${first}" "${last}" "${stringArray[@]}"
+       }
+       printf "\telse\n"
+       printf "\t{\n"
+       printf "\t\tstring = NULL;\n"
+       printf "\t}\n"
+       printf "\treturn( string );\n"
+       printf "}\n"
+}
+
+#============================================================================================================================
+
+PrintStringToValueFunction()
+{
+       local -r inputFile=${1}
+       printf "#include <stdlib.h>\n"
+       printf "\n"
+       printf "typedef struct\n"
+       printf "{\n"
+       printf "\tconst char *\t\tname;\n"
+       printf "\tuint16_t\t\t\tvalue;\n"
+       printf "\t\n"
+       printf "}\t_DNSRecordTypeItem;\n"
+       printf "\n"
+       printf "static int\t_DNSRecordTypeStringToValueCmp( const void *inKey, const void *inElement );\n"
+       printf "\n"
+       printf "uint16_t\tDNSRecordTypeStringToValue( const char *inString )\n"
+       printf "{\n"
+       printf "\t// The name-value table is sorted by name in ascending lexicographical order to allow going from name to\n"
+       printf "\t// value in logarithmic time via a binary search.\n"
+       printf "\t\n"
+       printf "\tstatic const _DNSRecordTypeItem\t\tsTable[] =\n"
+       printf "\t{\n"
+       
+       < "${inputFile}" sort --field-separator=, --key=1,1 --ignore-case --unique |
+       while IFS=',' read name value override; do
+               printf "\t\t{ %-13s %5d }," "\"${name}\"," "${value}"
+               if [ "${override}" -ne 0 ]; then
+                       printf " // OVERRIDE"
+               fi
+               printf "\n"
+       done
+       printf "\t};\n"
+       printf "\tconst _DNSRecordTypeItem *\t\t\titem;\n"
+       printf "\t\n"
+       printf "\titem = (_DNSRecordTypeItem *) bsearch( inString, sTable, sizeof( sTable ) / sizeof( sTable[ 0 ] ),\n"
+       printf "\t\tsizeof( sTable[ 0 ] ), _DNSRecordTypeStringToValueCmp );\n"
+       printf "\treturn( item ? item->value : 0 );\n"
+       printf "}\n"
+       printf "\n"
+       printf "static int\t_DNSRecordTypeStringToValueCmp( const void *inKey, const void *inElement )\n"
+       printf "{\n"
+       printf "\tconst _DNSRecordTypeItem * const\t\titem = (const _DNSRecordTypeItem *) inElement;\n"
+       printf "\treturn( strcasecmp( (const char *) inKey, item->name ) );\n"
+       printf "}\n"
+}
+
+#============================================================================================================================
+
+ExitHandler()
+{
+       if [ -d "${tempDir}" ]; then
+               rm -fr "${tempDir}"
+       fi
+}
+
+#============================================================================================================================
+
+PrintAutoGenNote()
+{
+       printf "// This code was autogenerated on $( date -u '+%Y-%m-%d' ) by $( basename ${script} ) version ${version}\n"
+       printf "// Data source URL: ${recordTypesURL}\n"
+       printf "// Overrides: "
+       if [ "${#}" -gt 0 ]; then
+               local separator=""
+               for override in "${@}"; do
+                       printf "%s'%s'" "${separator}" "${override}"
+                       separator=", "
+               done
+               printf "\n"
+       else
+               printf "none\n"
+       fi
+       printf "\n"
+}
+
+#============================================================================================================================
+
+main()
+{
+       local -a overrides
+       while getopts ":hO:V" option; do
+               case "${option}" in
+                       h)
+                               PrintHelp
+                               exit 0
+                               ;;
+                       O)
+                               overrides+=( "${OPTARG}" )
+                               ;;
+                       V)
+                               echo "$( basename "${script}" ) version ${version}"
+                               exit 0
+                               ;;
+                       :)
+                               ErrQuit "option '${OPTARG}' requires an argument."
+                               ;;
+                       *)
+                               ErrQuit "unknown option '${OPTARG}'."
+                               ;;
+               esac
+       done
+       
+       [ "${OPTIND}" -gt "$#" ] || ErrQuit "unexpected argument \"${!OPTIND}\"."
+       
+       trap ExitHandler EXIT
+       tempDir=$( mktemp -d ) || ErrQuit "Failed to make temporary directory."
+       declare -r originalRecordTypesFile="${tempDir}/recordTypesOriginal.csv"
+       curl --output "${originalRecordTypesFile}" "${recordTypesURL}" || ErrQuit "Failed to download CSV file."
+       
+       declare -r overridesFile="${tempDir}/overrides.csv"
+       for override in "${overrides[@]}"; do
+               echo "${override}"
+       done | GetNamesAndValues 1 > "${overridesFile}"
+       
+       declare -r recordTypesFile="${tempDir}/recordTypes.csv"
+       < "${originalRecordTypesFile}" GetNamesAndValues 0 > "${recordTypesFile}"
+       
+       declare -r tempFile="${tempDir}/temp.csv"
+       cat "${overridesFile}" "${recordTypesFile}" | sort --field-separator=, --key=2,2 --unique --numeric-sort > "${tempFile}"
+       cat "${overridesFile}" "${tempFile}" | sort --field-separator=, --key=1,1 --unique --ignore-case > "${recordTypesFile}"
+       
+       PrintAutoGenNote "${overrides[@]}"
+       PrintRecordTypesEnum "${recordTypesFile}"
+       printf "\n"
+       PrintAutoGenNote "${overrides[@]}"
+       PrintValueToStringFunction "${recordTypesFile}"
+       printf "\n"
+       PrintAutoGenNote "${overrides[@]}"
+       PrintStringToValueFunction "${recordTypesFile}"
+}
+
+main "$@"
index 47eba9692c3bce6d9eb0557dcacf11b04ac2217d..143afa487f15afc6285cf506d56e6a179bc18714 100644 (file)
@@ -6,19 +6,23 @@
        <true/>
        <key>com.apple.mDNSResponder_Helper</key>
        <true/>
+       <key>com.apple.networkd_privileged</key>
+       <true/>
+       <key>com.apple.private.necp.match</key>
+       <true/>
+       <key>com.apple.private.nehelper.privileged</key>
+       <true/>
        <key>com.apple.security.network.client</key>
        <true/>
        <key>com.apple.security.network.server</key>
        <true/>
+       <key>com.apple.private.network.socket-delegate</key>
+       <true/>
        <key>com.apple.SystemConfiguration.SCDynamicStore-write-access</key>
        <true/>
        <key>com.apple.SystemConfiguration.SCPreferences-write-access</key>
        <array>
                <string>preferences.plist</string>
        </array>
-       <key>com.apple.private.nehelper.privileged</key>
-       <true/>
-       <key>com.apple.networkd_privileged</key>
-       <true/>
 </dict>
 </plist>
index d56ed00b850ab91bae8acab5beeec084e334a90b..31aebedb8a0ebea59c1c30ec26fd7b2f5cd8d591 100644 (file)
@@ -1,14 +1,26 @@
 /*
-       Copyright (c) 2016-2019 Apple Inc. All rights reserved.
-       
-       dnssdutil is a command-line utility for testing the DNS-SD API.
-*/
+ * Copyright (c) 2016-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 #include "DNSMessage.h"
+#include "DNSServerDNSSEC.h"
 
 #include <CoreUtils/CoreUtils.h>
 #include <dns_sd.h>
 #include <dns_sd_private.h>
+#include <pcap.h>
 
 #include CF_RUNTIME_HEADER
 
@@ -19,7 +31,8 @@
        #include <dnsinfo.h>
        #include <libproc.h>
        #include <netdb.h>
-       #include <pcap.h>
+       #include <netinet6/in6_var.h>
+       #include <netinet6/nd6.h>
        #include <spawn.h>
        #include <sys/proc_info.h>
        #include <xpc/xpc.h>
@@ -27,6 +40,7 @@
 
 #if( TARGET_OS_POSIX )
        #include <sys/resource.h>
+       #include <spawn.h>
 #endif
 
 #if( !defined( DNSSDUTIL_INCLUDE_DNSCRYPT ) )
 #endif
 
 #if( MDNSRESPONDER_PROJECT )
+       #include <CoreFoundation/CFXPCBridge.h>
        #include <dns_services.h>
+       #include "dnssd_private.h"
        #include "mdns_private.h"
-    #include "TestUtils.h"
+       #include "TestUtils.h"
+       // Set ENABLE_DNSSDUTIL_DNSSEC_TEST to 1 to enable DNSSEC test functionality.
+       #define ENABLE_DNSSDUTIL_DNSSEC_TEST 0
 #endif
 
 //===========================================================================================================================
        "\x12" "WakeOnResolve\0"                        \
        "\x13" "BackgroundTrafficClass\0"       \
        "\x14" "IncludeAWDL\0"                          \
-       "\x15" "Validate\0"                                     \
+       "\x15" "EnableDNSSEC\0"                         \
        "\x16" "UnicastResponse\0"                      \
        "\x17" "ValidateOptional\0"                     \
        "\x18" "WakeOnlyService\0"                      \
 #define kDefaultMDNSMessageID          0
 #define kDefaultMDNSQueryFlags         0
 
-#define kQClassUnicastResponseBit              ( 1U << 15 )
-#define kRRClassCacheFlushBit                  ( 1U << 15 )
-
 // Recommended Resource Record TTL values. See <https://tools.ietf.org/html/rfc6762#section-10>.
 
 #define kMDNSRecordTTL_Host                    120             // TTL for resource records related to a host name, e.g., A, AAAA, SRV, etc.
 
 #define kDNSServerBaseAddrV4           UINT32_C( 0xCB007100 )  // 203.0.113.0/24
 
+#define kDNSServerReverseIPv4DomainStr         "113.0.203.in-addr.arpa."
+#define kDNSServerReverseIPv4DomainName \
+       ( (const uint8_t *) "\x3" "113" "\x1" "0" "\x3" "203" "\x7" "in-addr" "\x4" "arpa" )
+
 // IPv6 address block 2001:db8::/32 is reserved for documentation. See <https://tools.ietf.org/html/rfc3849>.
 
 static const uint8_t           kDNSServerBaseAddrV6[] =
 {
-       0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // 2001:db8:1::/120
+       0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // 2001:db8:1::/96
 };
+check_compile_time( sizeof( kDNSServerBaseAddrV6 ) == 16 );
+
+#define kDNSServerReverseIPv6DomainStr         "0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa."
+#define kDNSServerReverseIPv6DomainName                                                                                                \
+       ( (const uint8_t *) "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" \
+       "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" \
+       "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "1" "\x1" "0" "\x1" "0" "\x1" "0" \
+       "\x1" "8" "\x1" "b" "\x1" "d" "\x1" "0" "\x1" "1" "\x1" "0" "\x1" "0" "\x1" "2" \
+       "\x3" "ip6" "\x4" "arpa" )
 
 static const uint8_t           kMDNSReplierBaseAddrV6[] =
 {
        0x20, 0x01, 0x0D, 0xB8, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // 2001:db8:2::/96
 };
-
-check_compile_time( sizeof( kDNSServerBaseAddrV6 )   == 16 );
 check_compile_time( sizeof( kMDNSReplierBaseAddrV6 ) == 16 );
 
+static const uint8_t           kMDNSReplierLinkLocalBaseAddrV6[] =
+{
+       0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // fe80::/96
+};
+check_compile_time( sizeof( kMDNSReplierLinkLocalBaseAddrV6 ) == 16 );
+
 // Bad IPv4 and IPv6 Address Blocks
 // Used by the DNS server when it needs to respond with intentionally "bad" A/AAAA record data, i.e., IP addresses neither
 // in 203.0.113.0/24 nor 2001:db8:1::/120.
@@ -180,26 +212,29 @@ static const uint8_t              kDNSServerBadBaseAddrV6[] =
 {
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00  // ::ffff:0:0/120
 };
-
 check_compile_time( sizeof( kDNSServerBadBaseAddrV6 ) == 16 );
 
+#if( TARGET_OS_DARWIN )
+// IPv6 Unique Local Address for assigning extra randomly-generated IPv6 addresses to the loopback interface.
+// 40-bit Global ID: 0xEDF03555E4 (randomly-generated)
+// 16-bit Subnet ID: 0
+// See <https://tools.ietf.org/html/rfc4193#section-3.1>.
+
+static const uint8_t           kExtraLoopbackIPv6Prefix[] =
+{
+       0xFD, 0xED, 0xF0, 0x35, 0x55, 0xE4, 0x00, 0x00  // fded:f035:55e4::/64
+};
+
+#define kExtraLoopbackIPv6PrefixBitLen         64
+check_compile_time( ( sizeof( kExtraLoopbackIPv6Prefix ) * 8 ) == kExtraLoopbackIPv6PrefixBitLen );
+#endif
+
 //===========================================================================================================================
-//     Soft Linking
+//     DNS Server Domains
 //===========================================================================================================================
 
-#if( TARGET_OS_DARWIN )
-SOFT_LINK_LIBRARY_EX( "/usr/lib/system", system_dnssd );
-SOFT_LINK_FUNCTION_EX( system_dnssd, DNSServiceSleepKeepalive_sockaddr,
-       DNSServiceErrorType, (
-               DNSServiceRef *                                 sdRef,
-               DNSServiceFlags                                 flags,
-               const struct sockaddr *                 localAddr,
-               const struct sockaddr *                 remoteAddr,
-               unsigned int                                    timeout,
-               DNSServiceSleepKeepaliveReply   callBack,
-               void *                                                  context ),
-       ( sdRef, flags, localAddr, remoteAddr, timeout, callBack, context ) );
-#endif
+#define kDNSServerDomain_Default               ( (const uint8_t *) "\x01" "d" "\x04" "test" )
+#define kDNSServerDomain_DNSSEC                        ( (const uint8_t *) "\x06" "dnssec" "\x04" "test" )
 
 //===========================================================================================================================
 //     Misc.
@@ -212,21 +247,20 @@ SOFT_LINK_FUNCTION_EX( system_dnssd, DNSServiceSleepKeepalive_sockaddr,
        #define kWhiteSpaceCharSet              "\t\n\v\f\r "
 #endif
 
-// Note: strcpy_literal() appears in CoreUtils code, but isn't currently defined in framework headers.
-
-#if( !defined( strcpy_literal ) )
-       #define strcpy_literal( DST, SRC )              memcpy( DST, SRC, sizeof( SRC ) )
-#endif
-
 #define _RandomStringExact( CHAR_SET, CHAR_SET_SIZE, CHAR_COUNT, OUT_STRING ) \
        RandomString( CHAR_SET, CHAR_SET_SIZE, CHAR_COUNT, CHAR_COUNT, OUT_STRING )
 
 #define kNoSuchRecordStr                       "No Such Record"
 #define kNoSuchRecordAStr                      "No Such Record (A)"
 #define kNoSuchRecordAAAAStr           "No Such Record (AAAA)"
+#define kNoSuchNameStr                         "No Such Name"
 
 #define kRootLabel             ( (const uint8_t *) "" )
 
+#if !defined( nw_forget )
+       #define nw_forget( X )          ForgetCustom( X, nw_release )
+#endif
+
 //===========================================================================================================================
 //     Gerneral Command Options
 //===========================================================================================================================
@@ -247,10 +281,13 @@ SOFT_LINK_FUNCTION_EX( system_dnssd, DNSServiceSleepKeepalive_sockaddr,
 #define MultiStringOption( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \
                MultiStringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, NULL )
 
+#define IntegerOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, LONG_HELP )        \
+       CLI_OPTION_INTEGER_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP,                                                                        \
+               (IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP,                                                                  \
+               (IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, LONG_HELP )
+
 #define IntegerOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED )     \
-       CLI_OPTION_INTEGER_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP,                                                \
-               (IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP,                                          \
-               (IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, NULL )
+       IntegerOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, NULL )
 
 #define DoubleOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED )      \
        CLI_OPTION_DOUBLE_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP,                                                 \
@@ -294,6 +331,7 @@ static int          gDNSSDFlag_Timeout                              = false;
 static int             gDNSSDFlag_UnicastResponse              = false;
 static int             gDNSSDFlag_Unique                               = false;
 static int             gDNSSDFlag_WakeOnResolve                = false;
+static int             gDNSSDFlag_EnableDNSSEC                 = false;
 
 #define DNSSDFlagsOption()                                                             \
        IntegerOption( 'f', "flags", &gDNSSDFlags, "flags",     \
@@ -318,6 +356,7 @@ static int          gDNSSDFlag_WakeOnResolve                = false;
 #define DNSSDFlagsOption_UnicastResponse()                     DNSSDFlagOption( 'U', UnicastResponse )
 #define DNSSDFlagsOption_Unique()                                      DNSSDFlagOption( 'U', Unique )
 #define DNSSDFlagsOption_WakeOnResolve()                       DNSSDFlagOption( 'W', WakeOnResolve )
+#define DNSSDFlagsOption_EnableDNSSEC()                                DNSSDFlagOption( 'D', EnableDNSSEC )
 
 // Interface option
 
@@ -393,6 +432,36 @@ static const char *                gConnectionOpt = kConnectionArg_Normal;
 
 #define RecordDataSection()            CLI_SECTION( kRecordDataSection_Name, kRecordDataSection_Text )
 
+// Fallback DNS service option
+
+#if( MDNSRESPONDER_PROJECT )
+static const char *            gFallbackDNSService = NULL;
+
+#define kFallbackDNSServiceArgPrefix_DoH               "doh:"
+#define kFallbackDNSServiceArgPrefix_DoT               "dot:"
+
+#define FallbackDNSServiceGroup()              CLI_OPTION_GROUP( "Default Fallback DNS Service" )
+#define FallbackDNSServiceOption()                                                                                                                                                                             \
+       StringOptionEx( 0, "fallback", &gFallbackDNSService, "DNS service", "Default fallback DNS service to set.", false,      \
+               "\n"                                                                                                                                                                                                                    \
+               "When this option is used, an nw_resolver_config is created for the specified DoH or DoT service.\n"                    \
+               "DNSServiceSetResolverDefaults() is then used to set the DNS service described by nw_resolver_config as the\n"  \
+               "default fallback DNS service for the dnssdutil process.\n"                                                                                                             \
+               "\n"                                                                                                                                                                                                                    \
+               "To specify a DNS over HTTPS (DoH) service, use\n"                                                                                                                              \
+               "\n"                                                                                                                                                                                                                    \
+               "    --fallback=doh:<URL>\n"                                                                                                                                                                    \
+               "\n"                                                                                                                                                                                                                    \
+               "Example: --fallback=doh:https://dns.example.com/dns-query\n"                                                                                                   \
+               "\n"                                                                                                                                                                                                                    \
+               "To specify a DNS over TLS (DoT) service, use\n"                                                                                                                                \
+               "\n"                                                                                                                                                                                                                    \
+               "    --fallback=dot:<hostname>\n"                                                                                                                                                               \
+               "\n"                                                                                                                                                                                                                    \
+               "Example: --fallback=dot:dns.example.com\n"                                                                                                                                             \
+       )
+#endif
+
 //===========================================================================================================================
 //     Output Formatting
 //===========================================================================================================================
@@ -473,6 +542,7 @@ static CLIOption            kGetAddrInfoOpts[] =
        DNSSDFlagsOption_DenyCellular(),
        DNSSDFlagsOption_DenyConstrained(),
        DNSSDFlagsOption_DenyExpensive(),
+       DNSSDFlagsOption_IncludeAWDL(),
        DNSSDFlagsOption_PathEvalDone(),
        DNSSDFlagsOption_ReturnIntermediates(),
        DNSSDFlagsOption_SuppressUnusable(),
@@ -483,6 +553,10 @@ static CLIOption           kGetAddrInfoOpts[] =
        BooleanOption( 'o', "oneshot",          &gGetAddrInfo_OneShot,                  "Finish after first set of results." ),
        IntegerOption( 'l', "timeLimit",        &gGetAddrInfo_TimeLimitSecs,    "seconds", "Maximum duration of the GetAddrInfo operation. Use '0' for no time limit.", false ),
        
+#if( MDNSRESPONDER_PROJECT )
+       FallbackDNSServiceGroup(),
+       FallbackDNSServiceOption(),
+#endif
        ConnectionSection(),
        CLI_OPTION_END()
 };
@@ -516,6 +590,7 @@ static CLIOption            kQueryRecordOpts[] =
        DNSSDFlagsOption_SuppressUnusable(),
        DNSSDFlagsOption_Timeout(),
        DNSSDFlagsOption_UnicastResponse(),
+       DNSSDFlagsOption_EnableDNSSEC(),
        
        CLI_OPTION_GROUP( "Operation" ),
        ConnectionOptions(),
@@ -523,6 +598,10 @@ static CLIOption           kQueryRecordOpts[] =
        IntegerOption( 'l', "timeLimit",        &gQueryRecord_TimeLimitSecs,    "seconds", "Maximum duration of the query record operation. Use '0' for no time limit.", false ),
        BooleanOption(  0 , "raw",                      &gQueryRecord_RawRData,                 "Show record data as a hexdump." ),
        
+#if( MDNSRESPONDER_PROJECT )
+       FallbackDNSServiceGroup(),
+       FallbackDNSServiceOption(),
+#endif 
        ConnectionSection(),
        CLI_OPTION_END()
 };
@@ -726,6 +805,10 @@ static CLIOption           kGetAddrInfoPOSIXOpts[] =
        BooleanOption(   0 , "flag-unusable",           &gGAIPOSIXFlag_Unusable,        "In hints ai_flags field, set AI_UNUSABLE." ),
 #endif
        
+#if( MDNSRESPONDER_PROJECT )
+       FallbackDNSServiceGroup(),
+       FallbackDNSServiceOption(),
+#endif
        CLI_SECTION( "Notes", "See getaddrinfo(3) man page for more details.\n" ),
        CLI_OPTION_END()
 };
@@ -894,14 +977,16 @@ static CLIOption          kGetAddrInfoStressOpts[] =
 //     DNSQuery Command Options
 //===========================================================================================================================
 
-static char *          gDNSQuery_Name                  = NULL;
-static char *          gDNSQuery_Type                  = "A";
-static char *          gDNSQuery_Server                = NULL;
-static int                     gDNSQuery_TimeLimitSecs = 5;
-static int                     gDNSQuery_UseTCP                = false;
-static int                     gDNSQuery_Flags                 = kDNSHeaderFlag_RecursionDesired;
-static int                     gDNSQuery_RawRData              = false;
-static int                     gDNSQuery_Verbose               = false;
+static char *          gDNSQuery_Name                          = NULL;
+static char *          gDNSQuery_Type                          = "A";
+static char *          gDNSQuery_Server                        = NULL;
+static int                     gDNSQuery_TimeLimitSecs         = 5;
+static int                     gDNSQuery_UseTCP                        = false;
+static int                     gDNSQuery_Flags                         = kDNSHeaderFlag_RecursionDesired;
+static int                     gDNSQuery_DNSSEC                        = false;
+static int                     gDNSQuery_CheckingDisabled      = false;
+static int                     gDNSQuery_RawRData                      = false;
+static int                     gDNSQuery_Verbose                       = false;
 
 #if( TARGET_OS_DARWIN )
        #define kDNSQueryServerOptionIsRequired         false
@@ -911,14 +996,16 @@ static int                        gDNSQuery_Verbose               = false;
 
 static CLIOption               kDNSQueryOpts[] =
 {
-       StringOption(  'n', "name",                     &gDNSQuery_Name,                        "name", "Question name (QNAME) to put in DNS query message.", true ),
-       StringOption(  't', "type",                     &gDNSQuery_Type,                        "type", "Question type (QTYPE) to put in DNS query message. Default value is 'A'.", false ),
-       StringOption(  's', "server",           &gDNSQuery_Server,                      "IP address", "DNS server's IPv4 or IPv6 address.", kDNSQueryServerOptionIsRequired ),
-       IntegerOption( 'l', "timeLimit",        &gDNSQuery_TimeLimitSecs,       "seconds", "Specifies query time limit. Use '-1' for no limit and '0' to exit immediately after sending.", false ),
-       BooleanOption(  0 , "tcp",                      &gDNSQuery_UseTCP,                      "Send the DNS query via TCP instead of UDP." ),
-       IntegerOption( 'f', "flags",            &gDNSQuery_Flags,                       "flags", "16-bit value for DNS header flags/codes field. Default value is 0x0100 (Recursion Desired).", false ),
-       BooleanOption(  0 , "raw",                      &gDNSQuery_RawRData,            "Present record data as a hexdump." ),
-       BooleanOption( 'v', "verbose",          &gDNSQuery_Verbose,                     "Prints the DNS message to be sent to the server." ),
+       StringOption(  'n', "name",             &gDNSQuery_Name,             "name", "Question name (QNAME) to put in DNS query message.", true ),
+       StringOption(  't', "type",             &gDNSQuery_Type,             "type", "Question type (QTYPE) to put in DNS query message. (default: A)", false ),
+       StringOption(  's', "server",           &gDNSQuery_Server,           "IP address", "DNS server's IPv4 or IPv6 address.", kDNSQueryServerOptionIsRequired ),
+       IntegerOption( 'l', "timeLimit",        &gDNSQuery_TimeLimitSecs,    "seconds", "Specifies query time limit. Use '-1' for no limit and '0' to exit immediately after sending.", false ),
+       BooleanOption(  0 , "tcp",              &gDNSQuery_UseTCP,           "Send the DNS query via TCP instead of UDP." ),
+       IntegerOption( 'f', "flags",            &gDNSQuery_Flags,            "flags", "16-bit value for DNS header flags/codes field. (default: 0x0100 [Recursion Desired])", false ),
+       BooleanOption(  0 , "dnssec",           &gDNSQuery_DNSSEC,           "Set the AD bit and include OPT record with DO extended flag bit set." ),
+       BooleanOption(  0 , "checkingDisabled", &gDNSQuery_CheckingDisabled, "Set the Checking Disabled (CD) bit." ),
+       BooleanOption(  0 , "raw",              &gDNSQuery_RawRData,         "Present record data as a hexdump." ),
+       BooleanOption( 'v', "verbose",          &gDNSQuery_Verbose,          "Prints the DNS message to be sent to the server." ),
        CLI_OPTION_END()
 };
 
@@ -1092,6 +1179,7 @@ static CLIOption          kMDNSColliderOpts[] =
 
 static void    MDNSColliderCmd( void );
 
+#if( TARGET_OS_DARWIN )
 //===========================================================================================================================
 //     PIDToUUID Command Options
 //===========================================================================================================================
@@ -1103,221 +1191,258 @@ static CLIOption              kPIDToUUIDOpts[] =
        IntegerOption( 'p', "pid", &gPIDToUUID_PID, "PID", "Process ID.", true ),
        CLI_OPTION_END()
 };
+#endif
 
 //===========================================================================================================================
 //     DNSServer Command Options
 //===========================================================================================================================
 
-#define kDNSServerInfoText_Intro                                                                                                                                                                               \
-       "The DNS server answers certain queries in the d.test. domain. Responses are dynamically generated based on the\n"      \
-       "presence of special labels in the query's QNAME. There are currently eight types of special labels that can be\n"      \
-       "used to generate specific responses: Alias labels, Alias-TTL labels, Count labels, Tag labels, TTL labels, the\n"      \
-       "IPv4 label, the IPv6 label, and SRV labels.\n"                                                                                                                                         \
-       "\n"                                                                                                                                                                                                                            \
-       "Note: Sub-strings representing integers in domain name labels are in decimal notation and without leading zeros.\n"
+static const char              kDNSServerInfoText_Intro[] =
+       "The DNS server answers certain queries in the d.test. domain. Responses are dynamically generated based on the\n"
+       "presence of special labels in the query's QNAME. There are currently nine types of special labels that can be\n"
+       "used to generate specific responses: Alias labels, Alias-TTL labels, Count labels, Tag labels, TTL labels, the\n"
+       "IPv4 label, the IPv6 label, Index labels, and SRV labels.\n"
+       "\n"
+       "Note: Sub-strings representing integers in domain name labels are in decimal notation and without leading zeros.\n";
 
-#define kDNSServerInfoText_NameExistence                                                                                                                                                               \
-       "A name is considered to exist if it's an Address name or an SRV name.\n"                                                                                       \
-       "\n"                                                                                                                                                                                                                            \
-       "An Address name is defined as a name that ends with d.test., and the other labels, if any, and in no particular\n"     \
-       "order, unless otherwise noted, consist of\n"                                                                                                                                           \
-       "\n"                                                                                                                                                                                                                            \
-       "    1. at most one Alias or Alias-TTL label as the first label;\n"                                                                                                     \
-       "    2. at most one Count label;\n"                                                                                                                                                                     \
-       "    3. zero or more Tag labels;\n"                                                                                                                                                                     \
-       "    4. at most one TTL label; and\n"                                                                                                                                                           \
-       "    5. at most one IPv4 or IPv6 label.\n"                                                                                                                                                      \
-       "\n"                                                                                                                                                                                                                            \
-       "An SRV name is defined as a name with the following form:\n"                                                                                                           \
-       "\n"                                                                                                                                                                                                                            \
-       " _<service>._<proto>[.<parent domain>][.<SRV label 1>[.<target 1>][.<SRV label 2>[.<target 2>][...]]].d.test.\n"       \
-       "\n"                                                                                                                                                                                                                            \
-       "See \"SRV Names\" for details.\n"
+static const char              kDNSServerInfoText_NameExistence[] =
+       "A name is considered to exist if it's an Address name or an SRV name.\n"
+       "\n"
+       "An Address name is defined as a name that ends with d.test., and the other labels, if any, and in no particular\n"
+       "order, unless otherwise noted, consist of\n"
+       "\n"
+       "    1. at most one Alias or Alias-TTL label as the first label;\n"
+       "    2. at most one Count label;\n"
+       "    3. zero or more Tag labels;\n"
+       "    4. at most one TTL label; and\n"
+       "    5. at most one IPv4 or IPv6 label.\n"
+       "    6. at most one Index label.\n"
+       "\n"
+       "An SRV name is defined as a name with the following form:\n"
+       "\n"
+       " _<service>._<proto>[.<parent domain>][.<SRV label 1>[.<target 1>][.<SRV label 2>[.<target 2>][...]]].d.test.\n"
+       "\n"
+       "See \"SRV Names\" for details.\n";
 
-#define kDNSServerInfoText_ResourceRecords                                                                                                                                                             \
-       "Currently, the server only supports CNAME, A, AAAA, and SRV records.\n"                                                                                        \
-       "\n"                                                                                                                                                                                                                            \
-       "Address names that begin with an Alias or Alias-TTL label are aliases of canonical names, i.e., they're the\n"         \
-       "names of CNAME records. See \"Alias Labels\" and \"Alias-TTL Labels\" for details.\n"                                                          \
-       "\n"                                                                                                                                                                                                                            \
-       "A canonical Address name can exclusively be the name of one or more A records, can exclusively be the name or\n"       \
-       "one or more AAAA records, or can be the name of both A and AAAA records. Address names that contain an IPv4\n"         \
-       "label have at least one A record, but no AAAA records. Address names that contain an IPv6 label, have at least\n"      \
-       "one AAAA record, but no A records. All other Address names have at least one A record and at least one AAAA\n"         \
-       "record. See \"Count Labels\" for how the number of address records for a given Address name is determined.\n"          \
-       "\n"                                                                                                                                                                                                                            \
-       "A records contain IPv4 addresses in the 203.0.113.0/24 block, while AAAA records contain IPv6 addresses in the\n"      \
-       "2001:db8:1::/120 block. Both of these address blocks are reserved for documentation. See\n"                                            \
-       "<https://tools.ietf.org/html/rfc5737> and <https://tools.ietf.org/html/rfc3849>.\n"                                                            \
-       "\n"                                                                                                                                                                                                                            \
-       "SRV names are names of SRV records.\n"                                                                                                                                                         \
-       "\n"                                                                                                                                                                                                                            \
-       "Unless otherwise specified, all resource records will use a default TTL. The default TTL can be set with the\n"        \
-       "--defaultTTL option. See \"Alias-TTL Labels\" and \"TTL Labels\" for details on how to query for CNAME, A, and\n"      \
-       "AAAA records with specific TTL values.\n"
+static const char              kDNSServerInfoText_ResourceRecords[] =
+       "Currently, the server only supports CNAME, A, AAAA, and SRV records.\n"
+       "\n"
+       "Address names that begin with an Alias or Alias-TTL label are aliases of canonical names, i.e., they're the\n"
+       "names of CNAME records. See \"Alias Labels\" and \"Alias-TTL Labels\" for details.\n"
+       "\n"
+       "A canonical Address name can exclusively be the name of one or more A records, can exclusively be the name or\n"
+       "one or more AAAA records, or can be the name of both A and AAAA records. Address names that contain an IPv4\n"
+       "label have at least one A record, but no AAAA records. Address names that contain an IPv6 label, have at least\n"
+       "one AAAA record, but no A records. All other Address names have at least one A record and at least one AAAA\n"
+       "record. See \"Count Labels\" for how the number of address records for a given Address name is determined.\n"
+       "\n"
+       "A records contain IPv4 addresses in the 203.0.113.0/24 block, while AAAA records contain IPv6 addresses in the\n"
+       "2001:db8:1::/120 block. Both of these address blocks are reserved for documentation. See\n"
+       "<https://tools.ietf.org/html/rfc5737> and <https://tools.ietf.org/html/rfc3849>.\n"
+       "\n"
+       "SRV names are names of SRV records.\n"
+       "\n"
+       "Unless otherwise specified, all resource records will use a default TTL. The default TTL can be set with the\n"
+       "--defaultTTL option. See \"Alias-TTL Labels\" and \"TTL Labels\" for details on how to query for CNAME, A, and\n"
+       "AAAA records with specific TTL values.\n";
 
-#define kDNSServerInfoText_AliasLabel                                                                                                                                                                  \
-       "Alias labels are of the form \"alias\" or \"alias-N\", where N is an integer in [2, 2^31 - 1].\n"                                      \
-       "\n"                                                                                                                                                                                                                            \
-       "If QNAME is an Address name and its first label is Alias label \"alias-N\", then the response will contain\n"          \
-       "exactly N CNAME records:\n"                                                                                                                                                                            \
-       "\n"                                                                                                                                                                                                                            \
-       "    1. For each i in [3, N], the response will contain a CNAME record whose name is identical to QNAME, except\n"      \
-       "       that the first label is \"alias-i\" instead, and whose RDATA is the name of the other CNAME record whose\n"     \
-       "       name has \"alias-(i - 1)\" as its first label.\n"                                                                                                                       \
-       "\n"                                                                                                                                                                                                                            \
-       "    2. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n"       \
-       "       is \"alias-2\" instead, and whose RDATA is the name identical to QNAME, except that the first label is\n"       \
-       "       \"alias\" instead.\n"                                                                                                                                                                           \
-       "\n"                                                                                                                                                                                                                            \
-       "    3. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n"       \
-       "       is \"alias\" instead, and whose RDATA is the name identical to QNAME minus its first label.\n"                          \
-       "\n"                                                                                                                                                                                                                            \
-       "If QNAME is an Address name and its first label is Alias label \"alias\", then the response will contain a\n"          \
-       "single CNAME record. The CNAME record's name will be equal to QNAME and its RDATA will be the name identical to\n"     \
-       "QNAME minus its first label.\n"                                                                                                                                                                        \
-       "\n"                                                                                                                                                                                                                            \
-       "Example. A response to a query with a QNAME of alias-3.count-5.d.test will contain the following CNAME\n"                      \
-       "records:\n"                                                                                                                                                                                                            \
-       "\n"                                                                                                                                                                                                                            \
-       "    alias-4.count-5.d.test.                        60    IN CNAME alias-3.count-5.d.test.\n"                                           \
-       "    alias-3.count-5.d.test.                        60    IN CNAME alias-2.count-5.d.test.\n"                                           \
-       "    alias-2.count-5.d.test.                        60    IN CNAME alias.count-5.d.test.\n"                                                     \
-       "    alias.count-5.d.test.                          60    IN CNAME count-5.d.test.\n"
-
-#define kDNSServerInfoText_AliasTTLLabel                                                                                                                                                               \
-       "Alias-TTL labels are of the form \"alias-ttl-T_1[-T_2[...-T_N]]\", where each T_i is an integer in\n"                          \
-       "[0, 2^31 - 1] and N is a positive integer bounded by the size of the maximum legal label length (63 octets).\n"        \
-       "\n"                                                                                                                                                                                                                            \
-       "If QNAME is an Address name and its first label is Alias-TTL label \"alias-ttl-T_1...-T_N\", then the response\n"      \
-       "will contain exactly N CNAME records:\n"                                                                                                                                                       \
-       "\n"                                                                                                                                                                                                                            \
-       "    1. For each i in [1, N - 1], the response will contain a CNAME record whose name is identical to QNAME,\n"         \
-       "       except that the first label is \"alias-ttl-T_i...-T_N\" instead, whose TTL value is T_i, and whose RDATA\n"     \
-       "       is the name of the other CNAME record whose name has \"alias-ttl-T_(i+1)...-T_N\" as its first label.\n"        \
-       "\n"                                                                                                                                                                                                                            \
-       "    2. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n"       \
-       "       is \"alias-ttl-T_N\", whose TTL is T_N, and whose RDATA is identical to QNAME stripped of its first\n"          \
-       "       label.\n"                                                                                                                                                                                                       \
-       "\n"                                                                                                                                                                                                                            \
-       "Example. A response to a query with a QNAME of alias-ttl-20-40-80.count-5.d.test will contain the following\n"         \
-       "CNAME records:\n"                                                                                                                                                                                                      \
-       "\n"                                                                                                                                                                                                                            \
-       "    alias-ttl-20-40-80.count-5.d.test.             20    IN CNAME alias-ttl-40-80.count-5.d.test.\n"                           \
-       "    alias-ttl-40-80.count-5.d.test.                40    IN CNAME alias-ttl-80.count-5.d.test.\n"                                      \
-       "    alias-ttl-80.count-5.d.test.                   80    IN CNAME count-5.d.test.\n"
+static const char              kDNSServerInfoText_AliasLabel[] =
+       "Alias labels are of the form \"alias\" or \"alias-N\", where N is an integer in [2, 2^31 - 1].\n"
+       "\n"
+       "If QNAME is an Address name and its first label is Alias label \"alias-N\", then the response will contain\n"
+       "exactly N CNAME records:\n"
+       "\n"
+       "    1. For each i in [3, N], the response will contain a CNAME record whose name is identical to QNAME, except\n"
+       "       that the first label is \"alias-i\" instead, and whose RDATA is the name of the other CNAME record whose\n"
+       "       name has \"alias-(i - 1)\" as its first label.\n"
+       "\n"
+       "    2. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n"
+       "       is \"alias-2\" instead, and whose RDATA is the name identical to QNAME, except that the first label is\n"
+       "       \"alias\" instead.\n"
+       "\n"
+       "    3. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n"
+       "       is \"alias\" instead, and whose RDATA is the name identical to QNAME minus its first label.\n"
+       "\n"
+       "If QNAME is an Address name and its first label is Alias label \"alias\", then the response will contain a\n"
+       "single CNAME record. The CNAME record's name will be equal to QNAME and its RDATA will be the name identical to\n"
+       "QNAME minus its first label.\n"
+       "\n"
+       "Example. A response to a query with a QNAME of alias-3.count-5.d.test will contain the following CNAME\n"
+       "records:\n"
+       "\n"
+       "    alias-4.count-5.d.test.                        60    IN CNAME alias-3.count-5.d.test.\n"
+       "    alias-3.count-5.d.test.                        60    IN CNAME alias-2.count-5.d.test.\n"
+       "    alias-2.count-5.d.test.                        60    IN CNAME alias.count-5.d.test.\n"
+       "    alias.count-5.d.test.                          60    IN CNAME count-5.d.test.\n";
+
+static const char              kDNSServerInfoText_AliasTTLLabel[] =
+       "Alias-TTL labels are of the form \"alias-ttl-T_1[-T_2[...-T_N]]\", where each T_i is an integer in\n"
+       "[0, 2^31 - 1] and N is a positive integer bounded by the size of the maximum legal label length (63 octets).\n"
+       "\n"
+       "If QNAME is an Address name and its first label is Alias-TTL label \"alias-ttl-T_1...-T_N\", then the response\n"
+       "will contain exactly N CNAME records:\n"
+       "\n"
+       "    1. For each i in [1, N - 1], the response will contain a CNAME record whose name is identical to QNAME,\n"
+       "       except that the first label is \"alias-ttl-T_i...-T_N\" instead, whose TTL value is T_i, and whose RDATA\n"
+       "       is the name of the other CNAME record whose name has \"alias-ttl-T_(i+1)...-T_N\" as its first label.\n"
+       "\n"
+       "    2. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n"
+       "       is \"alias-ttl-T_N\", whose TTL is T_N, and whose RDATA is identical to QNAME stripped of its first\n"
+       "       label.\n"
+       "\n"
+       "Example. A response to a query with a QNAME of alias-ttl-20-40-80.count-5.d.test will contain the following\n"
+       "CNAME records:\n"
+       "\n"
+       "    alias-ttl-20-40-80.count-5.d.test.             20    IN CNAME alias-ttl-40-80.count-5.d.test.\n"
+       "    alias-ttl-40-80.count-5.d.test.                40    IN CNAME alias-ttl-80.count-5.d.test.\n"
+       "    alias-ttl-80.count-5.d.test.                   80    IN CNAME count-5.d.test.\n";
 
-#define kDNSServerInfoText_CountLabel                                                                                                                                                                  \
-       "Count labels are of the form \"count-N_1\" or \"count-N_1-N_2\", where N_1 is an integer in [1, 255] and N_2 is\n"     \
-       "an integer in [N_1, 255].\n"                                                                                                                                                                           \
-       "\n"                                                                                                                                                                                                                            \
-       "If QNAME is an Address name, contains Count label \"count-N\", and has the type of address records specified by\n"     \
-       "QTYPE, then the response will contain exactly N address records:\n"                                                                                            \
-       "\n"                                                                                                                                                                                                                            \
-       "    1. For i in [1, N], the response will contain an address record of type QTYPE whose name is equal to QNAME\n"      \
-       "       and whose RDATA is an address equal to a constant base address + i.\n"                                                                          \
-       "\n"                                                                                                                                                                                                                            \
-       "    2. The address records will be ordered by the address contained in RDATA in ascending order.\n"                            \
-       "\n"                                                                                                                                                                                                                            \
-       "Example. A response to an A record query with a QNAME of alias.count-3.d.test will contain the following A\n"          \
-       "records:\n"                                                                                                                                                                                                            \
-       "\n"                                                                                                                                                                                                                            \
-       "    count-3.d.test.                                60    IN A     203.0.113.1\n"                                                                       \
-       "    count-3.d.test.                                60    IN A     203.0.113.2\n"                                                                       \
-       "    count-3.d.test.                                60    IN A     203.0.113.3\n"                                                                       \
-       "\n"                                                                                                                                                                                                                            \
-       "If QNAME is an Address name, contains Count label \"count-N_1-N_2\", and has the type of address records\n"            \
-       "specified by QTYPE, then the response will contain exactly N_1 address records:\n"                                                                     \
-       "\n"                                                                                                                                                                                                                            \
-       "    1. Each of the address records will be of type QTYPE, have name equal to QNAME, and have as its RDATA a\n"         \
-       "       unique address equal to a constant base address + i, where i is a randomly chosen integer in [1, N_2].\n"       \
-       "\n"                                                                                                                                                                                                                            \
-       "    2. The order of the address records will be random.\n"                                                                                                                     \
-       "\n"                                                                                                                                                                                                                            \
-       "Example. A response to a AAAA record query with a QNAME of count-3-100.ttl-20.d.test could contain the\n"                      \
-       "following AAAA records:\n"                                                                                                                                                                                     \
-       "\n"                                                                                                                                                                                                                            \
-       "    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8:1::c\n"                                                                     \
-       "    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8:1::3a\n"                                                            \
-       "    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8:1::4f\n"                                                            \
-       "\n"                                                                                                                                                                                                                            \
-       "If QNAME is an Address name, but doesn't have the type of address records specified by QTYPE, then the response\n"     \
-       "will contain no address records, regardless of whether it contains a Count label.\n"                                                           \
-       "\n"                                                                                                                                                                                                                            \
-       "Address names that don't have a Count label are treated as though they contain a count label equal to\n"                       \
-       "count-1\".\n"
+static const char              kDNSServerInfoText_CountLabel[] =
+       "Count labels are of the form \"count-N_1\" or \"count-N_1-N_2\", where N_1 is an integer in [0, 255] and N_2 is\n"
+       "an integer in [N_1, 255].\n"
+       "\n"
+       "If QNAME is an Address name, contains Count label \"count-N\", and has the type of address records specified by\n"
+       "QTYPE, then the response will contain exactly N address records:\n"
+       "\n"
+       "    1. For i in [1, N], the response will contain an address record of type QTYPE whose name is equal to QNAME\n"
+       "       and whose RDATA is an address equal to a constant base address + i.\n"
+       "\n"
+       "    2. The address records will be ordered by the address contained in RDATA in ascending order.\n"
+       "\n"
+       "Example. A response to an A record query with a QNAME of alias.count-3.d.test will contain the following A\n"
+       "records:\n"
+       "\n"
+       "    count-3.d.test.                                60    IN A     203.0.113.1\n"
+       "    count-3.d.test.                                60    IN A     203.0.113.2\n"
+       "    count-3.d.test.                                60    IN A     203.0.113.3\n"
+       "\n"
+       "If QNAME is an Address name, contains Count label \"count-N_1-N_2\", and has the type of address records\n"
+       "specified by QTYPE, then the response will contain exactly N_1 address records:\n"
+       "\n"
+       "    1. Each of the address records will be of type QTYPE, have name equal to QNAME, and have as its RDATA a\n"
+       "       unique address equal to a constant base address + i, where i is a randomly chosen integer in [1, N_2].\n"
+       "\n"
+       "    2. The order of the address records will be random.\n"
+       "\n"
+       "Example. A response to a AAAA record query with a QNAME of count-3-100.ttl-20.d.test could contain the\n"
+       "following AAAA records:\n"
+       "\n"
+       "    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8:1::c\n"
+       "    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8:1::3a\n"
+       "    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8:1::4f\n"
+       "\n"
+       "If QNAME is an Address name, but doesn't have the type of address records specified by QTYPE, then the response\n"
+       "will contain no address records, regardless of whether it contains a Count label.\n"
+       "\n"
+       "Address names that don't have a Count label are treated as though they contain a count label equal to\n"
+       "count-1\".\n";
 
-#define kDNSServerInfoText_TagLabel                                                                                                                                                                            \
-       "Tag labels are labels prefixed with \"tag-\" and contain zero or more arbitrary octets after the prefix.\n"            \
-       "\n"                                                                                                                                                                                                                            \
-       "This type of label exists to allow testers to \"uniquify\" domain names. Tag labels can also serve as padding\n"       \
-       "to increase the sizes of domain names.\n"
+static const char              kDNSServerInfoText_TagLabel[] =
+       "Tag labels are labels prefixed with \"tag-\" and contain zero or more arbitrary octets after the prefix.\n"
+       "\n"
+       "This type of label exists to allow testers to \"uniquify\" domain names. Tag labels can also serve as padding\n"
+       "to increase the sizes of domain names.\n";
 
-#define kDNSServerInfoText_TTLLabel                                                                                                                                                                            \
-       "TTL labels are of the form \"ttl-T\", where T is an integer in [0, 2^31 - 1].\n"                                                                       \
-       "\n"                                                                                                                                                                                                                            \
-       "If QNAME is an Address name and contains TTL label \"ttl-T\", then all non-CNAME records contained in the\n"           \
-       "response will have a TTL value equal to T.\n"
+static const char              kDNSServerInfoText_TTLLabel[] =
+       "TTL labels are of the form \"ttl-T\", where T is an integer in [0, 2^31 - 1].\n"
+       "\n"
+       "If QNAME is an Address name and contains TTL label \"ttl-T\", then all non-CNAME records contained in the\n"
+       "response will have a TTL value equal to T.\n";
 
-#define kDNSServerInfoText_IPv4Label \
-       "The IPv4 label is \"ipv4\". See \"Resource Records\" for the affect of this label.\n"
+static const char              kDNSServerInfoText_IPv4Label[] =
+       "The IPv4 label is \"ipv4\". See \"Resource Records\" for the affect of this label.\n";
 
-#define kDNSServerInfoText_IPv6Label \
-       "The IPv6 label is \"ipv6\". See \"Resource Records\" for the affect of this label.\n"
+static const char              kDNSServerInfoText_IPv6Label[] =
+       "The IPv6 label is \"ipv6\". See \"Resource Records\" for the affect of this label.\n";
 
-#define kDNSServerInfoText_SRVNames                                                                                                                                                                            \
-       "SRV labels are of the form \"srv-R-W-P\", where R, W, and P are integers in [0, 2^16 - 1].\n"                                          \
-       "\n"                                                                                                                                                                                                                            \
-       "After the first two labels, i.e., the service and protocol labels, the sequence of labels, which may be empty,\n"      \
-       "leading up to the the first SRV label, if one exists, or the d.test. labels will be used as a parent domain for\n"     \
-       "the target hostname of each of the SRV name's SRV records.\n"                                                                                                          \
-       "\n"                                                                                                                                                                                                                            \
-       "If QNAME is an SRV name and QTYPE is SRV, then for each SRV label, the response will contain an SRV record with\n"     \
-       "priority R, weight W, port P, and target hostname <target>[.<parent domain>]., where <target> is the sequence\n"       \
-       "of labels, which may be empty, that follows the SRV label leading up to either the next SRV label or the\n"            \
-       "d.test. labels, whichever comes first.\n"                                                                                                                                                      \
-       "\n"                                                                                                                                                                                                                            \
-       "Example. A response to an SRV record query with a QNAME of\n"                                                                                                          \
-       "_http._tcp.example.com.srv-0-0-80.www.srv-1-0-8080.www.d.test. will contain the following SRV records:\n"                      \
-       "\n"                                                                                                                                                                                                                            \
-       "_http._tcp.example.com.srv-0-0-80.www.srv-1-0-8080.www.d.test.     60    IN SRV   0 0 80 www.example.com.\n"           \
-       "_http._tcp.example.com.srv-0-0-80.www.srv-1-0-8080.www.d.test.     60    IN SRV   1 0 8080 www.example.com.\n"
+static const char              kDNSServerInfoText_IndexLabel[] =
+       "Index labels are of the form \"index-N\", where N is an integer in [1, 2^31 - 1].\n"
+       "\n"
+       "When the server runs in loopback-only mode, each of the server's addresses is assigned a sequential index value\n"
+       "starting from 1. For example, if the server is running in loopback-only mode and listening exclusively on IPv6\n"
+       "with two extra IPv6 addresses, then address ::1 would be assigned index 1, the first extra IPv6 address would be\n"
+       "assigned index 2, and the second extra IPv6 address would be assigned index 3.\n"
+       "\n"
+       "If QNAME is an Address name and has an index label, then the query will be ignored unless the query was received\n"
+       "on an address whose index value equals that of the index label. This is useful for simulating unresponsive servers.\n";
 
-#define kDNSServerInfoText_BadUDPMode \
-       "The purpose of Bad UDP mode is to test mDNSResponder's TCP fallback mechanism by which mDNSResponder reissues a\n"     \
-       "UDP query as a TCP query if the UDP response contains the expected QNAME, QTYPE, and QCLASS, but a message ID\n"       \
-       "that's not equal to the query's message ID.\n"                                                                                                                                         \
-       "\n"                                                                                                                                                                                                                            \
-       "This mode is identical to the normal mode except that all responses sent via UDP have a message ID equal to the\n"     \
-       "query's message ID plus one. Also, in this mode, to aid in debugging, A records in responses sent via UDP have\n"      \
-       "IPv4 addresses in the 0.0.0.0/24 block instead of the 203.0.113.0/24 block, i.e., 0.0.0.0 is used as the IPv4\n"       \
-       "base address, and AAAA records in responses sent via UDP have IPv6 addresses in the ::ffff:0:0/120 block\n"            \
-       "instead of the 2001:db8:1::/120 block, i.e., ::ffff:0:0 is used as the IPv6 base address.\n"
-
-static int                             gDNSServer_LoopbackOnly         = false;
-static int                             gDNSServer_Foreground           = false;
-static int                             gDNSServer_ResponseDelayMs      = 0;
-static int                             gDNSServer_DefaultTTL           = 60;
-static int                             gDNSServer_Port                         = kDNSPort;
-static const char *            gDNSServer_DomainOverride       = NULL;
+static const char              kDNSServerInfoText_SRVNames[] =
+       "SRV labels are of the form \"srv-R-W-P\", where R, W, and P are integers in [0, 2^16 - 1].\n"
+       "\n"
+       "After the first two labels, i.e., the service and protocol labels, the sequence of labels, which may be empty,\n"
+       "leading up to the the first SRV label, if one exists, or the d.test. labels will be used as a parent domain for\n"
+       "the target hostname of each of the SRV name's SRV records.\n"
+       "\n"
+       "If QNAME is an SRV name and QTYPE is SRV, then for each SRV label, the response will contain an SRV record with\n"
+       "priority R, weight W, port P, and target hostname <target>[.<parent domain>]., where <target> is the sequence\n"
+       "of labels, which may be empty, that follows the SRV label leading up to either the next SRV label or the\n"
+       "d.test. labels, whichever comes first.\n"
+       "\n"
+       "Example. A response to an SRV record query with a QNAME of\n"
+       "_http._tcp.example.com.srv-0-0-80.www.srv-1-0-8080.www.d.test. will contain the following SRV records:\n"
+       "\n"
+       "_http._tcp.example.com.srv-0-0-80.www.srv-1-0-8080.www.d.test.     60    IN SRV   0 0 80 www.example.com.\n"
+       "_http._tcp.example.com.srv-0-0-80.www.srv-1-0-8080.www.d.test.     60    IN SRV   1 0 8080 www.example.com.\n";
+
+static const char              kDNSServerInfoText_BadUDPMode[] =
+       "The purpose of Bad UDP mode is to test mDNSResponder's TCP fallback mechanism by which mDNSResponder reissues a\n"
+       "UDP query as a TCP query if the UDP response contains the expected QNAME, QTYPE, and QCLASS, but a message ID\n"
+       "that's not equal to the query's message ID.\n"
+       "\n"
+       "This mode is identical to the normal mode except that all responses sent via UDP have a message ID equal to the\n"
+       "query's message ID plus one. Also, in this mode, to aid in debugging, A records in responses sent via UDP have\n"
+       "IPv4 addresses in the 0.0.0.0/24 block instead of the 203.0.113.0/24 block, i.e., 0.0.0.0 is used as the IPv4\n"
+       "base address, and AAAA records in responses sent via UDP have IPv6 addresses in the ::ffff:0:0/120 block\n"
+       "instead of the 2001:db8:1::/120 block, i.e., ::ffff:0:0 is used as the IPv6 base address.\n";
+
+static int                             gDNSServer_LoopbackOnly                 = false;
+static int                             gDNSServer_Foreground                   = false;
+static int                             gDNSServer_ResponseDelayMs              = 0;
+static int                             gDNSServer_DefaultTTL                   = 60;
+static int                             gDNSServer_Port                                 = kDNSPort;
+static const char *            gDNSServer_DomainOverride               = NULL;
+static char **                 gDNSServer_IgnoredQTypes                = NULL;
+static size_t                  gDNSServer_IgnoredQTypesCount   = 0;
+static int                             gDNSServer_ListenOnV4                   = false;
+static int                             gDNSServer_ListenOnV6                   = false;
+static int                             gDNSServer_BadUDPMode                   = false;
 #if( TARGET_OS_DARWIN )
-static const char *            gDNSServer_FollowPID            = NULL;
+static const char *            gDNSServer_FollowPID                    = NULL;
+static int                             gDNSServer_ExtraV6Count                 = 0;
 #endif
-static int                             gDNSServer_BadUDPMode           = false;
 
 static CLIOption               kDNSServerOpts[] =
 {
-       BooleanOption( 'l', "loopback",      &gDNSServer_LoopbackOnly,    "Bind to to the loopback interface." ),
-       BooleanOption( 'f', "foreground",    &gDNSServer_Foreground,      "Direct log output to stdout instead of system logging." ),
-       IntegerOption( 'd', "responseDelay", &gDNSServer_ResponseDelayMs, "ms", "The amount of additional delay in milliseconds to apply to responses. (default: 0)", false ),
-       IntegerOption(  0 , "defaultTTL",    &gDNSServer_DefaultTTL,      "seconds", "Resource record TTL value to use when unspecified. (default: 60)", false ),
-       IntegerOption( 'p', "port",          &gDNSServer_Port,            "port number", "UDP/TCP port number to use. Use 0 for any port. (default: 53)", false ),
-       StringOption(   0 , "domain",        &gDNSServer_DomainOverride,  "domain", "Used to override 'd.test.' as the server's domain.", false ),
+       BooleanOption(     'l', "loopback",      &gDNSServer_LoopbackOnly,    "Bind only to the loopback interface." ),
+       BooleanOption(     'f', "foreground",    &gDNSServer_Foreground,      "Direct log output to stdout instead of system logging." ),
+       IntegerOption(     'd', "responseDelay", &gDNSServer_ResponseDelayMs, "ms", "The amount of additional delay in milliseconds to apply to responses. (default: 0)", false ),
+       IntegerOption(      0 , "defaultTTL",    &gDNSServer_DefaultTTL,      "seconds", "Resource record TTL value to use when unspecified. (default: 60)", false ),
+       IntegerOption(     'p', "port",          &gDNSServer_Port,            "port number", "UDP/TCP port number to use. Use 0 for any port. (default: 53)", false ),
+       StringOption(       0 , "domain",        &gDNSServer_DomainOverride,  "domain", "Use to override 'd.test.' as the server's domain.", false ),
+       MultiStringOption( 'i', "ignoreQType",   &gDNSServer_IgnoredQTypes, &gDNSServer_IgnoredQTypesCount, "qtype", "A QTYPE to ignore. This option can be specified more than once.", false ),
+       BooleanOption(      0 , "ipv4",          &gDNSServer_ListenOnV4,      "Listen on IPv4. Will listen on both IPv4 and IPv6 if neither --ipv4 nor --ipv6 is used." ),
+       BooleanOption(      0 , "ipv6",          &gDNSServer_ListenOnV6,      "Listen on IPv6. Will listen on both IPv4 and IPv6 if neither --ipv4 nor --ipv6 is used." ),
 #if( TARGET_OS_DARWIN )
-       StringOption(   0 , "follow",        &gDNSServer_FollowPID,       "pid", "Exit when the process, usually the parent process, specified by PID exits.", false ),
+       StringOption(       0 , "follow",        &gDNSServer_FollowPID,       "pid", "Exit when the process, usually the parent process, specified by PID exits.", false ),
 #endif
-       BooleanOption(  0 , "badUDPMode",    &gDNSServer_BadUDPMode,      "Run in Bad UDP mode to trigger mDNSResponder's TCP fallback mechanism." ),
+       BooleanOption(      0 , "badUDPMode",    &gDNSServer_BadUDPMode,      "Run in Bad UDP mode to trigger mDNSResponder's TCP fallback mechanism." ),
        
+#if( TARGET_OS_DARWIN )
+       CLI_OPTION_GROUP( "Loopback-Only Mode Options" ),
+       IntegerOptionEx( 0 , "extraIPv6",     &gDNSServer_ExtraV6Count,    "count", "The number of extra IPv6 addresses to listen on. (default: 0)", false,
+               "\n"
+               "This option will add extra IPv6 addresses from the fded:f035:55e4::/64 address block to the loopback interface.\n"
+               "The server will then bind to those addresses in addition to the standard loopback IP addresses, i.e., 127.0.0.1.\n"
+               "and/or ::1, depending on the specified IP protocol options.\n"
+               "\n"
+               "This option is useful for setting up a DNS configuration with multiple server addresses, e.g., one for the\n"
+               "primary server, one for the secondary server, etc. The Index label can then be used to simulate unresponsive\n"
+               "servers.\n"
+               "\n"
+               "Note: This option is ignored unless the server is in loopback only mode and listening on IPv6.\n"
+               "Note: This option currently requires root privileges.\n"
+       ),
+#endif
        CLI_SECTION( "Intro",                           kDNSServerInfoText_Intro ),
        CLI_SECTION( "Name Existence",          kDNSServerInfoText_NameExistence ),
        CLI_SECTION( "Resource Records",        kDNSServerInfoText_ResourceRecords ),
@@ -1328,6 +1453,7 @@ static CLIOption          kDNSServerOpts[] =
        CLI_SECTION( "TTL Labels",                      kDNSServerInfoText_TTLLabel ),
        CLI_SECTION( "IPv4 Label",                      kDNSServerInfoText_IPv4Label ),
        CLI_SECTION( "IPv6 Label",                      kDNSServerInfoText_IPv6Label ),
+       CLI_SECTION( "Index Labels",            kDNSServerInfoText_IndexLabel ),
        CLI_SECTION( "SRV Names",                       kDNSServerInfoText_SRVNames ),
        CLI_SECTION( "Bad UDP Mode",            kDNSServerInfoText_BadUDPMode ),
        CLI_OPTION_END()
@@ -1341,170 +1467,163 @@ static void   DNSServerCmd( void );
 
 #define kMDNSReplierPortBase           50000
 
-#define kMDNSReplierInfoText_Intro                                                                                                                                                                             \
-       "The mDNS replier answers mDNS queries for its authoritative records. These records are of class IN and of types\n"     \
-       "PTR, SRV, TXT, A, and AAAA as described below.\n"                                                                                                                                      \
-       "\n"                                                                                                                                                                                                                            \
-       "Note: Sub-strings representing integers in domain name labels are in decimal notation and without leading zeros.\n"
+static const char              kMDNSReplierInfoText_Intro[] =
+       "The mDNS replier answers mDNS queries for its authoritative records. These records are of class IN and of types\n"
+       "PTR, SRV, TXT, A, and AAAA as described below.\n"
+       "\n"
+       "Note: Sub-strings representing integers in domain name labels are in decimal notation and without leading zeros.\n";
 
-#define kMDNSReplierInfoText_Parameters                                                                                                                                                                        \
-       "There are five parameters that control the replier's set of authoritative records.\n"                                                          \
-       "\n"                                                                                                                                                                                                                            \
-       "    1. <hostname> is the base name used for service instance names and the names of A and AAAA records. This\n"        \
-       "       parameter is specified with the --hostname option.\n"                                                                                                           \
-       "    2. <tag> is an arbitrary string used to uniquify service types. This parameter is specified with the --tag\n"      \
-       "       option.\n"                                                                                                                                                                                                      \
-       "    3. N_max in an integer in [1, 65535] and limits service types to those that have no more than N_max\n"                     \
-       "       instances. It also limits the number of hostnames to N_max, i.e., <hostname>.local.,\n"                                         \
-       "       <hostname>-1.local., ..., <hostname>-N_max.local. This parameter is specified with the\n"                                       \
-       "       --maxInstanceCount option.\n"                                                                                                                                                           \
-       "    4. N_a is an integer in [1, 255] and the number of A records per hostname. This parameter is specified\n"          \
-       "       with the --countA option.\n"                                                                                                                                                            \
-       "    5. N_aaaa is an integer in [1, 255] and the number of AAAA records per hostname. This parameter is\n"                      \
-       "       specified with the --countAAAA option.\n"
-
-#define kMDNSReplierInfoText_PTR                                                                                                                                                                               \
-       "The replier's authoritative PTR records have names of the form _t-<tag>-<L>-<N>._tcp.local., where L is an\n"          \
-       "integer in [1, 65535], and N is an integer in [1, N_max].\n"                                                                                                           \
-       "\n"                                                                                                                                                                                                                            \
-       "For a given L and N, the replier has exactly N authoritative PTR records:\n"                                                                           \
-       "\n"                                                                                                                                                                                                                            \
-       "    1. The first PTR record is defined as\n"                                                                                                                                           \
-       "\n"                                                                                                                                                                                                                            \
-       "        NAME:  _t-<tag>-<L>-<N>._tcp.local.\n"                                                                                                                                         \
-       "        TYPE:  PTR\n"                                                                                                                                                                                          \
-       "        CLASS: IN\n"                                                                                                                                                                                           \
-       "        TTL:   4500\n"                                                                                                                                                                                         \
-       "        RDATA: <hostname>._t-<tag>-<L>-<N>._tcp.local.\n"                                                                                                                      \
-       "\n"                                                                                                                                                                                                                            \
-       "    2. For each i in [2, N], there is one PTR record defined as\n"                                                                                                     \
-       "\n"                                                                                                                                                                                                                            \
-       "        NAME:  _t-<tag>-<L>-<N>._tcp.local.\n"                                                                                                                                         \
-       "        TYPE:  PTR\n"                                                                                                                                                                                          \
-       "        CLASS: IN\n"                                                                                                                                                                                           \
-       "        TTL:   4500\n"                                                                                                                                                                                         \
-       "        RDATA: \"<hostname> (<i>)._t-<tag>-<L>-<N>._tcp.local.\"\n"
-
-#define kMDNSReplierInfoText_SRV                                                                                                                                                                               \
-       "The replier's authoritative SRV records have names of the form <instance name>._t-<tag>-<L>-<N>._tcp.local.,\n"        \
-       "where L is an integer in [1, 65535], N is an integer in [1, N_max], and <instance name> is <hostname> or\n"            \
-       "\"<hostname> (<i>)\", where i is in [2, N].\n"                                                                                                                                         \
-       "\n"                                                                                                                                                                                                                            \
-       "For a given L and N, the replier has exactly N authoritative SRV records:\n"                                                                           \
-       "\n"                                                                                                                                                                                                                            \
-       "    1. The first SRV record is defined as\n"                                                                                                                                           \
-       "\n"                                                                                                                                                                                                                            \
-       "        NAME:  <hostname>._t-<tag>-<L>-<N>._tcp.local.\n"                                                                                                                      \
-       "        TYPE:  SRV\n"                                                                                                                                                                                          \
-       "        CLASS: IN\n"                                                                                                                                                                                           \
-       "        TTL:   120\n"                                                                                                                                                                                          \
-       "        RDATA:\n"                                                                                                                                                                                                      \
-       "            Priority: 0\n"                                                                                                                                                                                     \
-       "            Weight:   0\n"                                                                                                                                                                                     \
-       "            Port:     (50000 + L) mod 2^16\n"                                                                                                                                          \
-       "            Target:   <hostname>.local.\n"                                                                                                                                                     \
-       "\n"                                                                                                                                                                                                                            \
-       "    2. For each i in [2, N], there is one SRV record defined as:\n"                                                                                            \
-       "\n"                                                                                                                                                                                                                            \
-       "        NAME:  \"<hostname> (<i>)._t-<tag>-<L>-<N>._tcp.local.\"\n"                                                                                            \
-       "        TYPE:  SRV\n"                                                                                                                                                                                          \
-       "        CLASS: IN\n"                                                                                                                                                                                           \
-       "        TTL:   120\n"                                                                                                                                                                                          \
-       "        RDATA:\n"                                                                                                                                                                                                      \
-       "            Priority: 0\n"                                                                                                                                                                                     \
-       "            Weight:   0\n"                                                                                                                                                                                     \
-       "            Port:     (50000 + L) mod 2^16\n"                                                                                                                                          \
-       "            Target:   <hostname>-<i>.local.\n"
-
-#define kMDNSReplierInfoText_TXT                                                                                                                                                                               \
-       "The replier's authoritative TXT records have names of the form <instance name>._t-<tag>-<L>-<N>._tcp.local.,\n"        \
-       "where L is an integer in [1, 65535], N is an integer in [1, N_max], and <instance name> is <hostname> or\n"            \
-       "\"<hostname> (<i>)\", where i is in [2, N].\n"                                                                                                                                         \
-       "\n"                                                                                                                                                                                                                            \
-       "For a given L and N, the replier has exactly N authoritative TXT records:\n"                                                                           \
-       "\n"                                                                                                                                                                                                                            \
-       "    1. The first TXT record is defined as\n"                                                                                                                                           \
-       "\n"                                                                                                                                                                                                                            \
-       "        NAME:     <hostname>._t-<tag>-<L>-<N>._tcp.local.\n"                                                                                                           \
-       "        TYPE:     TXT\n"                                                                                                                                                                                       \
-       "        CLASS:    IN\n"                                                                                                                                                                                        \
-       "        TTL:      4500\n"                                                                                                                                                                                      \
-       "        RDLENGTH: L\n"                                                                                                                                                                                         \
-       "        RDATA:    <one or more strings with an aggregate length of L octets>\n"                                                                        \
-       "\n"                                                                                                                                                                                                                            \
-       "    2. For each i in [2, N], there is one TXT record:\n"                                                                                                                       \
-       "\n"                                                                                                                                                                                                                            \
-       "        NAME:     \"<hostname> (<i>)._t-<tag>-<L>-<N>._tcp.local.\"\n"                                                                                         \
-       "        TYPE:     TXT\n"                                                                                                                                                                                       \
-       "        CLASS:    IN\n"                                                                                                                                                                                        \
-       "        TTL:      4500\n"                                                                                                                                                                                      \
-       "        RDLENGTH: L\n"                                                                                                                                                                                         \
-       "        RDATA:    <one or more strings with an aggregate length of L octets>\n"                                                                        \
-       "\n"                                                                                                                                                                                                                            \
-       "The RDATA of each TXT record is exactly L octets and consists of a repeating series of the 15-byte string\n"           \
-       "\"hash=0x<32-bit FNV-1 hash of the record name as an 8-character hexadecimal string>\". The last instance of\n"        \
-       "the string may be truncated to satisfy the TXT record data's size requirement.\n"
+static const char              kMDNSReplierInfoText_Parameters[] =
+       "There are five parameters that control the replier's set of authoritative records.\n"
+       "\n"
+       "    1. <hostname> is the base name used for service instance names and the names of A and AAAA records. This\n"
+       "       parameter is specified with the --hostname option.\n"
+       "    2. <tag> is an arbitrary string used to uniquify service types. This parameter is specified with the --tag\n"
+       "       option.\n"
+       "    3. N_max in an integer in [1, 65535] and limits service types to those that have no more than N_max\n"
+       "       instances. It also limits the number of hostnames to N_max, i.e., <hostname>.local.,\n"
+       "       <hostname>-1.local., ..., <hostname>-N_max.local. This parameter is specified with the\n"
+       "       --maxInstanceCount option.\n"
+       "    4. N_a is an integer in [1, 255] and the number of A records per hostname. This parameter is specified\n"
+       "       with the --countA option.\n"
+       "    5. N_aaaa is an integer in [1, 255] and the number of AAAA records per hostname. This parameter is\n"
+       "       specified with the --countAAAA option.\n";
+
+static const char              kMDNSReplierInfoText_PTR[] =
+       "The replier's authoritative PTR records have names of the form _t-<tag>-<L>-<N>._tcp.local., where L is an\n"
+       "integer in [1, 65535], and N is an integer in [1, N_max].\n"
+       "\n"
+       "For a given L and N, the replier has exactly N authoritative PTR records:\n"
+       "\n"
+       "    1. The first PTR record is defined as\n"
+       "\n"
+       "        NAME:  _t-<tag>-<L>-<N>._tcp.local.\n"
+       "        TYPE:  PTR\n"
+       "        CLASS: IN\n"
+       "        TTL:   4500\n"
+       "        RDATA: <hostname>._t-<tag>-<L>-<N>._tcp.local.\n"
+       "\n"
+       "    2. For each i in [2, N], there is one PTR record defined as\n"
+       "\n"
+       "        NAME:  _t-<tag>-<L>-<N>._tcp.local.\n"
+       "        TYPE:  PTR\n"
+       "        CLASS: IN\n"
+       "        TTL:   4500\n"
+       "        RDATA: \"<hostname> (<i>)._t-<tag>-<L>-<N>._tcp.local.\"\n";
+
+static const char              kMDNSReplierInfoText_SRV[] =
+       "The replier's authoritative SRV records have names of the form <instance name>._t-<tag>-<L>-<N>._tcp.local.,\n"
+       "where L is an integer in [1, 65535], N is an integer in [1, N_max], and <instance name> is <hostname> or\n"
+       "\"<hostname> (<i>)\", where i is in [2, N].\n"
+       "\n"
+       "For a given L and N, the replier has exactly N authoritative SRV records:\n"
+       "\n"
+       "    1. The first SRV record is defined as\n"
+       "\n"
+       "        NAME:  <hostname>._t-<tag>-<L>-<N>._tcp.local.\n"
+       "        TYPE:  SRV\n"
+       "        CLASS: IN\n"
+       "        TTL:   120\n"
+       "        RDATA:\n"
+       "            Priority: 0\n"
+       "            Weight:   0\n"
+       "            Port:     (50000 + L) mod 2^16\n"
+       "            Target:   <hostname>.local.\n"
+       "\n"
+       "    2. For each i in [2, N], there is one SRV record defined as:\n"
+       "\n"
+       "        NAME:  \"<hostname> (<i>)._t-<tag>-<L>-<N>._tcp.local.\"\n"
+       "        TYPE:  SRV\n"
+       "        CLASS: IN\n"
+       "        TTL:   120\n"
+       "        RDATA:\n"
+       "            Priority: 0\n"
+       "            Weight:   0\n"
+       "            Port:     (50000 + L) mod 2^16\n"
+       "            Target:   <hostname>-<i>.local.\n";
+
+static const char              kMDNSReplierInfoText_TXT[] =
+       "The replier's authoritative TXT records have names of the form <instance name>._t-<tag>-<L>-<N>._tcp.local.,\n"
+       "where L is an integer in [1, 65535], N is an integer in [1, N_max], and <instance name> is <hostname> or\n"
+       "\"<hostname> (<i>)\", where i is in [2, N].\n"
+       "\n"
+       "For a given L and N, the replier has exactly N authoritative TXT records:\n"
+       "\n"
+       "    1. The first TXT record is defined as\n"
+       "\n"
+       "        NAME:     <hostname>._t-<tag>-<L>-<N>._tcp.local.\n"
+       "        TYPE:     TXT\n"
+       "        CLASS:    IN\n"
+       "        TTL:      4500\n"
+       "        RDLENGTH: L\n"
+       "        RDATA:    <one or more strings with an aggregate length of L octets>\n"
+       "\n"
+       "    2. For each i in [2, N], there is one TXT record:\n"
+       "\n"
+       "        NAME:     \"<hostname> (<i>)._t-<tag>-<L>-<N>._tcp.local.\"\n"
+       "        TYPE:     TXT\n"
+       "        CLASS:    IN\n"
+       "        TTL:      4500\n"
+       "        RDLENGTH: L\n"
+       "        RDATA:    <one or more strings with an aggregate length of L octets>\n"
+       "\n"
+       "The RDATA of each TXT record is exactly L octets and consists of a repeating series of the 15-byte string\n"
+       "\"hash=0x<32-bit FNV-1 hash of the record name as an 8-character hexadecimal string>\". The last instance of\n"
+       "the string may be truncated to satisfy the TXT record data's size requirement.\n";
 
-#define kMDNSReplierInfoText_A                                                                                                                                                                                 \
-       "The replier has exactly N_max x N_a authoritative A records:\n"                                                                                                        \
-       "\n"                                                                                                                                                                                                                            \
-       "    1. For each j in [1, N_a], an A record is defined as\n"                                                                                                            \
-       "\n"                                                                                                                                                                                                                            \
-       "        NAME:     <hostname>.local.\n"                                                                                                                                                         \
-       "        TYPE:     A\n"                                                                                                                                                                                         \
-       "        CLASS:    IN\n"                                                                                                                                                                                        \
-       "        TTL:      120\n"                                                                                                                                                                                       \
-       "        RDLENGTH: 4\n"                                                                                                                                                                                         \
-       "        RDATA:    0.0.1.<j>\n"                                                                                                                                                                         \
-       "\n"                                                                                                                                                                                                                            \
-       "    2. For each i in [2, N_max], for each j in [1, N_a], an A record is defined as\n"                                                          \
-       "\n"                                                                                                                                                                                                                            \
-       "        NAME:     <hostname>-<i>.local.\n"                                                                                                                                                     \
-       "        TYPE:     A\n"                                                                                                                                                                                         \
-       "        CLASS:    IN\n"                                                                                                                                                                                        \
-       "        TTL:      120\n"                                                                                                                                                                                       \
-       "        RDLENGTH: 4\n"                                                                                                                                                                                         \
-       "        RDATA:    0.<ceil(i / 256)>.<i mod 256>.<j>\n"
-
-#define kMDNSReplierInfoText_AAAA                                                                                                                                                                              \
-       "The replier has exactly N_max x N_aaaa authoritative AAAA records:\n"                                                                                          \
-       "\n"                                                                                                                                                                                                                            \
-       "    1. For each j in [1, N_aaaa], a AAAA record is defined as\n"                                                                                                       \
-       "\n"                                                                                                                                                                                                                            \
-       "        NAME:     <hostname>.local.\n"                                                                                                                                                         \
-       "        TYPE:     AAAA\n"                                                                                                                                                                                      \
-       "        CLASS:    IN\n"                                                                                                                                                                                        \
-       "        TTL:      120\n"                                                                                                                                                                                       \
-       "        RDLENGTH: 16\n"                                                                                                                                                                                        \
-       "        RDATA:    2001:db8:2::1:<j>\n"                                                                                                                                                         \
-       "\n"                                                                                                                                                                                                                            \
-       "    2. For each i in [2, N_max], for each j in [1, N_aaaa], a AAAA record is defined as\n"                                                     \
-       "\n"                                                                                                                                                                                                                            \
-       "        NAME:     <hostname>-<i>.local.\n"                                                                                                                                                     \
-       "        TYPE:     AAAA\n"                                                                                                                                                                                      \
-       "        CLASS:    IN\n"                                                                                                                                                                                        \
-       "        TTL:      120\n"                                                                                                                                                                                       \
-       "        RDLENGTH: 16\n"                                                                                                                                                                                        \
-       "        RDATA:    2001:db8:2::<i>:<j>\n"
-
-#define kMDNSReplierInfoText_Responses                                                                                                                                                                 \
-       "When generating answers for a query message, any two records pertaining to the same hostname will be grouped\n"        \
-       "together in the same response message, and any two records pertaining to different hostnames will be in\n"                     \
-       "separate response messages.\n"
-
-static const char *            gMDNSReplier_Hostname                   = NULL;
-static const char *            gMDNSReplier_ServiceTypeTag             = NULL;
-static int                             gMDNSReplier_MaxInstanceCount   = 1000;
-static int                             gMDNSReplier_NoAdditionals              = false;
-static int                             gMDNSReplier_RecordCountA               = 1;
-static int                             gMDNSReplier_RecordCountAAAA    = 1;
-static double                  gMDNSReplier_UnicastDropRate    = 0.0;
-static double                  gMDNSReplier_MulticastDropRate  = 0.0;
-static int                             gMDNSReplier_MaxDropCount               = 0;
-static int                             gMDNSReplier_UseIPv4                    = false;
-static int                             gMDNSReplier_UseIPv6                    = false;
-static int                             gMDNSReplier_Foreground                 = false;
-static const char *            gMDNSReplier_FollowPID              = NULL;
+static const char              kMDNSReplierInfoText_A[] =
+       "The replier has exactly N_max ✕ N_a authoritative A records:\n"
+       "\n"
+       "    For each i in [1, N_max], for each j in [1, N_a], an A record is defined as\n"
+       "\n"
+       "        NAME:     \"<hostname>.local.\" if i = 1, otherwise \"<hostname>-<i>.local.\"\n"
+       "        TYPE:     A\n"
+       "        CLASS:    IN\n"
+       "        TTL:      120\n"
+       "        RDLENGTH: 4\n"
+       "        RDATA:    0.<⌊i / 256⌋>.<i mod 256>.<j>\n";
+
+static const char              kMDNSReplierInfoText_AAAA[] =
+       "The replier has exactly N_max ✕ N_aaaa authoritative AAAA records:\n"
+       "\n"
+       "    1. For each j in [1, N_aaaa], a AAAA record is defined as\n"
+       "\n"
+       "        NAME:     <hostname>.local.\n"
+       "        TYPE:     AAAA\n"
+       "        CLASS:    IN\n"
+       "        TTL:      120\n"
+       "        RDLENGTH: 16\n"
+       "        RDATA:    fe80::1:<j>\n"
+       "\n"
+       "    2. For each i in [2, N_max], for each j in [1, N_aaaa], a AAAA record is defined as\n"
+       "\n"
+       "        NAME:     <hostname>-<i>.local.\n"
+       "        TYPE:     AAAA\n"
+       "        CLASS:    IN\n"
+       "        TTL:      120\n"
+       "        RDLENGTH: 16\n"
+       "        RDATA:    2001:db8:2::<i>:<j>\n";
+
+static const char              kMDNSReplierInfoText_Responses[] =
+       "When generating answers for a query message, any two records pertaining to the same hostname will be grouped\n"
+       "together in the same response message, and any two records pertaining to different hostnames will be in\n"
+       "separate response messages.\n";
+
+static const char *            gMDNSReplier_Hostname                           = NULL;
+static const char *            gMDNSReplier_ServiceTypeTag                     = NULL;
+static int                             gMDNSReplier_MaxInstanceCount           = 1000;
+static int                             gMDNSReplier_NoAdditionals                      = false;
+static int                             gMDNSReplier_RecordCountA                       = 1;
+static int                             gMDNSReplier_RecordCountAAAA            = 1;
+static double                  gMDNSReplier_UnicastDropRate            = 0.0;
+static double                  gMDNSReplier_MulticastDropRate          = 0.0;
+static int                             gMDNSReplier_MaxDropCount                       = 0;
+static int                             gMDNSReplier_UseIPv4                            = false;
+static int                             gMDNSReplier_UseIPv6                            = false;
+static int                             gMDNSReplier_Foreground                         = false;
+#if( TARGET_OS_POSIX )
+static const char *            gMDNSReplier_FollowPID                          = NULL;
+#endif
 
 static CLIOption               kMDNSReplierOpts[] =
 {
@@ -1521,7 +1640,7 @@ static CLIOption          kMDNSReplierOpts[] =
        BooleanOption(  0 , "ipv4",             &gMDNSReplier_UseIPv4,           "Use IPv4." ),
        BooleanOption(  0 , "ipv6",             &gMDNSReplier_UseIPv6,           "Use IPv6." ),
        BooleanOption( 'f', "foreground",       &gMDNSReplier_Foreground,        "Direct log output to stdout instead of system logging." ),
-#if( TARGET_OS_DARWIN )
+#if( TARGET_OS_POSIX )
        StringOption(   0 , "follow",           &gMDNSReplier_FollowPID,         "pid", "Exit when the process, usually the parent process, specified by PID exits.", false ),
 #endif
        
@@ -1673,6 +1792,9 @@ static double                     gMDNSDiscoveryTest_MulticastDropRate    = 0.0;
 static int                             gMDNSDiscoveryTest_MaxDropCount                 = 0;
 static int                             gMDNSDiscoveryTest_UseIPv4                              = false;
 static int                             gMDNSDiscoveryTest_UseIPv6                              = false;
+#if( MDNSRESPONDER_PROJECT )
+static int                             gMDNSDiscoveryTest_UseNewGAI                    = false;
+#endif
 static const char *            gMDNSDiscoveryTest_OutputFormat                 = kOutputFormatStr_JSON;
 static int                             gMDNSDiscoveryTest_OutputAppendNewline  = false;
 static const char *            gMDNSDiscoveryTest_OutputFilePath               = NULL;
@@ -1694,6 +1816,9 @@ static CLIOption          kMDNSDiscoveryTestOpts[] =
        IntegerOption(  0 , "maxDropCount",   &gMDNSDiscoveryTest_MaxDropCount,        "count", "If > 0, drop probabilities are limted to first <count> responses from each instance. (default: 0)", false ),
        BooleanOption(  0 , "ipv4",           &gMDNSDiscoveryTest_UseIPv4,             "Use IPv4." ),
        BooleanOption(  0 , "ipv6",           &gMDNSDiscoveryTest_UseIPv6,             "Use IPv6." ),
+#if( MDNSRESPONDER_PROJECT )
+       BooleanOption(  0 , "useNewGAI",      &gMDNSDiscoveryTest_UseNewGAI,           "Use dnssd_getaddrinfo_* instead of DNSServiceGetAddrInfo()." ),
+#endif
        
        CLI_OPTION_GROUP( "Results" ),
        FormatOption(   'f', "format",        &gMDNSDiscoveryTest_OutputFormat,        "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ),
@@ -1754,17 +1879,21 @@ static void     ProbeConflictTestCmd( void );
 
 static const char *            gProbeConflictTest_Interface            = NULL;
 static int                             gProbeConflictTest_UseComputerName      = false;
+static int                             gProbeConflictTest_UseIPv4                      = false;
+static int                             gProbeConflictTest_UseIPv6                      = false;
 static const char *            gProbeConflictTest_OutputFormat         = kOutputFormatStr_JSON;
 static const char *            gProbeConflictTest_OutputFilePath       = NULL;
 
 static CLIOption               kProbeConflictTestOpts[] =
 {
-       StringOption(  'i', "interface",       &gProbeConflictTest_Interface,           "name or index", "mdnsreplier's network interface. If not set, any mDNS-capable interface will be used.", false ),
-       BooleanOption( 'c', "useComputerName", &gProbeConflictTest_UseComputerName,     "Use the device's \"computer name\" for the test service's name." ),
+       StringOption(  'i', "interface",       &gProbeConflictTest_Interface,       "name or index", "mdnsreplier's network interface. If not set, any mDNS-capable interface will be used.", false ),
+       BooleanOption( 'c', "useComputerName", &gProbeConflictTest_UseComputerName, "Use the device's \"computer name\" for the test service's name." ),
+       BooleanOption(  0 , "ipv4",            &gProbeConflictTest_UseIPv4,         "Use IPv4 instead of IPv6. (Default behavior.)" ),
+       BooleanOption(  0 , "ipv6",            &gProbeConflictTest_UseIPv6,         "Use IPv6 instead of IPv4." ),
        
        CLI_OPTION_GROUP( "Results" ),
-       FormatOption(  'f', "format",          &gProbeConflictTest_OutputFormat,        "Specifies the test report output format. (default: " kOutputFormatStr_JSON ")", false ),
-       StringOption(  'o', "output",          &gProbeConflictTest_OutputFilePath,      "path", "Path of the file to write test report to instead of standard output (stdout).", false ),
+       FormatOption(  'f', "format",          &gProbeConflictTest_OutputFormat,    "Specifies the test report output format. (default: " kOutputFormatStr_JSON ")", false ),
+       StringOption(  'o', "output",          &gProbeConflictTest_OutputFilePath,  "path", "Path of the file to write test report to instead of standard output (stdout).", false ),
        
        TestExitStatusSection(),
        CLI_OPTION_END()
@@ -1795,6 +1924,24 @@ static CLIOption         kRegistrationTestOpts[] =
        CLI_OPTION_END()
 };
 
+#if( MDNSRESPONDER_PROJECT )
+static void    FallbackTestCmd( void );
+
+static int                             gFallbackTest_UseRefused                = false;
+static const char *            gFallbackTest_OutputFormat              = kOutputFormatStr_JSON;
+static const char *            gFallbackTest_OutputFilePath    = NULL;
+
+static CLIOption               kFallbackTestOpts[] =
+{
+       BooleanOption( 0 , "useRefused", &gFallbackTest_UseRefused,     "Have the server use the Refused RCODE in responses when a query is not allowed to be answered." ),
+       CLI_OPTION_GROUP( "Results" ),
+       FormatOption( 'f', "format",     &gFallbackTest_OutputFormat,   "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ),
+       StringOption( 'o', "output",     &gFallbackTest_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ),
+       
+       TestExitStatusSection(),
+       CLI_OPTION_END()
+};
+
 static void ExpensiveConstrainedTestCmd( void );
 
 static const char *     gExpensiveConstrainedTest_Interface                 = NULL;
@@ -1817,7 +1964,36 @@ static CLIOption        kExpensiveConstrainedTestOpts[] =
     CLI_OPTION_END()
 };
 
-#if( MDNSRESPONDER_PROJECT )
+static void    DNSProxyTestCmd( void );
+
+static const char *            gDNSProxyTest_OutputFormat              = kOutputFormatStr_JSON;
+static const char *            gDNSProxyTest_OutputFilePath    = NULL;
+
+static CLIOption               kDNSProxyTestOpts[] =
+{
+       CLI_OPTION_GROUP( "Results" ),
+       FormatOption( 'f', "format", &gDNSProxyTest_OutputFormat,   "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ),
+       StringOption( 'o', "output", &gDNSProxyTest_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ),
+       
+       TestExitStatusSection(),
+       CLI_OPTION_END()
+};
+
+static void    RCodeTestCmd( void );
+
+static const char *            gRCodeTest_OutputFormat         = kOutputFormatStr_JSON;
+static const char *            gRCodeTest_OutputFilePath       = NULL;
+
+static CLIOption               kRCodeTestOpts[] =
+{
+       CLI_OPTION_GROUP( "Results" ),
+       FormatOption( 'f', "format", &gRCodeTest_OutputFormat,   "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ),
+       StringOption( 'o', "output", &gRCodeTest_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ),
+       
+       TestExitStatusSection(),
+       CLI_OPTION_END()
+};
+
 static void XCTestCmd( void );
 
 static const char *     gXCTest_Classname        = NULL;
@@ -1827,7 +2003,17 @@ static CLIOption        kXCTestOpts[] =
     StringOption(      'c', "class", &gXCTest_Classname, "classname", "The classname of the XCTest to run (from /AppleInternal/XCTests/com.apple.mDNSResponder/Tests.xctest)", true ),
     CLI_OPTION_END()
 };
-#endif
+
+static void MultiConnectTestCmd( void );
+
+static int                     gMultiConnectTest_ConnectionCount = 4; // default to 4
+
+static CLIOption        kMultiConnectTestOpts[] =
+{
+       IntegerOption( 0, "connections", &gMultiConnectTest_ConnectionCount,    "count", "Number of simultanious connections. (default: 4)", false ),
+    CLI_OPTION_END()
+};
+#endif // MDNSRESPONDER_PROJECT
 
 #if( TARGET_OS_DARWIN )
 static void    KeepAliveTestCmd( void );
@@ -1844,8 +2030,28 @@ static CLIOption         kKeepAliveTestOpts[] =
        TestExitStatusSection(),
        CLI_OPTION_END()
 };
+#endif // TARGET_OS_DARWIN
+
+static void DNSSECTestCmd( void );
+
+static const char * gDNSSECTest_TestCaseName   = NULL;
+#if ( ENABLE_DNSSDUTIL_DNSSEC_TEST == 1 )
+static const char * gDNSSECTest_OutputFormat   = kOutputFormatStr_JSON;
+static const char * gDNSSECTest_OutputFilePath = NULL;
 #endif
 
+static CLIOption       kDNSSECTestOpts[] =
+{
+       StringOption( 'n', "testCaseName", &gDNSSECTest_TestCaseName,                  "Specifies the DNSSEC test that the user intends to run", "test name", true ),
+
+       CLI_OPTION_GROUP( "Results" ),
+       FormatOption( 'f', "format",       &gExpensiveConstrainedTest_OutputFormat,    "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ),
+       StringOption( 'o', "output",       &gExpensiveConstrainedTest_OutputFilePath,  "path",    "Path of the file to write test results to instead of standard output (stdout).", false ),
+
+       TestExitStatusSection(),
+       CLI_OPTION_END()
+};
+
 static CLIOption               kTestOpts[] =
 {
        Command( "gaiperf",        GAIPerfCmd,           kGAIPerfOpts,            "Runs DNSServiceGetAddrInfo() performance tests.", false ),
@@ -1853,13 +2059,18 @@ static CLIOption                kTestOpts[] =
        Command( "dotlocal",       DotLocalTestCmd,      kDotLocalTestOpts,       "Tests DNS and mDNS queries for domain names in the local domain.", false ),
        Command( "probeconflicts", ProbeConflictTestCmd, kProbeConflictTestOpts,  "Tests various probing conflict scenarios.", false ),
        Command( "registration",   RegistrationTestCmd,  kRegistrationTestOpts,   "Tests service registrations.", false ),
-    Command( "expensive_constrained_updates", ExpensiveConstrainedTestCmd, kExpensiveConstrainedTestOpts, "Tests if the mDNSResponder can handle expensive and constrained property change correctly", false),
 #if( MDNSRESPONDER_PROJECT )
-    Command( "xctest",         XCTestCmd,            kXCTestOpts,              "Run a XCTest from /AppleInternal/XCTests/com.apple.mDNSResponder/Tests.xctest.", true ),
+       Command( "fallback",       FallbackTestCmd,      kFallbackTestOpts,       "Tests DNS server fallback.", false ),
+    Command( "expensive_constrained_updates", ExpensiveConstrainedTestCmd, kExpensiveConstrainedTestOpts, "Tests if the mDNSResponder can handle expensive and constrained property change correctly", false),
+       Command( "dnsproxy",       DNSProxyTestCmd,      kDNSProxyTestOpts,       "Tests mDNSResponder's DNS proxy.", false ),
+       Command( "rcodes",         RCodeTestCmd,         kRCodeTestOpts,         "Tests handling of all DNS RCODEs.", false ),
+    Command( "xctest",         XCTestCmd,            kXCTestOpts,                        "Run a XCTest from /AppleInternal/XCTests/com.apple.mDNSResponder/Tests.xctest.", true ),
+       Command( "multiconnect",   MultiConnectTestCmd,  kMultiConnectTestOpts,   "Tests multiple simultanious connections.", false ),
 #endif
 #if( TARGET_OS_DARWIN )
        Command( "keepalive",      KeepAliveTestCmd,     kKeepAliveTestOpts,      "Tests keepalive record registrations.", false ),
 #endif
+       Command( "dnssec",         DNSSECTestCmd,        kDNSSECTestOpts,         "Tests mDNSResponder's DNSSEC validation", false),
        CLI_OPTION_END()
 };
 
@@ -1971,13 +2182,15 @@ static size_t                   gDNSConfigAdd_IPAddrCount       = 0;
 static char **                 gDNSConfigAdd_DomainArray       = NULL;
 static size_t                  gDNSConfigAdd_DomainCount       = 0;
 static const char *            gDNSConfigAdd_Interface         = NULL;
+static int                             gDNSConfigAdd_SearchOrder       = -1;
 
 static CLIOption               kDNSConfigAddOpts[] =
 {
-       CFStringOption(     0 , "id",        &gDNSConfigAdd_ID,                                      "ID", "Arbitrary ID to use for resolver entry.", true ),
-       MultiStringOption( 'a', "address",   &gDNSConfigAdd_IPAddrArray, &gDNSConfigAdd_IPAddrCount, "IP address", "DNS server IP address(es). Can be specified more than once.", true ),
-       MultiStringOption( 'd', "domain",    &gDNSConfigAdd_DomainArray, &gDNSConfigAdd_DomainCount, "domain", "Specific domain(s) for the resolver entry. Can be specified more than once.", false ),
-       StringOption(      'i', "interface", &gDNSConfigAdd_Interface,                               "interface name", "Specific interface for the resolver entry.", false ),
+       CFStringOption(     0 , "id",          &gDNSConfigAdd_ID,                                      "ID", "Arbitrary ID to use for resolver entry.", true ),
+       MultiStringOption( 'a', "address",     &gDNSConfigAdd_IPAddrArray, &gDNSConfigAdd_IPAddrCount, "IP address", "DNS server IP address(es). Can be specified more than once.", true ),
+       MultiStringOption( 'd', "domain",      &gDNSConfigAdd_DomainArray, &gDNSConfigAdd_DomainCount, "domain", "Specific domain(s) for the resolver entry. Can be specified more than once.", false ),
+       StringOption(      'i', "interface",   &gDNSConfigAdd_Interface,                               "interface name", "Specific interface for the resolver entry.", false ),
+       IntegerOption(     'o', "searchOrder", &gDNSConfigAdd_SearchOrder,                             "integer", "Resolver entry's search order. Will only be set for values >= 0. (default: -1)", false ),
        
        CLI_SECTION( "Notes", "Run 'scutil -d -v --dns' to see the current DNS configuration. See scutil(8) man page for more details.\n" ),
        CLI_OPTION_END()
@@ -2050,7 +2263,7 @@ static CLIOption          kXPCSendOpts[] =
 
 #if( MDNSRESPONDER_PROJECT )
 //===========================================================================================================================
-//     InterfaceMonitor
+//     InterfaceMonitor Command Options
 //===========================================================================================================================
 
 static void InterfaceMonitorCmd( void );
@@ -2062,7 +2275,69 @@ static CLIOption         kInterfaceMonitorOpts[] =
 };
 
 //===========================================================================================================================
-//     DNSProxy
+//     Querier Command Options
+//===========================================================================================================================
+
+#define kMDNSResolverTypeStr_Normal            "normal"
+#define kMDNSResolverTypeStr_TCPOnly   "tcp"
+#define kMDNSResolverTypeStr_TLS               "tls"
+#define kMDNSResolverTypeStr_HTTPS             "https"
+
+static const char *            gQuerier_Name                           = NULL;
+static const char *            gQuerier_Type                           = "A";
+static const char *            gQuerier_Class                          = "IN";
+static const char *            gQuerier_Delegator                      = NULL;
+static int                             gQuerier_DNSSECOK                       = false;
+static int                             gQuerier_CheckingDisabled       = false;
+static const char *            gQuerier_ResolverType           = NULL;
+static char **                 gQuerier_ServerAddrs            = NULL;
+static size_t                  gQuerier_ServerAddrCount        = 0;
+static const char *            gQuerier_ProviderName           = NULL;
+static const char *            gQuerier_URLPath                        = NULL;
+static int                             gQuerier_NoConnectionReuse      = false;
+static int                             gQuerier_SquashCNAMEs           = false;
+
+static CLIOption               kQuerierOpts[] =
+{
+       StringOption( 'i', "interface",        &gInterface,                "name or index", "If specified, network traffic is scoped to this interface.", false ),
+       StringOption( 'n', "name",             &gQuerier_Name,             "name", "Question name (QNAME).", true ),
+       StringOption( 't', "type",             &gQuerier_Type,             "type", "Question type (QTYPE). (default: A)", false ),
+       StringOption( 'c', "class",            &gQuerier_Class,            "class", "Question class (QCLASS). (default: IN)", false ),
+       StringOption(  0 , "delegator",        &gQuerier_Delegator,        "PID|UUID", "Delegator's PID or UUID.", false ),
+       BooleanOption( 0 , "dnssec",           &gQuerier_DNSSECOK,         "Have queries include an OPT record with the DNSSEC OK (DO) bit set." ),
+       BooleanOption( 0 , "checkingDisabled", &gQuerier_CheckingDisabled, "Set the Checking Disabled (CD) bit in queries." ),
+       
+       CLI_OPTION_GROUP( "Resolver-Specific Options" ),
+       StringOptionEx( 'r', "resolverType", &gQuerier_ResolverType, "resolver type", "Specifies the type of resolver to use.", false,
+               "\n"
+               "Use '" kMDNSResolverTypeStr_Normal  "' for DNS over UDP and TCP.\n"
+               "Use '" kMDNSResolverTypeStr_TCPOnly "' for DNS over TCP.\n"
+               "Use '" kMDNSResolverTypeStr_TLS     "' for DNS over TLS.\n"
+               "Use '" kMDNSResolverTypeStr_HTTPS   "' for DNS over HTTPS.\n"
+               "\n"
+               "If no resolver type is specified, an mdns_dns_service_manager will be used to determine which DNS service to use\n"
+               "based on the system's DNS settings. In this case, the other resolver-specific options will be ignored.\n"
+               "\n"
+       ),
+       MultiStringOptionEx( 's', "server", &gQuerier_ServerAddrs, &gQuerier_ServerAddrCount, "IP address", "Server's IPv4 or IPv6 address with optionally-specified port.", false,
+               "\n"
+               "Use this option one or more times to specify a DNS service's server(s) by IP address.\n"
+               "\n"
+               "If no server IP addresses are specified for DNS over TLS/HTTPS resolvers, then connections to the DNS service\n"
+               "will use the specified provider name as the DNS service's hostname.\n"
+               "\n"
+       ),
+       StringOption( 'p', "providerName",      &gQuerier_ProviderName,      "domain name", "Provider's domain name for DNS over TLS/HTTPS.", false ),
+       StringOption( 'q', "urlPath",           &gQuerier_URLPath,           "path", "URL path for DNS over HTTPS.", false ),
+       BooleanOption( 0 , "noConnectionReuse", &gQuerier_NoConnectionReuse, "Disable connection reuse." ),
+       BooleanOption( 0 , "squashCNAMEs",      &gQuerier_SquashCNAMEs,      "Squash CNAME chains in responses." ),
+       CLI_OPTION_END()
+};
+
+static void QuerierCommand( void );
+
+//===========================================================================================================================
+//     DNSProxy Command Options
 //===========================================================================================================================
 
 static void DNSProxyCmd( void );
@@ -2070,14 +2345,82 @@ static void DNSProxyCmd( void );
 static char **                 gDNSProxy_InputInterfaces               = NULL;
 static size_t                  gDNSProxy_InputInterfaceCount   = 0;
 static const char *            gDNSProxy_OutputInterface               = NULL;
+static const char *            gDNSProxy_DNS64IPv6Prefix               = NULL;
 
 static CLIOption               kDNSProxyOpts[] =
 {
        MultiStringOption( 'i', "inputInterface",  &gDNSProxy_InputInterfaces, &gDNSProxy_InputInterfaceCount, "name or index", "Interface to accept queries on. Can be specified more than once.", true ),
        StringOption(      'o', "outputInterface", &gDNSProxy_OutputInterface, "name or index", "Interface to forward queries over. Use '0' for primary interface. (default: 0)", false ),
+       StringOption(      'p', "dns64Prefix",     &gDNSProxy_DNS64IPv6Prefix, "IPv6 prefix", "IPv6 prefix to use for DNS64 AAAA record synthesis.", false ),
        CLI_OPTION_END()
 };
 
+//===========================================================================================================================
+//     GetAddrInfoNew Command Options
+//===========================================================================================================================
+
+static const char *            gGAINew_Hostname                = NULL;
+static const char *            gGAINew_DelegatorID             = NULL;
+static const char *            gGAINew_ServiceScheme   = NULL;
+static const char *            gGAINew_AccountID               = NULL;
+static int                             gGAINew_ProtocolIPv4    = false;
+static int                             gGAINew_ProtocolIPv6    = false;
+static int                             gGAINew_WantAuthTags    = false;
+static int                             gGAINew_OneShot                 = false;
+static int                             gGAINew_TimeLimitSecs   = 0;
+static const char *            gGAINew_QoS                             = NULL;
+
+#define kQoSTypeStr_Unspecified                        "unspecified"
+#define kQoSTypeStr_Background                 "background"
+#define kQoSTypeStr_Utility                            "utility"
+#define kQoSTypeStr_Default                            "default"
+#define kQoSTypeStr_UserInitiated              "userInitiated"
+#define kQoSTypeStr_UserInteractive            "userInteractive"
+
+#define kQoSArgShortName               "QoS class"
+
+static CLIOption               kGetAddrInfoNewOpts[] =
+{
+       InterfaceOption(),
+       StringOption(  'n', "name",          &gGAINew_Hostname,      "domain name", "Hostname to resolve.", true ),
+       StringOption(  'd', "delegate",      &gGAINew_DelegatorID,   "PID|UUID", "Delegator's PID or UUID. If PID p < 0, the audit token of PID |p| will be used.", false ),
+       StringOption(   0,  "accountID",     &gGAINew_AccountID,     "account ID", "Account ID string.", false ),
+       StringOption(   0,  "serviceScheme", &gGAINew_ServiceScheme, "scheme", "Service scheme such as '_443._https'.", false ),
+       BooleanOption(  0 , "ipv4",          &gGAINew_ProtocolIPv4,  "Use kDNSServiceProtocol_IPv4." ),
+       BooleanOption(  0 , "ipv6",          &gGAINew_ProtocolIPv6,  "Use kDNSServiceProtocol_IPv6." ),
+       BooleanOption( 'a', "wantAuthTags",  &gGAINew_WantAuthTags,  "Want authentication tags." ),
+       
+       CLI_OPTION_GROUP( "Flags" ),
+       DNSSDFlagsOption(),
+       DNSSDFlagsOption_AllowExpiredAnswers(),
+       DNSSDFlagsOption_DenyCellular(),
+       DNSSDFlagsOption_DenyConstrained(),
+       DNSSDFlagsOption_DenyExpensive(),
+       DNSSDFlagsOption_IncludeAWDL(),
+       DNSSDFlagsOption_PathEvalDone(),
+       DNSSDFlagsOption_ReturnIntermediates(),
+       DNSSDFlagsOption_SuppressUnusable(),
+       DNSSDFlagsOption_Timeout(),
+       
+       CLI_OPTION_GROUP( "Operation" ),
+       ConnectionOptions(),
+       BooleanOption(  'o', "oneshot",   &gGAINew_OneShot,       "Finish after first set of results." ),
+       IntegerOption(  'l', "timeLimit", &gGAINew_TimeLimitSecs, "seconds", "Time limit for dnssd_getaddrinfo operation. Use '0' for no limit. (default: 0)", false ),
+       StringOptionEx( 'q', "qos",       &gGAINew_QoS,           kQoSArgShortName, "Specifies the QoS of the queue used for the dnssd_getaddrinfo object.", false,
+               "\n"
+               "Use '" kQoSTypeStr_Unspecified     "' for QOS_CLASS_UNSPECIFIED.\n"
+               "Use '" kQoSTypeStr_Background      "' for QOS_CLASS_BACKGROUND.\n"
+               "Use '" kQoSTypeStr_Utility         "' for QOS_CLASS_UTILITY.\n"
+               "Use '" kQoSTypeStr_Default         "' for QOS_CLASS_DEFAULT.\n"
+               "Use '" kQoSTypeStr_UserInitiated   "' for QOS_CLASS_USER_INITIATED.\n"
+               "Use '" kQoSTypeStr_UserInteractive "' for QOS_CLASS_USER_INTERACTIVE.\n"
+               "\n"
+       ),
+       CLI_OPTION_END()
+};
+
+static void    GetAddrInfoNewCommand( void );
+
 #endif // MDNSRESPONDER_PROJECT
 
 //===========================================================================================================================
@@ -2103,7 +2446,9 @@ static void       DNSQueryCmd( void );
 static void    DNSCryptCmd( void );
 #endif
 static void    MDNSQueryCmd( void );
+#if( TARGET_OS_DARWIN )
 static void    PIDToUUIDCmd( void );
+#endif
 static void    DaemonVersionCmd( void );
 
 static CLIOption               kGlobalOpts[] =
@@ -2139,7 +2484,9 @@ static CLIOption          kGlobalOpts[] =
 #endif
        Command( "mdnsquery",                   MDNSQueryCmd,                   kMDNSQueryOpts,                 "Crafts and sends an mDNS query over the specified interface.", true ),
        Command( "mdnscollider",                MDNSColliderCmd,                kMDNSColliderOpts,              "Creates record name collision scenarios.", true ),
+#if( TARGET_OS_DARWIN )
        Command( "pid2uuid",                    PIDToUUIDCmd,                   kPIDToUUIDOpts,                 "Prints the UUID of a process.", true ),
+#endif
        Command( "server",                              DNSServerCmd,                   kDNSServerOpts,                 "DNS server for testing.", true ),
        Command( "mdnsreplier",                 MDNSReplierCmd,                 kMDNSReplierOpts,               "Responds to mDNS queries for a set of authoritative resource records.", true ),
        Command( "test",                                NULL,                                   kTestOpts,                              "Commands for testing DNS-SD.", true ),
@@ -2150,8 +2497,10 @@ static CLIOption         kGlobalOpts[] =
        Command( "xpcsend",                             XPCSendCmd,                             kXPCSendOpts,                   "Sends a message to an XPC service.", true ),
 #endif
 #if( MDNSRESPONDER_PROJECT )
-       Command( "interfaceMonitor",    InterfaceMonitorCmd,    kInterfaceMonitorOpts,  "mDNSResponder's interface monitor.", true ),
+       Command( "interfaceMonitor",    InterfaceMonitorCmd,    kInterfaceMonitorOpts,  "Instantiates an mdns_interface_monitor.", true ),
+       Command( "querier",                             QuerierCommand,                 kQuerierOpts,                   "Sends a DNS query using mdns_querier.", true ),
        Command( "dnsproxy",                    DNSProxyCmd,                    kDNSProxyOpts,                  "Enables mDNSResponder's DNS proxy.", true ),
+       Command( "getaddrinfo-new",             GetAddrInfoNewCommand,  kGetAddrInfoNewOpts,    "Uses dnssd_getaddrinfo to resolve a hostname to IP addresses.", false ),
 #endif
        Command( "daemonVersion",               DaemonVersionCmd,               NULL,                                   "Prints the version of the DNS-SD daemon.", true ),
        
@@ -2208,24 +2557,10 @@ static OSStatus                 RecordClassFromArgString( const char *inString, uint16_t *outV
 #define kInterfaceNameBufLen           ( Max( IF_NAMESIZE, 16 ) + 1 )
 
 static char *                  InterfaceIndexToName( uint32_t inIfIndex, char inNameBuf[ kInterfaceNameBufLen ] );
-static const char *            RecordTypeToString( unsigned int inValue );
-
-static OSStatus
-       DNSRecordDataToString(
-               const void *    inRDataPtr,
-               size_t                  inRDataLen,
-               unsigned int    inRDataType,
-               const void *    inMsgPtr,
-               size_t                  inMsgLen,
-               char **                 outString );
-
-static OSStatus
-       DNSMessageToText(
-               const uint8_t * inMsgPtr,
-               size_t                  inMsgLen,
-               Boolean                 inIsMDNS,
-               Boolean                 inPrintRaw,
-               char **                 outText );
+static const char *            RecordTypeToString( int inValue );
+#if( MDNSRESPONDER_PROJECT )
+static const char *            RecordClassToString( int inValue );
+#endif
 
 static OSStatus
        WriteDNSQueryMessage(
@@ -2278,6 +2613,7 @@ static OSStatus
 #define DispatchTimerOneShotCreate( IN_START, IN_LEEWAY, IN_QUEUE, IN_EVENT_HANDLER, IN_CONTEXT, OUT_TIMER )   \
        DispatchTimerCreate( IN_START, DISPATCH_TIME_FOREVER, IN_LEEWAY, IN_QUEUE, IN_EVENT_HANDLER, NULL, IN_CONTEXT, OUT_TIMER )
 
+#if( TARGET_OS_DARWIN )
 static OSStatus
        DispatchProcessMonitorCreate(
                pid_t                           inPID,
@@ -2287,21 +2623,32 @@ static OSStatus
                DispatchHandler         inCancelHandler,
                void *                          inContext,
                dispatch_source_t *     outMonitor );
+#endif
 
 static const char *    ServiceTypeDescription( const char *inName );
 
+typedef void ( *SocketContextFinalizer_f )( void *inUserCtx );
+
 typedef struct
 {
-       SocketRef               sock;                   // Socket.
-       void *                  userContext;    // User context.
-       int32_t                 refCount;               // Reference count.
+       SocketRef                                               sock;                   // Socket.
+       int32_t                                                 refCount;               // Reference count.
+       void *                                                  userContext;    // User's context.
+       SocketContextFinalizer_f                userFinalizer;  // User's finalizer.
        
 }      SocketContext;
 
-static OSStatus                        SocketContextCreate( SocketRef inSock, void * inUserContext, SocketContext **outContext );
+static SocketContext * SocketContextCreate( SocketRef inSock, void *inUserContext, OSStatus *outError );
+static SocketContext *
+       SocketContextCreateEx(
+               SocketRef                                       inSock,
+               void *                                          inUserContext,
+               SocketContextFinalizer_f        inUserFinalizer,
+               OSStatus *                                      outError );
 static SocketContext * SocketContextRetain( SocketContext *inContext );
 static void                            SocketContextRelease( SocketContext *inContext );
 static void                            SocketContextCancelHandler( void *inContext );
+static void                            SocketContextFinalizerCF( void *inUserCtx );
 
 #define ForgetSocketContext( X )       ForgetCustom( X, SocketContextRelease )
 
@@ -2357,7 +2704,15 @@ static OSStatus  DecimalTextToUInt32( const char *inSrc, const char *inEnd, uint3
 static OSStatus        CheckIntegerArgument( int inArgValue, const char *inArgName, int inMin, int inMax );
 static OSStatus        CheckDoubleArgument( double inArgValue, const char *inArgName, double inMin, double inMax );
 static OSStatus        CheckRootUser( void );
-static OSStatus        SpawnCommand( pid_t *outPID, const char *inFormat, ... );
+#if( TARGET_OS_POSIX )
+static OSStatus
+       _SpawnCommand(
+               pid_t *                 outPID,
+               const char *    inStdOutRedirect,
+               const char *    inStdErrRedirect,
+               const char *    inFormat,
+               ... );
+#endif
 static OSStatus        OutputFormatFromArgString( const char *inArgString, OutputFormatType *outFormat );
 static OSStatus        OutputPropertyList( CFPropertyListRef inPList, OutputFormatType inType, const char *inOutputFilePath );
 static OSStatus        CreateSRVRecordDataFromString( const char *inString, uint8_t **outPtr, size_t *outLen );
@@ -2444,6 +2799,38 @@ static OSStatus  _SetComputerName( CFStringRef inComputerName, CFStringEncoding i
 static OSStatus        _SetComputerNameWithUTF8CString( const char *inComputerName );
 static OSStatus        _SetLocalHostName( CFStringRef inLocalHostName );
 static OSStatus        _SetLocalHostNameWithUTF8CString( const char *inLocalHostName );
+#if( TARGET_OS_DARWIN )
+static OSStatus        _InterfaceIPv6AddressAdd( const char *inIfName, uint8_t inAddr[ STATIC_PARAM 16 ], int inMaskBitLen );
+static OSStatus        _InterfaceIPv6AddressRemove( const char *inIfName, const uint8_t inAddr[ STATIC_PARAM 16 ] );
+#endif
+static int64_t _TicksDiff( uint64_t inT1, uint64_t inT2 );
+static void            _SockAddrInitIPv4( struct sockaddr_in *inSA, uint32_t inIPv4, uint16_t inPort );
+static void
+       _SockAddrInitIPv6(
+               struct sockaddr_in6 *   inSA,
+               const uint8_t                   inIPv6[ STATIC_PARAM 16 ],
+               uint32_t                                inScope,
+               uint16_t                                inPort );
+
+#define kIP6ArpaDomainStr                                      "ip6.arpa."
+#define kReverseIPv6DomainNameBufLen           ( ( 4 * 16 ) + sizeof_string( kIP6ArpaDomainStr ) + 1 )
+
+static void
+       _WriteReverseIPv6DomainNameString(
+               const uint8_t   inIPv6Addr[ STATIC_PARAM 16 ],
+               char                    outBuffer[ STATIC_PARAM kReverseIPv6DomainNameBufLen ] );
+
+#define kInAddrArpaDomainStr                           "in-addr.arpa."
+#define kReverseIPv4DomainNameBufLen           ( ( 4 * 4 ) + sizeof_string( kInAddrArpaDomainStr ) + 1 )
+
+static void
+       _WriteReverseIPv4DomainNameString(
+               uint32_t        inIPv4Addr,
+               char            outBuffer[ STATIC_PARAM kReverseIPv4DomainNameBufLen ] );
+
+#if( MDNSRESPONDER_PROJECT )
+static OSStatus        _SetDefaultFallbackDNSService( const char *inFallbackDNSServiceStr );
+#endif
 
 static OSStatus        _SocketWriteAll( SocketRef inSock, const void *inData, size_t inSize, int32_t inTimeoutSecs );
 static OSStatus
@@ -2576,6 +2963,9 @@ static OSStatus
                unsigned int            inBrowseTimeSecs,
                Boolean                         inIncludeAWDL,
                ServiceBrowserRef *     outBrowser );
+#if( MDNSRESPONDER_PROJECT )
+static void            ServiceBrowserSetUseNewGAI( ServiceBrowserRef inBrowser, Boolean inUseNewGAI );
+#endif
 static void            ServiceBrowserStart( ServiceBrowserRef inBrowser );
 static OSStatus        ServiceBrowserAddServiceType( ServiceBrowserRef inBrowser, const char *inServiceType );
 static void
@@ -2588,13 +2978,42 @@ static void             ServiceBrowserResultsRelease( ServiceBrowserResults *inResults );
 
 #define ForgetServiceBrowserResults( X )               ForgetCustom( X, ServiceBrowserResultsRelease )
 
+//===========================================================================================================================
+//     DNSServer
+//===========================================================================================================================
+
+typedef struct DNSServerPrivate *              DNSServerRef;
+
+typedef void ( *DNSServerStartHandler_f )( const sockaddr_ip *inServerArray, size_t inServerCount, void *inCtx );
+typedef void ( *DNSServerStopHandler_f )( OSStatus inError, void *inCtx );
+
+static CFTypeID        DNSServerGetTypeID( void );
+static OSStatus
+       _DNSServerCreate(
+               dispatch_queue_t                inQueue,
+               DNSServerStartHandler_f inStartHandler,
+               DNSServerStopHandler_f  inStopHandler,
+               void *                                  inUserContext,
+               unsigned int                    inResponseDelayMs,
+               uint32_t                                inDefaultTTL,
+               const sockaddr_ip *             inServerArray,
+               size_t                                  inServerCount,
+               const char *                    inDomain,
+               Boolean                                 inBadUDPMode,
+               DNSServerRef *                  outServer );
+static OSStatus        _DNSServerSetIgnoredQType( DNSServerRef inServer, int inQType );
+static void            _DNSServerStart( DNSServerRef inServer );
+static void            _DNSServerStop( DNSServerRef inServer );
+
+#define DNSServerForget( X )           ForgetCustomEx( X, _DNSServerStop, CFRelease )
+
 //===========================================================================================================================
 //     main
 //===========================================================================================================================
 
 #define _PRINTF_EXTENSION_HANDLER_DECLARE( NAME )      \
        static int                                                                              \
-               _PrintFExtension ## NAME ## Handler(            \
+               _PrintFExtensionHandler_ ## NAME (                      \
                        PrintFContext * inContext,                              \
                        PrintFFormat *  inFormat,                               \
                        PrintFVAList *  inArgs,                                 \
@@ -2602,8 +3021,10 @@ static void              ServiceBrowserResultsRelease( ServiceBrowserResults *inResults );
 
 _PRINTF_EXTENSION_HANDLER_DECLARE( Timestamp );
 _PRINTF_EXTENSION_HANDLER_DECLARE( DNSMessage );
+_PRINTF_EXTENSION_HANDLER_DECLARE( RawDNSMessage );
 _PRINTF_EXTENSION_HANDLER_DECLARE( CallbackFlags );
 _PRINTF_EXTENSION_HANDLER_DECLARE( DNSRecordData );
+_PRINTF_EXTENSION_HANDLER_DECLARE( DomainName );
 
 int    main( int argc, const char **argv )
 {
@@ -2613,13 +3034,15 @@ int     main( int argc, const char **argv )
        
        dlog_control( "DebugServices:output=file;stderr" );
        
-       PrintFRegisterExtension( "du:time",    _PrintFExtensionTimestampHandler,     NULL );
-       PrintFRegisterExtension( "du:dnsmsg",  _PrintFExtensionDNSMessageHandler,    NULL );
-       PrintFRegisterExtension( "du:cbflags", _PrintFExtensionCallbackFlagsHandler, NULL );
-       PrintFRegisterExtension( "du:rdata",   _PrintFExtensionDNSRecordDataHandler, NULL );
+       PrintFRegisterExtension( "du:time",             _PrintFExtensionHandler_Timestamp,              NULL );
+       PrintFRegisterExtension( "du:dnsmsg",   _PrintFExtensionHandler_DNSMessage,             NULL );
+       PrintFRegisterExtension( "du:rdnsmsg",  _PrintFExtensionHandler_RawDNSMessage,  NULL );
+       PrintFRegisterExtension( "du:cbflags",  _PrintFExtensionHandler_CallbackFlags,  NULL );
+       PrintFRegisterExtension( "du:rdata",    _PrintFExtensionHandler_DNSRecordData,  NULL );
+       PrintFRegisterExtension( "du:dname",    _PrintFExtensionHandler_DomainName,             NULL );
        CLIInit( argc, argv );
        err = CLIParse( kGlobalOpts, kCLIFlags_None );
-       if( err ) exit( 1 );
+       if( err ) gExitCode = 1;
        
        return( gExitCode );
 }
@@ -3151,6 +3574,13 @@ static void      GetAddrInfoCmd( void )
                goto exit;
        }
        
+#if( MDNSRESPONDER_PROJECT )
+       if( gFallbackDNSService )
+       {
+               err = _SetDefaultFallbackDNSService( gFallbackDNSService );
+               require_noerr_quiet( err, exit );
+       }
+#endif
        // Create context.
        
        context = (GetAddrInfoContext *) calloc( 1, sizeof( *context ) );
@@ -3407,6 +3837,13 @@ static void      QueryRecordCmd( void )
                goto exit;
        }
        
+#if( MDNSRESPONDER_PROJECT )
+       if( gFallbackDNSService )
+       {
+               err = _SetDefaultFallbackDNSService( gFallbackDNSService );
+               require_noerr_quiet( err, exit );
+       }
+#endif
        // Create main connection.
        
        if( gConnectionOpt )
@@ -3531,7 +3968,8 @@ static void DNSSD_API
        QueryRecordContext * const              context         = (QueryRecordContext *) inContext;
        struct timeval                                  now;
        OSStatus                                                err;
-       char *                                                  rdataStr        = NULL;
+       char *                                                  rdataStrMem     = NULL;
+       const char *                                    rdataStr;
        
        Unused( inSDRef );
        
@@ -3540,8 +3978,21 @@ static void DNSSD_API
        switch( inError )
        {
                case kDNSServiceErr_NoError:
+                       if( !context->printRawRData ) DNSRecordDataToString( inRDataPtr, inRDataLen, inType, &rdataStrMem );
+                       if( !rdataStrMem )
+                       {
+                               ASPrintF( &rdataStrMem, "%#H", inRDataPtr, (int) inRDataLen, INT_MAX );
+                               require_action( rdataStrMem, exit, err = kNoMemoryErr );
+                       }
+                       rdataStr = rdataStrMem;
+                       break;
+               
                case kDNSServiceErr_NoSuchRecord:
-                       err = kNoErr;
+                       rdataStr = kNoSuchRecordStr;
+                       break;
+               
+               case kDNSServiceErr_NoSuchName:
+                       rdataStr = kNoSuchNameStr;
                        break;
                
                case kDNSServiceErr_Timeout:
@@ -3551,17 +4002,6 @@ static void DNSSD_API
                        err = inError;
                        goto exit;
        }
-       
-       if( inError != kDNSServiceErr_NoSuchRecord )
-       {
-               if( !context->printRawRData ) DNSRecordDataToString( inRDataPtr, inRDataLen, inType, NULL, 0, &rdataStr );
-               if( !rdataStr )
-               {
-                       ASPrintF( &rdataStr, "%#H", inRDataPtr, inRDataLen, INT_MAX );
-                       require_action( rdataStr, exit, err = kNoMemoryErr );
-               }
-       }
-       
        if( !context->printedHeader )
        {
                FPrintF( stdout, "%-26s  %-16s IF %-32s %-5s %-5s %6s RData\n",
@@ -3570,8 +4010,7 @@ static void DNSSD_API
        }
        FPrintF( stdout, "%{du:time}  %{du:cbflags} %2d %-32s %-5s %?-5s%?5u %6u %s\n",
                &now, inFlags, (int32_t) inInterfaceIndex, inFullName, RecordTypeToString( inType ),
-               ( inClass == kDNSServiceClass_IN ), "IN", ( inClass != kDNSServiceClass_IN ), inClass, inTTL,
-               rdataStr ? rdataStr : kNoSuchRecordStr );
+               ( inClass == kDNSServiceClass_IN ), "IN", ( inClass != kDNSServiceClass_IN ), inClass, inTTL, rdataStr );
        
        if( context->oneShotMode )
        {
@@ -3582,10 +4021,11 @@ static void DNSSD_API
                }
                if( !( inFlags & kDNSServiceFlagsMoreComing ) && context->gotRecord ) Exit( kExitReason_OneShotDone );
        }
+       err = kNoErr;
        
 exit:
-       FreeNullSafe( rdataStr );
-       if( err ) exit( 1 );
+       ForgetMem( &rdataStrMem );
+       if( err ) ErrQuit( 1, "error: %#m\n", err );
 }
 
 //===========================================================================================================================
@@ -4487,6 +4927,13 @@ static void      GetAddrInfoPOSIXCmd( void )
        if( gGAIPOSIXFlag_Unusable )    hints.ai_flags |= AI_UNUSABLE;
 #endif
        
+#if( MDNSRESPONDER_PROJECT )
+       if( gFallbackDNSService )
+       {
+               err = _SetDefaultFallbackDNSService( gFallbackDNSService );
+               require_noerr_quiet( err, exit );
+       }
+#endif
        // Print prologue.
        
        FPrintF( stdout, "Hostname:       %s\n",        gGAIPOSIX_HostName );
@@ -4533,8 +4980,6 @@ exit:
 //     ReverseLookupCmd
 //===========================================================================================================================
 
-#define kIP6ARPADomainStr              "ip6.arpa."
-
 static void    ReverseLookupCmd( void )
 {
        OSStatus                                        err;
@@ -4543,7 +4988,7 @@ static void       ReverseLookupCmd( void )
        dispatch_source_t                       signalSource    = NULL;
        uint32_t                                        ipv4Addr;
        uint8_t                                         ipv6Addr[ 16 ];
-       char                                            recordName[ ( 16 * 4 ) + sizeof( kIP6ARPADomainStr ) ];
+       char                                            recordName[ kReverseIPv6DomainNameBufLen ];
        int                                                     useMainConnection;
        const char *                            endPtr;
        
@@ -4597,9 +5042,6 @@ static void       ReverseLookupCmd( void )
                &ipv4Addr, NULL, NULL, NULL, &endPtr );
        if( err || ( *endPtr != '\0' ) )
        {
-               char *          dst;
-               int                     i;
-               
                err = _StringToIPv6Address( gReverseLookup_IPAddr,
                        kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix | kStringToIPAddressFlagsNoScope,
                        ipv6Addr, NULL, NULL, NULL, &endPtr );
@@ -4609,24 +5051,11 @@ static void     ReverseLookupCmd( void )
                        err = kParamErr;
                        goto exit;
                }
-               dst = recordName;
-               for( i = 15; i >= 0; --i )
-               {
-                       *dst++ = kHexDigitsLowercase[ ipv6Addr[ i ] & 0x0F ];
-                       *dst++ = '.';
-                       *dst++ = kHexDigitsLowercase[ ipv6Addr[ i ] >> 4 ];
-                       *dst++ = '.';
-               }
-               strcpy_literal( dst, kIP6ARPADomainStr );
-               check( ( strlen( recordName ) + 1 ) <= sizeof( recordName ) );
+               _WriteReverseIPv6DomainNameString( ipv6Addr, recordName );
        }
        else
        {
-               SNPrintF( recordName, sizeof( recordName ), "%u.%u.%u.%u.in-addr.arpa.",
-                         ipv4Addr         & 0xFF,
-                       ( ipv4Addr >>  8 ) & 0xFF,
-                       ( ipv4Addr >> 16 ) & 0xFF,
-                       ( ipv4Addr >> 24 ) & 0xFF );
+               _WriteReverseIPv4DomainNameString( ipv4Addr, recordName );
        }
        
        // Set remaining parameters.
@@ -4894,12 +5323,6 @@ static void      RegisterKACmd( void )
        OSStatus                                        err;
        RegisterKACmdContext *          cmd = NULL;
        
-       if( !SOFT_LINK_HAS_FUNCTION( system_dnssd, DNSServiceSleepKeepalive_sockaddr ) )
-       {
-               FPrintF( stderr, "error: Failed to soft link DNSServiceSleepKeepalive_sockaddr from libsystem_dnssd.\n" );
-               err = kNotFoundErr;
-               goto exit;
-       }
        cmd = (RegisterKACmdContext *) calloc( 1, sizeof( *cmd ) );
        require_action( cmd, exit, err = kNoMemoryErr );
        
@@ -4975,10 +5398,18 @@ static void     _RegisterKACmdStart( void *inContext )
        FPrintF( stdout, "Start time: %{du:time}\n",    NULL );
        FPrintF( stdout, "---\n" );
        
-       err = soft_DNSServiceSleepKeepalive_sockaddr( &cmd->keepalive, cmd->flags, &cmd->local.sa, &cmd->remote.sa,
-               cmd->timeout, _RegisterKACmdKeepaliveCallback, cmd );
-       require_noerr( err, exit );
-       
+       if( __builtin_available( macOS 10.15.4, iOS 13.2.2, watchOS 6.2, tvOS 13.2, * ) )
+       {
+               err = DNSServiceSleepKeepalive_sockaddr( &cmd->keepalive, cmd->flags, &cmd->local.sa, &cmd->remote.sa, cmd->timeout,
+                       _RegisterKACmdKeepaliveCallback, cmd );
+               require_noerr( err, exit );
+       }
+       else
+       {
+               FPrintF( stderr, "error: DNSServiceSleepKeepalive_sockaddr() is not available on this OS.\n" );
+               err = kUnsupportedErr;
+               goto exit;
+       }
        err = DNSServiceSetDispatchQueue( cmd->keepalive, cmd->queue );
        require_noerr( err, exit );
        
@@ -5902,7 +6333,7 @@ static void       DNSQueryCmd( void )
        
        // Write query message.
        
-       check_compile_time_code( sizeof( context->msgBuf ) >= ( kDNSQueryMessageMaxLen + 2 ) );
+       check_compile_time_code( sizeof( context->msgBuf ) >= ( 2 + kDNSQueryMessageMaxLen + sizeof( dns_fixed_fields_opt ) ) );
        
        msgPtr = context->useTCP ? &context->msgBuf[ 2 ] : context->msgBuf;
        err = WriteDNSQueryMessage( msgPtr, context->queryID, (uint16_t) gDNSQuery_Flags, context->name, context->type,
@@ -5910,6 +6341,30 @@ static void      DNSQueryCmd( void )
        require_noerr( err, exit );
        check( msgLen <= UINT16_MAX );
        
+       if( gDNSQuery_DNSSEC )
+       {
+               DNSHeader * const                                       hdr = (DNSHeader *) msgPtr;
+               dns_fixed_fields_opt * const            opt = (dns_fixed_fields_opt *) &msgPtr[ msgLen ];
+               unsigned int                                            flags;
+               
+               memset( opt, 0, sizeof( *opt ) );
+               dns_fixed_fields_opt_set_type( opt, kDNSServiceType_OPT );
+               dns_fixed_fields_opt_set_udp_payload_size( opt, 512 );
+               dns_fixed_fields_opt_set_extended_flags( opt, kDNSExtendedFlag_DNSSECOK );
+               
+               flags = DNSHeaderGetFlags( hdr ) | kDNSHeaderFlag_AuthenticData;
+               DNSHeaderSetFlags( hdr, flags );
+               DNSHeaderSetAdditionalCount( hdr, 1 );
+               msgLen += sizeof( dns_fixed_fields_opt );
+       }
+       if( gDNSQuery_CheckingDisabled )
+       {
+               DNSHeader * const               hdr = (DNSHeader *) msgPtr;
+               unsigned int                    flags;
+               
+               flags = DNSHeaderGetFlags( hdr ) | kDNSHeaderFlag_CheckingDisabled;
+               DNSHeaderSetFlags( hdr, flags );
+       }
        if( context->useTCP )
        {
                WriteBig16( context->msgBuf, msgLen );
@@ -5924,7 +6379,7 @@ static void       DNSQueryCmd( void )
        
        if( gDNSQuery_Verbose )
        {
-               FPrintF( stdout, "DNS message to send:\n\n%{du:dnsmsg}", msgPtr, msgLen );
+               FPrintF( stdout, "DNS message to send:\n\n%{du:dnsmsg}\n", msgPtr, msgLen );
                FPrintF( stdout, "---\n" );
        }
        
@@ -6051,7 +6506,8 @@ static void       DNSQueryReadHandler( void *inContext )
        FPrintF( stdout, "Source:       %##a\n",                &context->serverAddr );
        FPrintF( stdout, "Message size: %zu\n",                 context->msgLen );
        FPrintF( stdout, "RTT:          %llu ms\n\n",   UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
-       FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgPtr, context->msgLen );
+       if( context->printRawRData )    FPrintF( stdout, "%{du:rdnsmsg}\n", context->msgPtr, context->msgLen );
+       else                                                    FPrintF( stdout, "%{du:dnsmsg}\n",  context->msgPtr, context->msgLen );
        
        if( ( context->msgLen >= kDNSHeaderLength ) && ( DNSHeaderGetID( (DNSHeader *) context->msgPtr ) == context->queryID ) )
        {
@@ -6252,7 +6708,7 @@ static void       DNSCryptCmd( void )
        err = _SocketWriteAll( sock, context->msgBuf, context->msgLen, 5 );
        require_noerr( err, exit );
        
-       err = SocketContextCreate( sock, context, &sockCtx );
+       sockCtx = SocketContextCreate( sock, context, &err );
        require_noerr( err, exit );
        sock = kInvalidSocketRef;
        
@@ -6308,7 +6764,8 @@ static void       DNSCryptReceiveCertHandler( void *inContext )
        FPrintF( stdout, "Source:       %##a\n",                &context->serverAddr );
        FPrintF( stdout, "Message size: %zu\n",                 context->msgLen );
        FPrintF( stdout, "RTT:          %llu ms\n\n",   UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
-       FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgBuf, context->msgLen );
+       if( context->printRawRData )    FPrintF( stdout, "%{du:rdnsmsg}\n", context->msgBuf, context->msgLen );
+       else                                                    FPrintF( stdout, "%{du:dnsmsg}\n",  context->msgBuf, context->msgLen );
        
        require_action_quiet( context->msgLen >= kDNSHeaderLength, exit, err = kSizeErr );
        
@@ -6432,7 +6889,8 @@ static void       DNSCryptReceiveResponseHandler( void *inContext )
        require_noerr( err, exit );
        
        response = plaintext + crypto_box_ZEROBYTES;
-       FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, response, (size_t)( end - response ) );
+       if( context->printRawRData )    FPrintF( stdout, "%{du:rdnsmsg}\n", response, (size_t)( end - response ) );
+       else                                                    FPrintF( stdout, "%{du:dnsmsg}\n",  response, (size_t)( end - response ) );
        Exit( kExitReason_ReceivedResponse );
        
 exit:
@@ -6621,7 +7079,7 @@ static OSStatus   DNSCryptSendQuery( DNSCryptContext *inContext )
        err = _SocketWriteAll( sock, inContext->msgBuf, inContext->msgLen, 5 );
        require_noerr( err, exit );
        
-       err = SocketContextCreate( sock, inContext, &sockCtx );
+       sockCtx = SocketContextCreate( sock, inContext, &err );
        require_noerr( err, exit );
        sock = kInvalidSocketRef;
        
@@ -6787,7 +7245,8 @@ static void       MDNSQueryCmd( void )
        
        check_compile_time_code( sizeof( context->msgBuf ) >= kDNSQueryMessageMaxLen );
        err = WriteDNSQueryMessage( context->msgBuf, kDefaultMDNSMessageID, kDefaultMDNSQueryFlags, context->qnameStr,
-               context->qtype, context->isQU ? ( kDNSServiceClass_IN | kQClassUnicastResponseBit ) : kDNSServiceClass_IN, &msgLen );
+               context->qtype, context->isQU ? ( kDNSServiceClass_IN | kMDNSClassUnicastResponseBit ) : kDNSServiceClass_IN,
+               &msgLen );
        require_noerr( err, exit );
        
        // Print prologue.
@@ -6841,7 +7300,7 @@ static void       MDNSQueryCmd( void )
        {
                SocketContext *         sockCtx;
                
-               err = SocketContextCreate( sockV4, context, &sockCtx );
+               sockCtx = SocketContextCreate( sockV4, context, &err );
                require_noerr( err, exit );
                sockV4 = kInvalidSocketRef;
                
@@ -6857,7 +7316,7 @@ static void       MDNSQueryCmd( void )
        {
                SocketContext *         sockCtx;
                
-               err = SocketContextCreate( sockV6, context, &sockCtx );
+               sockCtx = SocketContextCreate( sockV6, context, &err );
                require_noerr( err, exit );
                sockV6 = kInvalidSocketRef;
                
@@ -7045,14 +7504,16 @@ static void     MDNSQueryReadHandler( void *inContext )
                FPrintF( stdout, "---\n" );
                FPrintF( stdout, "Receive time: %{du:time}\n",  &now );
                FPrintF( stdout, "Source:       %##a\n",                &fromAddr );
-               FPrintF( stdout, "Message size: %zu\n\n%#.*{du:dnsmsg}",
-                       msgLen, context->printRawRData ? 1 : 0, context->msgBuf, msgLen );
+               FPrintF( stdout, "Message size: %zu\n\n",               msgLen );
+               if( context->printRawRData )    FPrintF( stdout, "%#{du:rdnsmsg}\n", context->msgBuf, msgLen );
+               else                                                    FPrintF( stdout, "%#{du:dnsmsg}\n",  context->msgBuf, msgLen );
        }
        
 exit:
        if( err ) exit( 1 );
 }
 
+#if( TARGET_OS_DARWIN )
 //===========================================================================================================================
 //     PIDToUUIDCmd
 //===========================================================================================================================
@@ -7072,61 +7533,47 @@ static void     PIDToUUIDCmd( void )
 exit:
        if( err ) exit( 1 );
 }
+#endif
 
 //===========================================================================================================================
 //     DNSServerCmd
 //===========================================================================================================================
 
-typedef struct DNSServerPrivate *              DNSServerRef;
-
 typedef struct
 {
        DNSServerRef                    server;                 // Reference to the DNS server.
-       dispatch_source_t               sigIntSource;   // Dispatch SIGINT source.
-       dispatch_source_t               sigTermSource;  // Dispatch SIGTERM source.
-       const char *                    domainOverride; // If non-NULL, the server is to use this domain instead of "d.test.".
+       dispatch_queue_t                queue;                  // Serial queue for server.
+       sockaddr_ip *                   addrArray;              // Server's addresses.
+       size_t                                  addrCount;              // Count of server's addresses.
+       dispatch_source_t               sourceSigInt;   // Dispatch source for SIGINT.
+       dispatch_source_t               sourceSigTerm;  // Dispatch source for SIGTERM.
+       const char *                    domainOverride; // If non-NULL, server is to use this domain instead of "d.test.".
+       dispatch_semaphore_t    doneSem;                // Semaphore to signal when the server is done.
+       OSStatus                                error;                  // Error encounted while running server.
+       Boolean                                 loopbackOnly;   // True if the server should be bound to the loopback interface.
 #if( TARGET_OS_DARWIN )
        dispatch_source_t               processMonitor; // Process monitor source for process being followed, if any.
-       pid_t                                   followPID;              // PID of process being followed, if any. (If it exits, we exit).
-       Boolean                                 addedResolver;  // True if system DNS settings contains a resolver entry for server.
+       pid_t                                   followPID;              // PID of process being followed, if any. If it exits, we exit.
+       Boolean                                 addedResolver;  // True if a resolver entry was added to the system DNS settings.
 #endif
-       Boolean                                 loopbackOnly;   // True if the server should be bound to the loopback interface.
        
 }      DNSServerCmdContext;
 
-typedef enum
-{
-       kDNSServerEvent_Started = 1,
-       kDNSServerEvent_Stopped = 2
-       
-}      DNSServerEventType;
-
-typedef void ( *DNSServerEventHandler_f )( DNSServerEventType inType, uintptr_t inEventData, void *inContext );
-
-CFTypeID       DNSServerGetTypeID( void );
-static OSStatus
-       DNSServerCreate(
-               dispatch_queue_t                inQueue,
-               DNSServerEventHandler_f inEventHandler,
-               void *                                  inEventContext,
-               unsigned int                    inResponseDelayMs,
-               uint32_t                                inDefaultTTL,
-               int                                             inPort,
-               Boolean                                 inLoopbackOnly,
-               const char *                    inDomain,
-               Boolean                                 inBadUDPMode,
-               DNSServerRef *                  outServer );
-static void    DNSServerStart( DNSServerRef inServer );
-static void    DNSServerStop( DNSServerRef inServer );
-
-#define ForgetDNSServer( X )           ForgetCustomEx( X, DNSServerStop, CFRelease )
-
-static void    DNSServerCmdContextFree( DNSServerCmdContext *inContext );
-static void    DNSServerCmdEventHandler( DNSServerEventType inType, uintptr_t inEventData, void *inContext );
-static void    DNSServerCmdSigIntHandler( void *inContext );
-static void    DNSServerCmdSigTermHandler( void *inContext );
+static void            _DNSServerCmdStart( void *inCtx );
+static void            _DNSServerCmdStop( DNSServerCmdContext *inCmd, OSStatus inError );
 #if( TARGET_OS_DARWIN )
-static void    DNSServerCmdFollowedProcessHandler( void *inContext );
+static OSStatus        _DNSServerCmdAddExtraLoopbackAddrs( sockaddr_ip *inAddrArray, size_t inAddrCount, uint16_t inPort );
+#endif
+static void            _DNSServerCmdContextFree( DNSServerCmdContext *inCmd );
+static void            _DNSServerCmdServerStartHandler( const sockaddr_ip *inAddrArray, size_t inAddrCount, void *inCtx );
+static void            _DNSServerCmdServerStopHandler( OSStatus inError, void *inCtx );
+static void            _DNSServerCmdSIGINTHandler( void *inCtx );
+static void            _DNSServerCmdSIGTERMHandler( void *inCtx );
+static void            _DNSServerCmdShutdown( DNSServerCmdContext *inCtx, int inSignal );
+#if( TARGET_OS_DARWIN )
+static void            _DNSServerCmdFollowedProcessHandler( void *inCtx );
+static OSStatus        _DNSServerCmdLoopbackResolverAdd( const char *inDomain, const sockaddr_ip *inAddrArray, size_t inAddrCount );
+static OSStatus        _DNSServerCmdLoopbackResolverRemove( void );
 #endif
 
 ulog_define_ex( kDNSSDUtilIdentifier, DNSServer, kLogLevelInfo, kLogFlags_None, "DNSServer", NULL );
@@ -7135,209 +7582,411 @@ ulog_define_ex( kDNSSDUtilIdentifier, DNSServer, kLogLevelInfo, kLogFlags_None,
 static void    DNSServerCmd( void )
 {
        OSStatus                                        err;
-       DNSServerCmdContext *           context = NULL;
+       DNSServerCmdContext *           cmd = NULL;
+       sockaddr_ip *                           sip;
+       size_t                                          addrCount;
+       Boolean                                         listenOnV4, listenOnV6;
+#if( TARGET_OS_DARWIN )
+       size_t                      extraLoopbackV6Count;
+#endif
        
-       if( gDNSServer_Foreground )
-       {
-               LogControl( "DNSServer:output=file;stdout,DNSServer:flags=time;prefix" );
-       }
+       // Check command arguments.
        
+       if( gDNSServer_Foreground ) LogControl( "DNSServer:output=file;stdout,DNSServer:flags=time;prefix" );
        err = CheckIntegerArgument( gDNSServer_ResponseDelayMs, "response delay (ms)", 0, INT_MAX );
        require_noerr_quiet( err, exit );
        
        err = CheckIntegerArgument( gDNSServer_DefaultTTL, "default TTL", 0, INT32_MAX );
        require_noerr_quiet( err, exit );
        
-       err = CheckIntegerArgument( gDNSServer_Port, "port number", -UINT16_MAX, UINT16_MAX );
+       err = CheckIntegerArgument( gDNSServer_Port, "port number", 0, UINT16_MAX );
        require_noerr_quiet( err, exit );
        
-       context = (DNSServerCmdContext *) calloc( 1, sizeof( *context ) );
-       require_action( context, exit, err = kNoMemoryErr );
-       
-       context->domainOverride = gDNSServer_DomainOverride;
-       context->loopbackOnly   = gDNSServer_LoopbackOnly ? true : false;
+       listenOnV4 = ( gDNSServer_ListenOnV4 || !gDNSServer_ListenOnV6 ) ? true : false;
+       listenOnV6 = ( gDNSServer_ListenOnV6 || !gDNSServer_ListenOnV4 ) ? true : false;
+#if( TARGET_OS_DARWIN )
+       if( gDNSServer_LoopbackOnly && listenOnV6 )
+       {
+               err = CheckIntegerArgument( gDNSServer_ExtraV6Count, "extra IPv6", 0, 100 );
+               require_noerr_quiet( err, exit );
+               extraLoopbackV6Count = (size_t) gDNSServer_ExtraV6Count;
+               if( extraLoopbackV6Count > 0 )
+               {
+                       err = CheckRootUser();
+                       require_noerr_quiet( err, exit );
+               }
+       }
+       else
+       {
+               extraLoopbackV6Count = 0;
+       }
+#endif
+       cmd = (DNSServerCmdContext *) calloc( 1, sizeof( *cmd ) );
+       require_action( cmd, exit, err = kNoMemoryErr );
        
+       cmd->domainOverride     = gDNSServer_DomainOverride;
+       cmd->loopbackOnly       = gDNSServer_LoopbackOnly ? true : false;
 #if( TARGET_OS_DARWIN )
+       cmd->followPID          = -1;
        if( gDNSServer_FollowPID )
        {
-               context->followPID = _StringToPID( gDNSServer_FollowPID, &err );
-               if( err || ( context->followPID < 0 ) )
+               cmd->followPID = _StringToPID( gDNSServer_FollowPID, &err );
+               if( err || ( cmd->followPID < 0 ) )
                {
                        FPrintF( stderr, "error: Invalid follow PID: %s\n", gDNSServer_FollowPID );
                        err = kParamErr;
                        goto exit;
                }
+       }
+#endif
+       
+       // Set up IP addresses.
+       
+       if( listenOnV4 ) ++cmd->addrCount;
+       if( listenOnV6 ) ++cmd->addrCount;
+#if( TARGET_OS_DARWIN )
+       cmd->addrCount += extraLoopbackV6Count;
+#endif
+       check( cmd->addrCount > 0 );
+       cmd->addrArray = (sockaddr_ip *) calloc( cmd->addrCount, sizeof( *cmd->addrArray ) );
+       require_action( cmd->addrArray, exit, err = kNoMemoryErr );
+       
+       addrCount = 0;
+       if( listenOnV4 )
+       {
+               sip = &cmd->addrArray[ addrCount++ ];
+               _SockAddrInitIPv4( &sip->v4, cmd->loopbackOnly ? INADDR_LOOPBACK : INADDR_ANY, (uint16_t) gDNSServer_Port );
+       }
+       if( listenOnV6 )
+       {
+               const struct in6_addr * const           addr = cmd->loopbackOnly ? &in6addr_loopback : &in6addr_any;
                
-               err = DispatchProcessMonitorCreate( context->followPID, DISPATCH_PROC_EXIT, dispatch_get_main_queue(),
-                       DNSServerCmdFollowedProcessHandler, NULL, context, &context->processMonitor );
-               require_noerr( err, exit );
-               dispatch_resume( context->processMonitor );
+               sip = &cmd->addrArray[ addrCount++ ];
+               _SockAddrInitIPv6( &sip->v6, addr->s6_addr, 0, (uint16_t) gDNSServer_Port );
        }
-       else
+#if( TARGET_OS_DARWIN )
+       if( extraLoopbackV6Count > 0 )
        {
-               context->followPID = -1;
+               err = _DNSServerCmdAddExtraLoopbackAddrs( &cmd->addrArray[ addrCount ], extraLoopbackV6Count,
+                       (uint16_t) gDNSServer_Port );
+               require_noerr( err, exit );
+               addrCount += extraLoopbackV6Count;
        }
 #endif
+       check( addrCount == cmd->addrCount );
+       
+       // Start command.
+       
+       cmd->queue = dispatch_queue_create( "com.apple.dnssdutil.server-command", DISPATCH_QUEUE_SERIAL );
+       require_action( cmd->queue, exit, err = kNoResourcesErr );
+       
+       cmd->doneSem = dispatch_semaphore_create( 0 );
+       require_action( cmd->doneSem, exit, err = kNoResourcesErr );
+       
+       dispatch_async_f( cmd->queue, cmd, _DNSServerCmdStart );
+    dispatch_semaphore_wait( cmd->doneSem, DISPATCH_TIME_FOREVER );
+       
+exit:
+       if( err ) FPrintF( stderr, "Failed to start DNS server: %#m\n", err );
+       if( cmd ) _DNSServerCmdContextFree( cmd );
+       gExitCode = err ? 1 : 0;
+}
+
+//===========================================================================================================================
+
+static void    _DNSServerCmdStart( void *inCtx )
+{
+       OSStatus                                                err;
+       DNSServerCmdContext * const             cmd = (DNSServerCmdContext *) inCtx;
+       size_t                                                  i;
        
        signal( SIGINT, SIG_IGN );
-       err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), DNSServerCmdSigIntHandler, context,
-               &context->sigIntSource );
+       err = DispatchSignalSourceCreate( SIGINT, cmd->queue, _DNSServerCmdSIGINTHandler, cmd, &cmd->sourceSigInt );
        require_noerr( err, exit );
-       dispatch_resume( context->sigIntSource );
+       dispatch_resume( cmd->sourceSigInt );
        
        signal( SIGTERM, SIG_IGN );
-       err = DispatchSignalSourceCreate( SIGTERM, dispatch_get_main_queue(), DNSServerCmdSigTermHandler, context,
-               &context->sigTermSource );
+       err = DispatchSignalSourceCreate( SIGTERM, cmd->queue, _DNSServerCmdSIGTERMHandler, cmd, &cmd->sourceSigTerm );
        require_noerr( err, exit );
-       dispatch_resume( context->sigTermSource );
+       dispatch_resume( cmd->sourceSigTerm );
        
-       err = DNSServerCreate( dispatch_get_main_queue(), DNSServerCmdEventHandler, context,
-               (unsigned int) gDNSServer_ResponseDelayMs, (uint32_t) gDNSServer_DefaultTTL, gDNSServer_Port, context->loopbackOnly,
-               context->domainOverride, gDNSServer_BadUDPMode ? true : false, &context->server );
+#if( TARGET_OS_DARWIN )
+       if( cmd->followPID >= 0 )
+       {
+               err = DispatchProcessMonitorCreate( cmd->followPID, DISPATCH_PROC_EXIT, cmd->queue,
+                       _DNSServerCmdFollowedProcessHandler, NULL, cmd, &cmd->processMonitor );
+               require_noerr( err, exit );
+               dispatch_resume( cmd->processMonitor );
+       }
+#endif
+       err = _DNSServerCreate( cmd->queue, _DNSServerCmdServerStartHandler, _DNSServerCmdServerStopHandler, cmd,
+               (unsigned int) gDNSServer_ResponseDelayMs, (uint32_t) gDNSServer_DefaultTTL, cmd->addrArray, cmd->addrCount,
+               cmd->domainOverride, gDNSServer_BadUDPMode ? true : false, &cmd->server );
        require_noerr( err, exit );
        
-       DNSServerStart( context->server );
-       dispatch_main();
+       for( i = 0; i < gDNSServer_IgnoredQTypesCount; ++i )
+       {
+               uint16_t                qtype;
+               
+               err = RecordTypeFromArgString( gDNSServer_IgnoredQTypes[ i ], &qtype );
+               require_noerr( err, exit );
+               
+               err = _DNSServerSetIgnoredQType( cmd->server, qtype );
+               require_noerr( err, exit );
+       }
+       _DNSServerStart( cmd->server );
        
 exit:
-       FPrintF( stderr, "Failed to start DNS server: %#m\n", err );
-       if( context ) DNSServerCmdContextFree( context );
-       if( err ) exit( 1 );
+       if( err ) _DNSServerCmdStop( cmd, err );
 }
 
-//===========================================================================================================================
-//     DNSServerCmdContextFree
 //===========================================================================================================================
 
-static void    DNSServerCmdContextFree( DNSServerCmdContext *inContext )
+static void    _DNSServerCmdStop( DNSServerCmdContext *inCmd, OSStatus inError )
 {
-       ForgetCF( &inContext->server );
-       dispatch_source_forget( &inContext->sigIntSource );
-       dispatch_source_forget( &inContext->sigTermSource );
+       if( !inCmd->error ) inCmd->error = inError;
+       check( !inCmd->server );
+       dispatch_source_forget( &inCmd->sourceSigInt );
+       dispatch_source_forget( &inCmd->sourceSigTerm );
 #if( TARGET_OS_DARWIN )
-       dispatch_source_forget( &inContext->processMonitor );
+       dispatch_source_forget( &inCmd->processMonitor );
 #endif
-       free( inContext );
+       dispatch_semaphore_signal( inCmd->doneSem );
 }
 
-//===========================================================================================================================
-//     DNSServerCmdEventHandler
-//===========================================================================================================================
-
 #if( TARGET_OS_DARWIN )
-static OSStatus        _DNSServerCmdLoopbackResolverAdd( const char *inDomain, int inPort );
-static OSStatus        _DNSServerCmdLoopbackResolverRemove( void );
-#endif
+//===========================================================================================================================
 
-static void    DNSServerCmdEventHandler( DNSServerEventType inType, uintptr_t inEventData, void *inContext )
+static OSStatus        _DNSServerCmdAddExtraLoopbackAddrs( sockaddr_ip *inAddrArray, size_t inAddrCount, uint16_t inPort )
 {
-       OSStatus                                                err;
-       DNSServerCmdContext * const             context = (DNSServerCmdContext *) inContext;
+       OSStatus                err;
+       uint8_t                 addrV6[ 16 ];
+       size_t                  i;
        
-       if( inType == kDNSServerEvent_Started )
+       check_compile_time_code( sizeof( kExtraLoopbackIPv6Prefix ) == 8 );
+       memcpy( addrV6, kExtraLoopbackIPv6Prefix, 8 );  // 64-bit prefix
+       RandomBytes( &addrV6[ 8 ], 4 );                                 // 32-bit random
+       WriteBig32( &addrV6[ 12 ], 2 );                                 // 16-bit base offset starting at 2
+       for( i = 0; i < inAddrCount; ++i )
        {
-       #if( TARGET_OS_DARWIN )
-               const int               port = (int) inEventData;
+               struct sockaddr_in6 * const             sin6 = &inAddrArray[ i ].v6;
                
-               err = _DNSServerCmdLoopbackResolverAdd( context->domainOverride ? context->domainOverride : "d.test.", port );
-               if( err )
-               {
-                       ds_ulog( kLogLevelError, "Failed to add loopback resolver to DNS configuration for \"d.test.\" domain: %#m\n",
-                               err );
-                       if( context->loopbackOnly ) ForgetDNSServer( &context->server );
-               }
-               else
-               {
-                       context->addedResolver = true;
-               }
-       #endif
+               err = _InterfaceIPv6AddressAdd( "lo0", addrV6, kExtraLoopbackIPv6PrefixBitLen );
+               require_noerr( err, exit );
+               
+               _SockAddrInitIPv6( sin6, addrV6, 0, inPort );
+               BigEndianIntegerIncrement( addrV6, sizeof( addrV6 ) );
        }
-       else if( inType == kDNSServerEvent_Stopped )
+       err = kNoErr;
+       
+exit:
+       return( err );
+}
+#endif
+
+//===========================================================================================================================
+
+static void    _DNSServerCmdContextFree( DNSServerCmdContext *inCmd )
+{
+#if( TARGET_OS_DARWIN )
+       size_t          i;
+#endif
+       
+       check( !inCmd->server );
+       check( !inCmd->sourceSigInt );
+       check( !inCmd->sourceSigTerm );
+#if( TARGET_OS_DARWIN )
+       check( !inCmd->processMonitor );
+       for( i = 0; i < inCmd->addrCount; ++i )
        {
-               const OSStatus          stopError = (OSStatus) inEventData;
+               OSStatus                                                                err;
+               const struct sockaddr_in6 * const               sin6 = &inCmd->addrArray[ i ].v6;
+               int                                                                             cmp;
                
-               if( stopError ) ds_ulog( kLogLevelError, "The server stopped unexpectedly with error: %#m.\n", stopError );
-               
-               err = kNoErr;
-       #if( TARGET_OS_DARWIN )
-               if( context->addedResolver )
+               if( sin6->sin6_family != AF_INET6 ) continue;
+               cmp = memcmp( sin6->sin6_addr.s6_addr, kExtraLoopbackIPv6Prefix, sizeof( kExtraLoopbackIPv6Prefix ) );
+               if( cmp != 0 ) continue;
+               err = _InterfaceIPv6AddressRemove( "lo0", sin6->sin6_addr.s6_addr );
+               check_noerr( err );
+       }
+#endif
+       dispatch_forget( &inCmd->queue );
+       dispatch_forget( &inCmd->doneSem );
+       ForgetMem( &inCmd->addrArray );
+       free( inCmd );
+}
+
+//===========================================================================================================================
+
+static void    _DNSServerCmdServerStartHandler( const sockaddr_ip *inServerArray, size_t inServerCount, void *inCtx )
+{
+#if( TARGET_OS_DARWIN )
+       OSStatus                                                err;
+       DNSServerCmdContext * const             cmd             = (DNSServerCmdContext *) inCtx;
+       const char * const                              domain  = cmd->domainOverride ? cmd->domainOverride : "d.test.";
+       
+       if( cmd->loopbackOnly )
+       {
+               err = _DNSServerCmdLoopbackResolverAdd( domain, inServerArray, inServerCount );
+               if( err )
                {
-                       err = _DNSServerCmdLoopbackResolverRemove();
-                       if( err )
-                       {
-                               ds_ulog( kLogLevelError, "Failed to remove loopback resolver from DNS configuration: %#m\n", err );
-                       }
-                       else
-                       {
-                               context->addedResolver = false;
-                       }
+                       ds_ulog( kLogLevelError, "Failed to add loopback resolver to DNS configuration for \"%s\" domain: %#m\n",
+                               domain, err );
+                       cmd->error = err;
+                       DNSServerForget( &cmd->server );
                }
-               else if( context->loopbackOnly )
+               else
                {
-                       err = kUnknownErr;
+                       cmd->addedResolver = true;
                }
-       #endif
-               DNSServerCmdContextFree( context );
-               exit( ( stopError || err ) ? 1 : 0 );
        }
+#else
+       Unused( inServerArray );
+       Unused( inServerCount );
+       Unused( inCtx );
+#endif
+}
+
+//===========================================================================================================================
+
+static void    _DNSServerCmdServerStopHandler( OSStatus inError, void *inCtx )
+{
+       DNSServerCmdContext * const             cmd = (DNSServerCmdContext *) inCtx;
+       
+       if( inError ) ds_ulog( kLogLevelError, "The server stopped unexpectedly with error: %#m.\n", inError );
+#if( TARGET_OS_DARWIN )
+       if( cmd->addedResolver )
+       {
+               OSStatus                err;
+               err = _DNSServerCmdLoopbackResolverRemove();
+               if( err ) ds_ulog( kLogLevelError, "Failed to remove loopback resolver from DNS configuration: %#m\n", err );
+               if( !err ) cmd->addedResolver = false;
+       }
+#endif
+       _DNSServerCmdStop( cmd, cmd->error ? cmd->error : inError );
+}
+
+//===========================================================================================================================
+
+static void    _DNSServerCmdSIGINTHandler( void *inCtx )
+{
+       _DNSServerCmdShutdown( (DNSServerCmdContext *) inCtx, SIGINT );
+}
+
+//===========================================================================================================================
+
+static void    _DNSServerCmdSIGTERMHandler( void *inCtx )
+{
+       _DNSServerCmdShutdown( (DNSServerCmdContext *) inCtx, SIGTERM );
+}
+
+//===========================================================================================================================
+
+static void    _DNSServerCmdShutdown( DNSServerCmdContext *inCmd, int inSignal )
+{
+       dispatch_source_forget( &inCmd->sourceSigInt );
+       dispatch_source_forget( &inCmd->sourceSigTerm );
+#if( TARGET_OS_DARWIN )
+       dispatch_source_forget( &inCmd->processMonitor );
+       if( inSignal == 0 )
+       {
+               ds_ulog( kLogLevelNotice, "Exiting: followed process (%lld) exited\n", (int64_t) inCmd->followPID );
+       }
+       else
+#endif
+       {
+               const char *            sigName;
+               
+               switch( inSignal )
+               {
+                       case SIGINT:    sigName = "SIGINT";             break;
+                       case SIGTERM:   sigName = "SIGTERM";    break;
+                       default:                sigName = "???";                break;
+               }
+               ds_ulog( kLogLevelNotice, "Exiting: received signal %d (%s)\n", inSignal, sigName );
+       }
+       DNSServerForget( &inCmd->server );
 }
 
 #if( TARGET_OS_DARWIN )
 //===========================================================================================================================
-//     _DNSServerCmdLoopbackResolverAdd
+
+static void    _DNSServerCmdFollowedProcessHandler( void *inCtx )
+{
+       DNSServerCmdContext * const             cmd = (DNSServerCmdContext *) inCtx;
+       
+       if( dispatch_source_get_data( cmd->processMonitor ) & DISPATCH_PROC_EXIT ) _DNSServerCmdShutdown( cmd, 0 );
+}
 //===========================================================================================================================
 
-static OSStatus        _DNSServerCmdLoopbackResolverAdd( const char *inDomain, int inPort )
+#define kDNSServerServiceID            CFSTR( "com.apple.dnssdutil.server" )
+
+static OSStatus        _DNSServerCmdLoopbackResolverAdd( const char *inDomain, const sockaddr_ip *inAddrArray, size_t inAddrCount )
 {
        OSStatus                                err;
-       SCDynamicStoreRef               store;
-       CFPropertyListRef               plist           = NULL;
-       CFStringRef                             key                     = NULL;
-       const uint32_t                  loopbackV4      = htonl( INADDR_LOOPBACK );
-       Boolean                                 success;
+       CFPropertyListRef               plist   = NULL;
+       SCDynamicStoreRef               store   = NULL;
+       CFStringRef                             key             = NULL;
+       CFMutableArrayRef               addresses;
+       size_t                                  i;
+       Boolean                                 ok;
+       char                                    dnssecDomainStr[ kDNSServiceMaxDomainName ];
        
-       store = SCDynamicStoreCreate( NULL, CFSTR( kDNSSDUtilIdentifier ), NULL, NULL );
-       err = map_scerror( store );
+       require_action_quiet( inAddrCount > 0, exit, err = kCountErr );
+       
+       err = DomainNameToString( kDNSServerDomain_DNSSEC, NULL, dnssecDomainStr, NULL );
        require_noerr( err, exit );
        
        err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
                "{"
-                       "%kO="
+                       "%kO="          // Domain array.
                        "["
-                               "%s"
+                               "%s"    // Non-DNSSEC domain.
+                               "%s"    // DNSSEC domain.
+                               "%O"    // Reverse IPv4 domain.
+                               "%O"    // Reverse IPv6 domain.
                        "]"
-                       "%kO="
-                       "["
-                               "%.4a"
-                               "%.16a"
-                       "]"
-                       "%kO=%i"
-                       "%kO=%O"
-                       "%kO=%O"
+                       "%kO=[%@]"      // Server IP addresses.
+                       "%kO=%i"        // Port number.
+                       "%kO=%O"        // Interface name.
+                       "%kO=%O"        // Service ID.
                "}",
                kSCPropNetDNSSupplementalMatchDomains,  inDomain,
-               kSCPropNetDNSServerAddresses,                   &loopbackV4, in6addr_loopback.s6_addr,
-               kSCPropNetDNSServerPort,                                inPort,
+                                                                                               dnssecDomainStr,
+                                                                                               CFSTR( kDNSServerReverseIPv4DomainStr ),
+                                                                                               CFSTR( kDNSServerReverseIPv6DomainStr ),
+               kSCPropNetDNSServerAddresses,                   &addresses,
+               kSCPropNetDNSServerPort,                                SockAddrGetPort( inAddrArray ),
                kSCPropInterfaceName,                                   CFSTR( "lo0" ),
-       kSCPropNetDNSConfirmedServiceID,                        CFSTR( "com.apple.dnssdutil.server" ) );
+               kSCPropNetDNSConfirmedServiceID,                kDNSServerServiceID );
+       require_noerr( err, exit );
+       
+       for( i = 0; i < inAddrCount; ++i )
+       {
+               sockaddr_ip             sip;
+               
+               SockAddrCopy( &inAddrArray[ i ], &sip );
+               SockAddrSetPort( &sip, 0 );
+               err = CFPropertyListAppendFormatted( kCFAllocatorDefault, addresses, "%##a", &sip );
+               require_noerr( err, exit );
+       }
+       store = SCDynamicStoreCreate( NULL, CFSTR( kDNSSDUtilIdentifier ), NULL, NULL );
+       err = map_scerror( store );
        require_noerr( err, exit );
        
-       key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState,
-               CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS );
+       key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, kDNSServerServiceID, kSCEntNetDNS );
        require_action( key, exit, err = kUnknownErr );
        
-       success = SCDynamicStoreSetValue( store, key, plist );
-       require_action( success, exit, err = kUnknownErr );
+       ok = SCDynamicStoreSetValue( store, key, plist );
+       require_action( ok, exit, err = kUnknownErr );
        
 exit:
-       CFReleaseNullSafe( store );
        CFReleaseNullSafe( plist );
+       CFReleaseNullSafe( store );
        CFReleaseNullSafe( key );
        return( err );
 }
 
-//===========================================================================================================================
-//     _DNSServerCmdLoopbackResolverRemove
 //===========================================================================================================================
 
 static OSStatus        _DNSServerCmdLoopbackResolverRemove( void )
@@ -7351,8 +8000,7 @@ static OSStatus   _DNSServerCmdLoopbackResolverRemove( void )
        err = map_scerror( store );
        require_noerr( err, exit );
        
-       key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState,
-               CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS );
+       key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, kDNSServerServiceID, kSCEntNetDNS );
        require_action( key, exit, err = kUnknownErr );
        
        success = SCDynamicStoreRemoveValue( store, key );
@@ -7363,127 +8011,132 @@ exit:
        CFReleaseNullSafe( key );
        return( err );
 }
-#endif
+#endif // TARGET_OS_DARWIN
 
 //===========================================================================================================================
-//     DNSServerCmdSigIntHandler
-//===========================================================================================================================
-
-static void    _DNSServerCmdShutdown( DNSServerCmdContext *inContext, int inSignal );
-
-static void    DNSServerCmdSigIntHandler( void *inContext )
-{
-       _DNSServerCmdShutdown( (DNSServerCmdContext *) inContext, SIGINT );
-}
 
-//===========================================================================================================================
-//     DNSServerCmdSigTermHandler
-//===========================================================================================================================
+typedef struct DNSServerConnectionPrivate *            DNSServerConnectionRef;
 
-static void    DNSServerCmdSigTermHandler( void *inContext )
+typedef struct DNSServerDelayedResponse                DNSServerDelayedResponse;
+struct DNSServerDelayedResponse
 {
-       _DNSServerCmdShutdown( (DNSServerCmdContext *) inContext, SIGTERM );
-}
-
-#if( TARGET_OS_DARWIN )
-//===========================================================================================================================
-//     DNSServerCmdFollowedProcessHandler
-//===========================================================================================================================
+       DNSServerDelayedResponse *              next;           // Next delayed response in list.
+       sockaddr_ip                                             client;         // Destination address.
+       uint64_t                                                dueTicks;       // Time, in ticks, when send is due.
+       uint8_t *                                               msgPtr;         // Response message pointer.
+       size_t                                                  msgLen;         // Response message length.
+       size_t                                                  index;          // Address index.
+       SocketRef                                               sock;           // Socket to use for send.
+};
 
-static void    DNSServerCmdFollowedProcessHandler( void *inContext )
+struct DNSServerPrivate
 {
-       DNSServerCmdContext * const             context = (DNSServerCmdContext *) inContext;
-       
-       if( dispatch_source_get_data( context->processMonitor ) & DISPATCH_PROC_EXIT ) _DNSServerCmdShutdown( context, 0 );
-}
-#endif
-
-//===========================================================================================================================
-//     _DNSServerCmdExternalExit
-//===========================================================================================================================
-
-#define SignalNumberToString( X ) (            \
-       ( (X) == SIGINT )  ? "SIGINT"  :        \
-       ( (X) == SIGTERM ) ? "SIGTERM" :        \
-                                                "???" )
+       CFRuntimeBase                                   base;                           // CF object base.
+       uint8_t *                                               domain;                         // Parent domain of server's resource records. (malloc'd)
+       dispatch_queue_t                                queue;                          // Queue for DNS server's events.
+       sockaddr_ip *                                   addrArray;                      // Array of addresses to listen on.
+       size_t                                                  addrCount;                      // Number of addresses to listen on.
+       dispatch_source_t *                             readSourceArrayUDP;     // Array of read sources for UDP sockets.
+       dispatch_source_t *                             readSourceArrayTCP;     // Array of read sources for TCP listening sockets.
+       DNSServerConnectionRef                  connectionList;         // List of TCP connections.
+       dispatch_source_t                               connectionTimer;        // Timer for idle connections.
+       DNSServerStartHandler_f                 startHandler;           // User's activation handler.
+       DNSServerStopHandler_f                  stopHandler;            // User's invalidation handler.
+    void *                                                     userContext;            // User's handler context.
+       DNSServerDelayedResponse *              responseList;           // List of delayed UDP responses.
+       dispatch_source_t                               responseTimer;          // Timer for when to send next delayed response.
+       int *                                                   ignoredQTypes;          // Array of QTYPEs to ignore.
+       size_t                                                  ignoredQTypeCount;      // Number of QTYPEs to ignore.
+       unsigned int                                    responseDelayMs;        // Response delay in milliseconds.
+       uint32_t                                                defaultTTL;                     // Default TTL for resource records.
+       uint32_t                                                serial;                         // Serial number for SOA record.
+       OSStatus                                                stopErr;                        // The error, if any, that caused the server to stop.
+       Boolean                                                 started;                        // True if the server was started.
+       Boolean                                                 stopped;                        // True if the server was stopped.
+       Boolean                                                 badUDPMode;                     // True if the server runs in Bad UDP mode.
+};
 
-static void    _DNSServerCmdShutdown( DNSServerCmdContext *inContext, int inSignal )
-{
-       dispatch_source_forget( &inContext->sigIntSource );
-       dispatch_source_forget( &inContext->sigTermSource );
-#if( TARGET_OS_DARWIN )
-       dispatch_source_forget( &inContext->processMonitor );
-       
-       if( inSignal == 0 )
-       {
-               ds_ulog( kLogLevelNotice, "Exiting: followed process (%lld) exited\n", (int64_t) inContext->followPID );
-       }
-       else
-#endif
-       {
-               ds_ulog( kLogLevelNotice, "Exiting: received signal %d (%s)\n", inSignal, SignalNumberToString( inSignal ) );
-       }
-       
-       ForgetDNSServer( &inContext->server );
-}
+static void    _DNSServerUDPReadHandler( void *inContext );
+static OSStatus
+       _DNSServerScheduleDelayedResponse(
+               DNSServerRef                    inServer,
+               SocketRef                               inSock,
+               const struct sockaddr * inDestAddr,
+               uint8_t *                               inMsgPtr,
+               size_t                                  inMsgLen,
+               size_t                                  inIndex );
+static void    _DNSServerDelayedResponseFree( DNSServerDelayedResponse *inResponse );
+static void    _DNSServerDelayedResponseListFree( DNSServerDelayedResponse *inList );
+static void    _DNSServerTCPAcceptHandler( void *inContext );
+static void    _DNSServerConnectionTimerHandler( void *inContext );
+static void    _DNSServerResetConnectionTimerMs( DNSServerRef me, uint64_t inTimeoutMs );
+static OSStatus
+       _DNSServerAnswerQuery(
+               DNSServerRef    inServer,
+               const uint8_t * inMsgPtr,
+               size_t                  inMsgLen,
+               size_t                  inIndex,
+               Boolean                 inForTCP,
+               uint8_t **              outResponsePtr,
+               size_t *                outResponseLen );
 
-//===========================================================================================================================
-//     DNSServerCreate
-//===========================================================================================================================
+#define _DNSServerAnswerQueryForUDP( SERVER, QUERY_PTR, QUERY_LEN, INDEX, RESPONSE_PTR, RESPONSE_LEN ) \
+       _DNSServerAnswerQuery( SERVER, QUERY_PTR, QUERY_LEN, INDEX, false, RESPONSE_PTR, RESPONSE_LEN )
 
-#define kDDotTestDomainName            (const uint8_t *) "\x01" "d" "\x04" "test"
+#define _DNSServerAnswerQueryForTCP( SERVER, QUERY_PTR, QUERY_LEN, INDEX, RESPONSE_PTR, RESPONSE_LEN ) \
+       _DNSServerAnswerQuery( SERVER, QUERY_PTR, QUERY_LEN, INDEX, true, RESPONSE_PTR, RESPONSE_LEN )
 
-typedef struct DNSDelayedResponse              DNSDelayedResponse;
-struct DNSDelayedResponse
-{
-       DNSDelayedResponse *            next;
-       sockaddr_ip                                     destAddr;
-       uint64_t                                        targetTicks;
-       uint8_t *                                       msgPtr;
-       size_t                                          msgLen;
-};
+CF_CLASS_DEFINE( DNSServer );
 
-struct DNSServerPrivate
+struct DNSServerConnectionPrivate
 {
        CFRuntimeBase                           base;                           // CF object base.
-       uint8_t *                                       domain;                         // Parent domain of server's resource records.
-       dispatch_queue_t                        queue;                          // Queue for DNS server's events.
-       dispatch_source_t                       readSourceUDPv4;        // Read source for IPv4 UDP socket.
-       dispatch_source_t                       readSourceUDPv6;        // Read source for IPv6 UDP socket.
-       dispatch_source_t                       readSourceTCPv4;        // Read source for IPv4 TCP socket.
-       dispatch_source_t                       readSourceTCPv6;        // Read source for IPv6 TCP socket.
-       SocketRef                                       sockUDPv4;
-       SocketRef                                       sockUDPv6;
-       DNSServerEventHandler_f         eventHandler;
-       void *                                          eventContext;
-       DNSDelayedResponse *            responseList;
-       dispatch_source_t                       responseTimer;
-       unsigned int                            responseDelayMs;
-       uint32_t                                        defaultTTL;
-       uint32_t                                        serial;                         // Serial number for SOA record.
-       int                                                     port;                           // Port to use for receiving and sending DNS messages.
-       OSStatus                                        stopError;
-       Boolean                                         stopped;
-       Boolean                                         loopbackOnly;
-       Boolean                                         badUDPMode;                     // True if the server runs in Bad UDP mode.
+       DNSServerConnectionRef          next;                           // Next connection in list.
+       DNSServerRef                            server;                         // Back pointer to server object.
+       sockaddr_ip                                     local;                          // TCP connection's local address.
+       sockaddr_ip                                     remote;                         // TCP connection's remote address.
+       size_t                                          index;                          // Sever address index.
+       uint64_t                                        expirationTicks;        // Expiration time in ticks. Renewed upon receiving a complete query.
+       dispatch_source_t                       readSource;                     // Dispatch read source for TCP connection.
+       dispatch_source_t                       writeSource;            // Dispatch write source for TCP connection.
+       size_t                                          offset;                         // Offset into receive buffer.
+       void *                                          msgPtr;                         // Pointer to dynamically allocated message buffer.
+       size_t                                          msgLen;                         // Length of message buffer.
+       iovec_t                                         iov[ 2 ];                       // IO vector for writing response message.
+       iovec_t *                                       iovPtr;                         // Vector pointer for SocketWriteData().
+       int                                                     iovCount;                       // Vector count for SocketWriteData().
+       Boolean                                         readSuspended;          // True if the read source is currently suspended.
+       Boolean                                         writeSuspended;         // True if the write source is currently suspended.
+       Boolean                                         haveLen;                        // True if currently receiving message instead of message length.
+       uint8_t                                         lenBuf[ 2 ];            // Buffer for two-octet message length field.
 };
 
-static void    _DNSServerUDPReadHandler( void *inContext );
-static void    _DNSServerTCPReadHandler( void *inContext );
-static void    _DNSDelayedResponseFree( DNSDelayedResponse *inResponse );
-static void    _DNSDelayedResponseFreeList( DNSDelayedResponse *inList );
-
-CF_CLASS_DEFINE( DNSServer );
+static CFTypeID        DNSServerConnectionGetTypeID( void );
+static OSStatus
+       _DNSServerConnectionCreate(
+               DNSServerRef                            inServer,
+               const struct sockaddr *         inLocal,
+               const struct sockaddr *         inRemote,
+               size_t                                          inIndex,
+               DNSServerConnectionRef *        outCnx );
+static OSStatus        _DNSServerConnectionStart( DNSServerConnectionRef inCnx, SocketRef inSock );
+static void            _DNSServerConnectionStop( DNSServerConnectionRef inCnx, Boolean inRemoveFromList );
+static void            _DNSServerConnectionReadHandler( void *inContext );
+static void            _DNSServerConnectionWriteHandler( void *inContext );
+static void            _DNSServerConnectionRenewExpiration( DNSServerConnectionRef inCnx );
+
+CF_CLASS_DEFINE( DNSServerConnection );
 
 static OSStatus
-       DNSServerCreate(
+       _DNSServerCreate(
                dispatch_queue_t                inQueue,
-               DNSServerEventHandler_f inEventHandler,
-               void *                                  inEventContext,
+               DNSServerStartHandler_f inStartHandler,
+               DNSServerStopHandler_f  inStopHandler,
+               void *                                  inUserContext,
                unsigned int                    inResponseDelayMs,
                uint32_t                                inDefaultTTL,
-               int                                             inPort,
-               Boolean                                 inLoopbackOnly,
+               const sockaddr_ip *             inAddrArray,
+               size_t                                  inAddrCount,
                const char *                    inDomain,
                Boolean                                 inBadUDPMode,
                DNSServerRef *                  outServer )
@@ -7496,13 +8149,22 @@ static OSStatus
        CF_OBJECT_CREATE( DNSServer, obj, err, exit );
        
        ReplaceDispatchQueue( &obj->queue, inQueue );
-       obj->eventHandler               = inEventHandler;
-       obj->eventContext               = inEventContext;
+       obj->startHandler               = inStartHandler;
+       obj->stopHandler                = inStopHandler;
+       obj->userContext                = inUserContext;
        obj->responseDelayMs    = inResponseDelayMs;
        obj->defaultTTL                 = inDefaultTTL;
-       obj->port                               = inPort;
-       obj->loopbackOnly               = inLoopbackOnly;
        obj->badUDPMode                 = inBadUDPMode;
+       obj->addrCount                  = inAddrCount;
+       
+       obj->addrArray = (sockaddr_ip *) _memdup( inAddrArray, obj->addrCount * sizeof( *obj->addrArray ) );
+       require_action( obj->addrArray, exit, err = kNoMemoryErr );
+       
+       obj->readSourceArrayUDP = (dispatch_source_t *) calloc( obj->addrCount, sizeof( *obj->readSourceArrayUDP ) );
+       require_action( obj->readSourceArrayUDP, exit, err = kNoMemoryErr );
+       
+       obj->readSourceArrayTCP = (dispatch_source_t *) calloc( obj->addrCount, sizeof( *obj->readSourceArrayTCP ) );
+       require_action( obj->readSourceArrayTCP, exit, err = kNoMemoryErr );
        
        if( inDomain )
        {
@@ -7511,10 +8173,9 @@ static OSStatus
        }
        else
        {
-               err = DomainNameDup( kDDotTestDomainName, &obj->domain, NULL );
+               err = DomainNameDup( kDNSServerDomain_Default, &obj->domain, NULL );
                require_noerr_quiet( err, exit );
        }
-       
        *outServer = obj;
        obj = NULL;
        err = kNoErr;
@@ -7524,127 +8185,93 @@ exit:
        return( err );
 }
 
-//===========================================================================================================================
-//     _DNSServerFinalize
 //===========================================================================================================================
 
 static void    _DNSServerFinalize( CFTypeRef inObj )
 {
        DNSServerRef const              me = (DNSServerRef) inObj;
+       size_t                                  i;
        
-       check( !me->readSourceUDPv4 );
-       check( !me->readSourceUDPv6 );
-       check( !me->readSourceTCPv4 );
-       check( !me->readSourceTCPv6 );
        check( !me->responseTimer );
+       check( !me->connectionList );
+       check( !me->connectionTimer );
+       ForgetMem( &me->addrArray );
+       if( me->readSourceArrayUDP )
+       {
+               for( i = 0; i < me->addrCount; ++i ) check( !me->readSourceArrayUDP[ i ] );
+               ForgetMem( &me->readSourceArrayUDP );
+       }
+       if( me->readSourceArrayTCP )
+       {
+               for( i = 0; i < me->addrCount; ++i ) check( !me->readSourceArrayTCP[ i ] );
+               ForgetMem( &me->readSourceArrayTCP );
+       }
        ForgetMem( &me->domain );
        dispatch_forget( &me->queue );
+       ForgetMem( &me->ignoredQTypes );
 }
 
 //===========================================================================================================================
-//     DNSServerStart
+
+static OSStatus        _DNSServerSetIgnoredQType( DNSServerRef me, int inQType )
+{
+       size_t          newCount;
+       int *           mem;
+       
+       newCount = me->ignoredQTypeCount + 1;
+       require_return_value( newCount <= SIZE_MAX / sizeof( int ), kSizeErr );
+       
+       mem = realloc( me->ignoredQTypes, newCount * sizeof( int ) );
+       require_return_value( mem, kNoMemoryErr );
+       
+       me->ignoredQTypes = mem;
+       me->ignoredQTypes[ me->ignoredQTypeCount++ ] = inQType;
+       return( kNoErr );
+}
+
 //===========================================================================================================================
 
-static void    _DNSServerStart( void *inContext );
-static void    _DNSServerStop( void *inContext, OSStatus inError );
+static void            _DNSServerStartOnQueue( void *inContext );
+static void            _DNSServerStartInternal( DNSServerRef inServer );
+static OSStatus        _DNSServerSetUpSockets( DNSServerRef inServer );
+static void            _DNSServerStopInternal( void *inContext, OSStatus inError );
+static SocketContext *
+       _DNSServerSocketContextCreate(
+               SocketRef               inSock,
+               DNSServerRef    inServer,
+               size_t                  inIndex,
+               OSStatus *              outError );
 
-static void    DNSServerStart( DNSServerRef me )
+static void    _DNSServerStart( DNSServerRef me )
 {
        CFRetain( me );
-       dispatch_async_f( me->queue, me, _DNSServerStart );
+       dispatch_async_f( me->queue, me, _DNSServerStartOnQueue );
+}
+
+static void    _DNSServerStartOnQueue( void *inContext )
+{
+       const DNSServerRef              me = (DNSServerRef) inContext;
+       
+       _DNSServerStartInternal( me );
+       CFRelease( me );
 }
 
-static void    _DNSServerStart( void *inContext )
+static void _DNSServerStartInternal( DNSServerRef me )
 {
        OSStatus                                err;
        struct timeval                  now;
-       DNSServerRef const              me                      = (DNSServerRef) inContext;
-       SocketRef                               sock            = kInvalidSocketRef;
-       SocketContext *                 sockCtx         = NULL;
-       const uint32_t                  loopbackV4      = htonl( INADDR_LOOPBACK );
+       SocketRef                               sock    = kInvalidSocketRef;
+       SocketContext *                 sockCtx = NULL;
        int                                             year, month, day;
        
-       // Create IPv4 UDP socket.
-       // Initially, me->port is the port requested by the user. If it's 0, then the user wants any available ephemeral port.
-       // If it's negative, then the user would like a port number equal to its absolute value, but will settle for any
-       // available ephemeral port, if it's not available. The actual port number that was used will be stored in me->port and
-       // used for the remaining sockets.
-       
-       err = _ServerSocketOpenEx2( AF_INET, SOCK_DGRAM, IPPROTO_UDP, me->loopbackOnly ? &loopbackV4 : NULL,
-               me->port, &me->port, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
-       require_noerr( err, exit );
-       check( me->port > 0 );
-       
-       // Create read source for IPv4 UDP socket.
-       
-       err = SocketContextCreate( sock, me, &sockCtx );
-       require_noerr( err, exit );
-       sock = kInvalidSocketRef;
-       
-       err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerUDPReadHandler, SocketContextCancelHandler, sockCtx,
-               &me->readSourceUDPv4 );
-       require_noerr( err, exit );
-       dispatch_resume( me->readSourceUDPv4 );
-       me->sockUDPv4 = sockCtx->sock;
-       sockCtx = NULL;
-       
-       // Create IPv6 UDP socket.
-       
-       err = _ServerSocketOpenEx2( AF_INET6, SOCK_DGRAM, IPPROTO_UDP, me->loopbackOnly ? &in6addr_loopback : NULL,
-               me->port, NULL, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
-       require_noerr( err, exit );
-       
-       // Create read source for IPv6 UDP socket.
-       
-       err = SocketContextCreate( sock, me, &sockCtx );
-       require_noerr( err, exit );
-       sock = kInvalidSocketRef;
-       
-       err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerUDPReadHandler, SocketContextCancelHandler, sockCtx,
-               &me->readSourceUDPv6 );
-       require_noerr( err, exit );
-       dispatch_resume( me->readSourceUDPv6 );
-       me->sockUDPv6 = sockCtx->sock;
-       sockCtx = NULL;
-       
-       // Create IPv4 TCP socket.
-       
-       err = _ServerSocketOpenEx2( AF_INET, SOCK_STREAM, IPPROTO_TCP, me->loopbackOnly ? &loopbackV4 : NULL,
-               me->port, NULL, kSocketBufferSize_DontSet, false, &sock );
-       require_noerr( err, exit );
-       
-       // Create read source for IPv4 TCP socket.
-       
-       err = SocketContextCreate( sock, me, &sockCtx );
-       require_noerr( err, exit );
-       sock = kInvalidSocketRef;
-       
-       err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerTCPReadHandler, SocketContextCancelHandler, sockCtx,
-               &me->readSourceTCPv4 );
-       require_noerr( err, exit );
-       dispatch_resume( me->readSourceTCPv4 );
-       sockCtx = NULL;
-       
-       // Create IPv6 TCP socket.
-       
-       err = _ServerSocketOpenEx2( AF_INET6, SOCK_STREAM, IPPROTO_TCP, me->loopbackOnly ? &in6addr_loopback : NULL,
-               me->port, NULL, kSocketBufferSize_DontSet, false, &sock );
-       require_noerr( err, exit );
-       
-       // Create read source for IPv6 TCP socket.
-       
-       err = SocketContextCreate( sock, me, &sockCtx );
-       require_noerr( err, exit );
-       sock = kInvalidSocketRef;
+       require_action_quiet( !me->started && !me->stopped, exit, err = kNoErr );
+       me->started = true;
+       CFRetain( me );
        
-       err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerTCPReadHandler, SocketContextCancelHandler, sockCtx,
-               &me->readSourceTCPv6 );
+       err = _DNSServerSetUpSockets( me );
        require_noerr( err, exit );
-       dispatch_resume( me->readSourceTCPv6 );
-       sockCtx = NULL;
        
-       ds_ulog( kLogLevelInfo, "Server is using port %d.\n", me->port );
-       if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Started, (uintptr_t) me->port, me->eventContext );
+       if( me->startHandler ) me->startHandler( me->addrArray, me->addrCount, me->userContext );
        
        // Create the serial number for the server's SOA record in the YYYMMDDnn convention recommended by
        // <https://tools.ietf.org/html/rfc1912#section-2.2> using the current time.
@@ -7653,422 +8280,701 @@ static void   _DNSServerStart( void *inContext )
        SecondsToYMD_HMS( ( INT64_C_safe( kDaysToUnixEpoch ) * kSecondsPerDay ) + now.tv_sec, &year, &month, &day,
                NULL, NULL, NULL );
        me->serial = (uint32_t)( ( year * 1000000 ) + ( month * 10000 ) + ( day * 100 ) + 1 );
+       err = kNoErr;
        
 exit:
        ForgetSocket( &sock );
        if( sockCtx ) SocketContextRelease( sockCtx );
-       if( err ) _DNSServerStop( me, err );
+       if( err ) _DNSServerStopInternal( me, err );
 }
 
-//===========================================================================================================================
-//     DNSServerStop
-//===========================================================================================================================
+typedef struct
+{
+       SocketRef               sockUDP;
+       SocketRef               sockTCP;
+       
+}      _DNSServerSocketPair;
 
-static void    _DNSServerUserStop( void *inContext );
-static void    _DNSServerStop2( void *inContext );
+#define kDNSServerMaxBindTryCount      10
 
-static void    DNSServerStop( DNSServerRef me )
+static OSStatus        _DNSServerSetUpSockets( DNSServerRef me )
+{
+       OSStatus                                        err;
+       SocketContext *                         sockCtx                 = NULL;
+       _DNSServerSocketPair *          sockPairs               = NULL;
+       _DNSServerSocketPair *          sockPairsHeap   = NULL;
+       _DNSServerSocketPair            sockPairsStack[ 16 ];
+       size_t                                          i;
+       const size_t                            addrCount               = me->addrCount; // Don't use me->addrCount to avoid false analyzer warning.
+       int                                                     portWanted, tryCount, tryCountMax;
+       
+       require_action_quiet( addrCount > 0, exit, err = kNoErr );
+       
+       sockPairs = sockPairsStack;
+       if( me->addrCount > countof( sockPairsStack ) )
+       {
+               sockPairsHeap = (_DNSServerSocketPair *) calloc( me->addrCount, sizeof( *sockPairsHeap ) );
+               require_action( sockPairsHeap, exit, err = kNoMemoryErr );
+               sockPairs = sockPairsHeap;
+       }
+       for( i = 0; i < addrCount; ++i )
+       {
+               sockPairs[ i ].sockUDP = kInvalidSocketRef;
+               sockPairs[ i ].sockTCP = kInvalidSocketRef;
+       }
+       // Create server sockets.
+       
+       err = kNoErr;
+       portWanted = SockAddrGetPort( &me->addrArray[ 0 ] );
+       tryCountMax = ( portWanted == 0 ) ? kDNSServerMaxBindTryCount : 1;
+       for( tryCount = 0; tryCount < tryCountMax; ++tryCount )
+       {
+               int             portDefault = 0;
+               
+               for( i = 0; i < addrCount; ++i )
+               {
+                       sockaddr_ip * const                                     sip             = &me->addrArray[ i ];
+                       _DNSServerSocketPair * const            pair    = &sockPairs[ i ];
+                       const void *                                            address;
+                       SocketRef                                                       sock;
+                       int                                                                     port, portActual;
+                       sockaddr_ip                                                     tmpSA;
+                       
+                       switch( sip->sa.sa_family )
+                       {
+                               case AF_INET:   address = &sip->v4.sin_addr.s_addr;  break;
+                               case AF_INET6:  address = sip->v6.sin6_addr.s6_addr; break;
+                               default:
+                                       ds_ulog( kLogLevelError, "Unhandled address family %d", sip->sa.sa_family );
+                                       err = kTypeErr;
+                                       goto exit;
+                       }
+                       // Create UDP socket.
+                       // Initially, portWanted is the port requested by the user. If it's 0, then the user wants any available
+                       // ephemeral port. If it's negative, then the user would like a port number equal to its absolute value, but
+                       // will settle for any available ephemeral port, if it's not available. The actual port number that was used
+                       // will be stored in portActual and used for the remaining addresses that don't specify a non-zero port.
+                       
+                       port = ( portWanted == 0 ) ? portDefault : portWanted;
+                       err = _ServerSocketOpenEx2( sip->sa.sa_family, SOCK_DGRAM, IPPROTO_UDP, address, port, &portActual,
+                               kSocketBufferSize_DontSet, true, &sock );
+                       if( err == EADDRINUSE )
+                       {
+                               SockAddrCopy( sip, &tmpSA );
+                               SockAddrSetPort( &tmpSA, port );
+                               ds_ulog( kLogLevelError, "IP address %##a is already in use for UDP\n", &tmpSA );
+                               break;
+                       }
+                       require_noerr( err, exit );
+                       check( ( portWanted == 0 ) || ( portActual == portWanted ) );
+                       
+                       ForgetSocket( &pair->sockUDP );
+                       pair->sockUDP = sock;
+                       sock = kInvalidSocketRef;
+                       if( portDefault == 0 ) portDefault = portActual;
+                       
+                       // Create TCP socket.
+                       
+                       err = _ServerSocketOpenEx2( sip->sa.sa_family, SOCK_STREAM, IPPROTO_TCP, address, portActual, NULL,
+                               kSocketBufferSize_DontSet, false, &sock );
+                       if( err == EADDRINUSE )
+                       {
+                               SockAddrCopy( sip, &tmpSA );
+                               SockAddrSetPort( &tmpSA, portActual );
+                               ds_ulog( kLogLevelError, "IP address %##a is already in use for TCP\n", &tmpSA );
+                               break;
+                       }
+                       require_noerr( err, exit );
+                       
+                       ForgetSocket( &pair->sockTCP );
+                       pair->sockTCP = sock;
+                       sock = kInvalidSocketRef;
+                       
+                       SockAddrSetPort( sip, portActual );
+               }
+               if( !err ) break;
+       }
+       require_noerr( err, exit );
+       
+       // Create read sources for server sockets.
+       
+       for( i = 0; i < addrCount; ++i )
+       {
+               const sockaddr_ip * const                       sip                                     = &me->addrArray[ i ];
+               dispatch_source_t * const                       readSourceUDPPtr        = &me->readSourceArrayUDP[ i ];
+               dispatch_source_t * const                       readSourceTCPPtr        = &me->readSourceArrayTCP[ i ];
+               _DNSServerSocketPair * const            pair                            = &sockPairs[ i ];
+               
+               // Create read source for UDP socket.
+               
+               check( IsValidSocket( pair->sockUDP ) );
+               sockCtx = _DNSServerSocketContextCreate( pair->sockUDP, me, i, &err );
+               require_noerr( err, exit );
+               pair->sockUDP = kInvalidSocketRef;
+               
+               err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerUDPReadHandler, SocketContextCancelHandler,
+                       sockCtx, readSourceUDPPtr );
+               require_noerr( err, exit );
+               dispatch_resume( *readSourceUDPPtr );
+               sockCtx = NULL;
+               
+               // Create read source for TCP socket.
+               
+               check( IsValidSocket( pair->sockTCP ) );
+               sockCtx = _DNSServerSocketContextCreate( pair->sockTCP, me, i, &err );
+               require_noerr( err, exit );
+               pair->sockTCP = kInvalidSocketRef;
+               
+               err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerTCPAcceptHandler, SocketContextCancelHandler,
+                       sockCtx, readSourceTCPPtr );
+               require_noerr( err, exit );
+               dispatch_resume( *readSourceTCPPtr );
+               sockCtx = NULL;
+               
+               ds_ulog( kLogLevelInfo, "Server is listening on %##a\n", sip );
+       }
+       
+exit:
+       if( sockPairs )
+       {
+               for( i = 0; i < addrCount; ++i )
+               {
+                       ForgetSocket( &sockPairs[ i ].sockUDP );
+                       ForgetSocket( &sockPairs[ i ].sockTCP );
+               }
+       }
+       FreeNullSafe( sockPairsHeap );
+       if( sockCtx ) SocketContextRelease( sockCtx );
+       return( err );
+}
+
+//===========================================================================================================================
+
+typedef struct
+{
+       DNSServerRef    server;
+       size_t                  index;
+       
+}      DNSServerContext;
+
+static void    _DNSServerContextFree( DNSServerContext *inCtx );
+static void    _DNSServerSocketContextFinalizer( void *inCtx );
+
+static SocketContext *
+       _DNSServerSocketContextCreate(
+               SocketRef               inSock,
+               DNSServerRef    inServer,
+               size_t                  inIndex,
+               OSStatus *              outError )
+{
+       OSStatus                                err;
+       SocketContext *                 sockCtx = NULL;
+       DNSServerContext *              ctx;
+       
+       ctx = (DNSServerContext *) calloc( 1, sizeof( *ctx ) );
+       require_action( ctx, exit, err = kNoMemoryErr );
+       
+       ctx->index      = inIndex;
+       ctx->server     = inServer;
+       CFRetain( ctx->server );
+       
+       sockCtx = SocketContextCreateEx( inSock, ctx, _DNSServerSocketContextFinalizer, &err );
+       require_noerr( err, exit );
+       ctx     = NULL;
+       
+exit:
+       if( outError ) *outError = err;
+       if( ctx ) _DNSServerContextFree( ctx );
+       return( sockCtx );
+}
+
+static void    _DNSServerSocketContextFinalizer( void *inCtx )
+{
+       _DNSServerContextFree( (DNSServerContext *) inCtx );
+}
+
+static void    _DNSServerContextFree( DNSServerContext *inCtx )
+{
+       ForgetCF( &inCtx->server );
+       free( inCtx );
+}
+
+//===========================================================================================================================
+
+static void    _DNSServerStopOnQueue( void *inContext );
+static void    _DNSServerStop2( void *inContext );
+
+static void    _DNSServerStop( DNSServerRef me )
 {
        CFRetain( me );
-       dispatch_async_f( me->queue, me, _DNSServerUserStop );
+       dispatch_async_f( me->queue, me, _DNSServerStopOnQueue );
 }
 
-static void    _DNSServerUserStop( void *inContext )
+static void    _DNSServerStopOnQueue( void *inContext )
 {
        DNSServerRef const              me = (DNSServerRef) inContext;
        
-       _DNSServerStop( me, kNoErr );
+       _DNSServerStopInternal( me, kNoErr );
        CFRelease( me );
 }
 
-static void    _DNSServerStop( void *inContext, OSStatus inError )
+static void    _DNSServerStopInternal( void *inContext, OSStatus inError )
 {
-       DNSServerRef const              me = (DNSServerRef) inContext;
+       DNSServerRef const                      me = (DNSServerRef) inContext;
+       DNSServerConnectionRef          cnx;
+       size_t                                          i;
        
-       me->stopError = inError;
-       dispatch_source_forget( &me->readSourceUDPv4 );
-       dispatch_source_forget( &me->readSourceUDPv6 );
-       dispatch_source_forget( &me->readSourceTCPv4 );
-       dispatch_source_forget( &me->readSourceTCPv6 );
-       dispatch_source_forget( &me->responseTimer );
-       me->sockUDPv4 = kInvalidSocketRef;
-       me->sockUDPv6 = kInvalidSocketRef;
+       require_quiet( !me->stopped, exit );
+       me->stopped = true;
        
+       me->stopErr = inError;
        if( me->responseList )
        {
-               _DNSDelayedResponseFreeList( me->responseList );
+               _DNSServerDelayedResponseListFree( me->responseList );
                me->responseList = NULL;
        }
+       dispatch_source_forget( &me->responseTimer );
+       for( i = 0; i < me->addrCount; ++i )
+       {
+               dispatch_source_forget( &me->readSourceArrayUDP[ i ] );
+               dispatch_source_forget( &me->readSourceArrayTCP[ i ] );
+       }
+       while( ( cnx = me->connectionList ) != NULL )
+       {
+               me->connectionList = cnx->next;
+               _DNSServerConnectionStop( cnx, false );
+               cnx->next = NULL;
+               CFRelease( cnx );
+       }
+       dispatch_source_forget( &me->connectionTimer );
+       
+       CFRetain( me );
        dispatch_async_f( me->queue, me, _DNSServerStop2 );
+       if( me->started ) CFRelease( me );
+       
+exit:
+       return;
 }
 
 static void    _DNSServerStop2( void *inContext )
 {
        DNSServerRef const              me = (DNSServerRef) inContext;
        
-       if( !me->stopped )
-       {
-               me->stopped = true;
-               if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Stopped, (uintptr_t) me->stopError, me->eventContext );
-               CFRelease( me );
-       }
+       if( me->stopHandler ) me->stopHandler( me->stopErr, me->userContext );
        CFRelease( me );
 }
 
-//===========================================================================================================================
-//     _DNSDelayedResponseFree
-//===========================================================================================================================
-
-static void    _DNSDelayedResponseFree( DNSDelayedResponse *inResponse )
-{
-       ForgetMem( &inResponse->msgPtr );
-       free( inResponse );
-}
-
-//===========================================================================================================================
-//     _DNSDelayedResponseFreeList
-//===========================================================================================================================
-
-static void    _DNSDelayedResponseFreeList( DNSDelayedResponse *inList )
-{
-       DNSDelayedResponse *            response;
-       
-       while( ( response = inList ) != NULL )
-       {
-               inList = response->next;
-               _DNSDelayedResponseFree( response );
-       }
-}
-
-//===========================================================================================================================
-//     _DNSServerUDPReadHandler
 //===========================================================================================================================
 
-static OSStatus
-       _DNSServerAnswerQuery(
-               DNSServerRef    inServer,
-               const uint8_t * inQueryPtr,
-               size_t                  inQueryLen,
-               Boolean                 inForTCP,
-               uint8_t **              outResponsePtr,
-               size_t *                outResponseLen );
-
-#define _DNSServerAnswerQueryForUDP( IN_SERVER, IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \
-       _DNSServerAnswerQuery( IN_SERVER, IN_QUERY_PTR, IN_QUERY_LEN, false, IN_RESPONSE_PTR, IN_RESPONSE_LEN )
-
-#define _DNSServerAnswerQueryForTCP( IN_SERVER, IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \
-       _DNSServerAnswerQuery( IN_SERVER, IN_QUERY_PTR, IN_QUERY_LEN, true, IN_RESPONSE_PTR, IN_RESPONSE_LEN )
-
-static OSStatus
-       _DNSServerScheduleDelayedResponse(
-               DNSServerRef                    inServer,
-               const struct sockaddr * inDestAddr,
-               uint8_t *                               inMsgPtr,
-               size_t                                  inMsgLen );
-static void    _DNSServerUDPDelayedSend( void *inContext );
-
 static void    _DNSServerUDPReadHandler( void *inContext )
 {
-       OSStatus                                        err;
-       SocketContext * const           sockCtx         = (SocketContext *) inContext;
-       DNSServerRef const                      me                      = (DNSServerRef) sockCtx->userContext;
-       struct timeval                          now;
-       ssize_t                                         n;
-       sockaddr_ip                                     clientAddr;
-       socklen_t                                       clientAddrLen;
-       uint8_t *                                       responsePtr     = NULL; // malloc'd
-       size_t                                          responseLen;
-       uint8_t                                         msg[ 512 ];
-       
-       gettimeofday( &now, NULL );
+       OSStatus                                                        err;
+       SocketContext * const                           sockCtx = (SocketContext *) inContext;
+       const DNSServerContext * const          ctx             = (DNSServerContext *) sockCtx->userContext;
+       const DNSServerRef                                      me              = ctx->server;
+       ssize_t                                                         n;
+       sockaddr_ip                                                     client;
+       socklen_t                                                       clientLen;
+       uint8_t *                                                       respPtr = NULL; // malloc'd
+       size_t                                                          respLen;
+       uint8_t                                                         msg[ 512 ];
        
        // Receive message.
        
-       clientAddrLen = (socklen_t) sizeof( clientAddr );
-       n = recvfrom( sockCtx->sock, (char *) msg, sizeof( msg ), 0, &clientAddr.sa, &clientAddrLen );
+       clientLen = (socklen_t) sizeof( client );
+       n = recvfrom( sockCtx->sock, (char *) msg, sizeof( msg ), 0, &client.sa, &clientLen );
        err = map_socket_value_errno( sockCtx->sock, n >= 0, n );
        require_noerr( err, exit );
        
-       ds_ulog( kLogLevelInfo, "UDP server received %zd bytes from %##a at %{du:time}.\n", n, &clientAddr, &now );
-       
        if( n < kDNSHeaderLength )
        {
-               ds_ulog( kLogLevelInfo, "UDP DNS message is too small (%zd < %d).\n", n, kDNSHeaderLength );
+               ds_ulog( kLogLevelInfo, "UDP: Received %zd bytes from %##a to %##a: Message is too small (< %d bytes)\n",
+                       n, &client, &me->addrArray[ ctx->index ], kDNSHeaderLength );
                goto exit;
        }
-       
-       ds_ulog( kLogLevelInfo, "UDP received message:\n\n%1{du:dnsmsg}", msg, (size_t) n );
+       ds_ulog( kLogLevelInfo, "UDP: Received %zd bytes from %##a to %##a -- %.1{du:dnsmsg}\n",
+               n, &client, &me->addrArray[ ctx->index ], msg, (size_t) n );
        
        // Create response.
        
-       err = _DNSServerAnswerQueryForUDP( me, msg, (size_t) n, &responsePtr, &responseLen );
+       err = _DNSServerAnswerQueryForUDP( me, msg, (size_t) n, ctx->index + 1, &respPtr, &respLen );
+       if( err == kSkipErr ) ds_ulog( kLogLevelInfo, "UDP: Ignoring query\n" );
        require_noerr_quiet( err, exit );
        
-       // Schedule response.
-       
-       if( me->responseDelayMs > 0 )
+       if( me->responseDelayMs > 0 )   // Defer response.
        {
-               err = _DNSServerScheduleDelayedResponse( me, &clientAddr.sa, responsePtr, responseLen );
+               err = _DNSServerScheduleDelayedResponse( me, sockCtx->sock, &client.sa, respPtr, respLen, ctx->index );
                require_noerr( err, exit );
-               responsePtr = NULL;
+               respPtr = NULL;
        }
-       else
+       else                                                    // Send response.
        {
-               ds_ulog( kLogLevelInfo, "UDP sending %zu byte response:\n\n%1{du:dnsmsg}", responseLen, responsePtr, responseLen );
+               ds_ulog( kLogLevelInfo, "UDP: Sending %zu byte response from %##a to %##a -- %.1{du:dnsmsg}\n",
+                       respLen, &me->addrArray[ ctx->index ], &client, respPtr, respLen );
                
-               n = sendto( sockCtx->sock, (char *) responsePtr, responseLen, 0, &clientAddr.sa, clientAddrLen );
-               err = map_socket_value_errno( sockCtx->sock, n == (ssize_t) responseLen, n );
+               n = sendto( sockCtx->sock, (char *) respPtr, respLen, 0, &client.sa, clientLen );
+               err = map_socket_value_errno( sockCtx->sock, n == (ssize_t) respLen, n );
                require_noerr( err, exit );
        }
        
 exit:
-       FreeNullSafe( responsePtr );
-       return;
+       FreeNullSafe( respPtr );
 }
 
+//===========================================================================================================================
+
+static void    _DNSServerSendDelayedResponses( void *inContext );
+
 static OSStatus
        _DNSServerScheduleDelayedResponse(
                DNSServerRef                    me,
+               SocketRef                               inSock,
                const struct sockaddr * inDestAddr,
                uint8_t *                               inMsgPtr,
-               size_t                                  inMsgLen )
+               size_t                                  inMsgLen,
+               size_t                                  inIndex )
 {
-       OSStatus                                        err;
-       DNSDelayedResponse *            response;
-       DNSDelayedResponse **           responsePtr;
-       DNSDelayedResponse *            newResponse;
-       uint64_t                                        targetTicks;
+       OSStatus                                                err;
+       DNSServerDelayedResponse *              resp;
+       DNSServerDelayedResponse *              newResp;
+       DNSServerDelayedResponse **             ptr;
+       uint64_t                                                dueTicks;
        
-       targetTicks = UpTicks() + MillisecondsToUpTicks( me->responseDelayMs );
+       dueTicks = UpTicks() + MillisecondsToUpTicks( me->responseDelayMs );
+       newResp = (DNSServerDelayedResponse *) calloc( 1, sizeof( *newResp ) );
+       require_action( newResp, exit, err = kNoMemoryErr );
        
-       newResponse = (DNSDelayedResponse *) calloc( 1, sizeof( *newResponse ) );
-       require_action( newResponse, exit, err = kNoMemoryErr );
+       newResp->dueTicks       = dueTicks;
+       newResp->msgPtr         = inMsgPtr;
+       newResp->msgLen         = inMsgLen;
+       newResp->index          = inIndex;
+       newResp->sock           = inSock;
+       SockAddrCopy( inDestAddr, &newResp->client );
        
-       if( !me->responseList || ( targetTicks < me->responseList->targetTicks ) )
+       if( !me->responseList || ( _TicksDiff( dueTicks, me->responseList->dueTicks ) < 0 ) )
        {
                dispatch_source_forget( &me->responseTimer );
-               
-               err = DispatchTimerCreate( dispatch_time_milliseconds( me->responseDelayMs ), DISPATCH_TIME_FOREVER,
-                       ( (uint64_t) me->responseDelayMs ) * kNanosecondsPerMillisecond / 10, me->queue, _DNSServerUDPDelayedSend,
-                       NULL, me, &me->responseTimer );
+               err = DispatchTimerOneShotCreate( dispatch_time_milliseconds( me->responseDelayMs ), 0, me->queue,
+                       _DNSServerSendDelayedResponses, me, &me->responseTimer );
                require_noerr( err, exit );
                dispatch_resume( me->responseTimer );
        }
-       
-       SockAddrCopy( inDestAddr, &newResponse->destAddr );
-       newResponse->targetTicks        = targetTicks;
-       newResponse->msgPtr                     = inMsgPtr;
-       newResponse->msgLen                     = inMsgLen;
-       
-       for( responsePtr = &me->responseList; ( response = *responsePtr ) != NULL; responsePtr = &response->next )
+       for( ptr = &me->responseList; ( resp = *ptr ) != NULL; ptr = &resp->next )
        {
-               if( newResponse->targetTicks < response->targetTicks ) break;
+               if( _TicksDiff( newResp->dueTicks, resp->dueTicks ) < 0 ) break;
        }
-       newResponse->next = response;
-       *responsePtr = newResponse;
-       newResponse = NULL;
+       newResp->next = resp;
+       *ptr = newResp;
+       newResp = NULL;
        err = kNoErr;
        
 exit:
-       if( newResponse ) _DNSDelayedResponseFree( newResponse );
+       if( newResp ) _DNSServerDelayedResponseFree( newResp );
        return( err );
 }
 
-static void    _DNSServerUDPDelayedSend( void *inContext )
+static void    _DNSServerSendDelayedResponses( void *inContext )
 {
-       OSStatus                                        err;
-       DNSServerRef const                      me                      = (DNSServerRef) inContext;
-       DNSDelayedResponse *            response;
-       SocketRef                                       sock;
-       ssize_t                                         n;
-       uint64_t                                        nowTicks;
-       uint64_t                                        remainingNs;
-       DNSDelayedResponse *            freeList        = NULL;
+       OSStatus                                                err;
+       const DNSServerRef                              me = (DNSServerRef) inContext;
+       DNSServerDelayedResponse *              resp;
+       DNSServerDelayedResponse *              freeList;
+       int64_t                                                 deltaTicks;
        
        dispatch_source_forget( &me->responseTimer );
        
-       nowTicks = UpTicks();
-       while( ( ( response = me->responseList ) != NULL ) && ( response->targetTicks <= nowTicks ) )
+       deltaTicks = -1;
+       freeList = NULL;
+       while( ( resp = me->responseList ) != NULL )
        {
-               me->responseList = response->next;
+               ssize_t                 n;
+               uint64_t                nowTicks = UpTicks();
                
-               ds_ulog( kLogLevelInfo, "UDP sending %zu byte response (delayed):\n\n%1{du:dnsmsg}",
-                       response->msgLen, response->msgPtr, response->msgLen );
+               deltaTicks = _TicksDiff( resp->dueTicks, nowTicks );
+               if( deltaTicks > 0 ) break;
+               me->responseList = resp->next;
                
-               sock = ( response->destAddr.sa.sa_family == AF_INET ) ? me->sockUDPv4 : me->sockUDPv6;
-               n = sendto( sock, (char *) response->msgPtr, response->msgLen, 0, &response->destAddr.sa,
-                       SockAddrGetSize( &response->destAddr ) );
-               err = map_socket_value_errno( sock, n == (ssize_t) response->msgLen, n );
+               ds_ulog( kLogLevelInfo, "UDP: Sending %zu byte delayed response from %##a to %##a -- %.1{du:dnsmsg}\n",
+                       resp->msgLen, &me->addrArray[ resp->index ], &resp->client, resp->msgPtr, resp->msgLen );
+               
+               n = sendto( resp->sock, (char *) resp->msgPtr, resp->msgLen, 0, &resp->client.sa, SockAddrGetSize( &resp->client ) );
+               err = map_socket_value_errno( resp->sock, n == (ssize_t) resp->msgLen, n );
                check_noerr( err );
                
-               response->next  = freeList;
-               freeList                = response;
-               nowTicks = UpTicks();
+               resp->next = freeList;
+               freeList = resp;
        }
-       
-       if( response )
+       if( deltaTicks > 0 )
        {
-               check( response->targetTicks > nowTicks );
-               remainingNs = UpTicksToNanoseconds( response->targetTicks - nowTicks );
-               if( remainingNs > INT64_MAX ) remainingNs = INT64_MAX;
+               uint64_t                deltaNs;
                
-               err = DispatchTimerCreate( dispatch_time( DISPATCH_TIME_NOW, (int64_t) remainingNs ), DISPATCH_TIME_FOREVER, 0,
-                       me->queue, _DNSServerUDPDelayedSend, NULL, me, &me->responseTimer );
+               deltaNs = UpTicksToNanoseconds( (uint64_t) deltaTicks );
+               if( deltaNs > INT64_MAX ) deltaNs = INT64_MAX;
+               
+               err = DispatchTimerOneShotCreate( dispatch_time( DISPATCH_TIME_NOW, (int64_t) deltaNs ), 0, me->queue,
+                       _DNSServerSendDelayedResponses, me, &me->responseTimer );
                require_noerr( err, exit );
                dispatch_resume( me->responseTimer );
        }
        
 exit:
-       if( freeList ) _DNSDelayedResponseFreeList( freeList );
+       if( freeList ) _DNSServerDelayedResponseListFree( freeList );
 }
 
 //===========================================================================================================================
-//     _DNSServerAnswerQuery
+
+static void    _DNSServerDelayedResponseFree( DNSServerDelayedResponse *inResp )
+{
+       ForgetMem( &inResp->msgPtr );
+       inResp->sock = kInvalidSocketRef;
+       free( inResp );
+}
+
 //===========================================================================================================================
 
-#define kLabelPrefix_Alias                     "alias"
-#define kLabelPrefix_AliasTTL          "alias-ttl"
-#define kLabelPrefix_Count                     "count-"
-#define kLabelPrefix_Tag                       "tag-"
-#define kLabelPrefix_TTL                       "ttl-"
-#define kLabel_IPv4                                    "ipv4"
-#define kLabel_IPv6                                    "ipv6"
-#define kLabelPrefix_SRV                       "srv-"
+static void    _DNSServerDelayedResponseListFree( DNSServerDelayedResponse *inList )
+{
+       DNSServerDelayedResponse *              resp;
+       
+       while( ( resp = inList ) != NULL )
+       {
+               inList = resp->next;
+               _DNSServerDelayedResponseFree( resp );
+       }
+}
 
-#define kMaxAliasTTLCount              ( ( kDomainLabelLengthMax - sizeof_string( kLabelPrefix_AliasTTL ) ) / 2 )
-#define kMaxParsedSRVCount             ( kDomainNameLengthMax / ( 1 + sizeof_string( kLabelPrefix_SRV ) + 5 ) )
+//===========================================================================================================================
 
-typedef struct
+#define kDNSServerConnectionExpirationTimeSecs         5
+#define kDNSServerConnectionExpirationTimeMs           ( kDNSServerConnectionExpirationTimeSecs * kMillisecondsPerSecond )
+
+static void    _DNSServerTCPAcceptHandler( void *inContext )
 {
-       uint16_t                        priority;       // Priority from SRV label.
-       uint16_t                        weight;         // Weight from SRV label.
-       uint16_t                        port;           // Port number from SRV label.
-       uint16_t                        targetLen;      // Total length of the target hostname labels that follow an SRV label.
-       const uint8_t *         targetPtr;      // Pointer to the target hostname embedded in a domain name.
+       OSStatus                                                        err;
+       SocketContext * const                           sockCtx = (SocketContext *) inContext;
+       const DNSServerContext * const          ctx             = (DNSServerContext *) sockCtx->userContext;
+       const DNSServerRef                                      me              = ctx->server;
+       DNSServerConnectionRef                          cnx             = NULL;
+       sockaddr_ip                                                     remote, local;
+       socklen_t                                                       len;
+       SocketRef                                                       sock;
+       
+       len = (socklen_t) sizeof( remote );
+       sock = accept( sockCtx->sock, &remote.sa, &len );
+       err = map_socket_creation_errno( sock );
+       require_noerr( err, exit );
        
-}      ParsedSRV;
+       len = (socklen_t) sizeof( local );
+       err = getsockname( sock, &local.sa, &len );
+       if( unlikely( err ) ) SockAddrCopy( &me->addrArray[ ctx->index ], &local );
+       
+       err = _DNSServerConnectionCreate( me, &local.sa, &remote.sa, ctx->index, &cnx );
+       require_noerr_quiet( err, exit );
+       
+       err = _DNSServerConnectionStart( cnx, sock );
+       require_noerr( err, exit );
+       sock = kInvalidSocketRef;
+       
+       if( !me->connectionList ) _DNSServerResetConnectionTimerMs( me, kDNSServerConnectionExpirationTimeMs );
+       cnx->next = me->connectionList;
+       me->connectionList = cnx;
+       cnx = NULL;
+       
+exit:
+       ForgetSocket( &sock );
+       if( cnx )
+       {
+               _DNSServerConnectionStop( cnx, true );
+               CFRelease( cnx );
+       }
+}
+
+//===========================================================================================================================
+
+static void    _DNSServerConnectionTimerHandler( void *inContext )
+{
+       const DNSServerRef                              me                      = (DNSServerRef) inContext;
+       DNSServerConnectionRef                  cnx;
+       DNSServerConnectionRef *                ptr;
+       uint64_t                                                nowTicks;
+       int64_t                                                 delta, deltaMin;
+       
+       nowTicks = UpTicks();
+       deltaMin = INT64_MAX;
+       ptr = &me->connectionList;
+       while( ( cnx = *ptr ) != NULL )
+       {
+               delta = _TicksDiff( cnx->expirationTicks, nowTicks );
+               if( delta <= 0 )
+               {
+                       ds_ulog( kLogLevelInfo, "Timing out TCP connection: %##a <-> %##a\n", &cnx->local, &cnx->remote );
+                       *ptr = cnx->next;
+                       cnx->next = NULL;
+                       _DNSServerConnectionStop( cnx, false );
+                       CFRelease( cnx );
+               }
+               else
+               {
+                       if( delta < deltaMin ) deltaMin = delta;
+                       ptr = &cnx->next;
+               }
+       }
+       if( me->connectionList )
+       {
+               const uint64_t          timeMs = UpTicksToMilliseconds( (uint64_t) deltaMin );
+               
+               check( timeMs <= kDNSServerConnectionExpirationTimeMs );
+               _DNSServerResetConnectionTimerMs( me, timeMs + 1 );
+       }
+}
+
+//===========================================================================================================================
+
+static void    _DNSServerResetConnectionTimerMs( DNSServerRef me, uint64_t inTimeoutMs )
+{
+       OSStatus                err;
+       
+       dispatch_source_forget( &me->connectionTimer );
+       if( inTimeoutMs == 0 ) inTimeoutMs = 1;
+       err = DispatchTimerOneShotCreate( dispatch_time_milliseconds( inTimeoutMs ),
+               UINT64_C( 10 ) * kNanosecondsPerMillisecond, me->queue, _DNSServerConnectionTimerHandler, me, &me->connectionTimer );
+       if( likely( !err ) )    dispatch_resume( me->connectionTimer );
+       else                                    ds_ulog( kLogLevelError, "Failed to create connection timer: %#m\n", err );
+}
+
+//===========================================================================================================================
 
 static OSStatus
        _DNSServerInitializeResponseMessage(
                DataBuffer *    inDB,
-               unsigned int    inID,
-               unsigned int    inFlags,
+               uint16_t                inID,
+               uint16_t                inFlags,
                const uint8_t * inQName,
-               unsigned int    inQType,
-               unsigned int    inQClass );
+               uint16_t                inQType,
+               uint16_t                inQClass );
 static OSStatus
        _DNSServerAnswerQueryDynamically(
                DNSServerRef    inServer,
                const uint8_t * inQName,
-               unsigned int    inQType,
-               unsigned int    inQClass,
+               int                             inQType,
+               int                             inQClass,
+               size_t          inIndex,
                Boolean                 inForTCP,
+               Boolean                 inDNSSEC,
                DataBuffer *    inDB );
-static Boolean
-       _DNSServerNameIsSRVName(
-               DNSServerRef            inServer,
-               const uint8_t *         inName,
-               const uint8_t **        outDomainPtr,
-               size_t *                        outDomainLen,
-               ParsedSRV                       inSRVArray[ kMaxParsedSRVCount ],
-               size_t *                        outSRVCount );
-static Boolean
-       _DNSServerNameIsHostname(
-               DNSServerRef    inServer,
-               const uint8_t * inName,
-               uint32_t *              outAliasCount,
-               uint32_t                inAliasTTLs[ kMaxAliasTTLCount ],
-               size_t *                outAliasTTLCount,
-               unsigned int *  outCount,
-               unsigned int *  outRandCount,
-               uint32_t *              outTTL,
-               Boolean *               outHasA,
-               Boolean *               outHasAAAA,
-               Boolean *               outHasSOA );
 
 static OSStatus
        _DNSServerAnswerQuery(
                DNSServerRef                    me,
-               const uint8_t * const   inQueryPtr,
-               const size_t                    inQueryLen,
-               Boolean                                 inForTCP,
-               uint8_t **                              outResponsePtr,
-               size_t *                                outResponseLen )
+               const uint8_t * const   inMsgPtr,
+               const size_t                    inMsgLen,
+               const size_t                    inIndex,
+               const Boolean                   inForTCP,
+               uint8_t ** const                outResponsePtr,
+               size_t * const                  outResponseLen )
 {
-       OSStatus                                                                err;
-       DataBuffer                                                              dataBuf;
-       const uint8_t *                                                 ptr;
-       const uint8_t * const                                   queryEnd = &inQueryPtr[ inQueryLen ];
-       const DNSHeader *                                               qhdr;
-       const dns_fixed_fields_question *               fields;
-       unsigned int                                                    msgID, qflags, qtype, qclass, rflags;
-       uint8_t                                                                 qname[ kDomainNameLengthMax ];
+       OSStatus                                err;
+       DataBuffer                              db;
+       const uint8_t *                 ptr;
+       const DNSHeader *               hdr;
+       const uint8_t *                 optPtr;
+       size_t                                  optLen;
+       unsigned int                    qflags, rcode;
+       uint16_t                                msgID, qtype, qclass, rflags;
+       uint8_t                                 qname[ kDomainNameLengthMax ];
+       uint8_t                                 dbBuf[ 512 ];
+       Boolean                                 dnssecOK;
        
-       DataBuffer_Init( &dataBuf, NULL, 0, kDNSMaxTCPMessageSize );
+       DataBuffer_Init( &db, dbBuf, sizeof( dbBuf ), kDNSMaxTCPMessageSize );
        
-       require_action_quiet( inQueryLen >= kDNSHeaderLength, exit, err = kUnderrunErr );
+       require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kUnderrunErr );
        
-       qhdr    = (const DNSHeader *) inQueryPtr;
-       msgID   = DNSHeaderGetID( qhdr );
-       qflags  = DNSHeaderGetFlags( qhdr );
+       hdr             = (const DNSHeader *) inMsgPtr;
+       qflags  = DNSHeaderGetFlags( hdr );
        
        // Minimal checking of the query message's header.
        
-       if( ( qflags & kDNSHeaderFlag_Response ) ||                                     // The message must be a query, not a response.
-               ( DNSFlagsGetOpCode( qflags ) != kDNSOpCode_Query ) ||  // OPCODE must be QUERY (standard query).
-               ( DNSHeaderGetQuestionCount( qhdr ) != 1 ) )                    // There should be a single question.
-       {
-               err = kRequestErr;
-               goto exit;
-       }
-       
-       // Get QNAME.
+       require_action_quiet( !( qflags & kDNSHeaderFlag_Response ), exit, err = kRequestErr );
+       require_action_quiet( DNSFlagsGetOpCode( qflags ) == kDNSOpCode_Query, exit, err = kRequestErr );
+       require_action_quiet( DNSHeaderGetQuestionCount( hdr ) == 1, exit, err = kRequestErr );
        
-       ptr = (const uint8_t *) &qhdr[ 1 ];
-       err = DNSMessageExtractDomainName( inQueryPtr, inQueryLen, ptr, qname, &ptr );
+       ptr = (const uint8_t *) &hdr[ 1 ];
+       err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, qname, &qtype, &qclass, &ptr );
        require_noerr( err, exit );
        
-       // Get QTYPE and QCLASS.
+       // Check if this query should be ignored because of its QTYPE.
        
-       require_action_quiet( ( (size_t)( queryEnd - ptr ) ) >= sizeof( *fields ), exit, err = kUnderrunErr );
-       fields  = (const dns_fixed_fields_question *) ptr;
-       qtype   = dns_fixed_fields_question_get_type( fields );
-       qclass  = dns_fixed_fields_question_get_class( fields );
-       
-       // Create a tentative response message.
+       if( qclass == kDNSClassType_IN )
+       {
+               size_t          i;
+               
+               for( i = 0; i < me->ignoredQTypeCount; ++i )
+               {
+                       if( qtype == me->ignoredQTypes[ i ] )
+                       {
+                               err = kSkipErr;
+                               goto exit;
+                       }
+               }
+       }
+       // Set up response flags.
        
        rflags = kDNSHeaderFlag_Response;
        if( qflags & kDNSHeaderFlag_RecursionDesired ) rflags |= kDNSHeaderFlag_RecursionDesired;
        DNSFlagsSetOpCode( rflags, kDNSOpCode_Query );
        
-       if( me->badUDPMode && !inForTCP ) msgID = (uint16_t)( msgID + 1 );
-       err = _DNSServerInitializeResponseMessage( &dataBuf, msgID, rflags, qname, qtype, qclass );
-       require_noerr( err, exit );
+       // Get OPT record, if any.
        
-       err = _DNSServerAnswerQueryDynamically( me, qname, qtype, qclass, inForTCP, &dataBuf );
-       if( err )
+       err = DNSMessageGetOptRecord( inMsgPtr, inMsgLen, &optPtr, &optLen );
+       require_noerr_action_quiet( err, done, rcode = kDNSRCode_FormErr );
+       
+       // Create a tentative response message.
+       
+       msgID = DNSHeaderGetID( hdr );
+       if( me->badUDPMode && !inForTCP ) ++msgID;
+       err = _DNSServerInitializeResponseMessage( &db, msgID, rflags, qname, qtype, qclass );
+       require_noerr_action( err, done, rcode = kDNSRCode_ServFail );
+       
+       // Complete the response message.
+       
+       dnssecOK = false;
+       if( optPtr )
        {
-               DNSFlagsSetRCode( rflags, kDNSRCode_ServerFailure );
-               err = _DNSServerInitializeResponseMessage( &dataBuf, msgID, rflags, qname, qtype, qclass );
-               require_noerr( err, exit );
+               const dns_fixed_fields_opt *            opt = (const dns_fixed_fields_opt *) optPtr;
+               
+               if( dns_fixed_fields_opt_get_extended_flags( opt ) & kDNSExtendedFlag_DNSSECOK ) dnssecOK = true;
        }
+       err = _DNSServerAnswerQueryDynamically( me, qname, qtype, qclass, inIndex, inForTCP, dnssecOK, &db );
+       if( err == kSkipErr ) goto exit;
+       rcode = err ? kDNSRCode_ServFail : 0;
        
-       err = DataBuffer_Detach( &dataBuf, outResponsePtr, outResponseLen );
+       // Create an error response if there was a format error or a server failure.
+       
+done:
+       if( rcode != 0 )
+       {
+               DNSFlagsSetRCode( rflags, rcode );
+               err = _DNSServerInitializeResponseMessage( &db, DNSHeaderGetID( hdr ), rflags, qname, qtype, qclass );
+               require_noerr( err, exit );
+       }
+       err = DataBuffer_Detach( &db, outResponsePtr, outResponseLen );
        require_noerr( err, exit );
        
 exit:
-       DataBuffer_Free( &dataBuf );
+       DataBuffer_Free( &db );
        return( err );
 }
 
+//===========================================================================================================================
+
 static OSStatus
        _DNSServerInitializeResponseMessage(
                DataBuffer *    inDB,
-               unsigned int    inID,
-               unsigned int    inFlags,
+               uint16_t                inID,
+               uint16_t                inFlags,
                const uint8_t * inQName,
-               unsigned int    inQType,
-               unsigned int    inQClass )
+               uint16_t                inQType,
+               uint16_t                inQClass )
 {
        OSStatus                err;
        DNSHeader               header;
@@ -8083,231 +8989,560 @@ static OSStatus
        err = DataBuffer_Append( inDB, &header, sizeof( header ) );
        require_noerr( err, exit );
        
-       err = _DataBuffer_AppendDNSQuestion( inDB, inQName, DomainNameLength( inQName ), (uint16_t) inQType,
-               (uint16_t) inQClass );
+       err = _DataBuffer_AppendDNSQuestion( inDB, inQName, DomainNameLength( inQName ), inQType, inQClass );
        require_noerr( err, exit );
        
 exit:
        return( err );
 }
 
+//===========================================================================================================================
+
+// DNS Server QNAME Labels
+
+#define kLabel_IPv4                                    "ipv4"
+#define kLabel_IPv6                                    "ipv6"
+#define kLabelPrefix_Alias                     "alias"
+#define kLabelPrefix_AliasTTL          "alias-ttl"
+#define kLabelPrefix_Count                     "count-"
+#define kLabelPrefix_Index                     "index-"
+#define kLabelPrefix_RCode                     "rcode-"
+#define kLabelPrefix_SRV                       "srv-"
+#define kLabelPrefix_Tag                       "tag-"
+#define kLabelPrefix_TTL                       "ttl-"
+
+// Experimental Labels
+
+#define kLabelPrefix_PDelay                    "pdelay-"               // Specifies an additional simulated processing delay in milliseconds.
+#define kLabelPrefix_Zone                      "z-"                    // format: z-<algorithm mnemonic exclude '-'>-<zone index>
+
+typedef struct
+{
+       uint16_t                        priority;       // Priority from SRV label.
+       uint16_t                        weight;         // Weight from SRV label.
+       uint16_t                        port;           // Port number from SRV label.
+       uint16_t                        targetLen;      // Total length of the target hostname labels that follow an SRV label.
+       const uint8_t *         targetPtr;      // Pointer to the target hostname embedded in a domain name.
+       
+}      ParsedSRV;
+
+typedef uint32_t               DNSNameFlags;
+#define kDNSNameFlag_HasA                      ( 1U << 0 )
+#define kDNSNameFlag_HasAAAA           ( 1U << 1 )
+#define kDNSNameFlag_HasSOA                    ( 1U << 2 )
+#define kDNSNameFlag_HasSRV                    ( 1U << 3 )
+#define kDNSNameFlag_HasPTRv4          ( 1U << 4 )
+#define kDNSNameFlag_HasPTRv6          ( 1U << 5 )
+#define kDNSNameFlag_HasRRSIG          ( 1U << 6 )
+#define kDNSNameFlag_HasDNSKEY         ( 1U << 7 )
+#define kDNSNameFlag_HasDS                     ( 1U << 8 )
+
+#define kAliasTTLCountMax              ( ( kDomainLabelLengthMax - sizeof_string( kLabelPrefix_AliasTTL ) ) / 2 )
+#define kParsedSRVCountMax             ( kDomainNameLengthMax / ( 1 + sizeof_string( kLabelPrefix_SRV ) + 5 ) )
+
+static Boolean
+       _DNSServerParseHostName(
+               DNSServerRef            inServer,
+               const uint8_t *         inQName,
+               uint32_t *                      outAliasCount,
+               uint32_t                        outAliasTTLs[ kAliasTTLCountMax ],
+               uint32_t *                      outAliasTTLCount,
+               uint32_t *                      outCount,
+               uint32_t *                      outRandCount,
+               uint32_t *                      outIndex,
+               int *                           outRCode,
+               uint32_t *                      outTTL,
+               uint32_t *                      outProcDelayMs,
+               DNSNameFlags *          outFlags,
+               const uint8_t **        outZone,
+               const uint8_t **        outZoneParent,
+               DNSKeyInfoRef *         outZSK,
+               DNSKeyInfoRef *         outKSK,
+               DNSKeyInfoRef *         outParentZSK );
+static Boolean
+       _DNSServerParseSRVName(
+               DNSServerRef            inServer,
+               const uint8_t *         inName,
+               const uint8_t **        outDomainPtr,
+               size_t *                        outDomainLen,
+               ParsedSRV                       outSRVArray[ kParsedSRVCountMax ],
+               size_t *                        outSRVCount );
+static Boolean _DNSServerParseReverseIPv4Name( DNSServerRef me, const uint8_t *inQName, unsigned int *outHostID );
+static Boolean _DNSServerParseReverseIPv6Name( DNSServerRef me, const uint8_t *inQName, unsigned int *outHostID );
+#if( DEBUG )
+static void
+       _DNSServerSigCheck(
+               const uint8_t *         inOwner,
+               int                                     inTypeCovered,
+               const void *            inMsgPtr,
+               size_t                          inMsgLen,
+               const uint8_t *         inSignaturePtr,
+               const size_t            inSignatureLen,
+               DNSKeyInfoRef           inKeyInfo );
+#endif
+
+typedef enum
+{
+       kQueryStatus_Null                       = 0,
+       kQueryStatus_OK                         = 1,
+       kQueryStatus_Truncated          = 2,
+       kQueryStatus_NotImplemented     = 3,
+       kQueryStatus_Refused            = 4
+       
+}      QueryStatus;
+
 static OSStatus
        _DNSServerAnswerQueryDynamically(
                DNSServerRef                    me,
                const uint8_t * const   inQName,
-               const unsigned int              inQType,
-               const unsigned int              inQClass,
+               const int                               inQType,
+               const int                               inQClass,
+               const size_t                    inIndex,
                const Boolean                   inForTCP,
+               const Boolean                   inDNSSEC,
                DataBuffer * const              inDB )
 {
-       OSStatus                                        err;
-       DNSHeader *                                     hdr;
-       unsigned int                            flags, rcode;
-       uint32_t                                        aliasCount, i;
-       uint32_t                                        aliasTTLs[ kMaxAliasTTLCount ];
-       size_t                                          aliasTTLCount;
-       unsigned int                            addrCount, randCount;
-       uint32_t                                        ttl;
-       ParsedSRV                                       srvArray[ kMaxParsedSRVCount ];
-       size_t                                          srvCount;
-       const uint8_t *                         srvDomainPtr;
-       size_t                                          srvDomainLen;
-       unsigned int                            answerCount;
-       Boolean                                         notImplemented, truncated;
-       Boolean                                         useAliasTTLs, nameExists, nameHasA, nameHasAAAA, nameHasSRV, nameHasSOA;
-       uint8_t                                         namePtr[ 2 ];
-       dns_fixed_fields_record         fields;
-       
-       answerCount     = 0;
-       truncated       = false;
-       nameExists      = false;
-       require_action_quiet( inQClass == kDNSServiceClass_IN, done, notImplemented = true );
-       
-       notImplemented  = false;
-       aliasCount              = 0;
-       nameHasA                = false;
-       nameHasAAAA             = false;
-       nameHasSOA              = false;
-       useAliasTTLs    = false;
-       nameHasSRV              = false;
-       srvDomainLen    = 0;
-       srvCount                = 0;
-       
-       if( _DNSServerNameIsHostname( me, inQName, &aliasCount, aliasTTLs, &aliasTTLCount, &addrCount, &randCount, &ttl,
-               &nameHasA, &nameHasAAAA, &nameHasSOA ) )
+       OSStatus                        err;
+       uint32_t                        aliasCount              = 0;
+       uint32_t                        aliasTTLs[ kAliasTTLCountMax ];
+       uint32_t                        aliasTTLCount   = 0;
+       uint32_t                        addrCount               = 0;
+       uint32_t                        randCount               = 0;
+       uint32_t                        index                   = 0;
+       int                                     rcodeOverride   = -1;
+       uint32_t                        ttl                             = 0;
+       uint32_t                        procDelayMs             = 0;
+       DNSNameFlags            nameFlags               = 0;
+       const uint8_t *         zone                    = NULL;
+       const uint8_t *         zoneParent              = NULL;
+       DNSKeyInfoRef           zsk                             = NULL;
+       DNSKeyInfoRef           ksk                             = NULL;
+       DNSKeyInfoRef           zskParent               = NULL;
+       const uint8_t *         srvDomainPtr    = NULL;
+       size_t                          srvDomainLen    = 0;
+       ParsedSRV                       srvArray[ kParsedSRVCountMax ];
+       size_t                          srvCount                = 0;
+       unsigned int            hostID                  = 0;
+       struct timeval          now;
+       DNSHeader *                     hdr;
+       unsigned int            flags;
+       int                                     rcode;
+       QueryStatus                     status;
+       unsigned int            answerCount             = 0;
+       unsigned int            additionalCount = 0;
+       Boolean                         nameExists              = false;
+       uint8_t *                       qnameLower              = NULL;
+       size_t                          qnameLowerLen;
+       const uint8_t *         ownerLower;
+       size_t                          ownerLowerLen;
+       DataBuffer *            sigMsg                  = NULL;
+       DataBuffer                      sigDB;
+       uint8_t                         sigBuf[ 256 ];
+       uint8_t                         nameCPtr[ 2 ];
+       uint64_t                        startTicks;
+       
+       startTicks = UpTicks();
+       require_action_quiet( inQClass == kDNSServiceClass_IN, done, status = kQueryStatus_NotImplemented );
+       
+       nameExists = _DNSServerParseHostName( me, inQName, &aliasCount, aliasTTLs, &aliasTTLCount, &addrCount, &randCount,
+               &index, &rcodeOverride, &ttl, &procDelayMs, &nameFlags, &zone, &zoneParent, &zsk, &ksk, &zskParent );
+       if( nameExists )
        {
                check( !( ( aliasCount > 0 ) && ( aliasTTLCount > 0 ) ) );
-               check( ( addrCount >= 1 ) && ( addrCount <= 255 ) );
                check( ( randCount == 0 ) || ( ( randCount >= addrCount ) && ( randCount <= 255 ) ) );
-               check( nameHasA || nameHasAAAA );
+               check( rcodeOverride <= 15 );
+               check( !( nameFlags & kDNSNameFlag_HasRRSIG ) || ( zsk && ksk && zskParent ) );
                
-               if( aliasTTLCount > 0 )
+               if( aliasTTLCount > 0 ) aliasCount = (uint32_t) aliasTTLCount;
+               if( index != 0 )
                {
-                       aliasCount              = (uint32_t) aliasTTLCount;
-                       useAliasTTLs    = true;
+                       if( index == inIndex )
+                       {
+                               rcodeOverride = -1;
+                       }
+                       else
+                       {
+                               if ( rcodeOverride < 0 )
+                               {
+                                       err = kSkipErr;
+                                       goto exit;
+                               }
+                               else
+                               {
+                                       addrCount = 0;
+                               }
+                       }
                }
-               nameExists = true;
        }
-       else if( _DNSServerNameIsSRVName( me, inQName, &srvDomainPtr, &srvDomainLen, srvArray, &srvCount ) )
+       else if( ( nameExists = _DNSServerParseSRVName( me, inQName, &srvDomainPtr, &srvDomainLen, srvArray, &srvCount ) ) )
        {
-               nameHasSRV = true;
-               nameExists = true;
+               nameFlags = kDNSNameFlag_HasSRV;
        }
-       require_quiet( nameExists, done );
-       
-       if( aliasCount > 0 )
+       else if( ( nameExists = _DNSServerParseReverseIPv4Name( me, inQName, &hostID ) ) )
        {
-               size_t                          nameOffset;
-               uint8_t                         rdataLabel[ 1 + kDomainLabelLengthMax + 1 ];
-               
-               // If aliasCount is non-zero, then the first label of QNAME is either "alias" or "alias-<N>". superPtr is a name
-               // compression pointer to the second label of QNAME, i.e., the immediate superdomain name of QNAME. It's used for
-               // the RDATA of CNAME records whose canonical name ends with the superdomain name. It may also be used to construct
-               // CNAME record names, when the offset to the previous CNAME's RDATA doesn't fit in a compression pointer.
+               check( ( hostID >= 1 ) && ( hostID <= 255 ) );
                
-               const uint8_t           superPtr[ 2 ] = { 0xC0, (uint8_t)( kDNSHeaderLength + 1 + inQName[ 0 ] ) };
-               
-               // The name of the first CNAME record is equal to QNAME, so nameOffset is set to offset of QNAME.
-               
-               nameOffset = kDNSHeaderLength;
+               nameFlags = kDNSNameFlag_HasPTRv4;
+       }
+       else if( ( nameExists = _DNSServerParseReverseIPv6Name( me, inQName, &hostID ) ) )
+       {
+               check( ( hostID >= 1 ) && ( hostID <= 255 ) );
                
+               nameFlags = kDNSNameFlag_HasPTRv6;
+       }
+       require_action_quiet( nameExists, done, status = kQueryStatus_OK );
+       
+       err = DomainNameDupLower( inQName, &qnameLower, &qnameLowerLen );
+       require_noerr( err, exit );
+       
+       gettimeofday( &now, NULL );
+       if( aliasCount > 0 )
+       {
+               size_t                                          nameOffset, rdataLabelLen;
+               const uint8_t *                         parentLower;
+               size_t                                          parentLowerLen = 0;
+               uint32_t                                        i;
+               dns_fixed_fields_record         recFields;
+               uint8_t                                         rdataLabel[ 1 + kDomainLabelLengthMax ];
+               uint8_t                                         parentCPtr[ 2 ];
+               Boolean                                         needSig;
+               
+               // If aliasCount is non-zero, then the first label of QNAME is either "alias" or "alias-<N>". parentCPtr is a
+               // name compression pointer to the second label of QNAME, i.e., the parent domain name of QNAME. It's used for
+               // the RDATA of CNAME records whose canonical name ends with the superdomain name. It may also be used to
+               // construct CNAME record names when the offset to the previous CNAME's RDATA doesn't fit in a compression
+               // pointer.
+               
+               DNSMessageWriteLabelPointer( parentCPtr, kDNSHeaderLength + ( 1 + inQName[ 0 ] ) );
+               
+               rdataLabel[ 0 ] = 0;
+               rdataLabelLen   = 1;
+               nameOffset              = kDNSHeaderLength; // The name of the first CNAME record is equal to QNAME.
+               
+               needSig = ( inDNSSEC && ( nameFlags & kDNSNameFlag_HasRRSIG ) ) ? true : false;
+               if( needSig )
+               {
+                       parentLower             = DomainNameGetNextLabel( qnameLower );
+                       parentLowerLen  = DomainNameLength( parentLower );
+               }
                for( i = aliasCount; i >= 1; --i )
                {
-                       size_t                  nameLen;
-                       size_t                  rdataLen;
-                       uint32_t                j;
-                       uint32_t                aliasTTL;
-                       uint8_t                 nameLabel[ 1 + kDomainLabelLengthMax ];
+                       size_t                          nameLabelLen, nameLen, rdataLen, recordLen;
+                       uint32_t                        aliasTTL;
+                       uint8_t                         nameLabel[ 1 + kDomainLabelLengthMax ];
+                       Boolean                         useNamePtr;
+                       const Boolean           useAliasTTLs = ( aliasTTLCount > 0 ) ? true : false;
                        
+                       memcpy( nameLabel, rdataLabel, rdataLabelLen );
+                       nameLabelLen = rdataLabelLen;
                        if( nameOffset <= kDNSCompressionOffsetMax )
                        {
-                               DNSMessageWriteLabelPointer( namePtr, nameOffset );
-                               nameLen = sizeof( namePtr );
+                               DNSMessageWriteLabelPointer( nameCPtr, nameOffset );
+                               nameLen         = sizeof( nameCPtr );
+                               useNamePtr      = true;
                        }
                        else
                        {
-                               memcpy( nameLabel, rdataLabel, 1 + rdataLabel[ 0 ] );
-                               nameLen = 1 + nameLabel[ 0 ] + sizeof( superPtr );
+                               nameLen         = nameLabelLen + sizeof( parentCPtr );
+                               useNamePtr      = false;
                        }
                        
-                       if( i >= 2 )
+                       if( i > 1 )
                        {
-                               char *                          dst = (char *) &rdataLabel[ 1 ];
-                               char * const            lim = (char *) &rdataLabel[ countof( rdataLabel ) ];
+                               // There's at least one alias/CNAME left.
+                               
+                               uint8_t *                       dst = &rdataLabel[ 1 ];
+                               const uint8_t *         lim = &rdataLabel[ countof( rdataLabel ) ];
+                               size_t                          maxLen;
+                               int                                     n;
                                
+                               maxLen = (size_t)( lim - dst );
                                if( useAliasTTLs )
                                {
-                                       err = SNPrintF_Add( &dst, lim, kLabelPrefix_AliasTTL );
-                                       require_noerr( err, exit );
+                                       uint32_t                j;
+                                       
+                                       n = MemPrintF( dst, maxLen, "%s", kLabelPrefix_AliasTTL );
+                                       require_fatal( ( n > 0 ) && ( ( (size_t) n ) <= maxLen ), "Failed to print AliasTTL label" );
+                                       dst += n;
                                        
                                        for( j = aliasCount - ( i - 1 ); j < aliasCount; ++j )
                                        {
-                                               err = SNPrintF_Add( &dst, lim, "-%u", aliasTTLs[ j ] );
-                                               require_noerr( err, exit );
+                                               maxLen = (size_t)( lim - dst );
+                                               n = MemPrintF( dst, maxLen, "-%u", aliasTTLs[ j ] );
+                                               require_fatal( ( n > 0 ) && ( ( (size_t) n ) <= maxLen ), "Failed to print AliasTTL label" );
+                                               dst += n;
                                        }
                                }
+                               else if( i == 2 )
+                               {
+                                       n = MemPrintF( dst, maxLen, "%s", kLabelPrefix_Alias );
+                                       require_fatal( ( n > 0 ) && ( ( (size_t) n ) <= maxLen ), "Failed to print Alias label" );
+                                       dst += n;
+                               }
                                else
                                {
-                                       err = SNPrintF_Add( &dst, lim, kLabelPrefix_Alias "%?{end}-%u", i == 2, i - 1 );
-                                       require_noerr( err, exit );
+                                       n = MemPrintF( dst, maxLen, "%s-%u", kLabelPrefix_Alias, i - 1 );
+                                       require_fatal( ( n > 0 ) && ( ( (size_t) n ) <= maxLen ), "Failed to print Alias label" );
+                                       dst += n;
                                }
-                               rdataLabel[ 0 ] = (uint8_t)( dst - (char *) &rdataLabel[ 1 ] );
-                               rdataLen                = 1 + rdataLabel[ 0 ] + sizeof( superPtr );
+                               rdataLabel[ 0 ] = (uint8_t)( dst - &rdataLabel[ 1 ] );
+                               rdataLabelLen   = 1 + rdataLabel[ 0 ];
+                               rdataLen                = rdataLabelLen + sizeof( parentCPtr );
                        }
                        else
                        {
-                               rdataLen = sizeof( superPtr );
+                               // This is the final CNAME.
+                               
+                               rdataLen = sizeof( parentCPtr );
                        }
                        
+                       // If the transport is UDP, make sure the message is within the UDP size limit.
+                       
                        if( !inForTCP )
                        {
-                               size_t          recordLen = nameLen + sizeof( fields ) + rdataLen;
-                               
+                               recordLen = nameLen + sizeof( recFields ) + rdataLen;
                                if( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize )
                                {
-                                       truncated = true;
+                                       status = kQueryStatus_Truncated;
                                        goto done;
                                }
                        }
-                       ++answerCount;
+                       // Append CNAME record's NAME to response.
                        
-                       // Set CNAME record's NAME.
-                       
-                       if( nameOffset <= kDNSCompressionOffsetMax )
+                       if( useNamePtr )
                        {
-                               err = DataBuffer_Append( inDB, namePtr, sizeof( namePtr ) );
+                               err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) );
                                require_noerr( err, exit );
                        }
                        else
                        {
-                               err = DataBuffer_Append( inDB, nameLabel, 1 + nameLabel[ 0 ] );
+                               err = DataBuffer_Append( inDB, nameLabel, nameLabelLen );
                                require_noerr( err, exit );
                                
-                               err = DataBuffer_Append( inDB, superPtr, sizeof( superPtr ) );
+                               err = DataBuffer_Append( inDB, parentCPtr, sizeof( parentCPtr ) );
                                require_noerr( err, exit );
                        }
-                       
-                       // Set CNAME record's TYPE, CLASS, TTL, and RDLENGTH.
+                       // Append CNAME record's TYPE, CLASS, TTL, and RDLENGTH to response.
                        
                        aliasTTL = useAliasTTLs ? aliasTTLs[ aliasCount - i ] : me->defaultTTL;
-                       dns_fixed_fields_record_init( &fields, kDNSServiceType_CNAME, kDNSServiceClass_IN, aliasTTL,
+                       dns_fixed_fields_record_init( &recFields, kDNSServiceType_CNAME, kDNSServiceClass_IN, aliasTTL,
                                (uint16_t) rdataLen );
-                       err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+                       err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) );
                        require_noerr( err, exit );
                        
                        // Save offset of CNAME record's RDATA, which may be used for the name of the next CNAME record.
                        
                        nameOffset = DataBuffer_GetLen( inDB );
                        
-                       // Set CNAME record's RDATA.
+                       // Append CNAME record's RDATA to response.
                        
-                       if( i >= 2 )
+                       if( i > 1 )
                        {
-                               err = DataBuffer_Append( inDB, rdataLabel, 1 + rdataLabel[ 0 ] );
+                               // There's at least one CNAME left.
+                               
+                               err = DataBuffer_Append( inDB, rdataLabel, rdataLabelLen );
                                require_noerr( err, exit );
                        }
-                       err = DataBuffer_Append( inDB, superPtr, sizeof( superPtr ) );
+                       err = DataBuffer_Append( inDB, parentCPtr, sizeof( parentCPtr ) );
                        require_noerr( err, exit );
+                       
+                       ++answerCount;
+                       
+                       if( needSig )
+                       {
+                               dns_fixed_fields_rrsig          sigFields;
+                               const size_t                            signerLen = DomainNameLength( zone );
+                               uint32_t                                        inceptionSecs;
+                               uint8_t                                         signature[ kDNSServerSignatureLengthMax ];
+                               size_t                                          signatureLen;
+                               int                                                     labelCount;
+                               Boolean                                         didSign;
+                               
+                               // Initialize signing buffer.
+                               
+                               check( !sigMsg );
+                               sigMsg = &sigDB;
+                               DataBuffer_Init( sigMsg, sigBuf, sizeof( sigBuf ), SIZE_MAX );
+                               
+                               // Append RRSIG record RDATA fixed fields to signing buffer.
+                               
+                               memset( &sigFields, 0, sizeof( sigFields ) );
+                               dns_fixed_fields_rrsig_set_type_covered( &sigFields, kDNSServiceType_CNAME );
+                               dns_fixed_fields_rrsig_set_algorithm( &sigFields, DNSKeyInfoGetAlgorithm( zsk ) );
+                               labelCount = DomainNameLabelCount( inQName );
+                               check( labelCount >= 0 );
+                               dns_fixed_fields_rrsig_set_labels( &sigFields, (uint8_t) labelCount );
+                               dns_fixed_fields_rrsig_set_original_ttl( &sigFields, aliasTTL );
+                               inceptionSecs = (uint32_t) now.tv_sec;
+                               dns_fixed_fields_rrsig_set_signature_expiration( &sigFields, inceptionSecs + kSecondsPerDay );
+                               dns_fixed_fields_rrsig_set_signature_inception( &sigFields, inceptionSecs );
+                               dns_fixed_fields_rrsig_set_key_tag( &sigFields, DNSKeyInfoGetKeyTag( zsk ) );
+                               
+                               err = DataBuffer_Append( sigMsg, &sigFields, sizeof( sigFields ) );
+                               require_noerr( err, exit );
+                               
+                               // Append RRSIG record RDATA signer to signing buffer.
+                               
+                               err = DataBuffer_Append( sigMsg, zone, signerLen );
+                               require_noerr( err, exit );
+                               
+                               // Append expanded CNAME record owner to signing buffer.
+                               
+                               if( i == aliasCount )
+                               {
+                                       err = DataBuffer_Append( sigMsg, qnameLower, qnameLowerLen );
+                                       require_noerr( err, exit );
+                               }
+                               else
+                               {
+                                       err = DataBuffer_Append( sigMsg, nameLabel, nameLabelLen );
+                                       require_noerr( err, exit );
+                                       
+                                       err = DataBuffer_Append( sigMsg, parentLower, parentLowerLen );
+                                       require_noerr( err, exit );
+                               }
+                               // Append CNAME record fixed fields to signing buffer.
+                               
+                               err = DataBuffer_Append( sigMsg, &recFields, sizeof( recFields ) );
+                               require_noerr( err, exit );
+                               
+                               // Append expanded CNAME record RDATA to signing buffer.
+                               
+                               if( i > 1 )
+                               {
+                                       // There's at least one CNAME left.
+                                       
+                                       err = DataBuffer_Append( sigMsg, rdataLabel, rdataLabelLen );
+                                       require_noerr( err, exit );
+                               }
+                               err = DataBuffer_Append( sigMsg, parentLower, parentLowerLen );
+                               require_noerr( err, exit );
+                               
+                               // Compute signature with ZSK.
+                               
+                               memset( signature, 0, sizeof( signature ) );
+                               didSign = DNSKeyInfoSign( zsk, DataBuffer_GetPtr( sigMsg ), DataBuffer_GetLen( sigMsg ),
+                                       signature, &signatureLen );
+                               require_quiet( didSign, exit );
+                               
+                               #if( DEBUG )
+                               {
+                                       const uint8_t *         tmpPtr;
+                                       uint8_t                         tmpBuf[ kDomainNameLengthMax ];
+                                       
+                                       if( i == aliasCount )
+                                       {
+                                               tmpPtr = inQName;
+                                       }
+                                       else
+                                       {
+                                               memcpy( tmpBuf, nameLabel, nameLabelLen );
+                                               memcpy( &tmpBuf[ nameLabelLen ], parentLower, parentLowerLen );
+                                               tmpPtr = tmpBuf;
+                                       }
+                                       _DNSServerSigCheck( tmpPtr, kDNSServiceType_CNAME, DataBuffer_GetPtr( sigMsg ),
+                                               DataBuffer_GetLen( sigMsg ), signature, signatureLen, zsk );
+                               }
+                               #endif
+                               // If the transport is UDP, make sure the message is within the UDP size limit.
+                               
+                               rdataLen        = sizeof( sigFields ) + signerLen + signatureLen;
+                               recordLen       = nameLen + sizeof( recFields ) + rdataLen;
+                               if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
+                               {
+                                       status = kQueryStatus_Truncated;
+                                       goto done;
+                               }
+                               // Append RRSIG record NAME to response.
+                               
+                               if( useNamePtr )
+                               {
+                                       err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) );
+                                       require_noerr( err, exit );
+                               }
+                               else
+                               {
+                                       err = DataBuffer_Append( inDB, nameLabel, nameLabelLen );
+                                       require_noerr( err, exit );
+                                       
+                                       err = DataBuffer_Append( inDB, parentCPtr, sizeof( parentCPtr ) );
+                                       require_noerr( err, exit );
+                               }
+                               // Append RRSIG record TYPE, CLASS, TTL, and RDLENGTH to response.
+                               
+                               dns_fixed_fields_record_init( &recFields, kDNSServiceType_RRSIG, kDNSServiceClass_IN, aliasTTL,
+                                       (uint16_t) rdataLen );
+                               err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) );
+                               require_noerr( err, exit );
+                               
+                               // Append RRSIG record RDATA fixed fields and signer to response.
+                               
+                               err = DataBuffer_Append( inDB, &sigFields, sizeof( sigFields ) );
+                               require_noerr( err, exit );
+                               
+                               err = DataBuffer_Append( inDB, zone, signerLen );
+                               require_noerr( err, exit );
+                               
+                               // Append RRSIG record RDATA signature to response.
+                               
+                               err = DataBuffer_Append( inDB, signature, signatureLen );
+                               require_noerr( err, exit );
+                               
+                               ++answerCount;
+                               DataBuffer_Free( sigMsg );
+                               sigMsg = NULL;
+                       }
                }
+               check_compile_time_code( sizeof( nameCPtr ) == sizeof( parentCPtr ) );
+               memcpy( nameCPtr, parentCPtr, sizeof( nameCPtr ) );
                
-               namePtr[ 0 ] = superPtr[ 0 ];
-               namePtr[ 1 ] = superPtr[ 1 ];
+               ownerLower              = DomainNameGetNextLabel( qnameLower );
+               ownerLowerLen   = DomainNameLength( ownerLower );
        }
        else
        {
                // There are no aliases, so initialize the name compression pointer to point to QNAME.
                
-               DNSMessageWriteLabelPointer( namePtr, kDNSHeaderLength );
+               DNSMessageWriteLabelPointer( nameCPtr, kDNSHeaderLength );
+               
+               ownerLower              = qnameLower;
+               ownerLowerLen   = qnameLowerLen;
        }
        
        if( ( inQType == kDNSServiceType_A ) || ( inQType == kDNSServiceType_AAAA ) )
        {
-               uint8_t *               lsb;                                    // Pointer to the least significant byte of record data.
-               size_t                  recordLen;                              // Length of the entire record.
-               size_t                  rdataLen;                               // Length of record's RDATA.
-               uint8_t                 rdata[ 16 ];                    // A buffer that's big enough for either A or AAAA RDATA.
-               uint8_t                 randIntegers[ 255 ];    // Array for random integers in [1, 255].
-               const int               useBadAddrs = ( me->badUDPMode && !inForTCP ) ? true : false;
+               dns_fixed_fields_record         recFields;
+               dns_fixed_fields_rrsig          sigFields;
+               uint8_t *                                       idPtr;                                  // Pointer to the host identifier portion of an IP address.
+               size_t                                          recordLen;                              // Length of the entire record.
+               size_t                                          rdataLen;                               // Length of record's RDATA.
+               size_t                                          signerLen = 0;
+               unsigned int                            i;                                              // For-loop counter.
+               uint8_t                                         rdata[ 16 ];                    // A buffer that's big enough for either A or AAAA RDATA.
+               uint8_t                                         randIntegers[ 255 ];    // Array for random integers in [1, 255].
+               Boolean                                         needSig;
                
                if( inQType == kDNSServiceType_A )
                {
-                       const uint32_t          baseAddrV4 = useBadAddrs ? kDNSServerBadBaseAddrV4 : kDNSServerBaseAddrV4;
+                       uint32_t                baseAddr;
                        
-                       require_quiet( nameHasA, done );
+                       require_action_quiet( nameFlags & kDNSNameFlag_HasA, done, status = kQueryStatus_OK );
                        
                        rdataLen = 4;
-                       WriteBig32( rdata, baseAddrV4 );
-                       lsb = &rdata[ 3 ];
+                       baseAddr = ( me->badUDPMode && !inForTCP ) ? kDNSServerBadBaseAddrV4 : kDNSServerBaseAddrV4;
+                       WriteBig32( rdata, baseAddr );
+                       idPtr = &rdata[ 3 ]; // The last octet is the host identifier since the IPv4 address block is /24.
                }
                else
                {
-                       const uint8_t * const           baseAddrV6 = useBadAddrs ? kDNSServerBadBaseAddrV6 : kDNSServerBaseAddrV6;
+                       const uint8_t           ( *baseAddr )[ 16 ];
                        
-                       require_quiet( nameHasAAAA, done );
+                       require_action_quiet( nameFlags & kDNSNameFlag_HasAAAA, done, status = kQueryStatus_OK );
                        
                        rdataLen = 16;
-                       memcpy( rdata, baseAddrV6, 16 );
-                       lsb = &rdata[ 15 ];
+                       baseAddr = ( me->badUDPMode && !inForTCP ) ? &kDNSServerBadBaseAddrV6 : &kDNSServerBaseAddrV6;
+                       memcpy( rdata, baseAddr, rdataLen );
+                       idPtr = &rdata[ 14 ]; // The last two octets are the host identifier since we allow up to 511 IPv6 addresses.
                }
                
                if( randCount > 0 )
@@ -8317,7 +9552,7 @@ static OSStatus
                        for( i = 0; i < randCount; ++i ) randIntegers[ i ] = (uint8_t)( i + 1 );
                        
                        // Prevent dubious static analyzer warning.
-                       // Note: _DNSServerNameIsHostname() already enforces randCount >= addrCount. Also, this require_fatal() check
+                       // Note: _DNSServerParseHostName() already enforces randCount >= addrCount. Also, this require_fatal() check
                        // needs to be placed right before the next for-loop. Any earlier, and the static analyzer warning will persist
                        // for some reason.
                        
@@ -8343,77 +9578,189 @@ static OSStatus
                                }
                        }
                }
-               
-               recordLen = sizeof( namePtr ) + sizeof( fields ) + rdataLen;
+               needSig = ( inDNSSEC && ( nameFlags & kDNSNameFlag_HasRRSIG ) ) ? true : false;
+               if( needSig )
+               {
+                       uint32_t                inceptionSecs;
+                       int                             labelCount;
+                       
+                       // Initialize signing buffer.
+                       
+                       check( !sigMsg );
+                       sigMsg = &sigDB;
+                       DataBuffer_Init( sigMsg, sigBuf, sizeof( sigBuf ), SIZE_MAX );
+                       
+                       // Append RRSIG record RDATA fixed fields to signing buffer.
+                       
+                       memset( &sigFields, 0, sizeof( sigFields ) );
+                       dns_fixed_fields_rrsig_set_type_covered( &sigFields, (uint16_t) inQType );
+                       dns_fixed_fields_rrsig_set_algorithm( &sigFields, DNSKeyInfoGetAlgorithm( zsk ) );
+                       labelCount = DomainNameLabelCount( ownerLower );
+                       check( labelCount >= 0 );
+                       dns_fixed_fields_rrsig_set_labels( &sigFields, (uint8_t) labelCount );
+                       dns_fixed_fields_rrsig_set_original_ttl( &sigFields, ttl );
+                       inceptionSecs = (uint32_t) now.tv_sec;
+                       dns_fixed_fields_rrsig_set_signature_expiration( &sigFields, inceptionSecs + kSecondsPerDay );
+                       dns_fixed_fields_rrsig_set_signature_inception( &sigFields, inceptionSecs );
+                       dns_fixed_fields_rrsig_set_key_tag( &sigFields, DNSKeyInfoGetKeyTag( zsk ) );
+                       
+                       err = DataBuffer_Append( sigMsg, &sigFields, sizeof( sigFields ) );
+                       require_noerr( err, exit );
+                       
+                       // Append RRSIG record RDATA signer to signing buffer.
+                       
+                       signerLen = DomainNameLength( zone );
+                       err = DataBuffer_Append( sigMsg, zone, signerLen );
+                       require_noerr( err, exit );
+               }
+               recordLen = sizeof( nameCPtr ) + sizeof( recFields ) + rdataLen;
+               dns_fixed_fields_record_init( &recFields, (uint16_t) inQType, kDNSServiceClass_IN, ttl, (uint16_t) rdataLen );
                for( i = 0; i < addrCount; ++i )
                {
+                       // If the transport is UDP, make sure the message is within the UDP size limit.
+                       
+                       if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
+                       {
+                               status = kQueryStatus_Truncated;
+                               goto done;
+                       }
+                       // Append A/AAAA record NAME to response.
+                       
+                       err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) );
+                       require_noerr( err, exit );
+                       
+                       // Append A/AAAA record TYPE, CLASS, TTL, and RDLENGTH to response.
+                       
+                       err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) );
+                       require_noerr( err, exit );
+                       
+                       // Append A/AAAA record RDATA to response.
+                       
+                       hostID = ( randCount > 0 ) ? randIntegers[ i ] : ( i + 1 );
+                       if( inQType == kDNSServiceType_A )
+                       {
+                               *idPtr = (uint8_t) hostID;
+                       }
+                       else
+                       {
+                               WriteBig16( idPtr, hostID );
+                       }
+                       err = DataBuffer_Append( inDB, rdata, rdataLen );
+                       require_noerr( err, exit );
+                       
+                       ++answerCount;
+                       if( needSig )
+                       {
+                               // Append A/AAAA record to signing buffer.
+                               
+                               err = DataBuffer_Append( sigMsg, ownerLower, ownerLowerLen );
+                               require_noerr( err, exit );
+                               
+                               err = DataBuffer_Append( sigMsg, &recFields, sizeof( recFields ) );
+                               require_noerr( err, exit );
+                               
+                               err = DataBuffer_Append( sigMsg, rdata, rdataLen );
+                               require_noerr( err, exit );
+                       }
+               }
+               if( needSig )
+               {
+                       uint8_t         signature[ kDNSServerSignatureLengthMax ];
+                       size_t          signatureLen;
+                       Boolean         didSign;
+                       
+                       // Compute signature with ZSK.
+                       
+                       memset( signature, 0, sizeof( signature ) );
+                       didSign = DNSKeyInfoSign( zsk, DataBuffer_GetPtr( sigMsg ), DataBuffer_GetLen( sigMsg ),
+                               signature, &signatureLen );
+                       require_quiet( didSign, exit );
+                       
+               #if( DEBUG )
+                       _DNSServerSigCheck( ownerLower, inQType, DataBuffer_GetPtr( sigMsg ), DataBuffer_GetLen( sigMsg ), signature,
+                               signatureLen, zsk );
+               #endif
+                       // If the transport is UDP, make sure the message is within the UDP size limit.
+                       
+                       rdataLen        = sizeof( sigFields ) + signerLen + signatureLen;
+                       recordLen       = sizeof( nameCPtr ) + sizeof( recFields ) + rdataLen;
                        if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
                        {
-                               truncated = true;
+                               status = kQueryStatus_Truncated;
                                goto done;
                        }
+                       // Append RRSIG record NAME to response.
+                       
+                       err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) );
+                       require_noerr( err, exit );
                        
-                       // Set record NAME.
+                       // Append RRSIG record TYPE, CLASS, TTL, and RDLENGTH to response.
                        
-                       err = DataBuffer_Append( inDB, namePtr, sizeof( namePtr ) );
+                       dns_fixed_fields_record_init( &recFields, kDNSServiceType_RRSIG, kDNSServiceClass_IN, ttl, (uint16_t) rdataLen );
+                       err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) );
                        require_noerr( err, exit );
                        
-                       // Set record TYPE, CLASS, TTL, and RDLENGTH.
+                       // Append RRSIG record RDATA fixed fields and signer to response.
                        
-                       dns_fixed_fields_record_init( &fields, (uint16_t) inQType, kDNSServiceClass_IN, ttl, (uint16_t) rdataLen );
-                       err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+                       err = DataBuffer_Append( inDB, &sigFields, sizeof( sigFields ) );
                        require_noerr( err, exit );
                        
-                       // Set record RDATA.
+                       err = DataBuffer_Append( inDB, zone, signerLen );
+                       require_noerr( err, exit );
                        
-                       *lsb = ( randCount > 0 ) ? randIntegers[ i ] : ( *lsb + 1 );
+                       // Append RRSIG record RDATA signature to response.
                        
-                       err = DataBuffer_Append( inDB, rdata, rdataLen );
+                       err = DataBuffer_Append( inDB, signature, signatureLen );
                        require_noerr( err, exit );
                        
                        ++answerCount;
+                       DataBuffer_Free( sigMsg );
+                       sigMsg = NULL;
                }
        }
        else if( inQType == kDNSServiceType_SRV )
        {
-               require_quiet( nameHasSRV, done );
+               dns_fixed_fields_record         recFields;
+               size_t                                          i;
                
-               dns_fixed_fields_record_init( &fields, kDNSServiceType_SRV, kDNSServiceClass_IN, me->defaultTTL, 0 );
+               require_action_quiet( nameFlags & kDNSNameFlag_HasSRV, done, status = kQueryStatus_OK );
                
+               dns_fixed_fields_record_init( &recFields, kDNSServiceType_SRV, kDNSServiceClass_IN, me->defaultTTL, 0 );
                for( i = 0; i < srvCount; ++i )
                {
-                       dns_fixed_fields_srv            fieldsSRV;
+                       dns_fixed_fields_srv            srvFields;
                        size_t                                          rdataLen;
                        size_t                                          recordLen;
                        const ParsedSRV * const         srv = &srvArray[ i ];
                        
-                       rdataLen  = sizeof( fieldsSRV ) + srvDomainLen + srv->targetLen + 1;
-                       recordLen = sizeof( namePtr ) + sizeof( fields ) + rdataLen;
+                       // If the transport is UDP, make sure the message is within the UDP size limit.
                        
+                       rdataLen  = sizeof( srvFields ) + srvDomainLen + srv->targetLen + 1;
+                       recordLen = sizeof( nameCPtr ) + sizeof( recFields ) + rdataLen;
                        if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
                        {
-                               truncated = true;
+                               status = kQueryStatus_Truncated;
                                goto done;
                        }
+                       // Append record NAME to response.
                        
-                       // Append record NAME.
-                       
-                       err = DataBuffer_Append( inDB, namePtr, sizeof( namePtr ) );
+                       err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) );
                        require_noerr( err, exit );
                        
-                       // Append record TYPE, CLASS, TTL, and RDLENGTH.
+                       // Append record TYPE, CLASS, TTL, and RDLENGTH to response.
                        
-                       WriteBig16( fields.rdlength, rdataLen );
-                       err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+                       dns_fixed_fields_record_set_rdlength( &recFields, (uint16_t) rdataLen );
+                       err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) );
                        require_noerr( err, exit );
                        
-                       // Append SRV RDATA.
-                       
-                       dns_fixed_fields_srv_init( &fieldsSRV, srv->priority, srv->weight, srv->port );
+                       // Append SRV RDATA priority, weight, and port to response.
                        
-                       err = DataBuffer_Append( inDB, &fieldsSRV, sizeof( fieldsSRV ) );
+                       dns_fixed_fields_srv_init( &srvFields, srv->priority, srv->weight, srv->port );
+                       err = DataBuffer_Append( inDB, &srvFields, sizeof( srvFields ) );
                        require_noerr( err, exit );
                        
+                       // Append SRV RDATA target non-root labels to response.
+                       
                        if( srv->targetLen > 0 )
                        {
                                err = DataBuffer_Append( inDB, srv->targetPtr, srv->targetLen );
@@ -8426,7 +9773,9 @@ static OSStatus
                                require_noerr( err, exit );
                        }
                        
-                       err = DataBuffer_Append( inDB, "", 1 ); // Append root label.
+                       // Append SRV RDATA target root label to response.
+                       
+                       err = DataBuffer_Append( inDB, "", 1 );
                        require_noerr( err, exit );
                        
                        ++answerCount;
@@ -8436,7 +9785,7 @@ static OSStatus
        {
                size_t          nameLen, recordLen;
                
-               require_quiet( nameHasSOA, done );
+               require_action_quiet( nameFlags & kDNSNameFlag_HasSOA, done, status = kQueryStatus_OK );
                
                nameLen = DomainNameLength( me->domain );
                if( !inForTCP )
@@ -8444,9 +9793,11 @@ static OSStatus
                        err = AppendSOARecord( NULL, me->domain, nameLen, 0, 0, 0, kRootLabel, kRootLabel, 0, 0, 0, 0, 0, &recordLen );
                        require_noerr( err, exit );
                        
+                       // If the transport is UDP, make sure the message is within the UDP size limit.
+                       
                        if( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize )
                        {
-                               truncated = true;
+                               status = kQueryStatus_Truncated;
                                goto done;
                        }
                }
@@ -8458,200 +9809,864 @@ static OSStatus
                
                ++answerCount;
        }
-       
-done:
-       hdr = (DNSHeader *) DataBuffer_GetPtr( inDB );
-       flags = DNSHeaderGetFlags( hdr );
-       if( notImplemented )
-       {
-               rcode = kDNSRCode_NotImplemented;
-       }
-       else
+       else if( inQType == kDNSServiceType_PTR )
        {
-               flags |= kDNSHeaderFlag_AuthAnswer;
-               if( truncated ) flags |= kDNSHeaderFlag_Truncation;
-               rcode = nameExists ? kDNSRCode_NoError : kDNSRCode_NXDomain;
+               dns_fixed_fields_record         recFields;
+               size_t                                          domainLen, rdataLen, recordLen;
+               uint8_t                                         label[ 1 + kDomainLabelLengthMax ];
+               uint8_t *                                       dst = &label[ 1 ];
+               const uint8_t *                         lim = &label[ countof( label ) ];
+               
+               if( nameFlags & kDNSNameFlag_HasPTRv4 )
+               {
+                       size_t                          maxLen;
+                       int                                     n;
+                       const uint32_t          ipv4Addr = kDNSServerBaseAddrV4 + hostID;
+                       
+                       maxLen = (size_t)( lim - dst );
+                       n = MemPrintF( dst, maxLen, "ipv4-%u-%u-%u-%u",
+                               ( ipv4Addr >> 24 ) & 0xFFU,
+                               ( ipv4Addr >> 16 ) & 0xFFU,
+                               ( ipv4Addr >>  8 ) & 0xFFU,
+                                 ipv4Addr         & 0xFFU );
+                       require_fatal( ( n > 0 ) && ( ( (size_t) n ) <= maxLen ), "Failed to print reverse IPv4 hostname label" );
+                       dst += n;
+               }
+               else if( nameFlags & kDNSNameFlag_HasPTRv6 )
+               {
+                       size_t          maxLen;
+                       int                     n, i;
+                       uint8_t         ipv6Addr[ 16 ];
+                       
+                       maxLen = (size_t)( lim - dst );
+                       n = MemPrintF( dst, maxLen, "ipv6" );
+                       require_fatal( ( n > 0 ) && ( ( (size_t) n ) <= maxLen ), "Failed to print reverse IPv6 hostname label" );
+                       dst += n;
+                       
+                       memcpy( ipv6Addr, kDNSServerBaseAddrV6, 16 );
+                       ipv6Addr[ 15 ] = (uint8_t) hostID;
+                       for( i = 0; i < 8; ++i )
+                       {
+                               maxLen = (size_t)( lim - dst );
+                               n = MemPrintF( dst, maxLen, "-%04x", ReadBig16( &ipv6Addr[ i * 2 ] ) );
+                               require_fatal( ( n > 0 ) && ( ( (size_t) n ) <= maxLen ), "Failed to print reverse IPv6 hostname label" );
+                               dst += n;
+                       }
+               }
+               else
+               {
+                       status = kQueryStatus_OK;
+                       goto done;
+               }
+               label[ 0 ] = (uint8_t)( dst - &label[ 1 ] );
+               
+               // If the transport is UDP, make sure the message is within the UDP size limit.
+               
+               domainLen       = DomainNameLength( me->domain );
+               rdataLen        = 1 + label[ 0 ] + domainLen;
+               recordLen       = sizeof( nameCPtr ) + sizeof( recFields ) + rdataLen;
+               if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
+               {
+                       status = kQueryStatus_Truncated;
+                       goto done;
+               }
+               
+               // Append PTR record NAME to response.
+               
+               err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) );
+               require_noerr( err, exit );
+               
+               // Append PTR record TYPE, CLASS, TTL, and RDLENGTH to response.
+               
+               dns_fixed_fields_record_init( &recFields, kDNSServiceType_PTR, kDNSServiceClass_IN, me->defaultTTL,
+                       (uint16_t) rdataLen );
+               err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) );
+               require_noerr( err, exit );
+               
+               // Append PTR record RDATA to response.
+               
+               err = DataBuffer_Append( inDB, label, 1 + label[ 0 ] );
+               require_noerr( err, exit );
+               
+               err = DataBuffer_Append( inDB, me->domain, domainLen );
+               require_noerr( err, exit );
+               
+               ++answerCount;
        }
-       DNSFlagsSetRCode( flags, rcode );
-       DNSHeaderSetFlags( hdr, flags );
-       DNSHeaderSetAnswerCount( hdr, answerCount );
-       err = kNoErr;
-       
-exit:
-       return( err );
-}
-
-static Boolean
-       _DNSServerNameIsHostname(
-               DNSServerRef    me,
-               const uint8_t * inName,
-               uint32_t *              outAliasCount,
-               uint32_t                inAliasTTLs[ kMaxAliasTTLCount ],
-               size_t *                outAliasTTLCount,
-               unsigned int *  outCount,
-               unsigned int *  outRandCount,
-               uint32_t *              outTTL,
-               Boolean *               outHasA,
-               Boolean *               outHasAAAA,
-               Boolean *               outHasSOA )
-{
-       OSStatus                        err;
-       const uint8_t *         label;
-    const uint8_t *            nextLabel;
-       uint32_t                        aliasCount              = 0;    // Arg from Alias label. Valid values are in [2, 2^31 - 1].
-       unsigned int            count                   = 0;    // First arg from Count label. Valid values are in [1, 255].
-       unsigned int            randCount               = 0;    // Second arg from Count label. Valid values are in [count, 255].
-       int32_t                         ttl                             = -1;   // Arg from TTL label. Valid values are in [0, 2^31 - 1].
-       size_t                          aliasTTLCount   = 0;    // Count of TTL args from Alias-TTL label.
-       int                                     hasTagLabel             = false;
-       int                                     hasIPv4Label    = false;
-       int                                     hasIPv6Label    = false;
-       int                                     isNameValid             = false;
-       
-       for( label = inName; label[ 0 ]; label = nextLabel )
+       else if( inQType == kDNSServiceType_DNSKEY )
        {
-               uint32_t                arg;
+               size_t                                          recordLen;
+               size_t                                          signerLen = 0;
+               dns_fixed_fields_record         recFields;
+               dns_fixed_fields_rrsig          sigFields;
+               Boolean                                         needSig;
+               
+               require_action_quiet( nameFlags & kDNSNameFlag_HasDNSKEY, done, status = kQueryStatus_OK );
+               
+               // If the transport is UDP, make sure the message is within the UDP size limit.
+               
+               recordLen = sizeof( nameCPtr ) + sizeof( recFields ) + DNSKeyInfoGetRDataLen( zsk );
+               if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
+               {
+                       status = kQueryStatus_Truncated;
+                       goto done;
+               }
+               // Append ZSK DNSKEY record NAME to response.
+               
+               err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) );
+               require_noerr( err, exit );
+               
+               // Append ZSK DNSKEY record TYPE, CLASS, TTL, and RDLENGTH to response.
+               
+               dns_fixed_fields_record_init( &recFields, kDNSServiceType_DNSKEY, kDNSServiceClass_IN, ttl,
+                       DNSKeyInfoGetRDataLen( zsk ) );
+               err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) );
+               require_noerr( err, exit );
+               
+               // Append ZSK DNSKEY record RDATA to response.
                
-               nextLabel = &label[ 1 + label[ 0 ] ];
+               err = DataBuffer_Append( inDB, DNSKeyInfoGetRDataPtr( zsk ), DNSKeyInfoGetRDataLen( zsk ) );
+               require_noerr( err, exit );
                
-               // Check if the first label is a valid alias TTL sequence label.
+               ++answerCount;
                
-               if( ( label == inName ) && ( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_AliasTTL ) == 0 ) )
+               needSig = ( inDNSSEC && ( nameFlags & kDNSNameFlag_HasRRSIG ) ) ? true : false;
+               if( needSig )
                {
-                       const char *                    ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_AliasTTL ) ];
-                       const char * const              end = (const char *) nextLabel;
-                       const char *                    next;
+                       uint32_t                inceptionSecs;
+                       int                             labelCount;
                        
-                       check( label[ 0 ] <= kDomainLabelLengthMax );
+                       // Initialize signing buffer.
                        
-                       while( ptr < end )
-                       {
-                               if( *ptr != '-' ) break;
-                               ++ptr;
-                               err = DecimalTextToUInt32( ptr, end, &arg, &next );
-                               if( err || ( arg > INT32_MAX ) ) break; // TTL must be in [0, 2^31 - 1].
-                               inAliasTTLs[ aliasTTLCount++ ] = arg;
-                               ptr = next;
-                       }
-                       if( ( aliasTTLCount == 0 ) || ( ptr != end ) ) break;
+                       check( !sigMsg );
+                       sigMsg = &sigDB;
+                       DataBuffer_Init( sigMsg, sigBuf, sizeof( sigBuf ), SIZE_MAX );
+                       
+                       // Append RRSIG record RDATA fixed fields to signing buffer.
+                       
+                       memset( &sigFields, 0, sizeof( sigFields ) );
+                       dns_fixed_fields_rrsig_set_type_covered( &sigFields, kDNSServiceType_DNSKEY );
+                       dns_fixed_fields_rrsig_set_algorithm( &sigFields, DNSKeyInfoGetAlgorithm( ksk ) );
+                       labelCount = DomainNameLabelCount( ownerLower );
+                       check( labelCount >= 0 );
+                       dns_fixed_fields_rrsig_set_labels( &sigFields, (uint8_t) labelCount );
+                       dns_fixed_fields_rrsig_set_original_ttl( &sigFields, ttl );
+                       inceptionSecs = (uint32_t) now.tv_sec;
+                       dns_fixed_fields_rrsig_set_signature_expiration( &sigFields, inceptionSecs + kSecondsPerDay );
+                       dns_fixed_fields_rrsig_set_signature_inception( &sigFields, inceptionSecs );
+                       dns_fixed_fields_rrsig_set_key_tag( &sigFields, DNSKeyInfoGetKeyTag( ksk ) );
+                       
+                       err = DataBuffer_Append( sigMsg, &sigFields, sizeof( sigFields ) );
+                       require_noerr( err, exit );
+                       
+                       // Append RRSIG record RDATA signer to signing buffer.
+                       
+                       signerLen = DomainNameLength( zone );
+                       err = DataBuffer_Append( sigMsg, zone, signerLen );
+                       require_noerr( err, exit );
+                       
+                       // Append ZSK DNSKEY record to signing buffer.
+                       
+                       err = DataBuffer_Append( sigMsg, ownerLower, ownerLowerLen );
+                       require_noerr( err, exit );
+                       
+                       err = DataBuffer_Append( sigMsg, &recFields, sizeof( recFields ) );
+                       require_noerr( err, exit );
+                       
+                       err = DataBuffer_Append( sigMsg, DNSKeyInfoGetRDataPtr( zsk ), DNSKeyInfoGetRDataLen( zsk ) );
+                       require_noerr( err, exit );
+               }
+               // If the transport is UDP, make sure the message is within the UDP size limit.
+               
+               recordLen = sizeof( nameCPtr ) + sizeof( recFields ) + DNSKeyInfoGetRDataLen( ksk );
+               if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
+               {
+                       status = kQueryStatus_Truncated;
+                       goto done;
                }
+               // Append KSK DNSKEY record NAME to response.
+               
+               err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) );
+               require_noerr( err, exit );
                
-               // Check if the first label is a valid alias label.
+               // Append KSK DNSKEY record TYPE, CLASS, TTL, and RDLENGTH to response.
                
-               else if( ( label == inName ) && ( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_Alias ) == 0 ) )
+               err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) );
+               require_noerr( err, exit );
+               
+               // Append KSK DNSKEY record RDATA to response.
+               
+               err = DataBuffer_Append( inDB, DNSKeyInfoGetRDataPtr( ksk ), DNSKeyInfoGetRDataLen( ksk ) );
+               require_noerr( err, exit );
+               
+               ++answerCount;
+               if( needSig )
                {
-                       const char *                    ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_Alias ) ];
-                       const char * const              end = (const char *) nextLabel;
+                       size_t          rdataLen;
+                       uint8_t         signature[ kDNSServerSignatureLengthMax ];
+                       size_t          signatureLen;
+                       Boolean         didSign;
                        
-                       if( ptr < end )
-                       {
-                               if( *ptr++ != '-' ) break;
-                               err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
-                               if( err || ( arg < 2 ) || ( arg > INT32_MAX ) ) break;  // Alias count must be in [2, 2^31 - 1].
-                               aliasCount = arg;
-                               if( ptr != end ) break;
-                       }
-                       else
+                       // Append KSK DNSKEY record to signing buffer.
+                       
+                       err = DataBuffer_Append( sigMsg, ownerLower, ownerLowerLen );
+                       require_noerr( err, exit );
+                       
+                       err = DataBuffer_Append( sigMsg, &recFields, sizeof( recFields ) );
+                       require_noerr( err, exit );
+                       
+                       err = DataBuffer_Append( sigMsg, DNSKeyInfoGetRDataPtr( ksk ), DNSKeyInfoGetRDataLen( ksk ) );
+                       require_noerr( err, exit );
+                       
+                       // Compute signature with KSK.
+                       
+                       memset( signature, 0, sizeof( signature ) );
+                       didSign = DNSKeyInfoSign( ksk, DataBuffer_GetPtr( sigMsg ), DataBuffer_GetLen( sigMsg ),
+                               signature, &signatureLen );
+                       require_quiet( didSign, exit );
+                       
+               #if( DEBUG )
+                       _DNSServerSigCheck( ownerLower, inQType, DataBuffer_GetPtr( sigMsg ), DataBuffer_GetLen( sigMsg ), signature,
+                               signatureLen, ksk );
+               #endif
+                       // If the transport is UDP, make sure the message is within the UDP size limit.
+                       
+                       rdataLen        = sizeof( sigFields ) + signerLen + signatureLen;
+                       recordLen       = sizeof( nameCPtr ) + sizeof( recFields ) + rdataLen;
+                       if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
                        {
-                               aliasCount = 1;
+                               status = kQueryStatus_Truncated;
+                               goto done;
                        }
+                       // Append RRSIG record NAME to response.
+                       
+                       err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) );
+                       require_noerr( err, exit );
+                       
+                       // Append RRSIG record TYPE, CLASS, TTL, and RDLENGTH to response.
+                       
+                       dns_fixed_fields_record_init( &recFields, kDNSServiceType_RRSIG, kDNSServiceClass_IN, ttl, (uint16_t) rdataLen );
+                       err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) );
+                       require_noerr( err, exit );
+                       
+                       // Append RRSIG record RDATA fixed fields and signer to response.
+                       
+                       err = DataBuffer_Append( inDB, &sigFields, sizeof( sigFields ) );
+                       require_noerr( err, exit );
+                       
+                       err = DataBuffer_Append( inDB, zone, signerLen );
+                       require_noerr( err, exit );
+                       
+                       // Append RRSIG record RDATA signature to response.
+                       
+                       err = DataBuffer_Append( inDB, signature, signatureLen );
+                       require_noerr( err, exit );
+                       
+                       ++answerCount;
+                       DataBuffer_Free( sigMsg );
+                       sigMsg = NULL;
                }
+       }
+       else if( inQType == kDNSServiceType_DS )
+       {
+               SHA256_CTX                                      ctx;
+               size_t                                          rdataLen, i, recordLen;
+               size_t                                          signerLen = 0;
+               dns_ds_sha256 *                         dsPtr;
+               dns_ds_sha256 *                         dsPtrs[ 2 ];
+               int                                                     cmp;
+               dns_ds_sha256                           dsZSK, dsKSK;
+               dns_fixed_fields_rrsig          sigFields;
+               dns_fixed_fields_record         recFields;
+               Boolean                                         needSig;
                
-               // Check if this label is a valid count label.
+               check_compile_time_code( sizeof( dsPtr->digest ) == SHA256_DIGEST_LENGTH );
+               
+               require_action_quiet( nameFlags & kDNSNameFlag_HasDS, done, status = kQueryStatus_OK );
+               
+               // Set up ZSK DS RDATA.
+               
+               dsPtr = &dsZSK;
+               memset( dsPtr, 0, sizeof( *dsPtr ) );
+               dns_ds_sha256_set_key_tag( dsPtr, DNSKeyInfoGetKeyTag( zsk ) );
+               dns_ds_sha256_set_algorithm( dsPtr, DNSKeyInfoGetAlgorithm( zsk ) );
+               dns_ds_sha256_set_digest_type( dsPtr, kDSDigestType_SHA256 );
+               
+               SHA256_Init( &ctx );
+               SHA256_Update( &ctx, ownerLower, ownerLowerLen );
+               SHA256_Update( &ctx, DNSKeyInfoGetRDataPtr( zsk ), DNSKeyInfoGetRDataLen( zsk ) );
+               SHA256_Final( dsPtr->digest, &ctx );
+               
+               // Set up KSK DS RDATA.
                
-               else if( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_Count ) == 0  )
+               dsPtr = &dsKSK;
+               memset( dsPtr, 0, sizeof( *dsPtr ) );
+               dns_ds_sha256_set_key_tag( dsPtr, DNSKeyInfoGetKeyTag( ksk ) );
+               dns_ds_sha256_set_algorithm( dsPtr, DNSKeyInfoGetAlgorithm( ksk ) );
+               dns_ds_sha256_set_digest_type( dsPtr, kDSDigestType_SHA256 );
+               
+               SHA256_Init( &ctx );
+               SHA256_Update( &ctx, ownerLower, ownerLowerLen );
+               SHA256_Update( &ctx, DNSKeyInfoGetRDataPtr( ksk ), DNSKeyInfoGetRDataLen( ksk ) );
+               SHA256_Final( dsPtr->digest, &ctx );
+               
+               // Order the DS RDATAs
+               
+               cmp = memcmp( &dsZSK, &dsKSK, sizeof( dns_ds_sha256 ) );
+               if( cmp <= 0 )
+               {
+                       dsPtrs[ 0 ] = &dsZSK;
+                       dsPtrs[ 1 ] = ( cmp == 0 ) ? NULL : &dsKSK;
+               }
+               else
+               {
+                       dsPtrs[ 0 ] = &dsKSK;
+                       dsPtrs[ 1 ] = &dsZSK;
+               }
+               needSig = ( inDNSSEC && ( nameFlags & kDNSNameFlag_HasRRSIG ) ) ? true : false;
+               if( needSig )
                {
-                       const char *                    ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_Count ) ];
-                       const char * const              end = (const char *) nextLabel;
+                       uint32_t                inceptionSecs;
+                       int                             labelCount;
                        
-                       if( count > 0 ) break;  // Count cannot be specified more than once.
+                       // Initialize signing buffer.
                        
-                       err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
-                       if( err || ( arg < 1 ) || ( arg > 255 ) ) break;        // Count must be in [1, 255].
-                       count = (unsigned int) arg;
+                       check( !sigMsg );
+                       sigMsg = &sigDB;
+                       DataBuffer_Init( sigMsg, sigBuf, sizeof( sigBuf ), SIZE_MAX );
                        
-                       if( ptr < end )
+                       // Append RRSIG record RDATA fixed fields to signing buffer.
+                       
+                       memset( &sigFields, 0, sizeof( sigFields ) );
+                       dns_fixed_fields_rrsig_set_type_covered( &sigFields, kDNSServiceType_DS );
+                       dns_fixed_fields_rrsig_set_algorithm( &sigFields, DNSKeyInfoGetAlgorithm( zskParent ) );
+                       labelCount = DomainNameLabelCount( ownerLower );
+                       check( labelCount >= 0 );
+                       dns_fixed_fields_rrsig_set_labels( &sigFields, (uint8_t) labelCount );
+                       dns_fixed_fields_rrsig_set_original_ttl( &sigFields, ttl );
+                       inceptionSecs = (uint32_t) now.tv_sec;
+                       dns_fixed_fields_rrsig_set_signature_expiration( &sigFields, inceptionSecs + kSecondsPerDay );
+                       dns_fixed_fields_rrsig_set_signature_inception( &sigFields, inceptionSecs );
+                       dns_fixed_fields_rrsig_set_key_tag( &sigFields, DNSKeyInfoGetKeyTag( zskParent ) );
+                       
+                       err = DataBuffer_Append( sigMsg, &sigFields, sizeof( sigFields ) );
+                       require_noerr( err, exit );
+                       
+                       // Append RRSIG record RDATA signer to signing buffer.
+                       
+                       signerLen = DomainNameLength( zoneParent );
+                       err = DataBuffer_Append( sigMsg, zoneParent, signerLen );
+                       require_noerr( err, exit );
+               }
+               rdataLen = sizeof( dns_ds_sha256 );
+               dns_fixed_fields_record_init( &recFields, kDNSServiceType_DS, kDNSServiceClass_IN, ttl, (uint16_t) rdataLen );
+               for( i = 0; i < countof( dsPtrs ); ++i )
+               {
+                       dsPtr = dsPtrs[ i ];
+                       if( !dsPtr ) continue;
+                       
+                       // If the transport is UDP, make sure the message is within the UDP size limit.
+                       
+                       recordLen = sizeof( nameCPtr ) + sizeof( recFields ) + rdataLen;
+                       if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
                        {
-                               if( *ptr++ != '-' ) break;
-                               err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
-                               if( err || ( arg < (uint32_t) count ) || ( arg > 255 ) ) break; // Rand count must be in [count, 255].
-                               randCount = (unsigned int) arg;
-                               if( ptr != end ) break;
+                               status = kQueryStatus_Truncated;
+                               goto done;
+                       }
+                       // Append DS record NAME to response.
+                       
+                       err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) );
+                       require_noerr( err, exit );
+                       
+                       // Append DS record TYPE, CLASS, TTL, and RDLENGTH to response.
+                       err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) );
+                       require_noerr( err, exit );
+                       
+                       // Append DS record RDATA to response.
+                       
+                       err = DataBuffer_Append( inDB, dsPtr, sizeof( *dsPtr ) );
+                       require_noerr( err, exit );
+                       
+                       ++answerCount;
+                       if( needSig )
+                       {
+                               // Append DS record to signing buffer.
+                               
+                               err = DataBuffer_Append( sigMsg, ownerLower, ownerLowerLen );
+                               require_noerr( err, exit );
+                               
+                               err = DataBuffer_Append( sigMsg, &recFields, sizeof( recFields ) );
+                               require_noerr( err, exit );
+                               
+                               err = DataBuffer_Append( sigMsg, dsPtr, sizeof( *dsPtr ) );
+                               require_noerr( err, exit );
                        }
                }
-               
-               // Check if this label is a valid TTL label.
-               
-               else if( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_TTL ) == 0  )
+               if( needSig )
                {
-                       const char *                    ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_TTL ) ];
-                       const char * const              end = (const char *) nextLabel;
+                       uint8_t         signature[ kDNSServerSignatureLengthMax ];
+                       size_t          signatureLen;
+                       Boolean         didSign;
                        
-                       if( ttl >= 0 ) break;   // TTL cannot be specified more than once.
+                       // Compute signature with parent ZSK.
                        
-                       err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
-                       if( err || ( arg > INT32_MAX ) ) break; // TTL must be in [0, 2^31 - 1].
-                       ttl = (int32_t) arg;
-                       if( ptr != end ) break;
+                       memset( signature, 0, sizeof( signature ) );
+                       didSign = DNSKeyInfoSign( zskParent, DataBuffer_GetPtr( sigMsg ), DataBuffer_GetLen( sigMsg ),
+                               signature, &signatureLen );
+                       require_quiet( didSign, exit );
+                       
+               #if( DEBUG )
+                       _DNSServerSigCheck( ownerLower, inQType, DataBuffer_GetPtr( sigMsg ), DataBuffer_GetLen( sigMsg ), signature,
+                               signatureLen, zskParent );
+               #endif
+                       // If the transport is UDP, make sure the message is within the UDP size limit.
+                       
+                       rdataLen        = sizeof( sigFields ) + signerLen + signatureLen;
+                       recordLen       = sizeof( nameCPtr ) + sizeof( recFields ) + rdataLen;
+                       if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
+                       {
+                               status = kQueryStatus_Truncated;
+                               goto done;
+                       }
+                       // Append RRSIG record NAME to response.
+                       
+                       err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) );
+                       require_noerr( err, exit );
+                       
+                       // Append RRSIG record TYPE, CLASS, TTL, and RDLENGTH to response.
+                       
+                       dns_fixed_fields_record_init( &recFields, kDNSServiceType_RRSIG, kDNSServiceClass_IN, ttl, (uint16_t) rdataLen );
+                       err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) );
+                       require_noerr( err, exit );
+                       
+                       // Append RRSIG record RDATA fixed fields and signer to response.
+                       
+                       err = DataBuffer_Append( inDB, &sigFields, sizeof( sigFields ) );
+                       require_noerr( err, exit );
+                       
+                       err = DataBuffer_Append( inDB, zoneParent, signerLen );
+                       require_noerr( err, exit );
+                       
+                       // Append RRSIG record RDATA signature to response.
+                       
+                       err = DataBuffer_Append( inDB, signature, signatureLen );
+                       require_noerr( err, exit );
+                       
+                       ++answerCount;
+                       
+                       DataBuffer_Free( sigMsg );
+                       sigMsg = NULL;
                }
+       }
+       status = kQueryStatus_OK;
+       
+done:
+       hdr = (DNSHeader *) DataBuffer_GetPtr( inDB );
+       flags = DNSHeaderGetFlags( hdr );
+       switch( status )
+       {
+               case kQueryStatus_OK:
+               case kQueryStatus_Truncated:
+                       flags |= kDNSHeaderFlag_AuthAnswer;
+                       if( status == kQueryStatus_Truncated ) flags |= kDNSHeaderFlag_Truncation;
+                       rcode = nameExists ? kDNSRCode_NoError : kDNSRCode_NXDomain;
+                       break;
                
-               // Check if this label is a valid IPv4 label.
-               
-               else if( strnicmpx( &label[ 1 ], label[ 0 ], kLabel_IPv4 ) == 0 )
-               {
-                       if( hasIPv4Label || hasIPv6Label ) break;       // Valid names have at most one IPv4 or IPv6 label.
-                       hasIPv4Label = true;
-               }
+               case kQueryStatus_NotImplemented:
+                       rcode = kDNSRCode_NotImp;
+                       break;
                
-               // Check if this label is a valid IPv6 label.
+               case kQueryStatus_Refused:
+                       rcode = kDNSRCode_Refused;
+                       break;
                
-               else if( strnicmpx( &label[ 1 ], label[ 0 ], kLabel_IPv6 ) == 0 )
-               {
-                       if( hasIPv4Label || hasIPv6Label ) break;       // Valid names have at most one IPv4 or IPv6 label.
-                       hasIPv6Label = true;
-               }
+               case kQueryStatus_Null:
+               default:
+                       err = kInternalErr;
+                       goto exit;
+       }
+       if( rcodeOverride >= 0 )
+       {
+               dns_fixed_fields_record         recFields;
+               const char *                            rcodeStr;
+               size_t                                          maxLen, txtStrLen, rdataLen, recordLen;
+               int                                                     n;
+               uint8_t                                         rdataBuf[ 1 + 255 ]; // Enough space for one TXT record string.
                
-               // Check if this label is a valid tag label.
+               // Create the RDATA for an informational TXT record that contains the original rcode to put in the Additional
+               // section.
                
-               else if( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_Tag ) == 0  )
-               {
-                       hasTagLabel = true;
-               }
+               maxLen = sizeof( rdataBuf ) - 1;
+               rcodeStr = DNSRCodeToString( rcode );
+               if( rcodeStr )  n = MemPrintF( &rdataBuf[ 1 ], maxLen, "original-rcode=%s", rcodeStr );
+               else                    n = MemPrintF( &rdataBuf[ 1 ], maxLen, "original-rcode=%d", rcode );
+               txtStrLen = ( n > 0 ) ? Min( (size_t) n, maxLen ) : 0;
+               rdataBuf[ 0 ] = (uint8_t) txtStrLen;
+               rdataLen = 1 + txtStrLen;
                
-               // If this and the remaining labels are equal to "d.test.", then the name exists. Otherwise, this label is invalid.
-               // In both cases, there are no more labels to check.
+               // The TXT record isn't strictly necessary, so only include it if it fits.
                
-               else
+               recordLen = sizeof( nameCPtr ) + sizeof( recFields ) + rdataLen;
+               maxLen = inForTCP ? kDNSMaxTCPMessageSize : kDNSMaxUDPMessageSize;
+               if( ( DataBuffer_GetLen( inDB ) < maxLen ) && ( ( maxLen - DataBuffer_GetLen( inDB ) ) >= recordLen ) )
                {
-                       if( DomainNameEqual( label, me->domain ) ) isNameValid = true;
-                       break;
+                       // Append TXT record NAME to response.
+                       
+                       err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) );
+                       require_noerr( err, exit );
+                       
+                       // Append TXT record TYPE, CLASS, TTL, and RDLENGTH to response.
+                       
+                       dns_fixed_fields_record_init( &recFields, kDNSRecordType_TXT, kDNSClassType_IN, 0, (uint16_t) rdataLen );
+                       err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) );
+                       require_noerr( err, exit );
+                       
+                       // Append TXT record RDATA to response.
+                       
+                       err = DataBuffer_Append( inDB, rdataBuf, rdataLen );
+                       require_noerr( err, exit );
+                       
+                       ++additionalCount;
                }
+               rcode = rcodeOverride;
        }
-       require_quiet( isNameValid, exit );
-       
-       if( outAliasCount )             *outAliasCount          = aliasCount;
-       if( outAliasTTLCount )  *outAliasTTLCount       = aliasTTLCount;
-       if( outCount )                  *outCount                       = ( count > 0 ) ? count : 1;
-       if( outRandCount )              *outRandCount           = randCount;
-       if( outTTL )                    *outTTL                         = ( ttl >= 0 ) ? ( (uint32_t) ttl ) : me->defaultTTL;
-       if( outHasA )                   *outHasA                        = ( hasIPv4Label || !hasIPv6Label ) ? true : false;
-       if( outHasAAAA )                *outHasAAAA                     = ( hasIPv6Label || !hasIPv4Label ) ? true : false;
-       if( outHasSOA )
+       DNSFlagsSetRCode( flags, rcode );
+       DNSHeaderSetFlags( hdr, flags );
+       DNSHeaderSetAnswerCount( hdr, answerCount );
+       DNSHeaderSetAdditionalCount( hdr, additionalCount );
+       if( procDelayMs > 0 )
        {
-               *outHasSOA = ( !count && ( ttl < 0 ) && !hasIPv4Label && !hasIPv6Label && !hasTagLabel ) ? true : false;
+               const uint64_t          delayTicks              = MillisecondsToUpTicks( procDelayMs );
+               const uint64_t          elapsedTicks    = UpTicks() - startTicks;
+               
+               if( delayTicks > elapsedTicks ) SleepForUpTicks( delayTicks - elapsedTicks );
        }
+       err = kNoErr;
        
 exit:
-       return( isNameValid ? true : false );
+       FreeNullSafe( qnameLower );
+       if( sigMsg ) DataBuffer_Free( sigMsg );
+       return( err );
 }
 
 static Boolean
-       _DNSServerNameIsSRVName(
-               DNSServerRef            me,
+       _DNSServerNameIsDNSSECZone(
                const uint8_t *         inName,
-               const uint8_t **        outDomainPtr,
-               size_t *                        outDomainLen,
-               ParsedSRV                       inSRVArray[ kMaxParsedSRVCount ],
+               const uint8_t **        outZoneParent,
+               DNSKeyInfoRef *         outZSK,
+               DNSKeyInfoRef *         outKSK,
+               DNSKeyInfoRef *         outParentZSK );
+
+static Boolean
+       _DNSServerParseHostName(
+               DNSServerRef            me,
+               const uint8_t *         inQName,
+               uint32_t *                      outAliasCount,
+               uint32_t                        outAliasTTLs[ kAliasTTLCountMax ],
+               uint32_t *                      outAliasTTLCount,
+               uint32_t *                      outCount,
+               uint32_t *                      outRandCount,
+               uint32_t *                      outIndex,
+               int *                           outRCode,
+               uint32_t *                      outTTL,
+               uint32_t *                      outProcDelayMs,
+               DNSNameFlags *          outFlags,
+               const uint8_t **        outZone,
+               const uint8_t **        outZoneParent,
+               DNSKeyInfoRef *         outZSK,
+               DNSKeyInfoRef *         outKSK,
+               DNSKeyInfoRef *         outParentZSK )
+{
+       OSStatus                        err;
+       const uint8_t *         label;
+       size_t                          labelLen;
+    const uint8_t *            labelNext;
+       uint32_t                        aliasTTLCount   = 0;    // Count of TTL args from Alias-TTL label.
+       uint32_t                        aliasCount              = 0;    // Arg from Alias label. Valid values are in [2, 2^31 - 1].
+       int32_t                         count                   = -1;   // First arg from Count label. Valid values are in [0, 255].
+       uint32_t                        randCount               = 0;    // Second arg from Count label. Valid values are in [count, 255].
+       uint32_t                        index                   = 0;    // Arg from Index label. Valid values are in [1, 2^32 - 1].
+       int                                     rcode                   = -1;   // Arg from RCode label. Valid values are in [0, 15].
+       int32_t                         ttl                             = -1;   // Arg from TTL label. Valid values are in [0, 2^31 - 1].
+       uint32_t                        procDelayMs             = 0;    // Arg from PDelay label. Valid values are in [1, 2000]. Units are in ms.
+       DNSNameFlags            flags                   = 0;
+       int32_t                         maxCount;
+       const uint8_t *         zone                    = NULL;
+       const uint8_t *         zoneParent              = NULL;
+       DNSKeyInfoRef           zsk                             = NULL;
+       DNSKeyInfoRef           ksk                             = NULL;
+       DNSKeyInfoRef           parentZSK               = NULL;
+       Boolean                         isAlias                 = false;
+       
+       for( label = inQName; ( labelLen = *label ) != 0; label = labelNext )
+       {
+               const uint8_t *         labelData;
+               uint32_t                        arg;
+
+               if( labelLen > kDomainLabelLengthMax ) break;
+               labelData = &label[ 1 ];
+               labelNext = &labelData[ labelLen ];
+
+               if( label == inQName )
+               {
+                       // Check if the first label is a valid alias TTL sequence label.
+                       // Note: Since "alias" is a prefix of "alias-ttl", check for "alias-ttl" first.
+
+                       if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_AliasTTL ) == 0 )
+                       {
+                               const char *                    ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_AliasTTL ) ];
+                               const char * const              end = (const char *) labelNext;
+
+                               while( ptr < end )
+                               {
+                                       if( *ptr++ != '-' ) break;
+                                       err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+                                       if( err || ( arg > INT32_MAX ) ) break; // TTL must be in [0, 2^31 - 1].
+                                       if( outAliasTTLs ) outAliasTTLs[ aliasTTLCount ] = arg;
+                                       ++aliasTTLCount;
+                               }
+                               if( ( aliasTTLCount == 0 ) || ( ptr != end ) ) break;
+                               isAlias = true;
+                               continue;
+                       }
+
+                       // Check if the first label is a valid alias label.
+
+                       if( ( strnicmp_prefix( labelData, labelLen, kLabelPrefix_Alias ) == 0 ) )
+                       {
+                               const char *                    ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_Alias ) ];
+                               const char * const              end = (const char *) labelNext;
+
+                               if( ptr < end )
+                               {
+                                       if( *ptr++ != '-' ) break;
+                                       err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+                                       if( err || ( arg < 2 ) || ( arg > INT32_MAX ) ) break; // Alias count must be in [2, 2^31 - 1].
+                                       aliasCount = arg;
+                                       if( ptr != end ) break;
+                               }
+                               else
+                               {
+                                       aliasCount = 1;
+                               }
+                               isAlias = true;
+                               continue;
+                       }
+               }
+
+               // Check if this label is a valid count label.
+
+               if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_Count ) == 0  )
+               {
+                       const char *                    ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_Count ) ];
+                       const char * const              end = (const char *) labelNext;
+
+                       if( count >= 0 ) break; // Count cannot be specified more than once.
+
+                       err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+                       if( err || ( arg > INT32_MAX ) ) break; // The actual upper bound for Count will be verified below.
+                       count = (int32_t) arg;
+
+                       if( ptr < end )
+                       {
+                               if( *ptr++ != '-' ) break;
+                               err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+                               if( err || ( arg < ( (uint32_t) count ) ) || ( arg > 255 ) ) break; // Rand count must be in [count, 255].
+                               randCount = arg;
+                               if( ptr != end ) break;
+                       }
+                       continue;
+               }
+               
+               // Check if this label is a valid Index label.
+               
+               if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_Index ) == 0  )
+               {
+                       const char *                    ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_Index ) ];
+                       const char * const              end = (const char *) labelNext;
+                       
+                       if( index > 0 ) break; // Index cannot be specified more than once.
+                       
+                       err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+                       if( err || ( arg < 1 ) || ( arg > UINT32_MAX ) ) break; // Index must be in [1, 2^32 - 1].
+                       index = arg;
+                       if( ptr != end ) break;
+                       continue;
+               }
+               
+               // Check if this label is a valid IPv4 label.
+               
+               if( strnicmpx( labelData, labelLen, kLabel_IPv4 ) == 0 )
+               {
+                       // Valid names have at most one IPv4 or IPv6 label.
+                       
+                       if( flags & ( kDNSNameFlag_HasA | kDNSNameFlag_HasAAAA ) ) break;
+                       flags |= kDNSNameFlag_HasA;
+                       continue;
+               }
+               
+               // Check if this label is a valid IPv6 label.
+               
+               if( strnicmpx( labelData, labelLen, kLabel_IPv6 ) == 0 )
+               {
+                       // Valid names have at most one IPv4 or IPv6 label.
+                       
+                       if( flags & ( kDNSNameFlag_HasA | kDNSNameFlag_HasAAAA ) ) break;
+                       flags |= kDNSNameFlag_HasAAAA;
+                       continue;
+               }
+               
+               // Check if this label is a valid tag label.
+               
+               if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_Tag ) == 0  )
+               {
+                       continue;
+               }
+               
+               // Check if this label is a valid RCode label.
+               
+               if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_RCode ) == 0  )
+               {
+                       const char *                    ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_RCode ) ];
+                       const char * const              end = (const char *) labelNext;
+                       const char *                    src;
+                       char *                                  dst;
+                       const char *                    lim;
+                       char                                    argStr[ kDomainLabelLengthMax + 1 ];
+                       
+                       if( rcode >= 0 ) break; // RCode cannot be specified more than once.
+                       
+                       // First check if the RCode label's argument is an RCODE mnemonic, e.g., ServFail, Refused, etc.
+                       // The argument part of a label consists of all of the characters up to the start of the next label.
+                       // In order to treat the argument as a C string, the argument must not contain any NUL characters.
+                       // For example, a malformed label such as "rcode-refused\x00garbage" has the argument "refused\x00garbage",
+                       // but as a C string, the NUL character makes it "refused".
+                       
+                       src = ptr;
+                       dst = argStr;
+                       lim = &argStr[ countof( argStr ) - 1 ];
+                       while( ( src < end ) && ( *src != '\0' ) && ( dst < lim ) ) *dst++ = *src++;
+                       if( src == end )
+                       {
+                               *dst = '\0';
+                               rcode = DNSRCodeFromString( argStr );
+                       }
+                       
+                       // If we don't have a valid rcode yet, try to parse the argument as a decimal integer.
+                       
+                       if( rcode < 0 )
+                       {
+                               err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+                               if( err || ( arg > 15 ) ) break; // RCode must be in [0, 15].
+                               rcode = (int) arg;
+                               if( ptr != end ) break;
+                       }
+                       continue;
+               }
+               
+               // Check if this label is a valid TTL label.
+               
+               if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_TTL ) == 0  )
+               {
+                       const char *                    ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_TTL ) ];
+                       const char * const              end = (const char *) labelNext;
+                       
+                       if( ttl >= 0 ) break; // TTL cannot be specified more than once.
+                       
+                       err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+                       if( err || ( arg > INT32_MAX ) ) break; // TTL must be in [0, 2^31 - 1].
+                       ttl = (int32_t) arg;
+                       if( ptr != end ) break;
+                       continue;
+               }
+               
+               // Check if this label is a valid PDelay label.
+               
+               if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_PDelay ) == 0 )
+               {
+                       const char *                    ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_PDelay ) ];
+                       const char * const              end = (const char *) labelNext;
+                       
+                       if( procDelayMs > 0 ) break; // PDelay cannot be specified more than once.
+                       
+                       err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+                       if( err || ( arg < 1 ) || ( arg > 2000 ) ) break; // PDelay must be in [1, 2000].
+                       procDelayMs = arg;
+                       if( ptr != end ) break;
+                       continue;
+               }
+               
+               // If this and the remaining labels are equal to "d.test.", then the name exists.
+               // Otherwise, this label is invalid. In both cases, there are no more labels to check.
+               
+               if( _DNSServerNameIsDNSSECZone( label, &zoneParent, &zsk, &ksk, &parentZSK ) )
+               {
+                       zone = label;
+                       flags |= kDNSNameFlag_HasRRSIG;
+                       if( ( label == inQName ) || ( isAlias && ( label == DomainNameGetNextLabel( inQName ) ) ) )
+                       {
+                               flags |= kDNSNameFlag_HasSOA;
+                               flags |= kDNSNameFlag_HasDNSKEY;
+                               flags |= kDNSNameFlag_HasDS;
+                       }
+               }
+               else if( DomainNameEqual( label, me->domain ) )
+               {
+                       zone = label;
+                       if( ( label == inQName ) || ( isAlias && ( label == DomainNameGetNextLabel( inQName ) ) ) )
+                       {
+                               flags |= kDNSNameFlag_HasSOA;
+                       }
+               }
+               break;
+       }
+       require_quiet( zone, exit );
+       
+       // If a Count value of 0 was specified, then the hostname has no A or AAAA records.
+       // Otherwise, if the hostname has no IPv4 or IPv6 labels, then it has both A and AAAA records.
+       
+       if( count == 0 )
+       {
+               flags &= ~( kDNSNameFlag_HasA | kDNSNameFlag_HasAAAA );
+       }
+       else if( !( flags & ( kDNSNameFlag_HasA | kDNSNameFlag_HasAAAA ) ) )
+       {
+               flags |= ( kDNSNameFlag_HasA | kDNSNameFlag_HasAAAA );
+       }
+       
+       // Allow IPv6-only hostnames to have a maximum address count of up to 511 instead of the normal 255.
+       
+       maxCount = ( ( flags & ( kDNSNameFlag_HasA | kDNSNameFlag_HasAAAA ) ) == kDNSNameFlag_HasAAAA ) ? 511 : 255;
+       require_action_quiet( count <= maxCount, exit, zone = NULL );
+       
+       if( outAliasCount )             *outAliasCount          = aliasCount;
+       if( outAliasTTLCount )  *outAliasTTLCount       = aliasTTLCount;
+       if( outCount )                  *outCount                       = ( count >= 0 ) ? ( (uint32_t) count ) : 1;
+       if( outRandCount )              *outRandCount           = randCount;
+       if( outIndex )                  *outIndex                       = index;
+       if( outRCode )                  *outRCode                       = rcode;
+       if( outTTL )                    *outTTL                         = ( ttl >= 0 ) ? ( (uint32_t) ttl ) : me->defaultTTL;
+       if( outProcDelayMs )    *outProcDelayMs         = procDelayMs;
+       if( outFlags )                  *outFlags                       = flags;
+       if( outZone )                   *outZone                        = zone;
+       if( outZoneParent )             *outZoneParent          = zoneParent;
+       if( outZSK )                    *outZSK                         = zsk;
+       if( outKSK )                    *outKSK                         = ksk;
+       if( outParentZSK )              *outParentZSK           = parentZSK;
+       
+exit:
+       return( zone ? true : false );
+}
+
+//===========================================================================================================================
+
+static Boolean
+       _DNSServerParseSRVName(
+               DNSServerRef            me,
+               const uint8_t *         inName,
+               const uint8_t **        outDomainPtr,
+               size_t *                        outDomainLen,
+               ParsedSRV                       outSRVArray[ kParsedSRVCountMax ],
                size_t *                        outSRVCount )
 {
        OSStatus                        err;
@@ -8726,13 +10741,13 @@ static Boolean
                }
                require_quiet( *label, exit );
                
-               if( inSRVArray )
+               if( outSRVArray )
                {
-                       inSRVArray[ srvCount ].priority         = (uint16_t) priority;
-                       inSRVArray[ srvCount ].weight           = (uint16_t) weight;
-                       inSRVArray[ srvCount ].port                     = (uint16_t) port;
-                       inSRVArray[ srvCount ].targetPtr        = target;
-                       inSRVArray[ srvCount ].targetLen        = (uint16_t)( label - target );
+                       outSRVArray[ srvCount ].priority        = (uint16_t) priority;
+                       outSRVArray[ srvCount ].weight          = (uint16_t) weight;
+                       outSRVArray[ srvCount ].port            = (uint16_t) port;
+                       outSRVArray[ srvCount ].targetPtr       = target;
+                       outSRVArray[ srvCount ].targetLen       = (uint16_t)( label - target );
                }
                ++srvCount;
        }
@@ -8748,207 +10763,418 @@ exit:
 }
 
 //===========================================================================================================================
-//     _DNSServerTCPReadHandler
-//===========================================================================================================================
-
-typedef struct
-{
-       DNSServerRef                    server;                 // Reference to DNS server object.
-       sockaddr_ip                             clientAddr;             // Client's address.
-       dispatch_source_t               readSource;             // Dispatch read source for client socket.
-       dispatch_source_t               writeSource;    // Dispatch write source for client socket.
-       size_t                                  offset;                 // Offset into receive buffer.
-       void *                                  msgPtr;                 // Pointer to dynamically allocated message buffer.
-       size_t                                  msgLen;                 // Length of message buffer.
-       Boolean                                 readSuspended;  // True if the read source is currently suspended.
-       Boolean                                 writeSuspended; // True if the write source is currently suspended.
-       Boolean                                 receivedLength; // True if receiving DNS message as opposed to the message length.
-       uint8_t                                 lenBuf[ 2 ];    // Buffer for two-octet message length field.
-       iovec_t                                 iov[ 2 ];               // IO vector for writing response message.
-       iovec_t *                               iovPtr;                 // Vector pointer for SocketWriteData().
-       int                                             iovCount;               // Vector count for SocketWriteData().
-       
-}      TCPConnectionContext;
-
-static void    TCPConnectionStop( TCPConnectionContext *inContext );
-static void    TCPConnectionContextFree( TCPConnectionContext *inContext );
-static void    TCPConnectionReadHandler( void *inContext );
-static void    TCPConnectionWriteHandler( void *inContext );
-
-#define        TCPConnectionForget( X )                ForgetCustomEx( X, TCPConnectionStop, TCPConnectionContextFree )
 
-static void    _DNSServerTCPReadHandler( void *inContext )
+static Boolean _DNSServerParseReverseIPv4Name( DNSServerRef me, const uint8_t *inQName, unsigned int *outHostID )
 {
-       OSStatus                                        err;
-       SocketContext * const           sockCtx         = (SocketContext *) inContext;
-       DNSServerRef const                      me                      = (DNSServerRef) sockCtx->userContext;
-       TCPConnectionContext *          connection;
-       socklen_t                                       clientAddrLen;
-       SocketRef                                       newSock         = kInvalidSocketRef;
-       SocketContext *                         newSockCtx      = NULL;
-       
-       connection = (TCPConnectionContext *) calloc( 1, sizeof( *connection ) );
-       require_action( connection, exit, err = kNoMemoryErr );
+       OSStatus                        err;
+       const uint8_t *         label;
+       size_t                          labelLen;
+       const uint8_t *         labelData;
+    const uint8_t *            labelNext;
+       const uint8_t *         ptr;
+       uint32_t                        hostID;
+       int                                     isNameValid = false;
        
-       CFRetain( me );
-       connection->server = me;
+       Unused( me );
        
-       clientAddrLen = (socklen_t) sizeof( connection->clientAddr );
-       newSock = accept( sockCtx->sock, &connection->clientAddr.sa, &clientAddrLen );
-       err = map_socket_creation_errno( newSock );
-       require_noerr( err, exit );
+       label           = inQName;
+       labelLen        = *label;
+       require_quiet( labelLen > 0, exit );
        
-       err = SocketContextCreate( newSock, connection, &newSockCtx );
-       require_noerr( err, exit );
-       newSock = kInvalidSocketRef;
+       labelData = &label[ 1 ];
+       labelNext = &labelData[ labelLen ];
+       err = DecimalTextToUInt32( (const char *) labelData, (const char *) labelNext, &hostID, (const char **) &ptr );
+       require_noerr_quiet( err, exit );
+       require_quiet( ( hostID >= 1 ) && ( hostID <= 255 ), exit );
+       require_quiet( ptr == labelNext, exit );
        
-       err = DispatchReadSourceCreate( newSockCtx->sock, me->queue, TCPConnectionReadHandler, SocketContextCancelHandler,
-               newSockCtx, &connection->readSource );
-       require_noerr( err, exit );
-       SocketContextRetain( newSockCtx );
-       dispatch_resume( connection->readSource );
+       require_quiet( DomainNameEqual( labelNext, kDNSServerReverseIPv4DomainName ), exit );
+       isNameValid = true;
        
-       err = DispatchWriteSourceCreate( newSockCtx->sock, me->queue, TCPConnectionWriteHandler, SocketContextCancelHandler,
-               newSockCtx, &connection->writeSource );
-       require_noerr( err, exit );
-       SocketContextRetain( newSockCtx );
-       connection->writeSuspended = true;
-       connection = NULL;
+       if( outHostID ) *outHostID = (unsigned int) hostID;
        
 exit:
-       ForgetSocket( &newSock );
-       SocketContextRelease( newSockCtx );
-       TCPConnectionForget( &connection );
+       return( isNameValid ? true : false );
 }
 
-//===========================================================================================================================
-//     TCPConnectionStop
 //===========================================================================================================================
 
-static void    TCPConnectionStop( TCPConnectionContext *inContext )
+static Boolean _DNSServerParseReverseIPv6Name( DNSServerRef me, const uint8_t *inQName, unsigned int *outHostID )
 {
-       dispatch_source_forget_ex( &inContext->readSource, &inContext->readSuspended );
-       dispatch_source_forget_ex( &inContext->writeSource, &inContext->writeSuspended );
+       const uint8_t *         label;
+       unsigned int            hostID;
+       int                                     i;
+       int                                     isNameValid = false;
+       
+       Unused( me );
+       
+       hostID  = 0;
+       label   = inQName;
+       for( i = 0; i < 2; ++i )
+       {
+               unsigned int            labelLen, c;
+               
+               labelLen = label[ 0 ];
+               require_quiet( labelLen == 1, exit );
+               
+               c = label[ 1 ];
+               require_quiet( isxdigit_safe( c ), exit );
+               
+               hostID = hostID | ( HexCharToValue( c ) << ( 4 * i ) );
+               label = &label[ 1 + labelLen ];
+       }
+       require_quiet( ( hostID >= 1 ) && ( hostID <= 255 ), exit );
+       require_quiet( DomainNameEqual( label, kDNSServerReverseIPv6DomainName ), exit );
+       isNameValid = true;
+       
+       if( outHostID ) *outHostID = hostID;
+       
+exit:
+       return( isNameValid ? true : false );
 }
 
-//===========================================================================================================================
-//     TCPConnectionContextFree
+#if( DEBUG )
 //===========================================================================================================================
 
-static void    TCPConnectionContextFree( TCPConnectionContext *inContext )
+static void
+       _DNSServerSigCheck(
+               const uint8_t * inOwner,
+               int                             inTypeCovered,
+               const void *    inMsgPtr,
+               size_t                  inMsgLen,
+               const uint8_t * inSignaturePtr,
+               const size_t    inSignatureLen,
+               DNSKeyInfoRef   inKeyInfo )
 {
-       check( !inContext->readSource );
-       check( !inContext->writeSource );
-       ForgetCF( &inContext->server );
-       ForgetMem( &inContext->msgPtr );
-       free( inContext );
+       if( !DNSKeyInfoVerify( inKeyInfo, inMsgPtr, inMsgLen, inSignaturePtr, inSignatureLen ) )
+       {
+               const char *            typeStr;
+               char                            typeBuf[ 16 ];
+               
+               typeStr = DNSRecordTypeValueToString( inTypeCovered );
+               if( !typeStr )
+               {
+                       SNPrintF( typeBuf, sizeof( typeBuf ), "TYPE%d", inTypeCovered );
+                       typeStr = typeBuf;
+               }
+               ds_ulog( kLogLevelError,
+                       "Signature for %{du:dname} %s is invalid! -- algorithm: %s (%d), public key: '%H'\n",
+                       inOwner, typeStr, DNSKeyInfoGetAlgorithmDescription( inKeyInfo ), DNSKeyInfoGetAlgorithm( inKeyInfo ),
+                       DNSKeyInfoGetPubKeyPtr( inKeyInfo ), DNSKeyInfoGetPubKeyLen( inKeyInfo ), SIZE_MAX );
+       }
 }
+#endif
 
-//===========================================================================================================================
-//     TCPConnectionReadHandler
 //===========================================================================================================================
 
-static void    TCPConnectionReadHandler( void *inContext )
+#define kDNSServerDefaultDNSSECAlgorithm               14      // TODO: Think about adding an option for the default algorithm.
+
+static Boolean
+       _DNSServerNameIsDNSSECZone(
+               const uint8_t *         inName,
+               const uint8_t **        outZoneParent,
+               DNSKeyInfoRef *         outZSK,
+               DNSKeyInfoRef *         outKSK,
+               DNSKeyInfoRef *         outParentZSK )
 {
-       OSStatus                                        err;
-       SocketContext * const           sockCtx         = (SocketContext *) inContext;
-       TCPConnectionContext *          connection      = (TCPConnectionContext *) sockCtx->userContext;
-       struct timeval                          now;
-       uint8_t *                                       responsePtr     = NULL; // malloc'd
-       size_t                                          responseLen;
-       
-       // Receive message length.
-       
-       if( !connection->receivedLength )
-       {
-               err = SocketReadData( sockCtx->sock, connection->lenBuf, sizeof( connection->lenBuf ), &connection->offset );
-               if( err == EWOULDBLOCK ) goto exit;
-               require_noerr( err, exit );
-               
-               connection->offset = 0;
-               connection->msgLen = ReadBig16( connection->lenBuf );
-               connection->msgPtr = malloc( connection->msgLen );
-               require_action( connection->msgPtr, exit, err = kNoMemoryErr );
-               connection->receivedLength = true;
-       }
-       
-       // Receive message.
-       
-       err = SocketReadData( sockCtx->sock, connection->msgPtr, connection->msgLen, &connection->offset );
-       if( err == EWOULDBLOCK ) goto exit;
-       require_noerr( err, exit );
-       
-       gettimeofday( &now, NULL );
-       dispatch_suspend( connection->readSource );
-       connection->readSuspended = true;
-       
-       ds_ulog( kLogLevelInfo, "TCP server received %zu bytes from %##a at %{du:time}.\n",
-               connection->msgLen, &connection->clientAddr, &now );
-       
-       if( connection->msgLen < kDNSHeaderLength )
-       {
-               ds_ulog( kLogLevelInfo, "TCP DNS message is too small (%zu < %d).\n", connection->msgLen, kDNSHeaderLength );
-               goto exit;
+       const uint8_t *         label;
+       size_t                          labelLen;
+       const uint8_t *         labelNext;
+       const uint8_t *         zoneParent                      = NULL;
+       DNSKeyInfoRef           zsk;
+       DNSKeyInfoRef           ksk;
+       DNSKeyInfoRef           parentZSK;
+       uint32_t                        zoneAlgorithm           = 0;
+       uint32_t                        zoneIndex                       = 0;
+       uint32_t                        zoneParentAlgorithm     = 0;
+       uint32_t                        zoneParentIndex         = 0;
+       Boolean                         parsedAllLabels         = false;
+       Boolean                         nameIsValid                     = false;
+       
+       for( label = inName; ( labelLen = *label ) != 0; label = labelNext )
+       {
+               const uint8_t *         labelData;
+               
+               if( labelLen > kDomainLabelLengthMax ) break;
+               labelData = &label[ 1 ];
+               labelNext = &labelData[ labelLen ];
+               
+               if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_Zone ) == 0  )
+               {
+                       OSStatus                                err;
+                       const char *                    ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_Zone ) ];
+                       const char * const              end = (const char *) labelNext;
+                       uint32_t                                algorithm;
+                       uint32_t                                index;
+                       
+                       err = DecimalTextToUInt32( ptr, end, &algorithm, &ptr );
+                       if( err ) break;
+                       if( ( ptr >= end ) || ( *ptr++ != '-' ) ) break;
+                       
+                       err = DecimalTextToUInt32( ptr, end, &index, &ptr );
+                       if( err || ( index < kZoneLabelIndexArgMin ) || ( index > kZoneLabelIndexArgMax ) ) break;
+                       if( ptr != end ) break;
+                       if( zoneIndex == 0 )
+                       {
+                               zoneAlgorithm   = algorithm;
+                               zoneIndex               = index;
+                       }
+                       else if( zoneParentIndex == 0 )
+                       {
+                               zoneParentAlgorithm     = algorithm;
+                               zoneParent                      = label;
+                               zoneParentIndex         = index;
+                       }
+                       continue;
+               }
+               if( DomainNameEqual( label, kDNSServerDomain_DNSSEC ) )
+               {
+                       if( !zoneParent ) zoneParent = label;
+                       parsedAllLabels = true;
+               }
+               break;
        }
+       require_quiet( parsedAllLabels, exit );
        
-       ds_ulog( kLogLevelInfo, "TCP received message:\n\n%1{du:dnsmsg}", connection->msgPtr, connection->msgLen );
-       
-       // Create response.
-       
-       err = _DNSServerAnswerQueryForTCP( connection->server, connection->msgPtr, connection->msgLen, &responsePtr,
-               &responseLen );
-       require_noerr_quiet( err, exit );
-       
-       // Send response.
-       
-       ds_ulog( kLogLevelInfo, "TCP sending %zu byte response:\n\n%1{du:dnsmsg}", responseLen, responsePtr, responseLen );
+       if( zoneAlgorithm == 0 ) zoneAlgorithm = kDNSServerDefaultDNSSECAlgorithm;
+       zsk = GetDNSKeyInfoZSK( zoneAlgorithm, zoneIndex );
+       require_quiet( zsk, exit );
        
-       free( connection->msgPtr );
-       connection->msgPtr = responsePtr;
-       connection->msgLen = responseLen;
-       responsePtr = NULL;
-       
-       check( connection->msgLen <= UINT16_MAX );
-       WriteBig16( connection->lenBuf, connection->msgLen );
-       connection->iov[ 0 ].iov_base   = connection->lenBuf;
-       connection->iov[ 0 ].iov_len    = sizeof( connection->lenBuf );
-       connection->iov[ 1 ].iov_base   = connection->msgPtr;
-       connection->iov[ 1 ].iov_len    = connection->msgLen;
+       ksk = GetDNSKeyInfoKSK( zoneAlgorithm, zoneIndex );
+       require_quiet( ksk, exit );
        
-       connection->iovPtr              = connection->iov;
-       connection->iovCount    = 2;
+       if( zoneParentAlgorithm == 0 ) zoneParentAlgorithm = kDNSServerDefaultDNSSECAlgorithm;
+       parentZSK = GetDNSKeyInfoZSK( zoneParentAlgorithm, zoneParentIndex );
+       require_quiet( parentZSK, exit );
        
-       check( connection->writeSuspended );
-       dispatch_resume( connection->writeSource );
-       connection->writeSuspended = false;
+       if( outZoneParent )     *outZoneParent  = zoneParent;
+       if( outZSK )            *outZSK                 = zsk;
+       if( outKSK )            *outKSK                 = ksk;
+       if( outParentZSK )      *outParentZSK   = parentZSK;
+       nameIsValid = true;
        
 exit:
-       FreeNullSafe( responsePtr );
-       if( err && ( err != EWOULDBLOCK ) ) TCPConnectionForget( &connection );
+       return( nameIsValid );
 }
 
-//===========================================================================================================================
-//     TCPConnectionWriteHandler
 //===========================================================================================================================
 
-static void    TCPConnectionWriteHandler( void *inContext )
+static OSStatus
+       _DNSServerConnectionCreate(
+               DNSServerRef                            inServer,
+               const struct sockaddr *         inLocal,
+               const struct sockaddr *         inRemote,
+               size_t                                          inIndex,
+               DNSServerConnectionRef *        outCnx )
 {
        OSStatus                                        err;
-       SocketContext * const           sockCtx         = (SocketContext *) inContext;
-       TCPConnectionContext *          connection      = (TCPConnectionContext *) sockCtx->userContext;
+       DNSServerConnectionRef          obj;
        
-       err = SocketWriteData( sockCtx->sock, &connection->iovPtr, &connection->iovCount );
-       if( err == EWOULDBLOCK ) goto exit;
-       check_noerr( err );
+       CF_OBJECT_CREATE( DNSServerConnection, obj, err, exit );
+       
+       obj->index      = inIndex;
+       obj->server     = inServer;
+       CFRetain( obj->server );
+       SockAddrCopy( inLocal, &obj->local );
+       SockAddrCopy( inRemote, &obj->remote );
        
-       TCPConnectionForget( &connection );
+       *outCnx = obj;
+       err = kNoErr;
        
 exit:
-       return;
+       return( err );
 }
 
 //===========================================================================================================================
-//     MDNSReplierCmd
+
+static void    _DNSServerConnectionFinalize( CFTypeRef inObj )
+{
+       const DNSServerConnectionRef            me = (DNSServerConnectionRef) inObj;
+       
+       check( !me->readSource );
+       check( !me->writeSource );
+       ForgetCF( &me->server );
+       ForgetMem( &me->msgPtr );
+}
+
+//===========================================================================================================================
+
+static OSStatus        _DNSServerConnectionStart( DNSServerConnectionRef me, SocketRef inSock )
+{
+       OSStatus                        err;
+       SocketContext *         sockCtx = NULL;
+       
+       err = SocketMakeNonBlocking( inSock );
+       require_noerr( err, exit );
+       
+#if( defined( SO_NOSIGPIPE ) )
+       setsockopt( inSock, SOL_SOCKET, SO_NOSIGPIPE, &(int){ 1 }, (socklen_t) sizeof( int ) );
+#endif
+       me->readSource = dispatch_source_create( DISPATCH_SOURCE_TYPE_READ, (uintptr_t) inSock, 0, me->server->queue );
+       require_action( me->readSource, exit, err = kNoResourcesErr );
+       me->readSuspended = true;
+       
+       me->writeSource = dispatch_source_create( DISPATCH_SOURCE_TYPE_WRITE, (uintptr_t) inSock, 0, me->server->queue );
+       require_action( me->writeSource, exit, err = kNoResourcesErr );
+       me->writeSuspended = true;
+       
+       sockCtx = SocketContextCreateEx( inSock, me, SocketContextFinalizerCF, &err );
+       require_noerr( err, exit );
+       CFRetain( me );
+       
+       SocketContextRetain( sockCtx );
+       dispatch_set_context( me->readSource, sockCtx );
+       dispatch_source_set_event_handler_f( me->readSource, _DNSServerConnectionReadHandler );
+       dispatch_source_set_cancel_handler_f( me->readSource, SocketContextCancelHandler );
+       dispatch_resume_if_suspended( me->readSource, &me->readSuspended );
+       
+       SocketContextRetain( sockCtx );
+       dispatch_set_context( me->writeSource, sockCtx );
+       dispatch_source_set_event_handler_f( me->writeSource, _DNSServerConnectionWriteHandler );
+       dispatch_source_set_cancel_handler_f( me->writeSource, SocketContextCancelHandler );
+       
+       _DNSServerConnectionRenewExpiration( me );
+       
+exit:
+       if( sockCtx ) SocketContextRelease( sockCtx );
+       if( err ) _DNSServerConnectionStop( me, true );
+       return( err );
+}
+
+//===========================================================================================================================
+
+static void    _DNSServerConnectionStop( DNSServerConnectionRef me, Boolean inRemoveFromList )
+{
+       dispatch_source_forget_ex( &me->readSource, &me->readSuspended );
+       dispatch_source_forget_ex( &me->writeSource, &me->writeSuspended );
+       if( inRemoveFromList )
+       {
+               DNSServerConnectionRef *                ptr;
+               
+               ptr = &me->server->connectionList;
+               while( *ptr && ( *ptr != me ) ) ptr = &( *ptr )->next;
+               if( *ptr )
+               {
+                       *ptr = me->next;
+                       me->next = NULL;
+                       CFRelease( me );
+               }
+       }
+}
+
+//===========================================================================================================================
+
+static void    _DNSServerConnectionReadHandler( void *inContext )
+{
+       OSStatus                                                        err;
+       const SocketContext * const                     sockCtx = (SocketContext *) inContext;
+       const DNSServerConnectionRef            me              = (DNSServerConnectionRef) sockCtx->userContext;
+       uint8_t *                                                       respPtr = NULL; // malloc'd
+       size_t                                                          respLen;
+       
+       // Receive message length.
+       
+       if( !me->haveLen )
+       {
+               err = SocketReadData( sockCtx->sock, me->lenBuf, sizeof( me->lenBuf ), &me->offset );
+               if( ( err == EWOULDBLOCK ) || ( err == kConnectionErr ) ) goto exit;
+               require_noerr( err, exit );
+               
+               me->haveLen     = true;
+               me->offset      = 0;
+               me->msgLen      = ReadBig16( me->lenBuf );
+               if( me->msgLen < kDNSHeaderLength )
+               {
+                       ds_ulog( kLogLevelInfo, "TCP: Message length of %zu bytes from %##a to %##a is too small (< %d bytes)\n",
+                               me->msgLen, &me->remote, &me->local, kDNSHeaderLength );
+                       err = kSizeErr;
+                       goto exit;
+               }
+               me->msgPtr = malloc( me->msgLen );
+               require_action( me->msgPtr, exit, err = kNoMemoryErr );
+       }
+       
+       // Receive message.
+       
+       err = SocketReadData( sockCtx->sock, me->msgPtr, me->msgLen, &me->offset );
+       if( ( err == EWOULDBLOCK ) || ( err == kConnectionErr ) ) goto exit;
+       require_noerr( err, exit );
+       dispatch_suspend_if_resumed( me->readSource, &me->readSuspended );
+       me->offset      = 0;
+       me->haveLen     = false;
+       
+       ds_ulog( kLogLevelInfo, "TCP: Received %zu bytes from %##a to %##a -- %.1{du:dnsmsg}\n",
+               me->msgLen, &me->remote, &me->local, me->msgPtr, me->msgLen );
+       
+       // Create response.
+       
+       err = _DNSServerAnswerQueryForTCP( me->server, me->msgPtr, me->msgLen, me->index + 1, &respPtr, &respLen );
+       if( err == kSkipErr ) ds_ulog( kLogLevelInfo, "TCP: Ignoring query\n" );
+       require_noerr_quiet( err, exit );
+       
+       _DNSServerConnectionRenewExpiration( me );
+       
+       // Prepare to send response.
+       
+       FreeNullSafe( me->msgPtr );
+       me->msgPtr = respPtr;
+       me->msgLen = respLen;
+       respPtr = NULL;
+       
+       ds_ulog( kLogLevelInfo, "TCP: Sending %zu byte response from %##a to %##a -- %.1{du:dnsmsg}\n",
+               me->msgLen, &me->local, &me->remote, me->msgPtr, me->msgLen );
+       
+       check( me->msgLen <= UINT16_MAX );
+       WriteBig16( me->lenBuf, me->msgLen );
+       me->iov[ 0 ].iov_base   = me->lenBuf;
+       me->iov[ 0 ].iov_len    = sizeof( me->lenBuf );
+       me->iov[ 1 ].iov_base   = me->msgPtr;
+       me->iov[ 1 ].iov_len    = me->msgLen;
+       me->iovPtr                              = me->iov;
+       me->iovCount                    = 2;
+       dispatch_resume_if_suspended( me->writeSource, &me->writeSuspended );
+       
+exit:
+       FreeNullSafe( respPtr );
+       if( err && ( err != EWOULDBLOCK ) )
+       {
+               _DNSServerConnectionStop( me, true );
+       }
+}
+
+//===========================================================================================================================
+
+static void    _DNSServerConnectionWriteHandler( void *inContext )
+{
+       OSStatus                                                        err;
+       const SocketContext * const                     sockCtx = (SocketContext *) inContext;
+       const DNSServerConnectionRef            me              = (DNSServerConnectionRef) sockCtx->userContext;
+       
+       err = SocketWriteData( sockCtx->sock, &me->iovPtr, &me->iovCount );
+       if( !err )
+       {
+               me->iovPtr              = NULL;
+               me->iovCount    = 0;
+               memset( me->iov, 0, sizeof( me->iov ) );
+               ForgetPtrLen( &me->msgPtr, &me->msgLen );
+               dispatch_suspend_if_resumed( me->writeSource, &me->writeSuspended );
+               dispatch_resume_if_suspended( me->readSource, &me->readSuspended );
+       }
+       else if( err != EWOULDBLOCK )
+       {
+               _DNSServerConnectionStop( me, true );
+       }
+}
+
+//===========================================================================================================================
+
+static void    _DNSServerConnectionRenewExpiration( DNSServerConnectionRef me )
+{
+       me->expirationTicks = UpTicks() + SecondsToUpTicks( kDNSServerConnectionExpirationTimeSecs );
+}
+
+//===========================================================================================================================
+//     MDNSReplierCmd
 //===========================================================================================================================
 
 typedef struct
@@ -9241,7 +11467,7 @@ static void      MDNSReplierCmd( void )
        {
                SocketContext *         sockCtx;
                
-               err = SocketContextCreate( sockV4, context, &sockCtx );
+               sockCtx = SocketContextCreate( sockV4, context, &err );
                require_noerr( err, exit );
                sockV4 = kInvalidSocketRef;
                
@@ -9257,7 +11483,7 @@ static void      MDNSReplierCmd( void )
        {
                SocketContext *         sockCtx;
                
-               err = SocketContextCreate( sockV6, context, &sockCtx );
+               sockCtx = SocketContextCreate( sockV6, context, &err );
                require_noerr( err, exit );
                sockV6 = kInvalidSocketRef;
                
@@ -9333,7 +11559,7 @@ static void      _MDNSReplierReadHandler( void *inContext )
        
        drop = ( !context->maxDropCount && ShouldDrop( context->mcastDropRate ) ) ? true : false;
        
-       mr_ulog( kLogLevelInfo, "Received %zu byte message from %##a%?s:\n\n%#1{du:dnsmsg}",
+       mr_ulog( kLogLevelInfo, "Received %zu byte message from %##a%?s -- %#.1{du:dnsmsg}\n",
                msgLen, &sender, drop, " (dropping)", context->msgBuf, msgLen );
        
        // Based on the QNAMEs in the query message, determine from which sets of records we may possibly need answers.
@@ -9354,7 +11580,7 @@ static void      _MDNSReplierReadHandler( void *inContext )
                err = DNSMessageExtractQuestion( context->msgBuf, msgLen, ptr, qname, &qtype, &qclass, &ptr );
                require_noerr_quiet( err, exit );
                
-               if( ( qclass & ~kQClassUnicastResponseBit ) != kDNSServiceClass_IN ) continue;
+               if( ( qclass & ~kMDNSClassUnicastResponseBit ) != kDNSServiceClass_IN ) continue;
                
                if( _MDNSReplierHostnameMatch( context, qname, &index ) ||
                        _MDNSReplierServiceInstanceNameMatch( context, qname, &index, NULL, NULL ) )
@@ -9457,9 +11683,9 @@ static OSStatus
                err = DNSMessageExtractQuestion( inQueryPtr, inQueryLen, ptr, qname, &qtype, &qclass, &ptr );
                require_noerr_quiet( err, exit );
                
-               if( qclass & kQClassUnicastResponseBit )
+               if( qclass & kMDNSClassUnicastResponseBit )
                {
-                       qclass &= ~kQClassUnicastResponseBit;
+                       qclass &= ~kMDNSClassUnicastResponseBit;
                        answerListPtr = &ucastAnswerList;
                }
                else
@@ -9665,11 +11891,14 @@ static OSStatus
                {
                        for( i = 1; i <= inContext->recordCountAAAA; ++i )
                        {
+                               const uint8_t           ( *baseAddr )[ 16 ];
+                               
                                err = DomainNameDupLower( inName, &recordName, NULL );
                                require_noerr( err, exit );
                                
                                rdataLen = 16;
-                               rdataPtr = (uint8_t *) _memdup( kMDNSReplierBaseAddrV6, rdataLen );
+                               baseAddr = ( i == 1 ) ? &kMDNSReplierLinkLocalBaseAddrV6 : &kMDNSReplierBaseAddrV6;
+                               rdataPtr = (uint8_t *) _memdup( baseAddr, rdataLen );
                                require_action( rdataPtr, exit, err = kNoMemoryErr );
                                
                                WriteBig16( &rdataPtr[ 12 ], inIndex );
@@ -9962,7 +12191,7 @@ static OSStatus
                destAddr = ( inQuerier->sa.sa_family == AF_INET ) ? GetMDNSMulticastAddrV4() : GetMDNSMulticastAddrV6();
        }
        
-       mr_ulog( kLogLevelInfo, "%s %zu byte response to %##a:\n\n%#1{du:dnsmsg}",
+       mr_ulog( kLogLevelInfo, "%s %zu byte response to %##a -- %#.1{du:dnsmsg}\n",
                drop ? "Dropping" : "Sending", responseLen, destAddr, responsePtr, responseLen );
        
        if( !drop )
@@ -10098,7 +12327,7 @@ static OSStatus
                        ( answer->type == kDNSServiceType_A )   || ( answer->type == kDNSServiceType_AAAA ) ||
                        ( answer->type == kDNSServiceType_NSEC ) )
                {
-                       class |= kRRClassCacheFlushBit;
+                       class |= kMDNSClassCacheFlushBit;
                }
                
                dns_fixed_fields_record_init( &fields, answer->type, (uint16_t) class, answer->ttl, (uint16_t) answer->rdlength );
@@ -10677,7 +12906,7 @@ static void
                void *                                          inContext );
 static void            GAIPerfSignalHandler( void *inContext );
 
-CFTypeID               GAITesterGetTypeID( void );
+static CFTypeID        GAITesterGetTypeID( void );
 static OSStatus
        GAITesterCreate(
                dispatch_queue_t        inQueue,
@@ -11159,11 +13388,12 @@ static void
        GAIPerfContext * const                  context = (GAIPerfContext *) inContext;
        int                                                             namesAreDynamic, namesAreUnique;
        const char *                                    ptr;
-       size_t                                                  count, startIndex;
+       size_t                                                  startIndex;
        CFMutableArrayRef                               results = NULL;
        GAIPerfStats                                    stats, firstStats, connStats;
        double                                                  sum, firstSum, connSum;
        size_t                                                  keyValueLen, i;
+       uint32_t                                                count;
        char                                                    keyValue[ 16 ]; // Size must be at least strlen( "name=dynamic" ) + 1 bytes.
        char                                                    startTime[ 32 ];
        char                                                    endTime[ 32 ];
@@ -11249,9 +13479,9 @@ static void
        
        if( count > 0 )
        {
-               stats.mean              = sum      / count;
-               firstStats.mean = firstSum / count;
-               connStats.mean  = connSum  / count;
+               stats.mean              = sum      / (double) count;
+               firstStats.mean = firstSum / (double) count;
+               connStats.mean  = connSum  / (double) count;
                
                sum                     = 0.0;
                firstSum        = 0.0;
@@ -11272,9 +13502,9 @@ static void
                        diff             = connStats.mean - (double) result->connectionTimeUs;
                        connSum         += ( diff * diff );
                }
-               stats.stdDev            = sqrt( sum      / count );
-               firstStats.stdDev       = sqrt( firstSum / count );
-               connStats.stdDev        = sqrt( connSum  / count );
+               stats.stdDev            = sqrt( sum      / (double) count );
+               firstStats.stdDev       = sqrt( firstSum / (double) count );
+               connStats.stdDev        = sqrt( connSum  / (double) count );
        }
        
        err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->testCaseResults,
@@ -11535,7 +13765,7 @@ static void     _GAITesterStart( void *inContext )
        char                                    name[ 64 ];
        char                                    tag[ kGAITesterTagStringLen + 1 ];
        
-       err = SpawnCommand( &me->serverPID, "dnssdutil server --loopback --follow %lld%?s%?d%?s%?d%?s",
+       err = _SpawnCommand( &me->serverPID, NULL, NULL, "dnssdutil server --loopback --follow %lld%?s%?d%?s%?d%?s",
                (int64_t) getpid(),
                me->serverDefaultTTL >= 0,      " --defaultTTL ",
                me->serverDefaultTTL >= 0,      me->serverDefaultTTL,
@@ -11544,7 +13774,7 @@ static void     _GAITesterStart( void *inContext )
                me->badUDPMode,                         " --badUDPMode" );
        require_noerr_quiet( err, exit );
        
-       SNPrintF( name, sizeof( name ), "tag-gaitester-probe-%s.ipv4.d.test",
+       SNPrintF( name, sizeof( name ), "tag-gaitester-probe-%s.ipv4.d.test.",
                _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
        
        flags = 0;
@@ -12508,6 +14738,9 @@ typedef struct
        Boolean                                 noAdditionals;                  // True if the replier is to not include additional records in responses.
        Boolean                                 useIPv4;                                // True if the replier is to use IPv4.
        Boolean                                 useIPv6;                                // True if the replier is to use IPv6.
+#if( MDNSRESPONDER_PROJECT )
+       Boolean                                 useNewGAI;                              // True if the browser is to use dnssd_getaddrinfo to resolve hostnames.
+#endif
        Boolean                                 flushedCache;                   // True if mDNSResponder's record cache was flushed before testing.
        char *                                  replierCommand;                 // Command used to run the replier.
        char *                                  serviceType;                    // Type of services to browse for.
@@ -12516,7 +14749,7 @@ typedef struct
        const char *                    outputFilePath;                 // File to write test results to. If NULL, then write to stdout.
        OutputFormatType                outputFormat;                   // Format of test results output.
        Boolean                                 outputAppendNewline;    // True if a newline character should be appended to JSON output.
-       char                                    hostname[ 32 + 1 ];             // Base hostname that the replier is to use for instance and host names.
+       char                                    hostname[ 16 + 1 ];             // Base hostname that the replier is to use for instance and host names.
        char                                    tag[ 4 + 1 ];                   // Tag that the replier is to use in its service types.
        
 }      MDNSDiscoveryTestContext;
@@ -12585,10 +14818,13 @@ static void   MDNSDiscoveryTestCmd( void )
        context->mcastDropRate                  = gMDNSDiscoveryTest_MulticastDropRate;
        context->maxDropCount                   = (unsigned int) gMDNSDiscoveryTest_MaxDropCount;
        context->outputFilePath                 = gMDNSDiscoveryTest_OutputFilePath;
-       context->outputAppendNewline    = gMDNSDiscoveryTest_OutputAppendNewline ? true : false;
-       context->noAdditionals                  = gMDNSDiscoveryTest_NoAdditionals       ? true : false;
+       context->outputAppendNewline    = gMDNSDiscoveryTest_OutputAppendNewline        ? true : false;
+       context->noAdditionals                  = gMDNSDiscoveryTest_NoAdditionals                      ? true : false;
        context->useIPv4                                = ( gMDNSDiscoveryTest_UseIPv4 || !gMDNSDiscoveryTest_UseIPv6 ) ? true : false;
        context->useIPv6                                = ( gMDNSDiscoveryTest_UseIPv6 || !gMDNSDiscoveryTest_UseIPv4 ) ? true : false;
+#if( MDNSRESPONDER_PROJECT )
+       context->useNewGAI                              = gMDNSDiscoveryTest_UseNewGAI                          ? true : false;
+#endif
        
        if( gMDNSDiscoveryTest_Interface )
        {
@@ -12640,10 +14876,10 @@ static void   MDNSDiscoveryTestCmd( void )
                context->useIPv6,               " --ipv6" );
        require_action_quiet( context->replierCommand, exit, err = kUnknownErr );
        
-       err = SpawnCommand( &context->replierPID, "%s", context->replierCommand );
+       err = _SpawnCommand( &context->replierPID, NULL, NULL, "%s", context->replierCommand );
        require_noerr_quiet( err, exit );
        
-       // Query for the replier's about TXT record. A response means it's fully up and running.
+       // Query for the replier's about TXT record. A response means that it's fully up and running.
        
        SNPrintF( queryName, sizeof( queryName ), "about.%s.local.", context->hostname );
        err = DNSServiceQueryRecord( &context->query, kDNSServiceFlagsForceMulticast, context->ifIndex, queryName,
@@ -12723,6 +14959,9 @@ static void DNSSD_API
        err = ServiceBrowserAddServiceType( context->browser, context->serviceType );
        require_noerr( err, exit );
        
+#if( MDNSRESPONDER_PROJECT )
+       ServiceBrowserSetUseNewGAI( context->browser, context->useNewGAI );
+#endif
        ServiceBrowserSetCallback( context->browser, _MDNSDiscoveryTestServiceBrowserCallback, context );
        ServiceBrowserStart( context->browser );
        
@@ -12740,6 +14979,7 @@ exit:
 #define kMDNSDiscoveryTestResultsKey_BrowseTimeSecs                                    CFSTR( "browseTimeSecs" )
 #define kMDNSDiscoveryTestResultsKey_ServiceType                                       CFSTR( "serviceType" )
 #define kMDNSDiscoveryTestResultsKey_FlushedCache                                      CFSTR( "flushedCache" )
+#define kMDNSDiscoveryTestResultsKey_UsedNewGAI                                                CFSTR( "usedNewGAI" )
 #define kMDNSDiscoveryTestResultsKey_UnexpectedInstances                       CFSTR( "unexpectedInstances" )
 #define kMDNSDiscoveryTestResultsKey_MissingInstances                          CFSTR( "missingInstances" )
 #define kMDNSDiscoveryTestResultsKey_IncorrectInstances                                CFSTR( "incorrectInstances" )
@@ -12821,6 +15061,9 @@ static void     _MDNSDiscoveryTestServiceBrowserCallback( ServiceBrowserResults *inR
                        "%kO=%lli"      // browseTimeSecs
                        "%kO=%s"        // serviceType
                        "%kO=%b"        // flushedCache
+               #if( MDNSRESPONDER_PROJECT )
+                       "%kO=%b"        // usedNewGAI
+               #endif
                        "%kO=[%@]"      // unexpectedInstances
                        "%kO=[%@]"      // missingInstances
                        "%kO=[%@]"      // incorrectInstances
@@ -12841,6 +15084,9 @@ static void     _MDNSDiscoveryTestServiceBrowserCallback( ServiceBrowserResults *inR
                kMDNSDiscoveryTestResultsKey_BrowseTimeSecs,            (int64_t) context->browseTimeSecs,
                kMDNSDiscoveryTestResultsKey_ServiceType,                       context->serviceType,
                kMDNSDiscoveryTestResultsKey_FlushedCache,                      context->flushedCache,
+       #if( MDNSRESPONDER_PROJECT )
+               kMDNSDiscoveryTestResultsKey_UsedNewGAI,                        context->useNewGAI,
+       #endif
                kMDNSDiscoveryTestResultsKey_UnexpectedInstances,       &unexpectedInstances,
                kMDNSDiscoveryTestResultsKey_MissingInstances,          &missingInstances,
                kMDNSDiscoveryTestResultsKey_IncorrectInstances,        &incorrectInstances );
@@ -13007,6 +15253,7 @@ static void     _MDNSDiscoveryTestServiceBrowserCallback( ServiceBrowserResults *inR
                        unsigned int            j;
                        uint8_t                         addrV4[ 4 ];
                        uint8_t                         addrV6[ 16 ];
+                       uint8_t                         addrV6LL[ 16 ];
                        
                        if( context->recordCountA < 64 )        addrV4Bitmap = ( UINT64_C( 1 ) << context->recordCountA ) - 1;
                        else                                                            addrV4Bitmap =  ~UINT64_C( 0 );
@@ -13021,6 +15268,9 @@ static void     _MDNSDiscoveryTestServiceBrowserCallback( ServiceBrowserResults *inR
                        memcpy( addrV6, kMDNSReplierBaseAddrV6, 16 );
                        WriteBig16( &addrV6[ 12 ], i );
                        
+                       memcpy( addrV6LL, kMDNSReplierLinkLocalBaseAddrV6, 16 );
+                       WriteBig16( &addrV6LL[ 12 ], i );
+                       
                        unexpectedAddrs = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
                        require_action( unexpectedAddrs, exit, err = kNoMemoryErr );
                        
@@ -13044,13 +15294,21 @@ static void   _MDNSDiscoveryTestServiceBrowserCallback( ServiceBrowserResults *inR
                                }
                                else if( ipaddr->sip.sa.sa_family == AF_INET6 )
                                {
-                                       addrPtr = ipaddr->sip.v6.sin6_addr.s6_addr;
+                                       const struct sockaddr_in6 * const               sin6 = &ipaddr->sip.v6;
+                                       
+                                       addrPtr = sin6->sin6_addr.s6_addr;
                                        lsb             = addrPtr[ 15 ];
-                                       if( ( memcmp( addrPtr, addrV6, 15 ) == 0 ) && ( lsb >= 1 ) && ( lsb <= context->recordCountAAAA ) )
+                                       if( ( lsb >= 1 ) && ( lsb <= context->recordCountAAAA ) )
                                        {
-                                               bitmask = UINT64_C( 1 ) << ( lsb - 1 );
-                                               addrV6Bitmap &= ~bitmask;
-                                               isAddrValid = true;
+                                               const uint32_t          scopeID = ( lsb == 1 ) ? context->ifIndex : 0;
+                                               
+                                               if( ( memcmp( addrPtr, ( lsb == 1 ) ? addrV6LL : addrV6, 15 ) == 0 ) &&
+                                                       ( sin6->sin6_scope_id == scopeID ) )
+                                               {
+                                                       bitmask = UINT64_C( 1 ) << ( lsb - 1 );
+                                                       addrV6Bitmap &= ~bitmask;
+                                                       isAddrValid = true;
+                                               }
                                        }
                                }
                                if( isAddrValid )
@@ -13092,9 +15350,14 @@ static void    _MDNSDiscoveryTestServiceBrowserCallback( ServiceBrowserResults *inR
                                bitmask = UINT64_C( 1 ) << ( j - 1 );
                                if( addrV6Bitmap & bitmask )
                                {
+                                       struct sockaddr_in6             sin6;
+                                       uint8_t                                 missingIPv6[ 16 ];
+                                       
                                        addrV6Bitmap &= ~bitmask;
-                                       addrV6[ 15 ] = (uint8_t) j;
-                                       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, missingAddrs, "%.16a", addrV6 );
+                                       memcpy( missingIPv6, ( j == 1 ) ? addrV6LL : addrV6, 16 );
+                                       missingIPv6[ 15 ] = (uint8_t) j;
+                                       _SockAddrInitIPv6( &sin6, missingIPv6, ( j == 1 ) ? context->ifIndex : 0, 0 );
+                                       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, missingAddrs, "%##a", &sin6 );
                                        require_noerr( err, exit );
                                }
                        }
@@ -13376,7 +15639,7 @@ static void     DotLocalTestCmd( void )
                _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, 4, randBuf ) );
        require_action_quiet( context->replierCmd, exit, err = kUnknownErr );
        
-       err = SpawnCommand( &context->replierPID, "%s", context->replierCmd );
+       err = _SpawnCommand( &context->replierPID, NULL, NULL, "%s", context->replierCmd );
        require_noerr( err, exit );
        
        // Spawn a test DNS server
@@ -13386,7 +15649,7 @@ static void     DotLocalTestCmd( void )
                (int64_t) getpid(), context->labelStr );
        require_action_quiet( context->serverCmd, exit, err = kUnknownErr );
        
-       err = SpawnCommand( &context->serverPID, "%s", context->serverCmd );
+       err = _SpawnCommand( &context->serverPID, NULL, NULL, "%s", context->serverCmd );
        require_noerr( err, exit );
        
        // Create a shared DNS-SD connection.
@@ -13432,8 +15695,8 @@ static void     DotLocalTestCmd( void )
        require_noerr( err, exit );
        
        err = DNSServiceRegisterRecord( context->connection, &context->localSOARef, kDNSServiceFlagsUnique,
-               kDNSServiceInterfaceIndexLocalOnly, "local.", kDNSServiceType_SOA, kDNSServiceClass_IN, 1,
-               rdataPtr, 1 * kSecondsPerHour, _DotLocalTestRegisterRecordCallback, context );
+               kDNSServiceInterfaceIndexLocalOnly, "local.", kDNSServiceType_SOA, kDNSServiceClass_IN,
+               (uint16_t) rdataLen, rdataPtr, 1 * kSecondsPerHour, _DotLocalTestRegisterRecordCallback, context );
        require_noerr( err, exit );
        
        // Start timer for probe responses and SOA record registration.
@@ -13572,8 +15835,8 @@ static OSStatus _DotLocalTestStartSubtest( DotLocalTestContext *inContext )
                subtest->hasMDNSv4 = subtest->needMDNSv4 = true;
                subtest->hasMDNSv6 = subtest->needMDNSv6 = true;
                
-               subtest->addrMDNSv4 = htonl( 0x00000201 );                                      // 0.0.2.1
-               memcpy( subtest->addrMDNSv6, kMDNSReplierBaseAddrV6, 16 );      // 2001:db8:2::2:1
+               subtest->addrMDNSv4 = htonl( 0x00000201 );                                                      // 0.0.2.1
+               memcpy( subtest->addrMDNSv6, kMDNSReplierLinkLocalBaseAddrV6, 16 );     // fe80::2:1
                subtest->addrMDNSv6[ 13 ] = 2;
                subtest->addrMDNSv6[ 15 ] = 1;
                
@@ -13588,8 +15851,8 @@ static OSStatus _DotLocalTestStartSubtest( DotLocalTestContext *inContext )
                subtest->hasDNSv4 = subtest->needDNSv4 = true;
                subtest->hasDNSv6 = subtest->needDNSv6 = true;
                
-               subtest->addrDNSv4 = htonl( kDNSServerBaseAddrV4 + 1 );         // 203.0.113.1
-               memcpy( subtest->addrDNSv6, kDNSServerBaseAddrV6, 16 );         // 2001:db8:1::1
+               subtest->addrDNSv4 = htonl( kDNSServerBaseAddrV4 + 1 );                         // 203.0.113.1
+               memcpy( subtest->addrDNSv6, kDNSServerBaseAddrV6, 16 );                         // 2001:db8:1::1
                subtest->addrDNSv6[ 15 ] = 1;
                
                subtest->testDesc = kDotLocalTestSubtestDesc_GAIDNSOnly;
@@ -13605,12 +15868,12 @@ static OSStatus       _DotLocalTestStartSubtest( DotLocalTestContext *inContext )
                subtest->hasMDNSv4      = subtest->needMDNSv4   = true;
                subtest->hasMDNSv6      = subtest->needMDNSv6   = true;
                
-               subtest->addrDNSv4 = htonl( kDNSServerBaseAddrV4 + 1 );         // 203.0.113.1
-               memcpy( subtest->addrDNSv6, kDNSServerBaseAddrV6, 16 );         // 2001:db8:1::1
+               subtest->addrDNSv4 = htonl( kDNSServerBaseAddrV4 + 1 );                         // 203.0.113.1
+               memcpy( subtest->addrDNSv6, kDNSServerBaseAddrV6, 16 );                         // 2001:db8:1::1
                subtest->addrDNSv6[ 15 ] = 1;
                
-               subtest->addrMDNSv4 = htonl( 0x00000101 );                                      // 0.0.1.1
-               memcpy( subtest->addrMDNSv6, kMDNSReplierBaseAddrV6, 16 );      // 2001:db8:2::1:1
+               subtest->addrMDNSv4 = htonl( 0x00000101 );                                                      // 0.0.1.1
+               memcpy( subtest->addrMDNSv6, kMDNSReplierLinkLocalBaseAddrV6, 16 );     // fe80::1:1
                subtest->addrMDNSv6[ 13 ] = 1;
                subtest->addrMDNSv6[ 15 ] = 1;
                
@@ -14184,7 +16447,7 @@ static void DNSSD_API
        }
        
        rdataStr = NULL;
-       DNSRecordDataToString( inRDataPtr, inRDataLen, kDNSServiceType_SRV, NULL, 0, &rdataStr );
+       DNSRecordDataToString( inRDataPtr, inRDataLen, kDNSServiceType_SRV, &rdataStr );
        if( !rdataStr )
        {
                ASPrintF( &rdataStr, "%#H", inRDataPtr, inRDataLen, inRDataLen );
@@ -14276,20 +16539,26 @@ static const ProbeConflictTestCase            kProbeConflictTestCases[] =
 
 typedef struct
 {
-       DNSServiceRef                   registration;   // Test service registration.
-       NanoTime64                              testStartTime;  // Test's start time.
-       NanoTime64                              startTime;              // Current test case's start time.
-       MDNSColliderRef                 collider;               // mDNS collider object.
-       CFMutableArrayRef               results;                // Array of test case results.
-       char *                                  serviceName;    // Test service's instance name as a string. (malloced)
-       char *                                  serviceType;    // Test service's service type as a string. (malloced)
-       uint8_t *                               recordName;             // FQDN of collider's record (same as test service's SRV+TXT records). (malloced)
-       unsigned int                    testCaseIndex;  // Index of the current test case.
-       uint32_t                                ifIndex;                // Index of the interface that the collider is to operate on.
-       char *                                  outputFilePath; // File to write test results to. If NULL, then write to stdout. (malloced)
-       OutputFormatType                outputFormat;   // Format of test report output.
-       Boolean                                 registered;             // True if the test service instance is currently registered.
-       Boolean                                 testFailed;             // True if at least one test case failed.
+       DNSServiceRef                           registration;   // Test service registration.
+       NanoTime64                                      testStartTime;  // Test's start time.
+       NanoTime64                                      startTime;              // Current test case's start time.
+       MDNSColliderRef                         collider;               // mDNS collider object.
+       CFMutableArrayRef                       results;                // Array of test case results.
+       char *                                          serviceName;    // Test service's instance name as a string. (malloced)
+       char *                                          serviceType;    // Test service's service type as a string. (malloced)
+       uint8_t *                                       recordName;             // FQDN of collider's record (same as test service's records). (malloced)
+       dispatch_source_t                       sigSourceINT;   // SIGINT signal handler.
+       dispatch_source_t                       sigSourceTERM;  // SIGTERM signal handler.
+       CFStringRef                                     exComputerName; // Previous ComputerName.
+       CFStringRef                                     exLocalHostName;// Previous LocalHostName.
+       CFStringEncoding                        exCompNameEnc;  // Previous ComputerName's encoding.
+       unsigned int                            testCaseIndex;  // Index of the current test case.
+       uint32_t                                        ifIndex;                // Index of the interface that the collider is to operate on.
+       MDNSColliderProtocols           protocol;               // mDNS collider's IP protocol.
+       char *                                          outputFilePath; // File to write test results to. If NULL, then write to stdout. (malloced)
+       OutputFormatType                        outputFormat;   // Format of test report output.
+       Boolean                                         registered;             // True if the test service instance is currently registered.
+       Boolean                                         testFailed;             // True if at least one test case failed.
        
 }      ProbeConflictTestContext;
 
@@ -14306,12 +16575,17 @@ static void           _ProbeConflictTestColliderStopHandler( void *inContext, OSStatus in
 static OSStatus        _ProbeConflictTestStartNextTest( ProbeConflictTestContext *inContext );
 static OSStatus        _ProbeConflictTestStopCurrentTest( ProbeConflictTestContext *inContext, Boolean inRenamed );
 static void            _ProbeConflictTestFinalizeAndExit( ProbeConflictTestContext *inContext ) ATTRIBUTE_NORETURN;
+static void            _ProbeConflictTestRestoreSystemNames( ProbeConflictTestContext *inContext );
+static void            _ProbeConflictTestSignalHandler( void *inContext );
 
 static void    ProbeConflictTestCmd( void )
 {
        OSStatus                                                err;
        ProbeConflictTestContext *              context;
        const char *                                    serviceName;
+       CFStringRef                                             computerName    = NULL;
+       CFStringRef                                             localHostName   = NULL;
+       char *                                                  uniqueName;
        char                                                    tag[ 6 + 1 ];
        
        context = (ProbeConflictTestContext *) calloc( 1, sizeof( *context ) );
@@ -14328,6 +16602,20 @@ static void    ProbeConflictTestCmd( void )
                require_noerr_quiet( err, exit );
        }
        
+       if( gProbeConflictTest_UseIPv6 )
+       {
+               if( gProbeConflictTest_UseIPv4 )
+               {
+                       FPrintF( stderr, "error: --ipv4 and --ipv6 are mutually exclusive options.\n" );
+                       goto exit;
+               }
+               context->protocol = kMDNSColliderProtocol_IPv6;
+       }
+       else
+       {
+               context->protocol = kMDNSColliderProtocol_IPv4;
+       }
+       
        if( gProbeConflictTest_OutputFilePath )
        {
                context->outputFilePath = strdup( gProbeConflictTest_OutputFilePath );
@@ -14340,13 +16628,62 @@ static void   ProbeConflictTestCmd( void )
        context->results = CFArrayCreateMutable( NULL, kProbeConflictTestCaseCount, &kCFTypeArrayCallBacks );
        require_action( context->results, exit, err = kNoMemoryErr );
        
+       context->testStartTime = NanoTimeGetCurrent();
+       
+       // Set a unique ComputerName.
+       
+       computerName = SCDynamicStoreCopyComputerName( NULL, &context->exCompNameEnc );
+       err = map_scerror( computerName );
+       require_noerr( err, exit );
+       
+       _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag );
+       ASPrintF( &uniqueName, "dnssdutil-pctest-computer-name-%s", tag );
+       require_action( uniqueName, exit, err = kNoMemoryErr );
+       
+       err = _SetComputerNameWithUTF8CString( uniqueName );
+       ForgetMem( &uniqueName );
+       require_noerr( err, exit );
+       context->exComputerName = computerName;
+       computerName = NULL;
+       
+       // Set a unique LocalHostName.
+       
+       localHostName = SCDynamicStoreCopyLocalHostName( NULL );
+       err = map_scerror( localHostName );
+       require_noerr( err, exit );
+       
+       ASPrintF( &uniqueName, "dnssdutil-pctest-local-hostname-%s", tag );
+       require_action( uniqueName, exit, err = kNoMemoryErr );
+       
+       err = _SetLocalHostNameWithUTF8CString( uniqueName );
+       ForgetMem( &uniqueName );
+       require_noerr( err, exit );
+       context->exLocalHostName = localHostName;
+       localHostName = NULL;
+       
+       // Set up SIGINT signal handler.
+       
+       signal( SIGINT, SIG_IGN );
+       err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), _ProbeConflictTestSignalHandler, context,
+               &context->sigSourceINT );
+       require_noerr( err, exit );
+       dispatch_resume( context->sigSourceINT );
+       
+       // Set up SIGTERM signal handler.
+       
+       signal( SIGTERM, SIG_IGN );
+       err = DispatchSignalSourceCreate( SIGTERM, dispatch_get_main_queue(), _ProbeConflictTestSignalHandler, context,
+               &context->sigSourceTERM );
+       require_noerr( err, exit );
+       dispatch_resume( context->sigSourceTERM );
+       
+       // Register the test service instance.
+       
        serviceName = gProbeConflictTest_UseComputerName ? NULL : kProbeConflictTestService_DefaultName;
        
-       ASPrintF( &context->serviceType, "_pctest-%s._udp",
-               _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
+       ASPrintF( &context->serviceType, "_pctest-%s._udp", tag );
        require_action( context->serviceType, exit, err = kNoMemoryErr );
        
-       context->testStartTime = NanoTimeGetCurrent();
        err = DNSServiceRegister( &context->registration, 0, context->ifIndex, serviceName, context->serviceType, "local.",
                NULL, htons( kProbeConflictTestService_Port ), 0, NULL, _ProbeConflictTestRegisterCallback, context );
        require_noerr( err, exit );
@@ -14357,7 +16694,10 @@ static void    ProbeConflictTestCmd( void )
        dispatch_main();
        
 exit:
-       exit( 1 );
+       CFReleaseNullSafe( computerName );
+       CFReleaseNullSafe( localHostName );
+       if( context ) _ProbeConflictTestRestoreSystemNames( context );
+       ErrQuit( 1, "error: %#m\n", err );
 }
 
 //===========================================================================================================================
@@ -14491,7 +16831,7 @@ static OSStatus _ProbeConflictTestStartNextTest( ProbeConflictTestContext *inCon
                kProbeConflictTestTXTPtr, kProbeConflictTestTXTLen );
        require_noerr( err, exit );
        
-       MDNSColliderSetProtocols( inContext->collider, kMDNSColliderProtocol_IPv4 );
+       MDNSColliderSetProtocols( inContext->collider, inContext->protocol );
        MDNSColliderSetInterfaceIndex( inContext->collider, inContext->ifIndex );
        MDNSColliderSetStopHandler( inContext->collider, _ProbeConflictTestColliderStopHandler, inContext );
        
@@ -14578,6 +16918,7 @@ static void     _ProbeConflictTestFinalizeAndExit( ProbeConflictTestContext *inConte
        OSStatus                                err;
        CFPropertyListRef               plist;
        NanoTime64                              now;
+       int                                             exitCode;
        char                                    startTime[ 32 ];
        char                                    endTime[ 32 ];
        
@@ -14608,530 +16949,989 @@ static void _ProbeConflictTestFinalizeAndExit( ProbeConflictTestContext *inConte
        CFRelease( plist );
        require_noerr( err, exit );
        
-       exit( inContext->testFailed ? 2 : 0 );
-       
 exit:
-       ErrQuit( 1, "error: %#m\n", err );
+       _ProbeConflictTestRestoreSystemNames( inContext );
+       if( err )
+       {
+               FPrintF( stderr, "error: %#m\n", err );
+               exitCode = 1;
+       }
+       else
+       {
+               exitCode = inContext->testFailed ? 2 : 0;
+       }
+       exit( exitCode );
 }
 
 //===========================================================================================================================
-//    ExpensiveConstrainedsTestCmd
+//     _ProbeConflictTestRestoreSystemNames
 //===========================================================================================================================
 
-#define NOTIFICATION_TIME_THRESHOLD 1500    // The maximum wating time allowed before notification happens
-#define TEST_REPETITION             2       // the number of repetition that one test has to passed
-#define LOOPBACK_INTERFACE_NAME     "lo0"
-#define WIFI_TEST_QUESTION_NAME     "www.example.com"
-#define EXPENSIVE_CONSTRAINED_MAX_RETRIES 1
-#define EXPENSIVE_CONSTRAINED_TEST_INTERVAL 5
-// Use "-n tag-expensive-test.ttl-86400.d.test." to run the test locally
-// #define LOOPBACK_TEST_QUESTION_NAME "tag-expensive-test.ttl-86400.d.test."
-
-#define EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_START_TIME                CFSTR( "Start Time" )
-#define EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_END_TIME                  CFSTR( "End Time" )
-#define EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_ALL_PASSED                CFSTR( "All Tests Passed" )
-#define EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_SUBTEST_RESULT            CFSTR( "Subtest Results" )
+static void    _ProbeConflictTestRestoreSystemNames( ProbeConflictTestContext *inContext )
+{
+       OSStatus                err;
+       
+       if( inContext->exComputerName )
+       {
+               err = _SetComputerName( inContext->exComputerName, inContext->exCompNameEnc );
+               check_noerr( err );
+               ForgetCF( &inContext->exComputerName );
+       }
+       if( inContext->exLocalHostName )
+       {
+               err = _SetLocalHostName( inContext->exLocalHostName );
+               check_noerr( err );
+               ForgetCF( &inContext->exLocalHostName );
+       }
+}
 
-#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_START_TIME             CFSTR( "Start Time" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_END_TIME               CFSTR( "End Time" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_QNAME                  CFSTR( "Question Name" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_FLAGS                  CFSTR( "DNS Service Flags" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_PROTOCOLS              CFSTR( "Protocols" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_INDEX        CFSTR( "Interface Index" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_NAME         CFSTR( "Interface Name" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_RESULT                 CFSTR( "Result" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_ERROR                  CFSTR( "Error Description" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_TEST_PROGRESS          CFSTR( "Test Progress" )
+//===========================================================================================================================
+//     _ProbeConflictTestSignalHandler
+//===========================================================================================================================
 
-#define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_START_TIME           CFSTR( "Start Time" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_END_TIME             CFSTR( "End Time" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_STATE                CFSTR( "State" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_EXPECT_RESULT        CFSTR( "Expected Result" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_ACTUAL_RESULT        CFSTR( "Actual Result" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_EXPENSIVE_PREV_NOW   CFSTR( "Expensive Prev->Now" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_CONSTRAINED_PREV_NOW CFSTR( "Constrained Prev->Now" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_CALL_BACK            CFSTR( "Call Back" )
+static void    _ProbeConflictTestSignalHandler( void *inContext )
+{
+       _ProbeConflictTestRestoreSystemNames( inContext );
+       FPrintF( stderr, "Probe conflict test got a SIGINT or SIGTERM signal, exiting...\n" );
+       exit( 1 );
+}
 
-#define EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_TIMESTAMP       CFSTR( "Timestamp" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_NAME            CFSTR( "Answer Name" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_FLAGS           CFSTR( "Add or Remove" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_INTERFACE       CFSTR( "Interface Index" )
-#define EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_ADDRESS         CFSTR( "Address" )
+#if( MDNSRESPONDER_PROJECT )
+//===========================================================================================================================
+//    FallbackTestCmd
+//===========================================================================================================================
 
-// All the states that ends with _PREPARE represents the state where the test state is reset and initialized.
-enum ExpensiveConstrainedTestState
+typedef struct
 {
-    TEST_BEGIN,
-    TEST_EXPENSIVE_PREPARE,
-    TEST_EXPENSIVE,                     // Test if mDNSResponder can handle "expensive" status change of the corresponding interface
-    TEST_CONSTRAINED_PREPARE,
-    TEST_CONSTRAINED,                   // Test if mDNSResponder can handle "constrained" status change of the corresponding interface
-    TEST_EXPENSIVE_CONSTRAINED_PREPARE,
-    TEST_EXPENSIVE_CONSTRAINED,          // Test if mDNSResponder can handle "expensive" and "constrained" status change of the corresponding interface at the same time
-    TEST_FAILED,
-    TEST_SUCCEEDED
-};
-enum ExpensiveConstrainedTestOperation
+       unsigned int            serverIndex;            // Index of server that is soley capable of answering query.
+       
+}      FallbackSubtestParams;
+
+#define kFallbackTestSubtestCount              8
+
+const FallbackSubtestParams            kFallbackSubtestParams[] = { { 2 }, { 4 }, { 1 }, { 3 }, { 2 }, { 1 }, { 4 }, { 3 } };
+check_compile_time( countof( kFallbackSubtestParams ) == kFallbackTestSubtestCount );
+
+typedef struct
 {
-    RESULT_ADD, // received response for the given query, which means mDNSResponder is able to send out the query over the interface, because the interface status is changed.
-    RESULT_RMV, // received negative response for the given query, which means mDNSResponder is not able to send out the query over the interface, because the interface status is changed.
-    NO_UPDATE   // no status update notification
-};
+       char *                  hostname;       // Hostname to resolve.
+       NanoTime64              startTime;      // Subtest's start time.
+       NanoTime64              endTime;        // Subtest's end time.
+       OSStatus                error;          // Subtest's current error.
+       
+}      FallbackSubtest;
 
 typedef struct
 {
-    uint32_t                    subtestIndex;           // The index of parameter for the subtest
-    DNSServiceRef               opRef;                  // sdRef for the DNSServiceGetAddrInfo operation.
-    const char *                name;                   // Hostname to resolve.
-    DNSServiceFlags             flags;                  // Flags argument for DNSServiceGetAddrInfo().
-    DNSServiceProtocol          protocols;              // Protocols argument for DNSServiceGetAddrInfo().
-    uint32_t                    ifIndex;                // Interface index argument for DNSServiceGetAddrInfo().
-    char                        ifName[IFNAMSIZ];       // Interface name for the given interface index.
-    dispatch_source_t           timer;                  // The test will check if the current behavior is valid, which is called by
-                                                        // the timer per 2s.
-    pid_t                       serverPID;
-    Boolean                     isExpensivePrev;        // If the interface is expensive in the previous test step.
-    Boolean                     isExpensiveNow;         // If the interface is expensive now.
-    Boolean                     isConstrainedPrev;      // If the interface is constrained in the previous test step.
-    Boolean                     isConstrainedNow;       // If the interface is constrained now.
-    Boolean                     startFromExpensive;     // All the test will start from expensive/constrained interface, so there won's be an answer until the interface is changed.
-    uint8_t                     numOfRetries;           // the number of retries we can have if the test fail
-    struct timeval              updateTime;             // The time when interface status(expensive or constrained) is changed.
-    struct timeval              notificationTime;       // The time when callback function, which is passed to DNSServiceGetAddrInfo, gets called.
-    uint32_t                    counter;                // To record how many times the test has repeated.
-    enum ExpensiveConstrainedTestState          state;              // The current test state.
-    enum ExpensiveConstrainedTestOperation      expectedOperation;  // the test expects this kind of notification
-    enum ExpensiveConstrainedTestOperation      operation;          // represents what notification the callback function gets.
-
-    NanoTime64                  testReport_startTime;       // when the entire test starts
-    CFMutableArrayRef           subtestReport;              // stores the log message for every subtest
-    NanoTime64                  subtestReport_startTime;    // when the subtest starts
-    CFMutableArrayRef           subtestProgress;            // one test iteration
-    NanoTime64                  subtestProgress_startTime;  // when the test iteration starts
-    CFMutableArrayRef           subtestProgress_callBack;   // array of ADD/REMOVE events
-    char *                      outputFilePath;             // File to write test results to. If NULL, then write to stdout. (malloced)
-    OutputFormatType            outputFormat;               // Format of test report output.
-} ExpensiveConstrainedContext;
-
-// structure that controls how the subtest is run
-typedef struct
-{
-    const char  *qname;                 // the name of the query, when the ends with ".d.test.", test will send query to local DNS server
-    Boolean     deny_expensive;         // if the query should avoid using expensive interface
-    Boolean     deny_constrained;       // if the query should avoid using constrained interface
-    Boolean     start_from_expensive;   // if the query should starts from using an expensive interface
-    Boolean     ipv4_query;             // only allow IPv4 query
-    Boolean     ipv6_query;             // only allow IPv6 query
-    int8_t      test_passed;            // if the subtest passes
-} ExpensiveConstrainedTestParams;
-
-static ExpensiveConstrainedTestParams   ExpensiveConstrainedSubtestParams[] =
-{
-//  qname                                                   deny_expensive  deny_constrained    start_from_expensive    ipv4_query  ipv6_query
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           false,              false,                  true,       true,       -1},
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           false,              true,                   true,       true,       -1},
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    false,          true,               false,                  true,       true,       -1},
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    false,          true,               true,                   true,       true,       -1},
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           true,               false,                  true,       true,       -1},
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           true,               true,                   true,       true,       -1},
-// IPv4 Only
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           false,              false,                  true,       false,      -1},
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           false,              true,                   true,       false,      -1},
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    false,          true,               false,                  true,       false,      -1},
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    false,          true,               true,                   true,       false,      -1},
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           true,               false,                  true,       false,      -1},
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           true,               true,                   true,       false,      -1},
-// IPv6 Only
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           false,              false,                  false,      true,       -1},
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           false,              true,                   false,      true,       -1},
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    false,          true,               false,                  false,      true,       -1},
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    false,          true,               true,                   false,      true,       -1},
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           true,               false,                  false,      true,       -1},
-    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           true,               true,                   false,      true,       -1}
-};
+       dispatch_queue_t                        queue;                  // Serial queue for test events.
+       dispatch_semaphore_t            doneSem;                // Semaphore to signal when the test is done.
+       DNSServiceRef                           gai;                    // Current DNSServiceGetAddrInfo request.
+       dispatch_source_t                       timer;                  // Timer for enforcing time limit on current DNSServiceGetAddrInfo request.
+       size_t                                          subtestIndex;   // Index of current subtest.
+       pid_t                                           serverPID;              // PID of spawned test DNS server.
+       OSStatus                                        error;                  // Current test error.
+       NanoTime64                                      startTime;              // Test's start time.
+       NanoTime64                                      endTime;                // Test's end time.
+       char *                                          serverCmd;              // Command used to invoke the test DNS server.
+       char *                                          probeHostname;  // Hostname queried to verify that server is up and running.
+       FallbackSubtest                         subtests[ kFallbackTestSubtestCount ];
+       Boolean                                         useRefused;             // True if server uses Refused RCODE for queries it's not allowed to answer.
+       
+}      FallbackTest;
 
-static void ExpensiveConstrainedSetupLocalDNSServer( ExpensiveConstrainedContext *context );
-static void ExpensiveConstrainedStartTestHandler( ExpensiveConstrainedContext *context );
-static void ExpensiveConstrainedStopTestHandler( ExpensiveConstrainedContext *context );
-static void ExpensiveConstrainedSetupTimer( ExpensiveConstrainedContext *context, uint32_t second );
-static void ExpensiveConstrainedTestTimerEventHandler( ExpensiveConstrainedContext *context );
-static void DNSSD_API
-    ExpensiveConstrainedCallback(
-        DNSServiceRef           inSDRef,
-        DNSServiceFlags         inFlags,
-        uint32_t                inInterfaceIndex,
-        DNSServiceErrorType     inError,
-        const char *            inHostname,
-        const struct sockaddr * inSockAddr,
-        uint32_t                inTTL,
-        void *                  inContext );
-static void ExpensiveConstrainedInitializeContext( ExpensiveConstrainedContext *context );
-static void ExpensiveConstrainedStopAndCleanTheTest( ExpensiveConstrainedContext *context );
-static void ExpensiveConstrainedSubtestProgressReport( ExpensiveConstrainedContext *context );
-static void ExpensiveConstrainedSubtestReport( ExpensiveConstrainedContext *context, const char *error_description );
-static void ExpensiveConstrainedFinalResultReport( ExpensiveConstrainedContext *context, Boolean allPassed );
-static const char *ExpensiveConstrainedProtocolString(DNSServiceProtocol protocol);
-static const char *ExpensiveConstrainedStateString(enum ExpensiveConstrainedTestState state);
-static const char *ExpensiveConstrainedOperationString(enum ExpensiveConstrainedTestOperation operation);
-static Boolean expensiveConstrainedEndsWith( const char *str, const char *suffix );
+static OSStatus        _FallbackTestCreate( FallbackTest **outTest );
+static OSStatus        _FallbackTestRun( FallbackTest *inTest );
+static void            _FallbackTestFree( FallbackTest *inTest );
 
-//===========================================================================================================================
-//    ExpensiveConstrainedTestCmd
-//===========================================================================================================================
+ulog_define_ex( kDNSSDUtilIdentifier, FallbackTest, kLogLevelInfo, kLogFlags_None, "FallbackTest", NULL );
+#define ft_ulog( LEVEL, ... )          ulog( &log_category_from_name( FallbackTest ), (LEVEL), __VA_ARGS__ )
 
-static void ExpensiveConstrainedTestCmd( void )
+static void    FallbackTestCmd( void )
 {
-    OSStatus                        err;
-    dispatch_source_t               signalSource   = NULL;
-    ExpensiveConstrainedContext *   context        = NULL;
-
-    // Set up SIGINT handler.
-    signal( SIGINT, SIG_IGN );
-    err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), Exit, kExitReason_SIGINT, &signalSource );
-    require_noerr( err, exit );
-    dispatch_resume( signalSource );
-
-    // create the test context
-    context = (ExpensiveConstrainedContext *) calloc( 1, sizeof(*context) );
-    require_action( context, exit, err = kNoMemoryErr );
-
-    // get the command line option
-    err = OutputFormatFromArgString( gExpensiveConstrainedTest_OutputFormat, &context->outputFormat );
-    require_noerr_quiet( err, exit );
-    if ( gExpensiveConstrainedTest_OutputFilePath )
-    {
-        context->outputFilePath = strdup( gExpensiveConstrainedTest_OutputFilePath );
-        require_noerr_quiet( context->outputFilePath, exit );
-    }
-
-    // initialize context
-    context->subtestIndex = 0;
-    context->numOfRetries = EXPENSIVE_CONSTRAINED_MAX_RETRIES;
-
-    // initialize the CFArray used to store the log
-    context->subtestReport = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
-    context->testReport_startTime = NanoTimeGetCurrent();
-
-    // setup local DNS server
-    ExpensiveConstrainedSetupLocalDNSServer( context );
-
-    ExpensiveConstrainedStartTestHandler( context );
-
-    dispatch_main();
-
+       OSStatus                                err;
+       FallbackTest *                  test            = NULL;
+       CFPropertyListRef               plist           = NULL;
+       OutputFormatType                outputFormat;
+       size_t                                  i;
+       CFMutableArrayRef               results;
+       Boolean                                 testPassed      = false;
+       Boolean                                 subtestFailed;
+       char                                    startTime[ 32 ];
+       char                                    endTime[ 32 ];
+       
+       err = CheckRootUser();
+       require_noerr_quiet( err, exit );
+       
+       err = OutputFormatFromArgString( gFallbackTest_OutputFormat, &outputFormat );
+       require_noerr_quiet( err, exit );
+       
+       err = _FallbackTestCreate( &test );
+       require_noerr( err, exit );
+       
+       if( gFallbackTest_UseRefused ) test->useRefused = true;
+       err = _FallbackTestRun( test );
+       require_noerr( err, exit );
+       
+       _NanoTime64ToTimestamp( test->startTime, startTime, sizeof( startTime ) );
+       _NanoTime64ToTimestamp( test->endTime, endTime, sizeof( endTime ) );
+       err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+               "{"
+                       "%kO=%s"        // startTime
+                       "%kO=%s"        // endTime
+                       "%kO=%s"        // serverCmd
+                       "%kO=[%@]"      // results
+               "}",
+               CFSTR( "startTime" ),   startTime,
+               CFSTR( "endTime" ),             endTime,
+               CFSTR( "serverCmd" ),   test->serverCmd,
+               CFSTR( "results" ),             &results );
+       require_noerr( err, exit );
+       
+       subtestFailed = false;
+       check( test->subtestIndex == kFallbackTestSubtestCount );
+       for( i = 0; i < kFallbackTestSubtestCount; ++i )
+       {
+               CFMutableDictionaryRef          resultDict;
+               FallbackSubtest * const         subtest = &test->subtests[ i ];
+               char                                            errorDesc[ 128 ];
+               
+               err = CFPropertyListAppendFormatted( kCFAllocatorDefault, results, "{%@}", &resultDict );
+               require_noerr( err, exit );
+               
+               err = CFDictionarySetCString( resultDict, CFSTR( "name" ), subtest->hostname, kSizeCString );
+               require_noerr( err, exit );
+               
+               _NanoTime64ToTimestamp( subtest->startTime, startTime, sizeof( startTime ) );
+               err = CFDictionarySetCString( resultDict, CFSTR( "startTime" ), startTime, kSizeCString );
+               require_noerr( err, exit );
+               
+               _NanoTime64ToTimestamp( subtest->endTime, endTime, sizeof( endTime ) );
+               err = CFDictionarySetCString( resultDict, CFSTR( "endTime" ), endTime, kSizeCString );
+               require_noerr( err, exit );
+               
+               SNPrintF( errorDesc, sizeof( errorDesc ), "%m", subtest->error );
+               err = CFPropertyListAppendFormatted( kCFAllocatorDefault, resultDict,
+                       "%kO="
+                       "{"
+                               "%kO=%lli"      // code
+                               "%kO=%s"        // description
+                       "}",
+                       CFSTR( "error" ),
+                       CFSTR( "code" ),                (int64_t) subtest->error,
+                       CFSTR( "description" ), errorDesc );
+               require_noerr( err, exit );
+               
+               if( subtest->error ) subtestFailed = true;
+       }
+       if( !subtestFailed ) testPassed = true;
+       CFPropertyListAppendFormatted( kCFAllocatorDefault, plist, "%kO=%b", CFSTR( "pass" ), testPassed );
+       
+       err = OutputPropertyList( plist, outputFormat, gFallbackTest_OutputFilePath );
+       require_noerr( err, exit );
+       
 exit:
-    exit( 1 );
+       if( test ) _FallbackTestFree( test );
+       CFReleaseNullSafe( plist );
+       gExitCode = err ? 1 : ( testPassed ? 0 : 2 );
 }
 
-//===========================================================================================================================
-//    ExpensiveConstrainedSetupLocalDNSServer
 //===========================================================================================================================
 
-static void ExpensiveConstrainedSetupLocalDNSServer( ExpensiveConstrainedContext *context )
+static void            _FallbackTestStart( void *inContext );
+static void            _FallbackTestStop( FallbackTest *inTest, OSStatus inError );
+static void DNSSD_API
+       _FallbackTestProbeGAICallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inHostname,
+               const struct sockaddr * inSockAddr,
+               uint32_t                                inTTL,
+               void *                                  inContext );
+static void            _FallbackTestProbeTimerHandler( void *inContext );
+static void DNSSD_API
+       _FallbackTestGetAddrInfoCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inHostname,
+               const struct sockaddr * inSockAddr,
+               uint32_t                                inTTL,
+               void *                                  inContext );
+static void            _FallbackTestGAITimerHandler( void *inContext );
+static OSStatus        _FallbackTestStartSubtest( FallbackTest *inTest );
+static void            _FallbackTestForgetSources( FallbackTest *inTest );
+
+#define kFallbackTestProbeTimeLimitSecs                 5
+#define kFallbackTestGAITimeLimitSecs          75
+
+static OSStatus        _FallbackTestCreate( FallbackTest **outTest )
 {
-    pid_t current_pid = getpid();
-    OSStatus err = SpawnCommand( &context->serverPID, "dnssdutil server -l --follow %d", current_pid );
-    if (err != 0)
-    {
-        FPrintF( stdout, "dnssdutil server -l --follow <PID> failed, error: %d\n", err );
-        exit( 1 );
-    }
-    sleep(2);
+       OSStatus                        err;
+       FallbackTest *          test;
+       
+       test = (FallbackTest *) calloc( 1, sizeof( *test ) );
+       require_action( test, exit, err = kNoMemoryErr );
+       
+       test->error             = kInProgressErr;
+       test->serverPID = -1;
+       
+       test->queue = dispatch_queue_create( "com.apple.dnssdutil.fallback-test", DISPATCH_QUEUE_SERIAL );
+       require_action( test->queue, exit, err = kNoResourcesErr );
+       
+       test->doneSem = dispatch_semaphore_create( 0 );
+       require_action( test->doneSem, exit, err = kNoResourcesErr );
+       
+       *outTest = test;
+       test = NULL;
+       err = kNoErr;
+       
+exit:
+       if( test ) _FallbackTestFree( test );
+       return( err );
 }
 
-//===========================================================================================================================
-//    ExpensiveConstrainedStartTestHandler
 //===========================================================================================================================
 
-static void ExpensiveConstrainedStartTestHandler( ExpensiveConstrainedContext *context )
+static OSStatus        _FallbackTestRun( FallbackTest *inTest )
 {
-    // setup 3s timer
-    ExpensiveConstrainedSetupTimer( context, EXPENSIVE_CONSTRAINED_TEST_INTERVAL );
-
-    // set the event handler for the 3s timer
-    dispatch_source_set_event_handler( context->timer, ^{
-        ExpensiveConstrainedTestTimerEventHandler( context );
-    } );
-
-    dispatch_resume( context->timer );
+       dispatch_async_f( inTest->queue, inTest, _FallbackTestStart );
+       dispatch_semaphore_wait( inTest->doneSem, DISPATCH_TIME_FOREVER );
+       return( inTest->error );
 }
 
-//===========================================================================================================================
-//    ExpensiveConstrainedStartTestHandler
 //===========================================================================================================================
 
-static void ExpensiveConstrainedStopTestHandler( ExpensiveConstrainedContext *context )
+static void    _FallbackTestFree( FallbackTest *inTest )
 {
-    dispatch_cancel( context->timer );
-    dispatch_release( context->timer );
-    context->timer = NULL;
+       size_t          i;
+       
+       check( !inTest->gai );
+       check( !inTest->timer );
+       check( inTest->serverPID < 0 );
+       
+       ForgetMem( &inTest->serverCmd );
+       ForgetMem( &inTest->probeHostname );
+       dispatch_forget( &inTest->queue );
+       dispatch_forget( &inTest->doneSem );
+       for( i = 0; i < kFallbackTestSubtestCount; ++i )
+       {
+               FallbackSubtest * const         subtest = &inTest->subtests[ i ];
+               
+               ForgetMem( &subtest->hostname );
+       }
+       free( inTest );
 }
 
-//===========================================================================================================================
-//    ExpensiveConstrainedSetupTimer
 //===========================================================================================================================
 
-static void ExpensiveConstrainedSetupTimer( ExpensiveConstrainedContext *context, uint32_t second )
+static void    _FallbackTestStart( void *inContext )
 {
-    // set the timer source, the event handler will be called for every "second" seconds
-    context->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue() );
-    if ( context->timer == NULL )
-    {
-        FPrintF( stdout, "dispatch_source_create:DISPATCH_SOURCE_TYPE_TIMER failed\n" );
-        exit( 1 );
-    }
-    // the first block will be put into the queue "second"s after calling dispatch_resume
-    dispatch_source_set_timer( context->timer, dispatch_time( DISPATCH_TIME_NOW, second * NSEC_PER_SEC ),
-                               (unsigned long long)(second) * NSEC_PER_SEC, 100ull * NSEC_PER_MSEC );
+       OSStatus                                        err;
+       FallbackTest * const            test = (FallbackTest *) inContext;
+       char                                            tag[ 6 + 1 ];
+       
+       test->startTime = NanoTimeGetCurrent();
+       
+       // The "dnssdutil server" command will create a resolver entry for the server's "d.test." domain containing an array
+       // of the server's IP addresses. Because configd favors IPv6 addresses, when there's a mix of IPv4 and IPv6
+       // addresses, configd may rearrange the array in order to ensure that IPv6 addresses come before the IPv4 addresses.
+       // To preserve the original address order, the server is specified to run in IPv6-only mode. This way,
+       // mDNSResponder's view of the address will be such that address with index value 1 is first, address with index
+       // value 2 is second, etc.
+       
+       ASPrintF( &test->serverCmd, "dnssdutil server --loopback --follow %lld --ipv6 --extraIPv6 3%s",
+               (int64_t) getpid(), test->useRefused ? " --useRefused" : "" );
+       require_action_quiet( test->serverCmd, exit, err = kUnknownErr );
+       
+       err = _SpawnCommand( &test->serverPID, "/dev/null", "/dev/null", "%s", test->serverCmd );
+       require_noerr( err, exit );
+       
+       ASPrintF( &test->probeHostname, "tag-fallback-test-probe-%s.count-1.ipv4.ttl-900.d.test.",
+               _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
+       require_action( test->probeHostname, exit, err = kNoMemoryErr );
+       
+       ft_ulog( kLogLevelInfo, "Starting GetAddrInfo request for %s\n", test->probeHostname );
+       
+       err = DNSServiceGetAddrInfo( &test->gai, 0, kDNSServiceInterfaceIndexAny, kDNSServiceProtocol_IPv4,
+               test->probeHostname, _FallbackTestProbeGAICallback, test );
+       require_noerr( err, exit );
+       
+       err = DNSServiceSetDispatchQueue( test->gai, test->queue );
+       require_noerr( err, exit );
+       
+       err = DispatchTimerOneShotCreate( dispatch_time_seconds( kFallbackTestProbeTimeLimitSecs ),
+               kFallbackTestProbeTimeLimitSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 10 ), test->queue,
+               _FallbackTestProbeTimerHandler, test, &test->timer );
+       require_noerr( err, exit );
+       dispatch_resume( test->timer );
+       
+exit:
+       if( err ) _FallbackTestStop( test, err );
 }
 
 //===========================================================================================================================
-//    ExpensiveConstrainedTestTimerEventHandler
+
+static void    _FallbackTestStop( FallbackTest *inTest, OSStatus inError )
+{
+       inTest->error   = inError;
+       inTest->endTime = NanoTimeGetCurrent();
+       _FallbackTestForgetSources( inTest );
+       if( inTest->serverPID >= 0 )
+       {
+               OSStatus                err;
+               
+               err = kill( inTest->serverPID, SIGTERM );
+               err = map_global_noerr_errno( err );
+               check_noerr( err );
+               inTest->serverPID = -1;
+       }
+       dispatch_semaphore_signal( inTest->doneSem );
+}
+
 //===========================================================================================================================
 
-static void ExpensiveConstrainedTestTimerEventHandler( ExpensiveConstrainedContext *context )
+static void DNSSD_API
+       _FallbackTestProbeGAICallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inHostname,
+               const struct sockaddr * inSockAddr,
+               uint32_t                                inTTL,
+               void *                                  inContext )
 {
-    OSStatus    err;
-    char        buffer[ 1024 ];
-    const char *errorDescription = NULL;
+       OSStatus                                        err;
+       FallbackTest * const            test = (FallbackTest *) inContext;
+       
+       Unused( inSDRef );
+       Unused( inInterfaceIndex );
+       Unused( inHostname );
+       Unused( inTTL );
+       
+       if( ( inFlags & kDNSServiceFlagsAdd ) && !inError )
+       {
+               _FallbackTestForgetSources( test );
+               
+               ft_ulog( kLogLevelInfo, "Probe: Got GAI address %##a for %s\n", inSockAddr, test->probeHostname );
+               
+               check( test->subtestIndex == 0 );
+               err = _FallbackTestStartSubtest( test );
+               require_noerr( err, exit );
+       }
+       err = kNoErr;
+       
+exit:
+       if( err ) _FallbackTestStop( test, err );
+}
 
-    // do not log the state if we are in transition state
-    if (context->state != TEST_BEGIN
-        && context->state != TEST_SUCCEEDED
-        && context->state != TEST_CONSTRAINED_PREPARE
-        && context->state != TEST_EXPENSIVE_CONSTRAINED_PREPARE)
-        ExpensiveConstrainedSubtestProgressReport( context );
+//===========================================================================================================================
 
-    switch ( context->state ) {
-        case TEST_BEGIN:
-        {
-            ExpensiveConstrainedStopTestHandler( context );
+static void    _FallbackTestProbeTimerHandler( void *inContext )
+{
+       FallbackTest * const            test = (FallbackTest *) inContext;
+       
+       ft_ulog( kLogLevelInfo, "GetAddrInfo probe request for \"%s\" timed out.\n", test->probeHostname );
+       _FallbackTestStop( test, kNotPreparedErr );
+}
 
-            // clear mDNSResponder cache
-            err = systemf( NULL, "killall -HUP mDNSResponder" );
-            require_noerr_action( err, test_failed, errorDescription = "systemf failed");
+//===========================================================================================================================
 
-            // initialize the global parameters
-            ExpensiveConstrainedInitializeContext( context );
+static void DNSSD_API
+       _FallbackTestGetAddrInfoCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inHostname,
+               const struct sockaddr * inSockAddr,
+               uint32_t                                inTTL,
+               void *                                  inContext )
+{
+       OSStatus                                        err;
+       struct sockaddr_in                      sin;
+       FallbackTest * const            test            = (FallbackTest *) inContext;
+       FallbackSubtest *const          subtest         = &test->subtests[ test->subtestIndex ];
+       Boolean                                         complete        = false;
+       
+       Unused( inSDRef );
+       Unused( inInterfaceIndex );
+       Unused( inTTL );
+       
+       _FallbackTestForgetSources( test );
+       
+       if( strcasecmp( inHostname, subtest->hostname ) != 0 )
+       {
+               ft_ulog( kLogLevelError, "GetAddrInfo(%s) result: Got unexpected hostname \"%s\".\n",
+                       subtest->hostname, inHostname );
+               err = kUnexpectedErr;
+               goto done;
+       }
+       if( inError )
+       {
+               ft_ulog( kLogLevelError, "GetAddrInfo(%s) result: Got unexpected error %#m.\n", subtest->hostname, inError );
+               err = inError;
+               goto done;
+       }
+       if( ( inFlags & kDNSServiceFlagsAdd ) == 0 )
+       {
+               ft_ulog( kLogLevelError, "GetAddrInfo(%s) result: Missing Add flag.\n", subtest->hostname );
+               err = kUnexpectedErr;
+               goto done;
+       }
+       _SockAddrInitIPv4( &sin, kDNSServerBaseAddrV4 + 1, 0 );
+       if( SockAddrCompareAddr( inSockAddr, &sin ) != 0 )
+       {
+               ft_ulog( kLogLevelError, "GetAddrInfo(%s) result: Got unexpected address %##a (expected %##a).\n",
+                       subtest->hostname, inSockAddr, &sin );
+               err = kUnexpectedErr;
+               goto done;
+       }
+       ft_ulog( kLogLevelInfo, "Subtest %zu/%d: Got expected GAI address %##a for %s\n",
+               test->subtestIndex + 1, kFallbackTestSubtestCount, inSockAddr, subtest->hostname );
+       err = kNoErr;
+       
+done:
+       subtest->endTime        = NanoTimeGetCurrent();
+       subtest->error          = err;
+       err = kNoErr;
+       if( ++test->subtestIndex < kFallbackTestSubtestCount )
+       {
+               err = _FallbackTestStartSubtest( test );
+               require_noerr( err, exit );
+       }
+       else
+       {
+               complete = true;
+       }
+       
+exit:
+       if( err || complete ) _FallbackTestStop( test, err );
+}
 
-            // The local DNS server is set up on the local only interface.
-            gExpensiveConstrainedTest_Interface = LOOPBACK_INTERFACE_NAME;
-            strncpy( context->ifName, gExpensiveConstrainedTest_Interface, sizeof( context->ifName ) );
+//===========================================================================================================================
 
-            // The local DNS server is unscoped, so we must set our question to unscoped.
-            context->ifIndex = kDNSServiceInterfaceIndexAny;
+static void    _FallbackTestGAITimerHandler( void *inContext )
+{
+       OSStatus                                        err;
+       FallbackTest * const            test            = (FallbackTest *) inContext;
+       FallbackSubtest * const         subtest         = &test->subtests[ test->subtestIndex ];
+       Boolean                                         complete        = false;
+       
+       _FallbackTestForgetSources( test );
+       
+       ft_ulog( kLogLevelInfo, "GetAddrInfo request for \"%s\" timed out.\n", subtest->hostname );
+       
+       subtest->endTime        = NanoTimeGetCurrent();
+       subtest->error          = kTimeoutErr;
+       if( ++test->subtestIndex < kFallbackTestSubtestCount )
+       {
+               err = _FallbackTestStartSubtest( test );
+               require_noerr( err, exit );
+       }
+       else
+       {
+               complete = true;
+               err = kNoErr;
+       }
+       
+exit:
+       if( err || complete ) _FallbackTestStop( test, err );
+}
 
-            // The question name must end with "d.test.", "tag-expensive-test.ttl-86400.d.test." for example, then the test will
-            // use the local dns server set up previously to run the test locally.
-            require_action( gExpensiveConstrainedTest_Name != NULL && expensiveConstrainedEndsWith( gExpensiveConstrainedTest_Name, "d.test." ), test_failed,
-                           SNPrintF( buffer, sizeof( buffer ), "The question name (%s) must end with \"d.test.\".\n", gExpensiveConstrainedTest_Name );
-                           errorDescription = buffer );
+//===========================================================================================================================
 
-            // get the quesion name
-            context->name = gExpensiveConstrainedTest_Name;
+static OSStatus        _FallbackTestStartSubtest( FallbackTest *inTest )
+{
+       OSStatus                                        err;
+       FallbackSubtest * const         subtest = &inTest->subtests[ inTest->subtestIndex ];
+       char                                            tag[ 6 + 1 ];
+       
+       subtest->error          = kInProgressErr;
+       subtest->startTime      = NanoTimeGetCurrent();
+       
+       ForgetMem( &subtest->hostname );
+       ASPrintF( &subtest->hostname, "index-%u.tag-fallback-test-%s.count-1.ipv4.ttl-900.d.test.",
+               kFallbackSubtestParams[ inTest->subtestIndex ].serverIndex,
+               _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
+       require_action( subtest->hostname, exit, err = kNoMemoryErr );
+       
+       ft_ulog( kLogLevelInfo, "Starting GetAddrInfo request for %s\n", subtest->hostname );
+       
+       check( !inTest->gai );
+       err = DNSServiceGetAddrInfo( &inTest->gai, 0, kDNSServiceInterfaceIndexAny, kDNSServiceProtocol_IPv4,
+               subtest->hostname, _FallbackTestGetAddrInfoCallback, inTest );
+       require_noerr( err, exit );
+       
+       err = DNSServiceSetDispatchQueue( inTest->gai, inTest->queue );
+       require_noerr( err, exit );
+       
+       check( !inTest->timer );
+       err = DispatchTimerOneShotCreate( dispatch_time_seconds( kFallbackTestGAITimeLimitSecs ),
+               kFallbackTestGAITimeLimitSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 10 ), inTest->queue,
+               _FallbackTestGAITimerHandler, inTest, &inTest->timer );
+       require_noerr( err, exit );
+       dispatch_resume( inTest->timer );
+       
+exit:
+       return( err );
+}
 
-            // set the initial state for the interface
-            context->startFromExpensive = gExpensiveConstrainedTest_StartFromExpensive;
-            err = systemf( NULL, "ifconfig %s %sexpensive && ifconfig %s -constrained", context->ifName, context->startFromExpensive ? "" : "-", context->ifName );
-            require_noerr_action( err, test_failed, errorDescription = "systemf failed");
-            sleep( 5 ); // wait for 5s to allow the interface change event de delivered to others
+//===========================================================================================================================
 
-            // get question flag
-            if ( gExpensiveConstrainedTest_DenyExpensive )    context->flags     |= kDNSServiceFlagsDenyExpensive;
-            if ( gExpensiveConstrainedTest_DenyConstrained )  context->flags     |= kDNSServiceFlagsDenyConstrained;
-            if ( gExpensiveConstrainedTest_ProtocolIPv4 )     context->protocols |= kDNSServiceProtocol_IPv4;
-            if ( gExpensiveConstrainedTest_ProtocolIPv6 )     context->protocols |= kDNSServiceProtocol_IPv6;
+static void    _FallbackTestForgetSources( FallbackTest *inTest )
+{
+       DNSServiceForget( &inTest->gai );
+       dispatch_source_forget( &inTest->timer );
+}
 
-            // prevent mDNSResponder from doing extra path evaluation and changing the interface to others(such as Bluetooth)
-            #if( TARGET_OS_WATCH )
-            context->flags |= kDNSServiceFlagsPathEvaluationDone;
-            #endif
+//===========================================================================================================================
+//    ExpensiveConstrainedsTestCmd
+//===========================================================================================================================
 
-            // start the query
-            DNSServiceGetAddrInfo( &context->opRef, context->flags, context->ifIndex, context->protocols, context->name, ExpensiveConstrainedCallback, context );
+#define NOTIFICATION_TIME_THRESHOLD 1500    // The maximum wating time allowed before notification happens
+#define TEST_REPETITION             2       // the number of repetition that one test has to passed
+#define LOOPBACK_INTERFACE_NAME     "lo0"
+#define WIFI_TEST_QUESTION_NAME     "www.example.com"
+#define EXPENSIVE_CONSTRAINED_MAX_RETRIES 1
+#define EXPENSIVE_CONSTRAINED_TEST_INTERVAL 5
+// Use "-n tag-expensive-test.ttl-86400.d.test." to run the test locally
+// #define LOOPBACK_TEST_QUESTION_NAME "tag-expensive-test.ttl-86400.d.test."
 
-            // set the initial test status
-            context->subtestReport_startTime    = NanoTimeGetCurrent();
-            context->subtestProgress_startTime  = NanoTimeGetCurrent();
-            context->state                      = TEST_EXPENSIVE_PREPARE; // start from expensive test
-            context->isExpensiveNow             = context->startFromExpensive ? true : false;
-            context->isConstrainedNow           = false;
-            context->expectedOperation          = context->isExpensiveNow && ( context->flags & kDNSServiceFlagsDenyExpensive ) ? NO_UPDATE : RESULT_ADD;
-            context->operation                  = NO_UPDATE;
-            context->subtestProgress            = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks);
-            require_action( context->subtestProgress != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" );
-            context->subtestProgress_callBack   = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks);
-            require_action( context->subtestProgress != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" );
+#define EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_START_TIME                CFSTR( "Start Time" )
+#define EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_END_TIME                  CFSTR( "End Time" )
+#define EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_ALL_PASSED                CFSTR( "All Tests Passed" )
+#define EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_SUBTEST_RESULT            CFSTR( "Subtest Results" )
 
-            // set the queue where the callback will be called when there is an answer for the query
-            err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() );
-            require_noerr( err, test_failed );
+#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_START_TIME             CFSTR( "Start Time" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_END_TIME               CFSTR( "End Time" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_QNAME                  CFSTR( "Question Name" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_FLAGS                  CFSTR( "DNS Service Flags" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_PROTOCOLS              CFSTR( "Protocols" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_INDEX        CFSTR( "Interface Index" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_NAME         CFSTR( "Interface Name" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_RESULT                 CFSTR( "Result" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_ERROR                  CFSTR( "Error Description" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_TEST_PROGRESS          CFSTR( "Test Progress" )
 
-            ExpensiveConstrainedStartTestHandler( context );
-        }
-            break;
-        case TEST_EXPENSIVE_PREPARE:
-            require_action( context->isConstrainedNow == false, test_failed,
-                            SNPrintF( buffer, sizeof( buffer ), "Interface %s should be unconstrained.\n", context->ifName );
-                            errorDescription = buffer );
-            require_action( context->expectedOperation == context->operation, test_failed,
-                            errorDescription = "Operation is not expected" );
+#define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_START_TIME           CFSTR( "Start Time" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_END_TIME             CFSTR( "End Time" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_STATE                CFSTR( "State" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_EXPECT_RESULT        CFSTR( "Expected Result" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_ACTUAL_RESULT        CFSTR( "Actual Result" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_EXPENSIVE_PREV_NOW   CFSTR( "Expensive Prev->Now" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_CONSTRAINED_PREV_NOW CFSTR( "Constrained Prev->Now" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_CALL_BACK            CFSTR( "Call Back" )
 
-            context->subtestProgress_startTime  = NanoTimeGetCurrent();
-            context->state                      = TEST_EXPENSIVE;   // begin to test expensive flag
-            context->counter                    = 0;                // the number of test repetition that has passed
-            context->isExpensivePrev            = context->isExpensiveNow;
-            context->isExpensiveNow             = !context->isExpensiveNow; // flip the expensive status
-            context->isConstrainedPrev          = false;            // the interface is currently unconstrained
-            context->isConstrainedNow           = false;            // the interface will be unconstrained in the current test
-            if ( gExpensiveConstrainedTest_DenyExpensive )
-                context->expectedOperation      = context->isExpensiveNow ? RESULT_RMV : RESULT_ADD;
-            else
-                context->expectedOperation      = NO_UPDATE;
-            context->operation                  = NO_UPDATE;        // NO_UPDATE means the call back function has not been called
-            context->subtestProgress_callBack   = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
-            require_action( context->subtestProgress_callBack != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" );
+#define EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_TIMESTAMP       CFSTR( "Timestamp" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_NAME            CFSTR( "Answer Name" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_FLAGS           CFSTR( "Add or Remove" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_INTERFACE       CFSTR( "Interface Index" )
+#define EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_ADDRESS         CFSTR( "Address" )
 
-            err = systemf( NULL, "ifconfig %s %sexpensive", context->ifName, context->isExpensiveNow ? "" : "-" );
-            require_noerr_action( err, test_failed, errorDescription = "systemf failed" );
+// All the states that ends with _PREPARE represents the state where the test state is reset and initialized.
+enum ExpensiveConstrainedTestState
+{
+    TEST_BEGIN,
+    TEST_EXPENSIVE_PREPARE,
+    TEST_EXPENSIVE,                     // Test if mDNSResponder can handle "expensive" status change of the corresponding interface
+    TEST_CONSTRAINED_PREPARE,
+    TEST_CONSTRAINED,                   // Test if mDNSResponder can handle "constrained" status change of the corresponding interface
+    TEST_EXPENSIVE_CONSTRAINED_PREPARE,
+    TEST_EXPENSIVE_CONSTRAINED,          // Test if mDNSResponder can handle "expensive" and "constrained" status change of the corresponding interface at the same time
+    TEST_FAILED,
+    TEST_SUCCEEDED
+};
+enum ExpensiveConstrainedTestOperation
+{
+    RESULT_ADD, // received response for the given query, which means mDNSResponder is able to send out the query over the interface, because the interface status is changed.
+    RESULT_RMV, // received negative response for the given query, which means mDNSResponder is not able to send out the query over the interface, because the interface status is changed.
+    NO_UPDATE   // no status update notification
+};
 
-            // record the starting timestamp
-            gettimeofday( &context->updateTime, NULL );
+typedef struct
+{
+    uint32_t                    subtestIndex;           // The index of parameter for the subtest
+    DNSServiceRef               opRef;                  // sdRef for the DNSServiceGetAddrInfo operation.
+    const char *                name;                   // Hostname to resolve.
+    DNSServiceFlags             flags;                  // Flags argument for DNSServiceGetAddrInfo().
+    DNSServiceProtocol          protocols;              // Protocols argument for DNSServiceGetAddrInfo().
+    uint32_t                    ifIndex;                // Interface index argument for DNSServiceGetAddrInfo().
+    char                        ifName[IFNAMSIZ];       // Interface name for the given interface index.
+    dispatch_source_t           timer;                  // The test will check if the current behavior is valid, which is called by
+                                                        // the timer per 2s.
+    pid_t                       serverPID;
+    Boolean                     isExpensivePrev;        // If the interface is expensive in the previous test step.
+    Boolean                     isExpensiveNow;         // If the interface is expensive now.
+    Boolean                     isConstrainedPrev;      // If the interface is constrained in the previous test step.
+    Boolean                     isConstrainedNow;       // If the interface is constrained now.
+    Boolean                     startFromExpensive;     // All the test will start from expensive/constrained interface, so there won's be an answer until the interface is changed.
+    uint8_t                     numOfRetries;           // the number of retries we can have if the test fail
+    struct timeval              updateTime;             // The time when interface status(expensive or constrained) is changed.
+    struct timeval              notificationTime;       // The time when callback function, which is passed to DNSServiceGetAddrInfo, gets called.
+    uint32_t                    counter;                // To record how many times the test has repeated.
+    enum ExpensiveConstrainedTestState          state;              // The current test state.
+    enum ExpensiveConstrainedTestOperation      expectedOperation;  // the test expects this kind of notification
+    enum ExpensiveConstrainedTestOperation      operation;          // represents what notification the callback function gets.
 
-            break;
-        case TEST_EXPENSIVE:
-            // Since we are testing expensive flag, we should always turn the expensive flag on and off.
-            require_action( context->isExpensivePrev ^ context->isExpensiveNow, test_failed,
-                            SNPrintF( buffer, sizeof( buffer ), "The current expensive status should be different with the previous one: %d -> %d\n", context->isExpensivePrev, context->isExpensiveNow);
-                            errorDescription = buffer );
-            // constrained flag is always turned off when testing expensive
-            require_action( context->isConstrainedNow == false, test_failed,
-                            SNPrintF( buffer, sizeof( buffer ), "The interface %s should be unconstrained when testing \"expensive\"\n", context->ifName );
-                            errorDescription = buffer );
-            require_action( context->expectedOperation == context->operation, test_failed, errorDescription = "Operation is not expected" );
+    NanoTime64                  testReport_startTime;       // when the entire test starts
+    CFMutableArrayRef           subtestReport;              // stores the log message for every subtest
+    NanoTime64                  subtestReport_startTime;    // when the subtest starts
+    CFMutableArrayRef           subtestProgress;            // one test iteration
+    NanoTime64                  subtestProgress_startTime;  // when the test iteration starts
+    CFMutableArrayRef           subtestProgress_callBack;   // array of ADD/REMOVE events
+    char *                      outputFilePath;             // File to write test results to. If NULL, then write to stdout. (malloced)
+    OutputFormatType            outputFormat;               // Format of test report output.
+} ExpensiveConstrainedContext;
 
-            context->counter++; // one test repetition has passed
-            if ( context->counter == TEST_REPETITION ) // expensive test finished
-            {
-                // prepare to test constrained flag
-                context->state = TEST_CONSTRAINED_PREPARE;
+// structure that controls how the subtest is run
+typedef struct
+{
+    const char  *qname;                 // the name of the query, when the ends with ".d.test.", test will send query to local DNS server
+    Boolean     deny_expensive;         // if the query should avoid using expensive interface
+    Boolean     deny_constrained;       // if the query should avoid using constrained interface
+    Boolean     start_from_expensive;   // if the query should starts from using an expensive interface
+    Boolean     ipv4_query;             // only allow IPv4 query
+    Boolean     ipv6_query;             // only allow IPv6 query
+    int8_t      test_passed;            // if the subtest passes
+} ExpensiveConstrainedTestParams;
 
-                // reset the interface
-                err = systemf( NULL, "ifconfig %s -expensive && ifconfig %s -constrained", context->ifName, context->ifName );
-                require_noerr_action( err, test_failed, errorDescription = "systemf failed" );
+static ExpensiveConstrainedTestParams   ExpensiveConstrainedSubtestParams[] =
+{
+//  qname                                                   deny_expensive  deny_constrained    start_from_expensive    ipv4_query  ipv6_query
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           false,              false,                  true,       true,       -1},
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           false,              true,                   true,       true,       -1},
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    false,          true,               false,                  true,       true,       -1},
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    false,          true,               true,                   true,       true,       -1},
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           true,               false,                  true,       true,       -1},
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           true,               true,                   true,       true,       -1},
+// IPv4 Only
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           false,              false,                  true,       false,      -1},
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           false,              true,                   true,       false,      -1},
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    false,          true,               false,                  true,       false,      -1},
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    false,          true,               true,                   true,       false,      -1},
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           true,               false,                  true,       false,      -1},
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           true,               true,                   true,       false,      -1},
+// IPv6 Only
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           false,              false,                  false,      true,       -1},
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           false,              true,                   false,      true,       -1},
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    false,          true,               false,                  false,      true,       -1},
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    false,          true,               true,                   false,      true,       -1},
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           true,               false,                  false,      true,       -1},
+    {"tag-expensive_constrained-test.ttl-86400.d.test.",    true,           true,               true,                   false,      true,       -1}
+};
 
-                context->isExpensiveNow = false;
-                context->isConstrainedNow = false;
-                gettimeofday( &context->updateTime, NULL );
-            }
-            else
-            {
-                context->subtestProgress_startTime  = NanoTimeGetCurrent();
-                context->isExpensivePrev            = context->isExpensiveNow;
-                context->isExpensiveNow             = !context->isExpensiveNow; // flip the expensive status
-                if ( gExpensiveConstrainedTest_DenyExpensive )
-                    context->expectedOperation      = context->isExpensiveNow ? RESULT_RMV : RESULT_ADD;
-                else
-                    context->expectedOperation      = NO_UPDATE;
-                context->operation                  = NO_UPDATE;
-                context->subtestProgress_callBack   = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
-                require_action( context->subtestProgress_callBack != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" );
+static void ExpensiveConstrainedSetupLocalDNSServer( ExpensiveConstrainedContext *context );
+static void ExpensiveConstrainedStartTestHandler( ExpensiveConstrainedContext *context );
+static void ExpensiveConstrainedStopTestHandler( ExpensiveConstrainedContext *context );
+static void ExpensiveConstrainedSetupTimer( ExpensiveConstrainedContext *context, uint32_t second );
+static void ExpensiveConstrainedTestTimerEventHandler( ExpensiveConstrainedContext *context );
+static void DNSSD_API
+    ExpensiveConstrainedCallback(
+        DNSServiceRef           inSDRef,
+        DNSServiceFlags         inFlags,
+        uint32_t                inInterfaceIndex,
+        DNSServiceErrorType     inError,
+        const char *            inHostname,
+        const struct sockaddr * inSockAddr,
+        uint32_t                inTTL,
+        void *                  inContext );
+static void ExpensiveConstrainedInitializeContext( ExpensiveConstrainedContext *context );
+static void ExpensiveConstrainedStopAndCleanTheTest( ExpensiveConstrainedContext *context );
+static void ExpensiveConstrainedSubtestProgressReport( ExpensiveConstrainedContext *context );
+static void ExpensiveConstrainedSubtestReport( ExpensiveConstrainedContext *context, const char *error_description );
+static void ExpensiveConstrainedFinalResultReport( ExpensiveConstrainedContext *context, Boolean allPassed );
+static const char *ExpensiveConstrainedProtocolString(DNSServiceProtocol protocol);
+static const char *ExpensiveConstrainedStateString(enum ExpensiveConstrainedTestState state);
+static const char *ExpensiveConstrainedOperationString(enum ExpensiveConstrainedTestOperation operation);
+static Boolean expensiveConstrainedEndsWith( const char *str, const char *suffix );
 
-                err = systemf( NULL, "ifconfig %s %sexpensive", context->ifName, context->isExpensiveNow ? "" : "-" );
-                require_noerr_action( err, test_failed, errorDescription = "systemf failed" );
+//===========================================================================================================================
+//    ExpensiveConstrainedTestCmd
+//===========================================================================================================================
 
-                gettimeofday( &context->updateTime, NULL );
-            }
-            break;
-        case TEST_CONSTRAINED_PREPARE:
-            // The interface should be inexpensive and unconstrained when the constrained test starts
-            require_action( context->isExpensiveNow == false, test_failed, SNPrintF( buffer, sizeof( buffer ), "Interface %s should be inexpensive.", context->ifName );
-                            errorDescription = buffer );
-            require_action( context->isConstrainedNow == false, test_failed, SNPrintF( buffer, sizeof( buffer ), "Interface %s should be unconstrained.\n", context->ifName );
-                            errorDescription = buffer );
+static void ExpensiveConstrainedTestCmd( void )
+{
+    OSStatus                        err;
+    dispatch_source_t               signalSource   = NULL;
+    ExpensiveConstrainedContext *   context        = NULL;
 
-            context->subtestProgress_startTime  = NanoTimeGetCurrent();
-            context->state                      = TEST_CONSTRAINED; // constrained interface is now under testing
-            context->counter                    = 0;
-            context->isExpensivePrev            = false;
-            context->isExpensiveNow             = false;
-            context->isConstrainedPrev          = false;
-            context->isConstrainedNow           = true;             // will set constrained flag on the interface
-            if ( gExpensiveConstrainedTest_DenyConstrained )
-                context->expectedOperation      = RESULT_RMV;
-            else
-                context->expectedOperation      = NO_UPDATE;
-            context->operation                  = NO_UPDATE;
-            context->subtestProgress_callBack   = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
-            require_action( context->subtestProgress_callBack != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" );
+    // Set up SIGINT handler.
+    signal( SIGINT, SIG_IGN );
+    err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), Exit, kExitReason_SIGINT, &signalSource );
+    require_noerr( err, exit );
+    dispatch_resume( signalSource );
 
-            // change interface to the constrained one
-            err = systemf( NULL, "ifconfig %s -expensive && ifconfig %s constrained", context->ifName, context->ifName );
-            require_noerr_action( err, test_failed, errorDescription = "systemf failed" );
+    // create the test context
+    context = (ExpensiveConstrainedContext *) calloc( 1, sizeof(*context) );
+    require_action( context, exit, err = kNoMemoryErr );
 
-            gettimeofday( &context->updateTime, NULL );
-            break;
-        case TEST_CONSTRAINED:
-            // Since we are testing constrained flag, we should always turn the constrained flag on and off.
-            require_action( context->isConstrainedPrev ^ context->isConstrainedNow, test_failed,
-                            SNPrintF( buffer, sizeof( buffer ), "The current constrained status should be different with the previous one: %d -> %d\n", context->isConstrainedPrev, context->isConstrainedNow );
-                            errorDescription = buffer );
-            require_action( context->isExpensiveNow == false, test_failed,
-                            SNPrintF( buffer, sizeof( buffer ), "The interface %s should be inexpensive when testing \"constrained\"\n", context->ifName );
-                            errorDescription = buffer );
-            require_action( context->expectedOperation == context->operation, test_failed, errorDescription = "Operation is not expected");
+    // get the command line option
+    err = OutputFormatFromArgString( gExpensiveConstrainedTest_OutputFormat, &context->outputFormat );
+    require_noerr_quiet( err, exit );
+    if ( gExpensiveConstrainedTest_OutputFilePath )
+    {
+        context->outputFilePath = strdup( gExpensiveConstrainedTest_OutputFilePath );
+        require_noerr_quiet( context->outputFilePath, exit );
+    }
 
-            context->counter++;
-            if (context->counter == TEST_REPETITION)
-            {
-                // test changing expensive and constrained flags at the same time
-                context->state = TEST_EXPENSIVE_CONSTRAINED_PREPARE;
+    // initialize context
+    context->subtestIndex = 0;
+    context->numOfRetries = EXPENSIVE_CONSTRAINED_MAX_RETRIES;
 
-                // reset interface
-                err = systemf( NULL, "ifconfig %s -expensive && ifconfig %s -constrained", context->ifName, context->ifName );
-                require_noerr_action( err, test_failed, errorDescription = "systemf failed" );
+    // initialize the CFArray used to store the log
+    context->subtestReport = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+    context->testReport_startTime = NanoTimeGetCurrent();
 
-                context->isExpensiveNow = false;
-                context->isConstrainedNow = false;
-                gettimeofday( &context->updateTime, NULL );
-            }
-            else
-            {
-                context->subtestProgress_startTime  = NanoTimeGetCurrent();
-                context->isConstrainedPrev          = context->isConstrainedNow;
-                context->isConstrainedNow           = !context->isConstrainedNow; // flip constrained flag
-                if ( gExpensiveConstrainedTest_DenyConstrained )
-                    context->expectedOperation      = context->isConstrainedNow ? RESULT_RMV : RESULT_ADD;
+    // setup local DNS server
+    ExpensiveConstrainedSetupLocalDNSServer( context );
+
+    ExpensiveConstrainedStartTestHandler( context );
+
+    dispatch_main();
+
+exit:
+    exit( 1 );
+}
+
+//===========================================================================================================================
+//    ExpensiveConstrainedSetupLocalDNSServer
+//===========================================================================================================================
+
+static void ExpensiveConstrainedSetupLocalDNSServer( ExpensiveConstrainedContext *context )
+{
+    pid_t current_pid = getpid();
+    OSStatus err = _SpawnCommand( &context->serverPID, NULL, NULL, "dnssdutil server -l --port 0 --follow %d", current_pid );
+    if (err != 0)
+    {
+        FPrintF( stdout, "dnssdutil server -l --port 0 --follow <PID> failed, error: %d\n", err );
+        exit( 1 );
+    }
+    sleep(2);
+}
+
+//===========================================================================================================================
+//    ExpensiveConstrainedStartTestHandler
+//===========================================================================================================================
+
+static void ExpensiveConstrainedStartTestHandler( ExpensiveConstrainedContext *context )
+{
+    // setup 3s timer
+    ExpensiveConstrainedSetupTimer( context, EXPENSIVE_CONSTRAINED_TEST_INTERVAL );
+
+    // set the event handler for the 3s timer
+    dispatch_source_set_event_handler( context->timer, ^{
+        ExpensiveConstrainedTestTimerEventHandler( context );
+    } );
+
+    dispatch_resume( context->timer );
+}
+
+//===========================================================================================================================
+//    ExpensiveConstrainedStartTestHandler
+//===========================================================================================================================
+
+static void ExpensiveConstrainedStopTestHandler( ExpensiveConstrainedContext *context )
+{
+    dispatch_cancel( context->timer );
+    dispatch_release( context->timer );
+    context->timer = NULL;
+}
+
+//===========================================================================================================================
+//    ExpensiveConstrainedSetupTimer
+//===========================================================================================================================
+
+static void ExpensiveConstrainedSetupTimer( ExpensiveConstrainedContext *context, uint32_t second )
+{
+    // set the timer source, the event handler will be called for every "second" seconds
+    context->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue() );
+    if ( context->timer == NULL )
+    {
+        FPrintF( stdout, "dispatch_source_create:DISPATCH_SOURCE_TYPE_TIMER failed\n" );
+        exit( 1 );
+    }
+    // the first block will be put into the queue "second"s after calling dispatch_resume
+    dispatch_source_set_timer( context->timer, dispatch_time( DISPATCH_TIME_NOW, second * NSEC_PER_SEC ),
+                               (unsigned long long)(second) * NSEC_PER_SEC, 100ull * NSEC_PER_MSEC );
+}
+
+//===========================================================================================================================
+//    ExpensiveConstrainedTestTimerEventHandler
+//===========================================================================================================================
+
+static void ExpensiveConstrainedTestTimerEventHandler( ExpensiveConstrainedContext *context )
+{
+    OSStatus    err;
+    char        buffer[ 1024 ];
+    const char *errorDescription = NULL;
+
+    // do not log the state if we are in transition state
+    if (context->state != TEST_BEGIN
+        && context->state != TEST_SUCCEEDED
+        && context->state != TEST_CONSTRAINED_PREPARE
+        && context->state != TEST_EXPENSIVE_CONSTRAINED_PREPARE)
+        ExpensiveConstrainedSubtestProgressReport( context );
+
+    switch ( context->state ) {
+        case TEST_BEGIN:
+        {
+            ExpensiveConstrainedStopTestHandler( context );
+
+            // clear mDNSResponder cache
+            err = systemf( NULL, "killall -HUP mDNSResponder" );
+            require_noerr_action( err, test_failed, errorDescription = "systemf failed");
+
+            // initialize the global parameters
+            ExpensiveConstrainedInitializeContext( context );
+
+            // The local DNS server is set up on the local only interface.
+            gExpensiveConstrainedTest_Interface = LOOPBACK_INTERFACE_NAME;
+            strncpy( context->ifName, gExpensiveConstrainedTest_Interface, sizeof( context->ifName ) );
+
+            // The local DNS server is unscoped, so we must set our question to unscoped.
+            context->ifIndex = kDNSServiceInterfaceIndexAny;
+
+            // The question name must end with "d.test.", "tag-expensive-test.ttl-86400.d.test." for example, then the test will
+            // use the local dns server set up previously to run the test locally.
+            require_action( gExpensiveConstrainedTest_Name != NULL && expensiveConstrainedEndsWith( gExpensiveConstrainedTest_Name, "d.test." ), test_failed,
+                           SNPrintF( buffer, sizeof( buffer ), "The question name (%s) must end with \"d.test.\".\n", gExpensiveConstrainedTest_Name );
+                           errorDescription = buffer );
+
+            // get the quesion name
+            context->name = gExpensiveConstrainedTest_Name;
+
+            // set the initial state for the interface
+            context->startFromExpensive = gExpensiveConstrainedTest_StartFromExpensive;
+            err = systemf( NULL, "ifconfig %s %sexpensive && ifconfig %s -constrained", context->ifName, context->startFromExpensive ? "" : "-", context->ifName );
+            require_noerr_action( err, test_failed, errorDescription = "systemf failed");
+            sleep( 5 ); // wait for 5s to allow the interface change event de delivered to others
+
+            // get question flag
+            if ( gExpensiveConstrainedTest_DenyExpensive )    context->flags     |= kDNSServiceFlagsDenyExpensive;
+            if ( gExpensiveConstrainedTest_DenyConstrained )  context->flags     |= kDNSServiceFlagsDenyConstrained;
+            if ( gExpensiveConstrainedTest_ProtocolIPv4 )     context->protocols |= kDNSServiceProtocol_IPv4;
+            if ( gExpensiveConstrainedTest_ProtocolIPv6 )     context->protocols |= kDNSServiceProtocol_IPv6;
+
+            // prevent mDNSResponder from doing extra path evaluation and changing the interface to others(such as Bluetooth)
+            #if( TARGET_OS_WATCH )
+            context->flags |= kDNSServiceFlagsPathEvaluationDone;
+            #endif
+
+            // start the query
+            DNSServiceGetAddrInfo( &context->opRef, context->flags, context->ifIndex, context->protocols, context->name, ExpensiveConstrainedCallback, context );
+
+            // set the initial test status
+            context->subtestReport_startTime    = NanoTimeGetCurrent();
+            context->subtestProgress_startTime  = NanoTimeGetCurrent();
+            context->state                      = TEST_EXPENSIVE_PREPARE; // start from expensive test
+            context->isExpensiveNow             = context->startFromExpensive ? true : false;
+            context->isConstrainedNow           = false;
+            context->expectedOperation          = context->isExpensiveNow && ( context->flags & kDNSServiceFlagsDenyExpensive ) ? NO_UPDATE : RESULT_ADD;
+            context->operation                  = NO_UPDATE;
+            context->subtestProgress            = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks);
+            require_action( context->subtestProgress != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" );
+            context->subtestProgress_callBack   = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks);
+            require_action( context->subtestProgress != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" );
+
+            // set the queue where the callback will be called when there is an answer for the query
+            err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() );
+            require_noerr( err, test_failed );
+
+            ExpensiveConstrainedStartTestHandler( context );
+        }
+            break;
+        case TEST_EXPENSIVE_PREPARE:
+            require_action( context->isConstrainedNow == false, test_failed,
+                            SNPrintF( buffer, sizeof( buffer ), "Interface %s should be unconstrained.\n", context->ifName );
+                            errorDescription = buffer );
+            require_action( context->expectedOperation == context->operation, test_failed,
+                            errorDescription = "Operation is not expected" );
+
+            context->subtestProgress_startTime  = NanoTimeGetCurrent();
+            context->state                      = TEST_EXPENSIVE;   // begin to test expensive flag
+            context->counter                    = 0;                // the number of test repetition that has passed
+            context->isExpensivePrev            = context->isExpensiveNow;
+            context->isExpensiveNow             = !context->isExpensiveNow; // flip the expensive status
+            context->isConstrainedPrev          = false;            // the interface is currently unconstrained
+            context->isConstrainedNow           = false;            // the interface will be unconstrained in the current test
+            if ( gExpensiveConstrainedTest_DenyExpensive )
+                context->expectedOperation      = context->isExpensiveNow ? RESULT_RMV : RESULT_ADD;
+            else
+                context->expectedOperation      = NO_UPDATE;
+            context->operation                  = NO_UPDATE;        // NO_UPDATE means the call back function has not been called
+            context->subtestProgress_callBack   = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+            require_action( context->subtestProgress_callBack != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" );
+
+            err = systemf( NULL, "ifconfig %s %sexpensive", context->ifName, context->isExpensiveNow ? "" : "-" );
+            require_noerr_action( err, test_failed, errorDescription = "systemf failed" );
+
+            // record the starting timestamp
+            gettimeofday( &context->updateTime, NULL );
+
+            break;
+        case TEST_EXPENSIVE:
+            // Since we are testing expensive flag, we should always turn the expensive flag on and off.
+            require_action( context->isExpensivePrev ^ context->isExpensiveNow, test_failed,
+                            SNPrintF( buffer, sizeof( buffer ), "The current expensive status should be different with the previous one: %d -> %d\n", context->isExpensivePrev, context->isExpensiveNow);
+                            errorDescription = buffer );
+            // constrained flag is always turned off when testing expensive
+            require_action( context->isConstrainedNow == false, test_failed,
+                            SNPrintF( buffer, sizeof( buffer ), "The interface %s should be unconstrained when testing \"expensive\"\n", context->ifName );
+                            errorDescription = buffer );
+            require_action( context->expectedOperation == context->operation, test_failed, errorDescription = "Operation is not expected" );
+
+            context->counter++; // one test repetition has passed
+            if ( context->counter == TEST_REPETITION ) // expensive test finished
+            {
+                // prepare to test constrained flag
+                context->state = TEST_CONSTRAINED_PREPARE;
+
+                // reset the interface
+                err = systemf( NULL, "ifconfig %s -expensive && ifconfig %s -constrained", context->ifName, context->ifName );
+                require_noerr_action( err, test_failed, errorDescription = "systemf failed" );
+
+                context->isExpensiveNow = false;
+                context->isConstrainedNow = false;
+                gettimeofday( &context->updateTime, NULL );
+            }
+            else
+            {
+                context->subtestProgress_startTime  = NanoTimeGetCurrent();
+                context->isExpensivePrev            = context->isExpensiveNow;
+                context->isExpensiveNow             = !context->isExpensiveNow; // flip the expensive status
+                if ( gExpensiveConstrainedTest_DenyExpensive )
+                    context->expectedOperation      = context->isExpensiveNow ? RESULT_RMV : RESULT_ADD;
                 else
                     context->expectedOperation      = NO_UPDATE;
                 context->operation                  = NO_UPDATE;
                 context->subtestProgress_callBack   = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
                 require_action( context->subtestProgress_callBack != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" );
 
-                err = systemf( NULL, "ifconfig %s %sconstrained", context->ifName, context->isConstrainedNow ? "" : "-" );
+                err = systemf( NULL, "ifconfig %s %sexpensive", context->ifName, context->isExpensiveNow ? "" : "-" );
                 require_noerr_action( err, test_failed, errorDescription = "systemf failed" );
 
-                gettimeofday(&context->updateTime, NULL);
+                gettimeofday( &context->updateTime, NULL );
             }
             break;
-        case TEST_EXPENSIVE_CONSTRAINED_PREPARE:
+        case TEST_CONSTRAINED_PREPARE:
             // The interface should be inexpensive and unconstrained when the constrained test starts
-            require_action( context->isExpensiveNow == false,   test_failed,
-                            SNPrintF( buffer, sizeof( buffer ), "Interface %s should be inexpensive.\n", context->ifName );
+            require_action( context->isExpensiveNow == false, test_failed, SNPrintF( buffer, sizeof( buffer ), "Interface %s should be inexpensive.", context->ifName );
                             errorDescription = buffer );
-            require_action( context->isConstrainedNow == false, test_failed,
-                            SNPrintF(buffer, sizeof( buffer ), "Interface %s should be unconstrained.\n", context->ifName );
+            require_action( context->isConstrainedNow == false, test_failed, SNPrintF( buffer, sizeof( buffer ), "Interface %s should be unconstrained.\n", context->ifName );
                             errorDescription = buffer );
 
-            // now flip expensive and constrained at the same time
             context->subtestProgress_startTime  = NanoTimeGetCurrent();
-            context->state                      = TEST_EXPENSIVE_CONSTRAINED;
+            context->state                      = TEST_CONSTRAINED; // constrained interface is now under testing
             context->counter                    = 0;
             context->isExpensivePrev            = false;
-            context->isExpensiveNow             = true;
+            context->isExpensiveNow             = false;
             context->isConstrainedPrev          = false;
-            context->isConstrainedNow           = true;
-            if (gExpensiveConstrainedTest_DenyConstrained || gExpensiveConstrainedTest_DenyExpensive)
+            context->isConstrainedNow           = true;             // will set constrained flag on the interface
+            if ( gExpensiveConstrainedTest_DenyConstrained )
                 context->expectedOperation      = RESULT_RMV;
             else
                 context->expectedOperation      = NO_UPDATE;
@@ -15139,480 +17939,2965 @@ static void ExpensiveConstrainedTestTimerEventHandler( ExpensiveConstrainedConte
             context->subtestProgress_callBack   = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
             require_action( context->subtestProgress_callBack != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" );
 
-            err = systemf(NULL, "ifconfig %s expensive && ifconfig %s constrained", context->ifName, context->ifName );
+            // change interface to the constrained one
+            err = systemf( NULL, "ifconfig %s -expensive && ifconfig %s constrained", context->ifName, context->ifName );
             require_noerr_action( err, test_failed, errorDescription = "systemf failed" );
 
             gettimeofday( &context->updateTime, NULL );
             break;
-        case TEST_EXPENSIVE_CONSTRAINED:
-            // expensive and constrained flag should always be changed
-            require_action( ( context->isExpensivePrev ^ context->isExpensiveNow ) && ( context->isConstrainedPrev ^ context->isConstrainedNow ), test_failed,
-                            SNPrintF( buffer, sizeof( buffer ), "Both expensive and constrained status need to be changed" );
+        case TEST_CONSTRAINED:
+            // Since we are testing constrained flag, we should always turn the constrained flag on and off.
+            require_action( context->isConstrainedPrev ^ context->isConstrainedNow, test_failed,
+                            SNPrintF( buffer, sizeof( buffer ), "The current constrained status should be different with the previous one: %d -> %d\n", context->isConstrainedPrev, context->isConstrainedNow );
                             errorDescription = buffer );
-            require_action( context->isExpensiveNow == context->isConstrainedNow, test_failed, errorDescription = "context->isExpensiveNow != context->isConstrainedNow" );
-            require_action( context->expectedOperation == context->operation, test_failed, errorDescription = "Operation is not expected" );
+            require_action( context->isExpensiveNow == false, test_failed,
+                            SNPrintF( buffer, sizeof( buffer ), "The interface %s should be inexpensive when testing \"constrained\"\n", context->ifName );
+                            errorDescription = buffer );
+            require_action( context->expectedOperation == context->operation, test_failed, errorDescription = "Operation is not expected");
+
+            context->counter++;
+            if (context->counter == TEST_REPETITION)
+            {
+                // test changing expensive and constrained flags at the same time
+                context->state = TEST_EXPENSIVE_CONSTRAINED_PREPARE;
+
+                // reset interface
+                err = systemf( NULL, "ifconfig %s -expensive && ifconfig %s -constrained", context->ifName, context->ifName );
+                require_noerr_action( err, test_failed, errorDescription = "systemf failed" );
+
+                context->isExpensiveNow = false;
+                context->isConstrainedNow = false;
+                gettimeofday( &context->updateTime, NULL );
+            }
+            else
+            {
+                context->subtestProgress_startTime  = NanoTimeGetCurrent();
+                context->isConstrainedPrev          = context->isConstrainedNow;
+                context->isConstrainedNow           = !context->isConstrainedNow; // flip constrained flag
+                if ( gExpensiveConstrainedTest_DenyConstrained )
+                    context->expectedOperation      = context->isConstrainedNow ? RESULT_RMV : RESULT_ADD;
+                else
+                    context->expectedOperation      = NO_UPDATE;
+                context->operation                  = NO_UPDATE;
+                context->subtestProgress_callBack   = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+                require_action( context->subtestProgress_callBack != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" );
+
+                err = systemf( NULL, "ifconfig %s %sconstrained", context->ifName, context->isConstrainedNow ? "" : "-" );
+                require_noerr_action( err, test_failed, errorDescription = "systemf failed" );
+
+                gettimeofday(&context->updateTime, NULL);
+            }
+            break;
+        case TEST_EXPENSIVE_CONSTRAINED_PREPARE:
+            // The interface should be inexpensive and unconstrained when the constrained test starts
+            require_action( context->isExpensiveNow == false,   test_failed,
+                            SNPrintF( buffer, sizeof( buffer ), "Interface %s should be inexpensive.\n", context->ifName );
+                            errorDescription = buffer );
+            require_action( context->isConstrainedNow == false, test_failed,
+                            SNPrintF(buffer, sizeof( buffer ), "Interface %s should be unconstrained.\n", context->ifName );
+                            errorDescription = buffer );
+
+            // now flip expensive and constrained at the same time
+            context->subtestProgress_startTime  = NanoTimeGetCurrent();
+            context->state                      = TEST_EXPENSIVE_CONSTRAINED;
+            context->counter                    = 0;
+            context->isExpensivePrev            = false;
+            context->isExpensiveNow             = true;
+            context->isConstrainedPrev          = false;
+            context->isConstrainedNow           = true;
+            if (gExpensiveConstrainedTest_DenyConstrained || gExpensiveConstrainedTest_DenyExpensive)
+                context->expectedOperation      = RESULT_RMV;
+            else
+                context->expectedOperation      = NO_UPDATE;
+            context->operation                  = NO_UPDATE;
+            context->subtestProgress_callBack   = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+            require_action( context->subtestProgress_callBack != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" );
+
+            err = systemf(NULL, "ifconfig %s expensive && ifconfig %s constrained", context->ifName, context->ifName );
+            require_noerr_action( err, test_failed, errorDescription = "systemf failed" );
+
+            gettimeofday( &context->updateTime, NULL );
+            break;
+        case TEST_EXPENSIVE_CONSTRAINED:
+            // expensive and constrained flag should always be changed
+            require_action( ( context->isExpensivePrev ^ context->isExpensiveNow ) && ( context->isConstrainedPrev ^ context->isConstrainedNow ), test_failed,
+                            SNPrintF( buffer, sizeof( buffer ), "Both expensive and constrained status need to be changed" );
+                            errorDescription = buffer );
+            require_action( context->isExpensiveNow == context->isConstrainedNow, test_failed, errorDescription = "context->isExpensiveNow != context->isConstrainedNow" );
+            require_action( context->expectedOperation == context->operation, test_failed, errorDescription = "Operation is not expected" );
+
+            context->counter++;
+            if ( context->counter == TEST_REPETITION )
+            {
+                context->state = TEST_SUCCEEDED;
+            }
+            else
+            {
+                context->subtestProgress_startTime  = NanoTimeGetCurrent();
+                context->isExpensivePrev            = context->isExpensiveNow;
+                context->isExpensiveNow             = !context->isExpensiveNow;
+                context->isConstrainedPrev          = context->isConstrainedNow;
+                context->isConstrainedNow           = !context->isConstrainedNow;
+                if (gExpensiveConstrainedTest_DenyConstrained || gExpensiveConstrainedTest_DenyExpensive)
+                    context->expectedOperation      = context->isExpensiveNow ? RESULT_RMV : RESULT_ADD;
+                else
+                    context->expectedOperation      = NO_UPDATE;
+                context->operation                  = NO_UPDATE;
+                context->subtestProgress_callBack   = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+                require_action( context->subtestProgress_callBack != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" );
+
+                err = systemf( NULL, "ifconfig %s %sexpensive && ifconfig %s %sconstrained", context->ifName, context->isExpensiveNow ? "" : "-", context->ifName, context->isConstrainedNow ? "" : "-" );
+                require_noerr_action( err, test_failed, errorDescription = "systemf failed" );
+
+                gettimeofday( &context->updateTime, NULL );
+            }
+            break;
+        case TEST_FAILED:
+        test_failed:
+            ExpensiveConstrainedSubtestReport( context, errorDescription );
+            ExpensiveConstrainedStopAndCleanTheTest( context );
+            if ( context->numOfRetries > 0 )
+            {
+                context->state = TEST_BEGIN;
+                context->numOfRetries--;
+                break;
+            }
+            ExpensiveConstrainedSubtestParams[context->subtestIndex++].test_passed = 0;
+            if (context->subtestIndex == (int) countof( ExpensiveConstrainedSubtestParams ))
+            {
+                ExpensiveConstrainedFinalResultReport( context, false );
+                exit( 2 );
+            }
+            if (context->timer == NULL)
+            {
+                // If timer is NULL, it means that we encounter error before we set up the test handler, which is unrecoverable.
+                ExpensiveConstrainedFinalResultReport( context, false );
+                exit( 1 );
+            }
+            context->state = TEST_BEGIN;
+            break;
+        case TEST_SUCCEEDED:
+            ExpensiveConstrainedSubtestReport( context, NULL );
+            ExpensiveConstrainedStopAndCleanTheTest( context );
+            ExpensiveConstrainedSubtestParams[context->subtestIndex++].test_passed = 1;
+            if (context->subtestIndex == (int) countof( ExpensiveConstrainedSubtestParams ))
+            {
+                // all the subtests have been run
+                Boolean hasFailed = false;
+                for ( int i = 0; i < (int) countof( ExpensiveConstrainedSubtestParams ) && !hasFailed; i++ )
+                    hasFailed = ( ExpensiveConstrainedSubtestParams[i].test_passed != 1 );
+
+                ExpensiveConstrainedFinalResultReport( context, !hasFailed );
+                exit( hasFailed ? 2 : 0 );
+            }
+            context->state = TEST_BEGIN;
+            break;
+        default:
+            FPrintF( stdout, "unknown error\n" );
+            exit( 1 );
+    }
+}
+
+//===========================================================================================================================
+//    ExpensiveConstrainedCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+    ExpensiveConstrainedCallback(
+        __unused DNSServiceRef  inSDRef,
+        DNSServiceFlags         inFlags,
+        uint32_t                inInterfaceIndex,
+        DNSServiceErrorType     inError,
+        const char *            inHostname,
+        const struct sockaddr * inSockAddr,
+        __unused uint32_t       inTTL,
+        void *                  inContext )
+{
+    ExpensiveConstrainedContext * const   context = (ExpensiveConstrainedContext *)inContext;
+    OSStatus                                    err;
+    const char *                                addrStr;
+    char                                        addrStrBuf[ kSockAddrStringMaxSize ];
+    char                                        inFlagsDescription[ 128 ];
+    NanoTime64                                  now;
+    char                                        nowTimestamp[ 32 ];
+
+    switch ( inError ) {
+        case kDNSServiceErr_NoError:
+        case kDNSServiceErr_NoSuchRecord:
+            break;
+
+        case kDNSServiceErr_Timeout:
+            Exit( kExitReason_Timeout );
+
+        default:
+            err = inError;
+            goto exit;
+    }
+
+    if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
+    {
+        dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
+        err = kTypeErr;
+        goto exit;
+    }
+
+    if( !inError )
+    {
+        err = SockAddrToString( inSockAddr, kSockAddrStringFlagsNone, addrStrBuf );
+        require_noerr( err, exit );
+        addrStr = addrStrBuf;
+    }
+    else
+    {
+        addrStr = ( inSockAddr->sa_family == AF_INET ) ? kNoSuchRecordAStr : kNoSuchRecordAAAAStr;
+    }
+
+    now = NanoTimeGetCurrent();
+    _NanoTime64ToTimestamp( now, nowTimestamp, sizeof( nowTimestamp ) );
+    SNPrintF( inFlagsDescription, sizeof( inFlagsDescription ), "%{du:cbflags}", inFlags );
+    err = CFPropertyListAppendFormatted( kCFAllocatorDefault,  context->subtestProgress_callBack,
+        "{"
+            "%kO=%s"
+            "%kO=%s"
+            "%kO=%s"
+            "%kO=%lli"
+            "%kO=%s"
+        "}",
+        EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_TIMESTAMP,  nowTimestamp,
+        EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_NAME,       inHostname,
+        EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_FLAGS,      inFlagsDescription,
+        EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_INTERFACE,  (int64_t) inInterfaceIndex,
+        EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_ADDRESS,    addrStr
+    );
+    require_noerr_quiet( err, exit );
+
+    if ( inFlags & kDNSServiceFlagsMoreComing )
+        return;
+
+    if ( inFlags & kDNSServiceFlagsAdd )
+        context->operation = RESULT_ADD;
+    else
+        context->operation = RESULT_RMV;
+
+    gettimeofday(&context->notificationTime, NULL);
+exit:
+    if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//    ExpensiveConstrainedInitializeContext
+//===========================================================================================================================
+
+static void ExpensiveConstrainedInitializeContext( ExpensiveConstrainedContext *context )
+{
+    // clear the flags of the previous subtest
+    context->flags      = 0;
+    context->protocols  = 0;
+
+    // get the parameter for the current subtest
+    const ExpensiveConstrainedTestParams *param     = &ExpensiveConstrainedSubtestParams[context->subtestIndex];
+    gExpensiveConstrainedTest_Name                  = param->qname;
+    gExpensiveConstrainedTest_DenyExpensive         = param->deny_expensive;
+    gExpensiveConstrainedTest_DenyConstrained       = param->deny_constrained;
+    gExpensiveConstrainedTest_StartFromExpensive    = param->start_from_expensive;
+    gExpensiveConstrainedTest_ProtocolIPv4          = param->ipv4_query;
+    gExpensiveConstrainedTest_ProtocolIPv6          = param->ipv6_query;
+}
+
+//===========================================================================================================================
+//    ExpensiveConstrainedStopAndCleanTheTest
+//===========================================================================================================================
+
+static void ExpensiveConstrainedStopAndCleanTheTest( ExpensiveConstrainedContext *context )
+{
+    // Stop the ongoing query
+    if ( context->opRef != NULL )
+        DNSServiceRefDeallocate( context->opRef );
+
+    context->opRef      = NULL;
+    context->flags      = 0;
+    context->protocols  = 0;
+}
+
+//===========================================================================================================================
+//    ExpensiveConstrainedSubtestProgressReport
+//===========================================================================================================================
+
+static void ExpensiveConstrainedSubtestProgressReport( ExpensiveConstrainedContext *context )
+{
+    OSStatus            err;
+    NanoTime64          now;
+    char                startTime[ 32 ];
+    char                endTime[ 32 ];
+    char                expensive[ 32 ];
+    char                constrained[ 32 ];
+
+    now = NanoTimeGetCurrent();
+    _NanoTime64ToTimestamp( context->subtestProgress_startTime, startTime, sizeof( startTime ) );
+    _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) );
+
+    snprintf( expensive, sizeof( expensive ), "%s -> %s", context->isExpensivePrev ? "True" : "False", context->isExpensiveNow ? "True" : "False" );
+    snprintf( constrained, sizeof( constrained ), "%s -> %s", context->isConstrainedPrev ? "True" : "False", context->isConstrainedNow ? "True" : "False" );
+
+    err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->subtestProgress,
+        "{"
+            "%kO=%s"
+            "%kO=%s"
+            "%kO=%s"
+            "%kO=%s"
+            "%kO=%s"
+            "%kO=%s"
+            "%kO=%s"
+            "%kO=%O"
+        "}",
+        EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_START_TIME,              startTime,
+        EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_END_TIME,                endTime,
+        EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_STATE,                   ExpensiveConstrainedStateString(context->state),
+        EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_EXPECT_RESULT,           ExpensiveConstrainedOperationString(context->expectedOperation),
+        EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_ACTUAL_RESULT,           ExpensiveConstrainedOperationString(context->operation),
+        EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_EXPENSIVE_PREV_NOW,      expensive,
+        EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_CONSTRAINED_PREV_NOW,    constrained,
+        EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_CALL_BACK,               context->subtestProgress_callBack
+    );
+    require_noerr( err, exit );
+    ForgetCF( &context->subtestProgress_callBack );
+    return;
+
+exit:
+    ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//    ExpensiveConstrainedFinalSubtestReport
+//===========================================================================================================================
+
+static void ExpensiveConstrainedSubtestReport( ExpensiveConstrainedContext *context, const char *error_description )
+{
+    OSStatus            err;
+    NanoTime64          now;
+    char                startTime[ 32 ];
+    char                endTime[ 32 ];
+    char                flagDescription[ 1024 ];
+
+    now = NanoTimeGetCurrent();
+    _NanoTime64ToTimestamp( context->subtestReport_startTime, startTime, sizeof( startTime ) );
+    _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) );
+    SNPrintF( flagDescription, sizeof( flagDescription ), "%#{flags}", context->flags, kDNSServiceFlagsDescriptors );
+
+    if (error_description != NULL)
+    {
+        err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->subtestReport,
+            "{"
+                "%kO=%s"
+                "%kO=%s"
+                "%kO=%s"
+                "%kO=%s"
+                "%kO=%s"
+                "%kO=%lli"
+                "%kO=%s"
+                "%kO=%O"
+                "%kO=%s"
+                "%kO=%O"
+            "}",
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_START_TIME,        startTime,
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_END_TIME,          endTime,
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_QNAME,             context->name,
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_FLAGS,             flagDescription,
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_PROTOCOLS,         ExpensiveConstrainedProtocolString( context->protocols ),
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_INDEX,   (int64_t) context->ifIndex,
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_NAME,    context->ifName,
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_RESULT,            CFSTR( "Fail" ),
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_ERROR,             error_description,
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_TEST_PROGRESS,     context->subtestProgress
+        );
+    }
+    else
+    {
+        err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->subtestReport,
+            "{"
+                "%kO=%s"
+                "%kO=%s"
+                "%kO=%s"
+                "%kO=%s"
+                "%kO=%s"
+                "%kO=%lli"
+                "%kO=%s"
+                "%kO=%O"
+                "%kO=%O"
+            "}",
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_START_TIME,        startTime,
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_END_TIME,          endTime,
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_QNAME,             context->name,
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_FLAGS,             flagDescription,
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_PROTOCOLS,         ExpensiveConstrainedProtocolString( context->protocols ),
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_INDEX,   (int64_t) context->ifIndex,
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_NAME,    context->ifName,
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_RESULT,            CFSTR( "Pass" ),
+            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_TEST_PROGRESS,     context->subtestProgress
+        );
+    }
+
+    require_noerr( err, exit );
+    ForgetCF( &context->subtestProgress );
+    return;
+exit:
+    ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//    ExpensiveConstrainedFinalResultReport
+//===========================================================================================================================
+
+static void ExpensiveConstrainedFinalResultReport( ExpensiveConstrainedContext *context, Boolean allPassed )
+{
+    OSStatus            err;
+    CFPropertyListRef   plist;
+    NanoTime64          now;
+    char                startTime[ 32 ];
+    char                endTime[ 32 ];
+
+    now = NanoTimeGetCurrent();
+    _NanoTime64ToTimestamp( context->testReport_startTime, startTime, sizeof( startTime ) );
+    _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) );
+
+    err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+        "{"
+            "%kO=%s"
+            "%kO=%s"
+            "%kO=%b"
+            "%kO=%O"
+        "}",
+        EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_START_TIME,       startTime,
+        EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_END_TIME,         endTime,
+        EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_ALL_PASSED,       allPassed,
+        EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_SUBTEST_RESULT,   context->subtestReport
+    );
+    require_noerr( err, exit );
+    ForgetCF( &context->subtestReport );
+
+    err = OutputPropertyList( plist, context->outputFormat, context->outputFilePath );
+    CFRelease( plist );
+    require_noerr( err, exit );
+
+    return;
+exit:
+    ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//    ExpensiveConstrainedProtocolString
+//===========================================================================================================================
+
+static const char *ExpensiveConstrainedProtocolString( DNSServiceProtocol protocol )
+{
+    const char *str = NULL;
+    switch ( protocol ) {
+        case kDNSServiceProtocol_IPv4:
+            str = "IPv4";
+            break;
+        case kDNSServiceProtocol_IPv6:
+            str = "IPv6";
+            break;
+        case kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6:
+            str = "IPv4 & IPv6";
+            break;
+        default:
+            break;
+    }
+    return str;
+}
+
+//===========================================================================================================================
+//    ExpensiveConstrainedStateString
+//===========================================================================================================================
+
+static const char *ExpensiveConstrainedStateString( enum ExpensiveConstrainedTestState state )
+{
+    const char *str = NULL;
+    switch ( state ) {
+        case TEST_BEGIN:
+            str = "TEST_BEGIN";
+            break;
+        case TEST_EXPENSIVE_PREPARE:
+            str = "TEST_EXPENSIVE_PREPARE";
+            break;
+        case TEST_EXPENSIVE:
+            str = "TEST_EXPENSIVE";
+            break;
+        case TEST_CONSTRAINED_PREPARE:
+            str = "TEST_CONSTRAINED_PREPARE";
+            break;
+        case TEST_CONSTRAINED:
+            str = "TEST_CONSTRAINED";
+            break;
+        case TEST_EXPENSIVE_CONSTRAINED_PREPARE:
+            str = "TEST_EXPENSIVE_CONSTRAINED_PREPARE";
+            break;
+        case TEST_EXPENSIVE_CONSTRAINED:
+            str = "TEST_EXPENSIVE_CONSTRAINED";
+            break;
+        case TEST_FAILED:
+            str = "TEST_FAILED";
+            break;
+        case TEST_SUCCEEDED:
+            str = "TEST_SUCCEEDED";
+            break;
+        default:
+            str = "UNKNOWN";
+            break;
+    }
+
+    return str;
+}
+
+//===========================================================================================================================
+//    ExpensiveConstrainedOperationString
+//===========================================================================================================================
+
+static const char *ExpensiveConstrainedOperationString( enum ExpensiveConstrainedTestOperation operation )
+{
+    const char *str = NULL;
+    switch ( operation ) {
+        case RESULT_ADD:
+            str = "RESULT_ADD";
+            break;
+        case RESULT_RMV:
+            str = "RESULT_RMV";
+            break;
+        case NO_UPDATE:
+            str = "NO_UPDATE";
+            break;
+        default:
+            str = "UNKNOWN";
+            break;
+    }
+    return str;
+}
+
+//===========================================================================================================================
+//    expensiveConstrainedEndsWith
+//===========================================================================================================================
+static Boolean expensiveConstrainedEndsWith( const char *str, const char *suffix )
+{
+    if ( !str || !suffix )
+        return false;
+    size_t lenstr = strlen( str );
+    size_t lensuffix = strlen( suffix );
+    if ( lensuffix > lenstr )
+        return false;
+    return strncmp( str + lenstr - lensuffix, suffix, lensuffix ) == 0;
+}
+
+//===========================================================================================================================
+//     DNSProxyTestCmd
+//===========================================================================================================================
+
+// DNS Proxy Test Mode Parameters
+
+typedef enum
+{
+       kDNSProxyTestMode_Normal = 0,
+       kDNSProxyTestMode_ForceAAAASynthesis,
+       kDNSProxyTestMode_Count
+       
+}      DNSProxyTestMode;
+
+check_compile_time( kDNSProxyTestMode_Count > 0 );
+
+// DNS Proxy Test DNS64 Prefix Parameters
+// See <https://tools.ietf.org/html/rfc6052#section-2.2>.
+
+static const char * const              kDNSProxyTestParams_DNS64Prefixes[] =
+{
+       NULL,                                                           // No prefix.
+       "3ffe:ffff::/32",                                       // 32-bit prefix. Note: Prefix is from deprecated 3ffe::/16 block (see RFC 3701).
+       "2001:db8:ff00::/40",                           // 40-bit prefix.
+       "2001:db8:ffff::/48",                           // 48-bit prefix.
+       "2001:db8:ffff:ff00::/56",                      // 56-bit prefix.
+       "2001:db8:ffff:ffff::/64",                      // 64-bit prefix.
+       "2001:db8:ffff:ff00:ffff:ffff::/96"     // 96-bit prefix. Note: bits 64 - 71 MUST be zero.
+};
+
+// DNS Proxy Test Transport Parameters
+
+typedef enum
+{
+       kDNSProxyTestTransport_UDPv4 = 0,
+       kDNSProxyTestTransport_TCPv4,
+       kDNSProxyTestTransport_UDPv6,
+       kDNSProxyTestTransport_TCPv6,
+       kDNSProxyTestTransport_Count
+       
+}      DNSProxyTestTransport;
+
+check_compile_time( kDNSProxyTestTransport_Count > 0 );
+
+// DNS Proxy Test Query Parameters
+
+typedef enum
+{
+       kDNSProxyTestQuery_A = 0,
+       kDNSProxyTestQuery_AAAA,
+       kDNSProxyTestQuery_IPv6OnlyA,
+       kDNSProxyTestQuery_IPv6OnlyAAAA,
+       kDNSProxyTestQuery_IPv4OnlyAAAA,
+       kDNSProxyTestQuery_AliasA,
+       kDNSProxyTestQuery_AliasAAAA,
+       kDNSProxyTestQuery_AliasIPv6OnlyA,
+       kDNSProxyTestQuery_AliasIPv6OnlyAAAA,
+       kDNSProxyTestQuery_AliasIPv4OnlyAAAA,
+       kDNSProxyTestQuery_NXDomainA,
+       kDNSProxyTestQuery_NXDomainAAAA,
+       kDNSProxyTestQuery_ReverseIPv6,
+       kDNSProxyTestQuery_ReverseIPv6NXDomain,
+       kDNSProxyTestQuery_ReverseIPv6DNS64,
+       kDNSProxyTestQuery_ReverseIPv6DNS64NXDomain,
+       kDNSProxyTestQuery_Count
+       
+}      DNSProxyTestQuery;
+
+check_compile_time( kDNSProxyTestQuery_Count > 0 );
+
+#define kDNSProxyTestQueryIterationCount               2
+
+typedef struct DNSProxyTest *          DNSProxyTestRef;
+struct DNSProxyTest
+{
+       dispatch_queue_t                        queue;                          // Serial queue for test events.
+       dispatch_semaphore_t            doneSem;                        // Semaphore to signal when the test is done.
+       DNSServiceRef                           probeGAI;                       // Probe GAI for DNS server.
+       char *                                          probeHostname;          // Probe hostname.
+       DNSXConnRef                                     dnsProxy;                       // DNS proxy connection reference.
+       dispatch_source_t                       timer;                          // Timer to put time limit on queries.
+       mdns_resolver_t                         resolver;                       // Resolver to represent the DNS proxy as a DNS service.
+       CFMutableDictionaryRef          report;                         // Test's report.
+       CFMutableArrayRef                       modeResults;            // "Weak" pointer to the 1st-level array of mode results.
+       CFMutableArrayRef                       prefixResults;          // "Weak" pointer to current 2nd-level array of DNS64 prefix results.
+       CFMutableArrayRef                       transportResults;       // "Weak" pointer to current 3rd-level array of transport results.
+       CFMutableArrayRef                       queryResults;           // "Weak" pointer to current 4th-level array of query results.
+       DNSProxyTestMode                        modeParam;                      // Current mode parameter.
+       unsigned int                            prefixParamIdx;         // Current DNS64 prefix parameter index.
+       DNSProxyTestTransport           transportParam;         // Current transport parameter.
+       DNSProxyTestQuery                       queryParam;                     // Current query parameter.
+       unsigned int                            queryParamIter;         // Current query iteration.
+       uint32_t                                        loopbackIndex;          // Loopback interface's index.
+       mdns_querier_t                          querier;                        // Subtest's querier to send queries to DNS proxy.
+       NanoTime64                                      startTime;                      // Subtest's start time.
+       char *                                          subtestDesc;            // Subtest's description.
+       char *                                          qnameStr;                       // Subtest's query QNAME as a C string.
+       uint8_t *                                       qname;                          // Subtest's query QNAME in label format.
+       uint16_t                                        qtype;                          // Subtest's query QTYPE.
+       unsigned int                            aliasCount;                     // Subtest's expected number of CNAMEs in response answer section.
+       unsigned int                            answerCount;            // Subtest's expected number of QTYPE records.
+       int                                                     responseCode;           // Subtest's expected response code.
+       uint8_t *                                       canonicalName;          // Subtest's expected CNAME rdata for reverse IPv6 queries.
+       uint8_t *                                       answerName;                     // Subtest's expected PTR rdata for reverse IPv6 queries.
+       pid_t                                           serverPID;                      // PID of spawned DNS server.
+       int                                                     subtestCount;           // Number of subtests that have completed or are in progress.
+       int                                                     subtestPassCount;       // Number of subtests that have passed so far.
+       int32_t                                         refCount;                       // Test object's reference count.
+       OSStatus                                        error;                          // Overall test's error.
+       int                                                     dns64PrefixBitLen;      // Current DNS64 prefix length (valid if > 0).
+       uint8_t                                         dns64Prefix[ 16 ];      // Current DNS64 prefix (valid if dns64PrefixBitLen > 0).
+       char                                            tag[ 6 + 1 ];           // Current subtest's random tag to uniquify QNAMEs.
+       Boolean                                         synthesizedAAAA;        // True if the current subtest expects DNS64 synthesized AAAA records.
+       Boolean                                         startedSubtests;        // True if the test has started running subtests.
+};
+
+ulog_define_ex( kDNSSDUtilIdentifier, DNSProxyTest, kLogLevelInfo, kLogFlags_None, "DNSProxyTest", NULL );
+#define dpt_ulog( LEVEL, ... )         ulog( &log_category_from_name( DNSProxyTest ), (LEVEL), __VA_ARGS__ )
+
+static OSStatus        _DNSProxyTestCreate( DNSProxyTestRef *outTest );
+static OSStatus        _DNSProxyTestRun( DNSProxyTestRef inTest, Boolean *outPassed );
+static void            _DNSProxyTestRetain( DNSProxyTestRef inTest );
+static void            _DNSProxyTestRelease( DNSProxyTestRef inTest );
+
+static void    DNSProxyTestCmd( void )
+{
+       OSStatus                                err;
+       OutputFormatType                outputFormat;
+       DNSProxyTestRef                 test    = NULL;
+       Boolean                                 passed  = false;
+       
+       err = OutputFormatFromArgString( gDNSProxyTest_OutputFormat, &outputFormat );
+       require_noerr_quiet( err, exit );
+       
+       err = _DNSProxyTestCreate( &test );
+       require_noerr( err, exit );
+       
+       err = _DNSProxyTestRun( test, &passed );
+       require_noerr( err, exit );
+       
+       err = OutputPropertyList( test->report, outputFormat, gDNSProxyTest_OutputFilePath );
+       require_noerr( err, exit );
+       
+exit:
+       if( test ) _DNSProxyTestRelease( test );
+    gExitCode = err ? 1 : ( passed ? 0 : 2 );
+}
+
+//===========================================================================================================================
+
+static void                            _DNSProxyTestStart( void *inCtx );
+static void                            _DNSProxyTestStop( DNSProxyTestRef inTest, OSStatus inError );
+static OSStatus                        _DNSProxyTestContinue( DNSProxyTestRef inTest, OSStatus inSubtestError, Boolean *outDone );
+static const char *            _DNSProxyTestGetCurrentDNS64PrefixParam( DNSProxyTestRef inTest );
+static OSStatus                        _DNSProxyTestPrepareMode( DNSProxyTestRef inTest );
+static OSStatus                        _DNSProxyTestPrepareDNSProxy( DNSProxyTestRef inTest );
+static OSStatus                        _DNSProxyTestPrepareResolver( DNSProxyTestRef inTest );
+static OSStatus                        _DNSProxyTestStartQuery( DNSProxyTestRef inTest, Boolean *outSkipQuery );
+static OSStatus
+       _DNSProxyTestSynthesizeIPv6(
+               const uint8_t * inIPv6Prefix,
+               int                             inIPv6PrefixBitLen,
+               uint32_t                inIPv4Addr,
+               uint8_t                 outIPv6Addr[ STATIC_PARAM 16 ] );
+static void    _DNSProxyTestHandleQuerierResult( DNSProxyTestRef inTest );
+static OSStatus
+       _DNSProxyTestVerifyAddressResponse(
+               const uint8_t * inMsgPtr,
+               size_t                  inMsgLen,
+               const uint8_t * inQName,
+               uint16_t                inQType,
+               int                             inResponseCode,
+               unsigned int    inAliasCount,
+               unsigned int    inAnswerCount,
+               const uint8_t * inDNS64Prefix,
+               int                             inDNS64PrefixBitLen );
+static OSStatus
+       _DNSProxyTestVerifyReverseIPv6Response(
+               const uint8_t * inMsgPtr,
+               size_t                  inMsgLen,
+               const uint8_t * inQName,
+               int                             inResponseCode,
+               const uint8_t * inCanonicalName,
+               const uint8_t * inAnswerName );
+static void DNSSD_API
+       _DNSProxyTestProbeGAICallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inHostname,
+               const struct sockaddr * inSockAddr,
+               uint32_t                                inTTL,
+               void *                                  inCtx );
+static void                    _DNSProxyTestProbeTimerHandler( void *inCtx );
+
+static OSStatus        _DNSProxyTestCreate( DNSProxyTestRef *outTest )
+{
+       OSStatus                        err;
+       DNSProxyTestRef         obj;
+       
+       obj = (DNSProxyTestRef) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
+       
+       obj->refCount   = 1;
+       obj->error              = kInProgressErr;
+       obj->serverPID  = -1;
+       
+       obj->queue = dispatch_queue_create( "com.apple.dnssdutil.dns-proxy-test", DISPATCH_QUEUE_SERIAL );
+       require_action( obj->queue, exit, err = kNoResourcesErr );
+       
+       obj->doneSem = dispatch_semaphore_create( 0 );
+       require_action( obj->doneSem, exit, err = kNoResourcesErr );
+       
+       *outTest = obj;
+       obj = NULL;
+       err = kNoErr;
+       
+exit:
+       if( obj ) _DNSProxyTestRelease( obj );
+       return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus        _DNSProxyTestRun( DNSProxyTestRef me, Boolean *outPassed )
+{
+       Boolean         passed;
+       
+       dispatch_async_f( me->queue, me, _DNSProxyTestStart );
+       dispatch_semaphore_wait( me->doneSem, DISPATCH_TIME_FOREVER );
+       
+       passed = ( !me->error && ( me->subtestPassCount == me->subtestCount ) ) ? true : false;
+       CFDictionarySetBoolean( me->report, CFSTR( "pass" ), passed );
+       dpt_ulog( kLogLevelInfo, "Test result: %s\n", passed ? "pass" : "fail" );
+       
+       if( outPassed ) *outPassed = passed;
+       return( me->error );
+}
+
+//===========================================================================================================================
+
+static void    _DNSProxyTestRetain( DNSProxyTestRef me )
+{
+       atomic_add_32( &me->refCount, 1 );
+}
+
+//===========================================================================================================================
+
+static void    _DNSProxyTestRelease( DNSProxyTestRef me )
+{
+       if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 )
+       {
+               check( !me->probeGAI );
+               check( !me->probeHostname );
+               check( !me->dnsProxy );
+               check( !me->timer );
+               check( !me->resolver );
+               check( !me->modeResults );
+               check( !me->prefixResults );
+               check( !me->transportResults );
+               check( !me->queryResults );
+               check( !me->querier );
+               check( !me->subtestDesc );
+               check( !me->qnameStr );
+               check( !me->qname );
+               check( !me->canonicalName );
+               check( !me->answerName );
+               check( me->serverPID < 0 );
+               dispatch_forget( &me->queue );
+               dispatch_forget( &me->doneSem );
+               ForgetCF( &me->report );
+               free( me );
+       }
+}
+
+//===========================================================================================================================
+
+#define kDNSProxyTestProbeQueryTimeoutSecs             5
+
+static void _DNSProxyTestStart( void *inCtx )
+{
+       OSStatus                                        err;
+       const DNSProxyTestRef           me                      = (DNSProxyTestRef) inCtx;
+       char *                                          serverCmd       = NULL;
+       NanoTime64                                      startTime;
+       char                                            startTimeStr[ 32 ];
+       char                                            tag[ 6 + 1 ];
+       
+       startTime = NanoTimeGetCurrent();
+       
+       dpt_ulog( kLogLevelInfo, "Starting test\n" );
+       
+       me->error                       = kInProgressErr;
+       me->loopbackIndex       = if_nametoindex( "lo0" );
+       err = map_global_value_errno( me->loopbackIndex != 0, me->loopbackIndex );
+       require_noerr_action_quiet( err, exit, dpt_ulog( kLogLevelError, "Failed to get interface index for lo0: %#m", err ) );
+       
+       serverCmd = NULL;
+       ASPrintF( &serverCmd, "dnssdutil server --loopback --follow %lld --port 0 --defaultTTL 300 --responseDelay 10",
+               (int64_t) getpid() );
+       require_action_quiet( serverCmd, exit, err = kUnknownErr );
+       
+       err = _SpawnCommand( &me->serverPID, "/dev/null", "/dev/null", "%s", serverCmd );
+       require_noerr( err, exit );
+       
+       check( !me->probeHostname );
+       ASPrintF( &me->probeHostname, "tag-dns-proxy-test-probe-%s.ipv4.d.test.",
+               _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
+       require_action( me->probeHostname, exit, err = kNoMemoryErr );
+       
+       err = DNSServiceGetAddrInfo( &me->probeGAI, 0, kDNSServiceInterfaceIndexAny, kDNSServiceProtocol_IPv4,
+               me->probeHostname, _DNSProxyTestProbeGAICallback, me );
+       require_noerr( err, exit );
+       
+       err = DNSServiceSetDispatchQueue( me->probeGAI, me->queue );
+       require_noerr( err, exit );
+       
+       check( !me->timer );
+       err = DispatchTimerOneShotCreate( dispatch_time_seconds( kDNSProxyTestProbeQueryTimeoutSecs ),
+               kDNSProxyTestProbeQueryTimeoutSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 10 ),
+               me->queue, _DNSProxyTestProbeTimerHandler, me, &me->timer );
+       require_noerr( err, exit );
+       dispatch_resume( me->timer );
+       
+       _NanoTime64ToTimestamp( startTime, startTimeStr, sizeof( startTimeStr ) );
+       err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &me->report,
+               "{"
+                       "%kO=%s"        // startTime
+                       "%kO=%s"        // serverCmd
+                       "%kO=%s"        // probeHostname
+                       "%kO=[%@]"      // results
+               "}",
+               CFSTR( "startTime" ),           startTimeStr,
+               CFSTR( "serverCmd" ),           serverCmd,
+               CFSTR( "probeHostname" ),       me->probeHostname,
+               CFSTR( "results" ),                     &me->modeResults );
+       require_noerr( err, exit );
+       
+exit:
+       FreeNullSafe( serverCmd );
+       if( err ) _DNSProxyTestStop( me, err );
+}
+
+//===========================================================================================================================
+
+static void            _DNSProxyTestSubtestCleanup( DNSProxyTestRef inTest );
+
+#define _DNSXForget( X )               ForgetCustom( X, DNSXRefDeAlloc )
+
+static void    _DNSProxyTestStop( DNSProxyTestRef me, OSStatus inError )
+{
+       OSStatus                err;
+       NanoTime64              endTime;
+       char                    endTimeStr[ 32 ];
+       
+       endTime = NanoTimeGetCurrent();
+       me->error = inError;
+       dpt_ulog( kLogLevelInfo, "Stopping test with error: %#m\n", me->error );
+       
+       DNSServiceForget( &me->probeGAI );
+       ForgetMem( &me->probeHostname );
+       _DNSXForget( &me->dnsProxy );
+       dispatch_source_forget( &me->timer );
+       mdns_resolver_forget( &me->resolver );
+       me->prefixResults               = NULL;
+       me->modeResults                 = NULL;
+       me->transportResults    = NULL;
+       me->queryResults                = NULL;
+       _DNSProxyTestSubtestCleanup( me );
+       if( me->serverPID >= 0 )
+       {
+               OSStatus                killErr;
+               
+               killErr = kill( me->serverPID, SIGTERM );
+               killErr = map_global_noerr_errno( killErr );
+               check_noerr( killErr );
+               me->serverPID = -1;
+       }
+       _NanoTime64ToTimestamp( endTime, endTimeStr, sizeof( endTimeStr ) );
+       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->report,
+               "%kO=%s"                // endTime
+               "%kO=%lli"              // subtestCount
+               "%kO=%lli",             // subtestPassCount
+               CFSTR( "endTime" ),                             endTimeStr,
+               CFSTR( "subtestCount" ),                (int64_t) me->subtestCount,
+               CFSTR( "subtestPassCount" ),    (int64_t) me->subtestPassCount );
+       check_noerr( err );
+       if( err && !me->error ) me->error = err;
+       dispatch_semaphore_signal( me->doneSem );
+}
+
+//===========================================================================================================================
+
+static void    _DNSProxyTestSubtestCleanup( DNSProxyTestRef me )
+{
+       dispatch_source_forget( &me->timer );
+       mdns_querier_forget( &me->querier );
+       ForgetMem( &me->subtestDesc );
+       ForgetMem( &me->qnameStr );
+       ForgetMem( &me->qname );
+       ForgetMem( &me->canonicalName );
+       ForgetMem( &me->answerName );
+}
+
+//===========================================================================================================================
+
+static OSStatus                _DNSProxyTestHandleSubtestCompletion( DNSProxyTestRef inTest, OSStatus inSubtestError );
+static char *          _DNSProxyTestCreateSubtestDescription( DNSProxyTestRef inTest );
+static const char *    _DNSProxyTestQueryToString( DNSProxyTestQuery inQuery );
+static const char *    _DNSProxyTestTransportToString( DNSProxyTestTransport inTransport );
+static const char *    _DNSProxyTestModeToString( DNSProxyTestMode inMode );
+
+static OSStatus        _DNSProxyTestContinue( DNSProxyTestRef me, OSStatus inSubtestError, Boolean *outDone )
+{
+       OSStatus                err;
+       Boolean                 skipQueries     = false;
+       Boolean                 done            = false;
+       
+       do
+       {
+               if( me->startedSubtests )
+               {
+                       if( !skipQueries )
+                       {
+                               err = _DNSProxyTestHandleSubtestCompletion( me, inSubtestError );
+                               require_noerr( err, exit );
+                       }
+                       else
+                       {
+                               dpt_ulog( kLogLevelInfo, "Skipped subtest: %s\n", me->subtestDesc );
+                       }
+                       _DNSProxyTestSubtestCleanup( me );
+                       if( skipQueries || ( ++me->queryParamIter == kDNSProxyTestQueryIterationCount ) )
+                       {
+                               me->queryParamIter = 0;
+                               if( ++me->queryParam == kDNSProxyTestQuery_Count )
+                               {
+                                       me->queryParam          = 0;
+                                       me->queryResults        = NULL;
+                                       dpt_ulog( kLogLevelInfo, "Invalidating resolver: %@\n", me->resolver );
+                                       mdns_resolver_forget( &me->resolver );
+                                       if( ++me->transportParam == kDNSProxyTestTransport_Count )
+                                       {
+                                               me->transportParam              = 0;
+                                               me->transportResults    = NULL;
+                                               dpt_ulog( kLogLevelInfo, "Disabling DNS proxy\n" );
+                                               _DNSXForget( &me->dnsProxy );
+                                               if( ++me->prefixParamIdx == countof( kDNSProxyTestParams_DNS64Prefixes ) )
+                                               {
+                                                       me->prefixParamIdx      = 0;
+                                                       me->prefixResults       = NULL;
+                                                       if( ++me->modeParam == kDNSProxyTestMode_Count )
+                                                       {
+                                                               me->modeParam   = 0;
+                                                               me->modeResults = NULL;
+                                                               done                    = true;
+                                                               err                             = kNoErr;
+                                                               goto exit;
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+               else
+               {
+                       me->startedSubtests = true;
+               }
+               if( ( me->queryParamIter == 0 ) && ( me->queryParam == 0 ) )
+               {
+                       if( me->transportParam == 0 )
+                       {
+                               if( me->prefixParamIdx == 0 )
+                               {
+                                       err = _DNSProxyTestPrepareMode( me );
+                                       require_noerr( err, exit );
+                               }
+                               err = _DNSProxyTestPrepareDNSProxy( me );
+                               require_noerr( err, exit );
+                       }
+                       err = _DNSProxyTestPrepareResolver( me );
+                       require_noerr( err, exit );
+               }
+               err = _DNSProxyTestStartQuery( me, &skipQueries );
+               require_noerr( err, exit );
+               
+               check( !me->subtestDesc );
+               me->subtestDesc = _DNSProxyTestCreateSubtestDescription( me );
+               require_action( me->subtestDesc, exit, err = kNoMemoryErr );
+               
+       }       while( skipQueries );
+       
+       ++me->subtestCount;
+       dpt_ulog( kLogLevelInfo, "Started subtest #%d: %s\n", me->subtestCount, me->subtestDesc );
+       
+exit:
+       if( outDone ) *outDone = done;
+       return( err );
+}
+
+static OSStatus        _DNSProxyTestHandleSubtestCompletion( DNSProxyTestRef me, OSStatus inSubtestError )
+{
+       OSStatus                err;
+       NanoTime64              endTime;
+       char                    errorStr[ 128 ];
+       char                    startTimeStr[ 32 ];
+       char                    endTimeStr[ 32 ];
+       
+       endTime = NanoTimeGetCurrent();
+       
+       if( !inSubtestError ) ++me->subtestPassCount;
+       
+       dpt_ulog( kLogLevelInfo, "Subtest #%d result: %s (pass rate: %d/%d)\n",
+               me->subtestCount, inSubtestError ? "fail" : "pass", me->subtestPassCount, me->subtestCount );
+       
+       _NanoTime64ToTimestamp( me->startTime, startTimeStr, sizeof( startTimeStr ) );
+       _NanoTime64ToTimestamp( endTime, endTimeStr, sizeof( endTimeStr ) );
+       SNPrintF( errorStr, sizeof( errorStr ), "%m", inSubtestError );
+       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->queryResults,
+               "{"
+                       "%kO=%s"                // description
+                       "%kO=%s"                // startTime
+                       "%kO=%s"                // endTime
+                       "%kO=%s"                // qname
+                       "%kO=%s"                // qtype
+                       "%kO=%b"                // pass
+                       "%kO="                  // error
+                       "{"
+                               "%kO=%lli"      // code
+                               "%kO=%s"        // description
+                       "}"
+               "}",
+               CFSTR( "description" ), me->subtestDesc,
+               CFSTR( "startTime" ),   startTimeStr,
+               CFSTR( "endTime" ),             endTimeStr,
+               CFSTR( "qname" ),               me->qnameStr,
+               CFSTR( "qtype" ),               DNSRecordTypeValueToString( me->qtype ),
+               CFSTR( "pass" ),                inSubtestError ? false : true,
+               CFSTR( "error" ),
+               CFSTR( "code" ),                (int64_t) inSubtestError,
+               CFSTR( "description" ), errorStr );
+       return( err );
+}
+
+static char *  _DNSProxyTestCreateSubtestDescription( DNSProxyTestRef me )
+{
+       const char *            queryStr;
+       const char *            transportStr;
+       const char *            dns64PrefixStr;
+       const char *            modeStr;
+       char *                          description;
+       
+       queryStr                = _DNSProxyTestQueryToString( me->queryParam );
+       transportStr    = _DNSProxyTestTransportToString( me->transportParam );
+       dns64PrefixStr  = _DNSProxyTestGetCurrentDNS64PrefixParam( me );
+       modeStr                 = _DNSProxyTestModeToString( me->modeParam );
+       description             = NULL;
+       if( dns64PrefixStr )
+       {
+               ASPrintF( &description, "%s over %s to DNS proxy using DNS64 prefix %s in %s mode (%u of %d)",
+                       queryStr, transportStr, dns64PrefixStr, modeStr, me->queryParamIter + 1, kDNSProxyTestQueryIterationCount );
+       }
+       else
+       {
+               ASPrintF( &description, "%s over %s to DNS proxy in %s mode (%u of %d)",
+                       queryStr, transportStr, modeStr, me->queryParamIter + 1, kDNSProxyTestQueryIterationCount );
+       }
+       return( description );
+}
+
+//===========================================================================================================================
+
+static const char *    _DNSProxyTestQueryToString( DNSProxyTestQuery inQuery )
+{
+       switch( inQuery )
+       {
+               case kDNSProxyTestQuery_A:                                                      return( "A record query" );
+               case kDNSProxyTestQuery_AAAA:                                           return( "AAAA record query" );
+               case kDNSProxyTestQuery_IPv6OnlyA:                                      return( "IPv6-only A record query" );
+               case kDNSProxyTestQuery_IPv6OnlyAAAA:                           return( "IPv6-only AAAA record query" );
+               case kDNSProxyTestQuery_IPv4OnlyAAAA:                           return( "IPv4-only AAAA record query" );
+               case kDNSProxyTestQuery_AliasA:                                         return( "A record query with CNAMEs" );
+               case kDNSProxyTestQuery_AliasAAAA:                                      return( "AAAA record query with CNAMEs" );
+               case kDNSProxyTestQuery_AliasIPv6OnlyA:                         return( "IPv6-only A record query with CNAMEs" );
+               case kDNSProxyTestQuery_AliasIPv6OnlyAAAA:                      return( "IPv6-only AAAA record query with CNAMEs" );
+               case kDNSProxyTestQuery_AliasIPv4OnlyAAAA:                      return( "IPv4-only AAAA record query with CNAMEs" );
+               case kDNSProxyTestQuery_NXDomainA:                                      return( "A record query (NXDomain)" );
+               case kDNSProxyTestQuery_NXDomainAAAA:                           return( "AAAA record query (NXDomain)" );
+               case kDNSProxyTestQuery_ReverseIPv6:                            return( "Reverse IPv6 query" );
+               case kDNSProxyTestQuery_ReverseIPv6NXDomain:            return( "Reverse IPv6 query (NXDomain)" );
+               case kDNSProxyTestQuery_ReverseIPv6DNS64:                       return( "Reverse IPv6 query with DNS64 prefix" );
+               case kDNSProxyTestQuery_ReverseIPv6DNS64NXDomain:       return( "Reverse IPv6 query with DNS64 prefix (NXDomain)" );
+               default:                                                                                        return( "<INVALID QUERY>" );
+    }
+}
+
+//===========================================================================================================================
+
+static const char *    _DNSProxyTestTransportToString( DNSProxyTestTransport inTransport )
+{
+    switch( inTransport )
+       {
+               case kDNSProxyTestTransport_UDPv4:      return( "IPv4-UDP" );
+               case kDNSProxyTestTransport_TCPv4:      return( "IPv4-TCP" );
+               case kDNSProxyTestTransport_UDPv6:      return( "IPv6-UDP" );
+               case kDNSProxyTestTransport_TCPv6:      return( "IPv6-TCP" );
+               default:                                                        return( "<INVALID TRANSPORT>" );
+       }
+}
+
+//===========================================================================================================================
+
+static const char *    _DNSProxyTestModeToString( DNSProxyTestMode inMode )
+{
+    switch( inMode )
+       {
+               case kDNSProxyTestMode_Normal:                          return( "normal" );
+               case kDNSProxyTestMode_ForceAAAASynthesis:      return( "force-AAAA-synthesis" );
+               default:                                                                        return( "<INVALID MODE>" );
+       }
+}
+
+//===========================================================================================================================
+
+static const char *    _DNSProxyTestGetCurrentDNS64PrefixParam( DNSProxyTestRef me )
+{
+       const unsigned int              index = me->prefixParamIdx;
+       const unsigned int              count = countof( kDNSProxyTestParams_DNS64Prefixes );
+       
+       require_fatal( index < count, "DNSProxyTest DNS Proxy DNS64 prefix parameter index %u out-of-range.", index );
+       return( kDNSProxyTestParams_DNS64Prefixes[ index ] );
+}
+
+//===========================================================================================================================
+
+static OSStatus        _DNSProxyTestPrepareMode( DNSProxyTestRef me )
+{
+       OSStatus                err;
+       
+       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->modeResults,
+               "{"
+                       "%kO=%s"        // mode
+                       "%kO=[%@]"      // results
+               "}",
+               CFSTR( "mode" ),        _DNSProxyTestModeToString( me->modeParam ),
+               CFSTR( "results" ),     &me->prefixResults );
+       return( err );
+}
+
+//===========================================================================================================================
+
+static void    _DNSProxyTestDNSProxyCallback( DNSXConnRef inDNSProxy, DNSXErrorType inError );
+
+static OSStatus        _DNSProxyTestPrepareDNSProxy( DNSProxyTestRef me )
+{
+       OSStatus                        err;
+       const char *            dns64PrefixStr;
+       IfIndex                         interfaces[ MaxInputIf ];
+       
+       me->dns64PrefixBitLen = 0;
+       memset( me->dns64Prefix, 0, sizeof( me->dns64Prefix ) );
+       
+       dns64PrefixStr = _DNSProxyTestGetCurrentDNS64PrefixParam( me );
+       if( dns64PrefixStr )
+       {
+               const char *            end;
+               
+               err = _StringToIPv6Address( dns64PrefixStr, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoScope,
+                       me->dns64Prefix, NULL, NULL, &me->dns64PrefixBitLen, &end );
+               if( !err && ( *end != '\0' ) ) err = kMalformedErr;
+               require_noerr_quiet( err, exit );
+       }
+       memset( interfaces, 0, sizeof( interfaces ) );
+       interfaces[ 0 ] = me->loopbackIndex;
+       
+       check( !me->dnsProxy );
+       sleep( 1 ); // Workaround: Allow one second for previous DNS proxy state to get cleaned up.
+       if( dns64PrefixStr )
+       {
+               if( __builtin_available( macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, * ) )
+               {
+                       DNSXProxyFlags          flags;
+                       
+                       switch( me->modeParam )
+                       {
+                               case kDNSProxyTestMode_Normal:
+                                       flags = kDNSXProxyFlagNull;
+                                       break;
+                               
+                               case kDNSProxyTestMode_ForceAAAASynthesis:
+                                       flags = kDNSXProxyFlagForceAAAASynthesis;
+                                       break;
+                               
+                               default:
+                                       FatalErrorF( "Unhandled DNSProxyTestMode value %ld", (long) me->modeParam );
+                       }
+                       dpt_ulog( kLogLevelInfo, "Enabling DNS proxy with DNS64 prefix %.16a/%d\n",
+                               me->dns64Prefix, me->dns64PrefixBitLen );
+                       err = DNSXEnableProxy64( &me->dnsProxy, kDNSProxyEnable, interfaces, 0, me->dns64Prefix, me->dns64PrefixBitLen,
+                               flags, me->queue, _DNSProxyTestDNSProxyCallback );
+                       require_noerr_quiet( err, exit );
+               }
+               else
+               {
+                       dpt_ulog( kLogLevelError, "DNSXEnableProxy64() is not available on this OS.\n" );
+                       err = kUnsupportedErr;
+                       goto exit;
+               }
+       }
+       else
+       {
+               dpt_ulog( kLogLevelInfo, "Enabling DNS proxy (without a DNS64 prefix)\n" );
+               err = DNSXEnableProxy( &me->dnsProxy, kDNSProxyEnable, interfaces, 0, me->queue, _DNSProxyTestDNSProxyCallback );
+               require_noerr_quiet( err, exit );
+       }
+       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->prefixResults,
+               "{"
+                       "%kO=%s"        // dns64Prefix
+                       "%kO=[%@]"      // results
+               "}",
+               CFSTR( "dns64Prefix" ), dns64PrefixStr ? dns64PrefixStr : "",
+               CFSTR( "results" ),             &me->transportResults );
+       require_noerr( err, exit );
+       
+exit:
+       return( err );
+}
+
+static void    _DNSProxyTestDNSProxyCallback( DNSXConnRef inDNSProxy, DNSXErrorType inError )
+{
+       Unused( inDNSProxy );
+       
+       if( !inError )  dpt_ulog( kLogLevelInfo, "DNS proxy callback: no error\n" );
+       else                    dpt_ulog( kLogLevelError, "DNS proxy callback: error %#m\n", inError );
+}
+
+//===========================================================================================================================
+
+#define kDNSProxyTestDNSProxyAddrStr_IPv4              "127.0.0.1:53"
+#define kDNSProxyTestDNSProxyAddrStr_IPv6              "[::1]:53"
+
+static OSStatus        _DNSProxyTestPrepareResolver( DNSProxyTestRef me )
+{
+       OSStatus                                        err;
+       const char *                            addrStr;
+       mdns_address_t                          addr = NULL;
+       mdns_resolver_type_t            resolverType;
+       
+       switch( me->transportParam )
+       {
+               case kDNSProxyTestTransport_UDPv4:
+                       addrStr                 = kDNSProxyTestDNSProxyAddrStr_IPv4;
+                       resolverType    = mdns_resolver_type_normal;
+                       break;
+               
+               case kDNSProxyTestTransport_TCPv4:
+                       addrStr                 = kDNSProxyTestDNSProxyAddrStr_IPv4;
+                       resolverType    = mdns_resolver_type_tcp;
+                       break;
+               
+               case kDNSProxyTestTransport_UDPv6:
+                       addrStr                 = kDNSProxyTestDNSProxyAddrStr_IPv6;
+                       resolverType    = mdns_resolver_type_normal;
+                       break;
+               
+               case kDNSProxyTestTransport_TCPv6:
+                       addrStr                 = kDNSProxyTestDNSProxyAddrStr_IPv6;
+                       resolverType    = mdns_resolver_type_tcp;
+                       break;
+               
+               default:
+                       FatalErrorF( "Unhandled DNSProxyTestTransport value %ld", (long) me->transportParam );
+       }
+       check( !me->resolver );
+       me->resolver = mdns_resolver_create( resolverType, 0, &err );
+       require_noerr( err, exit );
+       
+       addr = mdns_address_create_from_ip_address_string( addrStr );
+       require_action( addr, exit, err = kUnknownErr );
+       
+       err = mdns_resolver_add_server_address( me->resolver, addr );
+       mdns_forget( &addr );
+       require_noerr( err, exit );
+       
+       dpt_ulog( kLogLevelInfo, "Activating resolver: %@\n", me->resolver );
+       mdns_resolver_activate( me->resolver );
+       
+       _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( me->tag ) - 1, me->tag );
+       
+       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->transportResults,
+               "{"
+                       "%kO=%s"        // transport
+                       "%kO=[%@]"      // results
+               "}",
+               CFSTR( "transport" ),   _DNSProxyTestTransportToString( me->transportParam ),
+               CFSTR( "results" ),             &me->queryResults );
+       require_noerr( err, exit );
+       
+exit:
+       mdns_release_null_safe( addr );
+       return( err );
+}
+
+//===========================================================================================================================
+
+#define kDNSProxyTestQuerierTimeLimitSecs              5
+#define kDNSProxyTestRecordTTL                                 ( 5 * kSecondsPerMinute )
+#define kDNSProxyTestAddressCount                              4
+#define kDNSProxyTestAliasCount                                        4
+
+static void    _DNSProxyTestQuerierTimerHandler( void *inCtx );
+
+static OSStatus        _DNSProxyTestStartQuery( DNSProxyTestRef me, Boolean *outSkipQuery )
+{
+       OSStatus                        err;
+       mdns_querier_t          querier;
+       uint8_t                         tmpName[ kDomainNameLengthMax ];
+       Boolean                         skipQuery = false;
+       
+       me->startTime           = NanoTimeGetCurrent();
+       me->qtype                       = 0;
+       me->aliasCount          = 0;
+       me->answerCount         = 0;
+       me->responseCode        = 0;
+       me->canonicalName       = NULL;
+       me->answerName          = NULL;
+       me->synthesizedAAAA     = false;
+       
+       check( !me->qnameStr );
+       switch( me->queryParam )
+       {
+               case kDNSProxyTestQuery_A:
+                       me->qtype                       = kDNSRecordType_A;
+                       me->answerCount         = kDNSProxyTestAddressCount;
+                       me->responseCode        = kDNSRCode_NoError;
+                       ASPrintF( &me->qnameStr, "count-%u.ttl-%u.tag-a-%s.d.test.",
+                               kDNSProxyTestAddressCount, kDNSProxyTestRecordTTL, me->tag );
+                       require_action( me->qnameStr, exit, err = kNoMemoryErr );
+                       break;
+               
+               case kDNSProxyTestQuery_AAAA:
+                       me->qtype                       = kDNSRecordType_AAAA;
+                       me->answerCount         = kDNSProxyTestAddressCount;
+                       me->responseCode        = kDNSRCode_NoError;
+                       if( ( me->dns64PrefixBitLen > 0 ) && ( me->modeParam == kDNSProxyTestMode_ForceAAAASynthesis ) )
+                       {
+                               me->synthesizedAAAA = true;
+                       }
+                       else
+                       {
+                               me->synthesizedAAAA = false;
+                       }
+                       ASPrintF( &me->qnameStr, "count-%u.ttl-%u.tag-aaaa-%s.d.test.",
+                               kDNSProxyTestAddressCount, kDNSProxyTestRecordTTL, me->tag );
+                       require_action( me->qnameStr, exit, err = kNoMemoryErr );
+                       break;
+               
+               case kDNSProxyTestQuery_IPv6OnlyA:
+                       me->qtype                       = kDNSRecordType_A;
+                       me->answerCount         = 0;
+                       me->responseCode        = kDNSRCode_NoError;
+                       ASPrintF( &me->qnameStr, "ipv6.ttl-%u.tag-ipv6-only-a-%s.d.test.", kDNSProxyTestRecordTTL, me->tag );
+                       require_action( me->qnameStr, exit, err = kNoMemoryErr );
+                       break;
+               
+               case kDNSProxyTestQuery_IPv6OnlyAAAA:
+                       me->qtype                       = kDNSRecordType_AAAA;
+                       me->responseCode        = kDNSRCode_NoError;
+                       if( ( me->dns64PrefixBitLen > 0 ) && ( me->modeParam == kDNSProxyTestMode_ForceAAAASynthesis ) )
+                       {
+                               me->answerCount = 0;
+                       }
+                       else
+                       {
+                               me->answerCount = kDNSProxyTestAddressCount;
+                       }
+                       ASPrintF( &me->qnameStr, "count-%u.ipv6.ttl-%u.tag-ipv6-only-aaaa-%s.d.test.",
+                               kDNSProxyTestAddressCount, kDNSProxyTestRecordTTL, me->tag );
+                       require_action( me->qnameStr, exit, err = kNoMemoryErr );
+                       break;
+               
+               case kDNSProxyTestQuery_IPv4OnlyAAAA:
+                       me->qtype                       = kDNSRecordType_AAAA;
+                       me->responseCode        = kDNSRCode_NoError;
+                       if( me->dns64PrefixBitLen > 0 )
+                       {
+                               me->answerCount         = kDNSProxyTestAddressCount;
+                               me->synthesizedAAAA     = true;
+                       }
+                       else
+                       {
+                               me->answerCount         = 0;
+                               me->synthesizedAAAA     = false;
+                       }
+                       ASPrintF( &me->qnameStr, "count-%u.ipv4.ttl-%u.tag-ipv4-only-aaaa-%s.d.test.",
+                               kDNSProxyTestAddressCount, kDNSProxyTestRecordTTL, me->tag );
+                       require_action( me->qnameStr, exit, err = kNoMemoryErr );
+                       break;
+               
+               case kDNSProxyTestQuery_AliasA:
+                       me->qtype                       = kDNSRecordType_A;
+                       me->aliasCount          = kDNSProxyTestAliasCount;
+                       me->answerCount         = kDNSProxyTestAddressCount;
+                       me->responseCode        = kDNSRCode_NoError;
+                       ASPrintF( &me->qnameStr, "alias-%u.count-%u.ttl-%u.tag-alias-a-%s.d.test.",
+                               kDNSProxyTestAliasCount, kDNSProxyTestAddressCount, kDNSProxyTestRecordTTL, me->tag );
+                       require_action( me->qnameStr, exit, err = kNoMemoryErr );
+                       break;
+               
+               case kDNSProxyTestQuery_AliasAAAA:
+                       me->qtype                       = kDNSRecordType_AAAA;
+                       me->aliasCount          = kDNSProxyTestAliasCount;
+                       me->answerCount         = kDNSProxyTestAddressCount;
+                       me->responseCode        = kDNSRCode_NoError;
+                       if( ( me->dns64PrefixBitLen > 0 ) && ( me->modeParam == kDNSProxyTestMode_ForceAAAASynthesis ) )
+                       {
+                               me->synthesizedAAAA = true;
+                       }
+                       else
+                       {
+                               me->synthesizedAAAA = false;
+                       }
+                       ASPrintF( &me->qnameStr, "alias-%u.count-%u.ttl-%u.tag-alias-aaaa-%s.d.test.",
+                               kDNSProxyTestAliasCount, kDNSProxyTestAddressCount, kDNSProxyTestRecordTTL, me->tag );
+                       require_action( me->qnameStr, exit, err = kNoMemoryErr );
+                       break;
+               
+               case kDNSProxyTestQuery_AliasIPv6OnlyA:
+                       me->qtype                       = kDNSRecordType_A;
+                       me->aliasCount          = kDNSProxyTestAliasCount;
+                       me->answerCount         = 0;
+                       me->responseCode        = kDNSRCode_NoError;
+                       ASPrintF( &me->qnameStr, "alias-%u.ipv6.ttl-%u.tag-alias-ipv6-only-a-%s.d.test.",
+                               kDNSProxyTestAliasCount, kDNSProxyTestRecordTTL, me->tag );
+                       require_action( me->qnameStr, exit, err = kNoMemoryErr );
+                       break;
+               
+               case kDNSProxyTestQuery_AliasIPv6OnlyAAAA:
+                       me->qtype                       = kDNSRecordType_AAAA;
+                       me->aliasCount          = kDNSProxyTestAliasCount;
+                       me->responseCode        = kDNSRCode_NoError;
+                       if( ( me->dns64PrefixBitLen > 0 ) && ( me->modeParam == kDNSProxyTestMode_ForceAAAASynthesis ) )
+                       {
+                               me->answerCount = 0;
+                       }
+                       else
+                       {
+                               me->answerCount = kDNSProxyTestAddressCount;
+                       }
+                       ASPrintF( &me->qnameStr, "alias-%u.ipv6.count-%u.ttl-%u.tag-alias-ipv6-only-aaaa-%s.d.test.",
+                               kDNSProxyTestAliasCount, kDNSProxyTestAddressCount, kDNSProxyTestRecordTTL, me->tag );
+                       require_action( me->qnameStr, exit, err = kNoMemoryErr );
+                       break;
+               
+               case kDNSProxyTestQuery_AliasIPv4OnlyAAAA:
+                       me->qtype                       = kDNSRecordType_AAAA;
+                       me->aliasCount          = kDNSProxyTestAliasCount;
+                       me->responseCode        = kDNSRCode_NoError;
+                       if( me->dns64PrefixBitLen > 0 )
+                       {
+                               me->answerCount         = kDNSProxyTestAddressCount;
+                               me->synthesizedAAAA     = true;
+                       }
+                       else
+                       {
+                               me->answerCount         = 0;
+                               me->synthesizedAAAA     = false;
+                       }
+                       ASPrintF( &me->qnameStr, "alias-%u.count-%u.ipv4.ttl-%u.tag-alias-ipv4-only-aaaa-%s.d.test.",
+                               kDNSProxyTestAliasCount, kDNSProxyTestAddressCount, kDNSProxyTestRecordTTL, me->tag );
+                       require_action( me->qnameStr, exit, err = kNoMemoryErr );
+                       break;
+               
+               case kDNSProxyTestQuery_NXDomainA:
+                       me->qtype                       = kDNSRecordType_A;
+                       me->answerCount         = 0;
+                       me->responseCode        = kDNSRCode_NXDomain;
+                       ASPrintF( &me->qnameStr, "does-not-exist.tag-nx-domain-a-%s.d.test.", me->tag );
+                       require_action( me->qnameStr, exit, err = kNoMemoryErr );
+                       break;
+               
+               case kDNSProxyTestQuery_NXDomainAAAA:
+                       me->qtype                       = kDNSRecordType_AAAA;
+                       me->answerCount         = 0;
+                       me->responseCode        = kDNSRCode_NXDomain;
+                       ASPrintF( &me->qnameStr, "does-not-exist.tag-nx-domain-aaaa-%s.d.test.", me->tag );
+                       require_action( me->qnameStr, exit, err = kNoMemoryErr );
+                       break;
+               
+               case kDNSProxyTestQuery_ReverseIPv6:
+               case kDNSProxyTestQuery_ReverseIPv6NXDomain:
+               {
+                       unsigned int            hostID;
+                       uint8_t                         ipv6Addr[ 16 ];
+                       char                            reverseIPv6NameStr[ kReverseIPv6DomainNameBufLen ];
+                       
+                       // To force mDNSResponder to have to send a query on the first query iteration, use a different reverse IPv6
+                       // PTR query for each mode/DNS64 prefix/transport combination.
+                       
+                       check_compile_time_code( kDNSProxyTestTransport_Count <= 4 );
+                       check_compile_time_code( countof( kDNSProxyTestParams_DNS64Prefixes ) <= 8 );
+                       check_compile_time_code( kDNSProxyTestMode_Count <= 4 );
+                       hostID  =   ( (unsigned int) me->transportParam ) & 0x03;                       // Set bits 1 - 0 to transport param.
+                       hostID |= (   me->prefixParamIdx                  & 0x07 ) << 2;        // Set bits 4 - 2 to prefix param index.
+                       hostID |= ( ( (unsigned int) me->modeParam )      & 0x03 ) << 5;        // Set bits 6 - 5 to mode param.
+                       hostID |= 1U << 7;                                                                                                      // Set bit 7 to ensure a non-zero hostID.
+                       check( ( hostID >= 1 ) && ( hostID <= 255 ) );
+                       
+                       memcpy( ipv6Addr, kDNSServerBaseAddrV6, 16 );
+                       ipv6Addr[ 15 ] = (uint8_t) hostID;
+                       
+                       me->qtype = kDNSRecordType_PTR;
+                       if( me->queryParam == kDNSProxyTestQuery_ReverseIPv6 )
+                       {
+                               char                            answerNameStr[ 128 ];
+                               char *                          dst = answerNameStr;
+                               char * const            lim = &answerNameStr[ countof( answerNameStr ) ];
+                               int                                     i;
+                               
+                               me->responseCode = kDNSRCode_NoError;
+                               
+                               // Create the expected RDATA.
+                               
+                               SNPrintF_Add( &dst, lim, "ipv6" );
+                               for( i = 0; i < 8; ++i )
+                               {
+                                       SNPrintF_Add( &dst, lim, "-%04x", ReadBig16( &ipv6Addr[ i * 2 ] ) );
+                               }
+                               SNPrintF_Add( &dst, lim, ".d.test." );
+                               err = DomainNameFromString( tmpName, answerNameStr, NULL );
+                               require_noerr_quiet( err, exit );
+                               
+                               err = DomainNameDup( tmpName, &me->answerName, NULL );
+                               require_noerr( err, exit );
+                       }
+                       else
+                       {
+                               me->responseCode        = kDNSRCode_NXDomain;
+                               me->answerName          = NULL;
+                               
+                               // Make the IPv6 address invalid by setting bits 15-8. This makes the host identifier bogus.
+                               // The DNS server's IPv6 prefix is 96 bits, but only host identifiers in [1, 255] are recognized.
+                               
+                               ipv6Addr[ 14 ] = 0xFF;
+                       }
+                       _WriteReverseIPv6DomainNameString( ipv6Addr, reverseIPv6NameStr );
+                       me->qnameStr = strdup( reverseIPv6NameStr );
+                       require_action( me->qnameStr, exit, err = kNoMemoryErr );
+                       break;
+               }
+               case kDNSProxyTestQuery_ReverseIPv6DNS64:
+               case kDNSProxyTestQuery_ReverseIPv6DNS64NXDomain:
+               {
+                       uint32_t        ipv4Addr;
+                       uint8_t         ipv6Addr[ 16 ];
+                       char            reverseIPNameStr[ kReverseIPv6DomainNameBufLen ];
+                       
+                       if( me->dns64PrefixBitLen <= 0 )
+                       {
+                               skipQuery = true;
+                               err = kNoErr;
+                               goto exit;
+                       }
+                       me->qtype = kDNSRecordType_PTR;
+                       if( me->queryParam == kDNSProxyTestQuery_ReverseIPv6DNS64 )
+                       {
+                               char            answerNameStr[ 64 ];
+                               
+                               me->responseCode = kDNSRCode_NoError;
+                               
+                               ipv4Addr = kDNSServerBaseAddrV4 + 1;
+                               _WriteReverseIPv4DomainNameString( ipv4Addr, reverseIPNameStr );
+                               
+                               err = DomainNameFromString( tmpName, reverseIPNameStr, NULL );
+                               require_noerr_quiet( err, exit );
+                               
+                               err = DomainNameDup( tmpName, &me->canonicalName, NULL );
+                               require_noerr( err, exit );
+                               
+                               SNPrintF( answerNameStr, sizeof( answerNameStr ), "ipv4-%u-%u-%u-%u.d.test.",
+                                       ( ipv4Addr >> 24 ) & 0xFF,
+                                       ( ipv4Addr >> 16 ) & 0xFF,
+                                       ( ipv4Addr >>  8 ) & 0xFF,
+                                         ipv4Addr         & 0xFF );
+                               err = DomainNameFromString( tmpName, answerNameStr, NULL );
+                               require_noerr_quiet( err, exit );
+                               
+                               err = DomainNameDup( tmpName, &me->answerName, NULL );
+                               require_noerr( err, exit );
+                       }
+                       else
+                       {
+                               ipv4Addr = kDNSServerBaseAddrV4 + 0;
+                               
+                               me->responseCode        = kDNSRCode_NXDomain;
+                               me->canonicalName       = NULL;
+                               me->answerName          = NULL;
+                       }
+                       err = _DNSProxyTestSynthesizeIPv6( me->dns64Prefix, me->dns64PrefixBitLen, ipv4Addr, ipv6Addr );
+                       require_noerr( err, exit );
+                       
+                       _WriteReverseIPv6DomainNameString( ipv6Addr, reverseIPNameStr );
+                       me->qnameStr = strdup( reverseIPNameStr );
+                       require_action( me->qnameStr, exit, err = kNoMemoryErr );
+                       break;
+               }
+               default:
+                       FatalErrorF( "Unhandled DNSProxyTestQuery value %ld", (long) me->queryParam );
+       }
+       check( !me->qname );
+       err = DomainNameFromString( tmpName, me->qnameStr, NULL );
+       require_noerr_quiet( err, exit );
+       
+       err = DomainNameDup( tmpName, &me->qname, NULL );
+       require_noerr( err, exit );
+       
+       check( !me->querier );
+       me->querier = mdns_resolver_create_querier( me->resolver, &err );
+       require_noerr( err, exit );
+       
+       err = mdns_querier_set_query( me->querier, me->qname, me->qtype, kDNSClassType_IN );
+       require_noerr( err, exit );
+       
+       mdns_querier_set_queue( me->querier, me->queue );
+       _DNSProxyTestRetain( me );
+       querier = me->querier;
+       mdns_retain( querier );
+       mdns_querier_set_result_handler( me->querier,
+       ^{
+               if( me->querier == querier ) _DNSProxyTestHandleQuerierResult( me );
+               _DNSProxyTestRelease( me );
+               mdns_release( querier );
+       } );
+       mdns_querier_activate( me->querier );
+       
+       check( !me->timer );
+       err = DispatchTimerOneShotCreate( dispatch_time_seconds( kDNSProxyTestQuerierTimeLimitSecs ),
+               kDNSProxyTestQuerierTimeLimitSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 10 ),
+               me->queue, _DNSProxyTestQuerierTimerHandler, me, &me->timer );
+       require_noerr( err, exit );
+       dispatch_resume( me->timer );
+       
+exit:
+       if( outSkipQuery ) *outSkipQuery = skipQuery;
+       return( err );
+}
+
+static void    _DNSProxyTestQuerierTimerHandler( void *inCtx )
+{
+       OSStatus                                        err;
+       const DNSProxyTestRef           me = (DNSProxyTestRef) inCtx;
+       Boolean                                         done;
+       
+       dpt_ulog( kLogLevelInfo, "Query for '%{du:dname}' timed out.\n", me->qname );
+       
+       err = _DNSProxyTestContinue( me, kTimeoutErr, &done );
+       check_noerr( err );
+       if( err || done ) _DNSProxyTestStop( me, err );
+}
+
+//===========================================================================================================================
+
+static OSStatus
+       _DNSProxyTestSynthesizeIPv6(
+               const uint8_t * inIPv6Prefix,
+               int                             inIPv6PrefixBitLen,
+               uint32_t                inIPv4Addr,
+               uint8_t                 outIPv6Addr[ STATIC_PARAM 16 ] )
+{
+       // From <https://tools.ietf.org/html/rfc6052#section-2.2>:
+       //
+       // 2.2.  IPv4-Embedded IPv6 Address Format
+       //
+       //   IPv4-converted IPv6 addresses and IPv4-translatable IPv6 addresses
+       //   follow the same format, described here as the IPv4-embedded IPv6
+       //   address Format.  IPv4-embedded IPv6 addresses are composed of a
+       //   variable-length prefix, the embedded IPv4 address, and a variable-
+       //   length suffix, as presented in the following diagram, in which PL
+       //   designates the prefix length:
+       //
+       //    +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+       //    |PL| 0-------------32--40--48--56--64--72--80--88--96--104---------|
+       //    +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+       //    |32|     prefix    |v4(32)         | u | suffix                    |
+       //    +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+       //    |40|     prefix        |v4(24)     | u |(8)| suffix                |
+       //    +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+       //    |48|     prefix            |v4(16) | u | (16)  | suffix            |
+       //    +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+       //    |56|     prefix                |(8)| u |  v4(24)   | suffix        |
+       //    +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+       //    |64|     prefix                    | u |   v4(32)      | suffix    |
+       //    +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+       //    |96|     prefix                                    |    v4(32)     |
+       //    +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+       //
+       //                                 Figure 1
+       switch( inIPv6PrefixBitLen )
+       {
+               case 32:
+               case 40:
+               case 48:
+               case 56:
+               case 64:
+               case 96:
+               {
+                       const int               prefixLen = inIPv6PrefixBitLen / 8;
+                       int                             i, j;
+                       uint8_t                 v4Addr[ 4 ];
+                       
+                       memcpy( outIPv6Addr, inIPv6Prefix, (size_t) prefixLen );
+                       WriteBig32( v4Addr, inIPv4Addr );
+                       
+                       // 1. Bits 64 - 71, i.e., reserved octet "u", MUST be zero.
+                       // 2. Except for bits 64 - 71, the 32 bits following the prefix are the bits of the embedded IPv4 address.
+                       // 3. The remaining bits, if any, are the suffix bits, which SHOULD be zero.
+                       
+                       j = 0;
+                       for( i = prefixLen; i < 16; ++i )
+                       {
+                               if( ( j < 4 ) && ( i != 8 ) )   outIPv6Addr[ i ] = v4Addr[ j++ ];
+                               else                                                    outIPv6Addr[ i ] = 0;
+                       }
+                       return( kNoErr );
+               }
+               default:
+                       return( kSizeErr );
+       }
+}
+
+//===========================================================================================================================
+
+static void    _DNSProxyTestHandleQuerierResult( DNSProxyTestRef me )
+{
+       OSStatus                                                                err, verifyErr;
+       const uint8_t *                                                 msgPtr;
+       size_t                                                                  msgLen;
+       const mdns_querier_result_type_t                resultType      = mdns_querier_get_result_type( me->querier );
+       Boolean                                                                 done            = false;
+       
+       if( resultType == mdns_querier_result_type_response )
+       {
+               msgPtr = mdns_querier_get_response_ptr( me->querier );
+               msgLen = mdns_querier_get_response_length( me->querier );
+               dpt_ulog( kLogLevelInfo, "Querier response: %.1{du:dnsmsg}\n", msgPtr, msgLen );
+       }
+       else
+       {
+               if( resultType == mdns_querier_result_type_error )
+               {
+                       err = mdns_querier_get_error( me->querier );
+                       if( !err ) err = kUnknownErr;
+               }
+               else
+               {
+                       err = kUnexpectedErr;
+               }
+               dpt_ulog( kLogLevelError, "Querier result: %s, error: %#m\n", mdns_querier_get_result_type( me->querier ), err );
+               goto exit;
+       }
+       switch( me->queryParam )
+       {
+               case kDNSProxyTestQuery_A:
+               case kDNSProxyTestQuery_AAAA:
+               case kDNSProxyTestQuery_IPv6OnlyA:
+               case kDNSProxyTestQuery_IPv6OnlyAAAA:
+               case kDNSProxyTestQuery_IPv4OnlyAAAA:
+               case kDNSProxyTestQuery_AliasA:
+               case kDNSProxyTestQuery_AliasAAAA:
+               case kDNSProxyTestQuery_AliasIPv6OnlyA:
+               case kDNSProxyTestQuery_AliasIPv6OnlyAAAA:
+               case kDNSProxyTestQuery_AliasIPv4OnlyAAAA:
+               case kDNSProxyTestQuery_NXDomainA:
+               case kDNSProxyTestQuery_NXDomainAAAA:
+                       verifyErr = _DNSProxyTestVerifyAddressResponse( msgPtr, msgLen, me->qname, me->qtype, me->responseCode,
+                               me->aliasCount, me->answerCount,
+                               me->synthesizedAAAA ? me->dns64Prefix : NULL,
+                               me->synthesizedAAAA ? me->dns64PrefixBitLen : 0 );
+                       break;
+               
+               case kDNSProxyTestQuery_ReverseIPv6:
+               case kDNSProxyTestQuery_ReverseIPv6NXDomain:
+               case kDNSProxyTestQuery_ReverseIPv6DNS64:
+               case kDNSProxyTestQuery_ReverseIPv6DNS64NXDomain:
+                       verifyErr = _DNSProxyTestVerifyReverseIPv6Response( msgPtr, msgLen, me->qname, me->responseCode,
+                               me->canonicalName, me->answerName );
+                       break;
+               
+               default:
+                       FatalErrorF( "Unhandled DNSProxyTestQuery value %ld", (long) me->queryParam );
+       }
+       err = _DNSProxyTestContinue( me, verifyErr, &done );
+       require_noerr( err, exit );
+       
+exit:
+       if( err || done ) _DNSProxyTestStop( me, err );
+}
+
+//===========================================================================================================================
+
+static OSStatus
+       _DNSProxyTestVerifyAddressResponse(
+               const uint8_t * inMsgPtr,
+               size_t                  inMsgLen,
+               const uint8_t * inQName,
+               uint16_t                inQType,
+               int                             inResponseCode,
+               unsigned int    inAliasCount,
+               unsigned int    inAnswerCount,
+               const uint8_t * inDNS64Prefix,
+               int                             inDNS64PrefixBitLen )
+{
+       OSStatus                                err;
+       const DNSHeader *               hdr;
+       const uint8_t *                 ptr;
+       const uint8_t *                 answerSection;
+       unsigned int                    flags, qCount, answerCount;
+       int                                             rcode;
+       uint16_t                                qtype, qclass;
+       uint8_t                                 qname[ kDomainNameLengthMax ];
+       
+       require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kMalformedErr );
+       
+       hdr             = (const DNSHeader *) inMsgPtr;
+       flags   = DNSHeaderGetFlags( hdr );
+       rcode   = DNSFlagsGetRCode( flags );
+       require_action_quiet( rcode == inResponseCode, exit, err = kValueErr );
+       
+       qCount = DNSHeaderGetQuestionCount( hdr );
+       require_action_quiet( qCount == 1, exit, err = kCountErr );
+       
+       ptr = (const uint8_t *) &hdr[ 1 ];
+       err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, qname, &qtype, &qclass, &ptr );
+       require_noerr_quiet( err, exit );
+       require_action_quiet( DomainNameEqual( qname, inQName ), exit, err = kNameErr );
+       require_action_quiet( qtype  == inQType, exit, err = kTypeErr );
+       require_action_quiet( qclass == kDNSClassType_IN, exit, kTypeErr );
+       
+       answerCount = DNSHeaderGetAnswerCount( hdr );
+       require_action_quiet( answerCount == ( inAliasCount + inAnswerCount ), exit, err = kCountErr );
+       
+       answerSection = ptr;
+       if( inAliasCount > 0 )
+       {
+               unsigned int            i;
+               const uint8_t *         parentDomain;
+               uint8_t                         target[ kDomainNameLengthMax ];
+               
+               parentDomain = DomainNameGetNextLabel( inQName );
+               require_fatal( parentDomain, "Invalid qname '%{du:dname}' for non-zero alias count.", inQName );
+               
+               target[ 0 ] = 0;
+               err = DomainNameAppendDomainName( target, inQName, NULL );
+               require_noerr( err, exit );
+               
+               for( i = 0; i < inAliasCount; ++i )
+               {
+                       unsigned int                    j;
+                       const unsigned int              aliasNumber     = inAliasCount - i;
+                       Boolean                                 foundCNAME      = false;
+                       
+                       ptr = answerSection;
+                       for( j = 0; j < answerCount; ++j )
+                       {
+                               const uint8_t *         rdataPtr;
+                               unsigned int            nextAliasNumber;
+                               uint16_t                        type;
+                               uint16_t                        class;
+                               uint8_t                         name[ kDomainNameLengthMax ];
+                               char                            aliasLabelStr[ 32 ];
+                               
+                               err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, name, &type, &class, NULL, &rdataPtr, NULL, &ptr );
+                               require_noerr( err, exit );
+                               
+                               if( type  != kDNSRecordType_CNAME )             continue;
+                               if( class != kDNSClassType_IN )         continue;
+                               if( !DomainNameEqual( name, target ) )  continue;
+                               
+                               target[ 0 ] = 0;
+                               nextAliasNumber = aliasNumber - 1;
+                               if( nextAliasNumber >= 2 )
+                               {
+                                       SNPrintF( aliasLabelStr, sizeof( aliasLabelStr ), "alias-%u", nextAliasNumber );
+                                       err = DomainNameAppendString( target, aliasLabelStr, NULL );
+                                       require_noerr( err, exit );
+                               }
+                               else if( nextAliasNumber == 1 )
+                               {
+                                       SNPrintF( aliasLabelStr, sizeof( aliasLabelStr ), "alias" );
+                                       err = DomainNameAppendString( target, aliasLabelStr, NULL );
+                                       require_noerr( err, exit );
+                               }
+                               err = DomainNameAppendDomainName( target, parentDomain, NULL );
+                               require_noerr( err, exit );
+                               
+                               err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, rdataPtr, name, NULL );
+                               require_noerr( err, exit );
+                               
+                               if( !DomainNameEqual( name, target ) ) continue;
+                               foundCNAME = true;
+                               break;
+                       }
+                       require_action_quiet( foundCNAME, exit, err = kNotFoundErr );
+               }
+       }
+       if( inAnswerCount > 0 )
+       {
+               const uint8_t *         target;
+               unsigned int            i;
+               
+               if( inAliasCount > 0 )
+               {
+                       target = DomainNameGetNextLabel( inQName );
+                       require_fatal( target, "Invalid qname '%{du:dname}' for non-zero alias count.", inQName );
+               }
+               else
+               {
+                       target = inQName;
+               }
+               for( i = 0; i < inAnswerCount; ++i )
+               {
+                       unsigned int            j;
+                       size_t                          expectedLen;
+                       uint8_t                         expectedData[ 16 ];
+                       Boolean                         foundRecord = false;
+                       
+                       if( inQType == kDNSRecordType_A )
+                       {
+                               const uint32_t          ipv4Addr = kDNSServerBaseAddrV4 + ( i + 1 );
+                               
+                               WriteBig32( expectedData, ipv4Addr );
+                               expectedLen = 4;
+                       }
+                       else
+                       {
+                               if( inDNS64PrefixBitLen != 0 )
+                               {
+                                       const uint32_t          ipv4Addr = kDNSServerBaseAddrV4 + ( i + 1 );
+                                       
+                                       err = _DNSProxyTestSynthesizeIPv6( inDNS64Prefix, inDNS64PrefixBitLen, ipv4Addr, expectedData );
+                                       require_noerr( err, exit );
+                               }
+                               else
+                               {
+                                       memcpy( expectedData, kDNSServerBaseAddrV6, 16 );
+                                       expectedData[ 15 ] = (uint8_t)( i + 1 );
+                               }
+                               expectedLen = 16;
+                       }
+                       ptr = answerSection;
+                       for( j = 0; j < answerCount; ++j )
+                       {
+                               const uint8_t *         rdataPtr;
+                               size_t                          rdataLen;
+                               uint16_t                        type;
+                               uint16_t                        class;
+                               uint8_t                         name[ kDomainNameLengthMax ];
+                               
+                               err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, name, &type, &class, NULL, &rdataPtr, &rdataLen,
+                                       &ptr );
+                               require_noerr( err, exit );
+                               
+                               if( type  != inQType )                                                                                          continue;
+                               if( class != kDNSClassType_IN )                                                                         continue;
+                               if( !DomainNameEqual( name, target ) )                                                          continue;
+                               if( !MemEqual( rdataPtr, rdataLen, expectedData, expectedLen ) )        continue;
+                               foundRecord = true;
+                               break;
+                       }
+                       require_action_quiet( foundRecord, exit, err = kNotFoundErr );
+               }
+       }
+       
+exit:
+       return( err );
+}
 
-            context->counter++;
-            if ( context->counter == TEST_REPETITION )
-            {
-                context->state = TEST_SUCCEEDED;
-            }
-            else
-            {
-                context->subtestProgress_startTime  = NanoTimeGetCurrent();
-                context->isExpensivePrev            = context->isExpensiveNow;
-                context->isExpensiveNow             = !context->isExpensiveNow;
-                context->isConstrainedPrev          = context->isConstrainedNow;
-                context->isConstrainedNow           = !context->isConstrainedNow;
-                if (gExpensiveConstrainedTest_DenyConstrained || gExpensiveConstrainedTest_DenyExpensive)
-                    context->expectedOperation      = context->isExpensiveNow ? RESULT_RMV : RESULT_ADD;
-                else
-                    context->expectedOperation      = NO_UPDATE;
-                context->operation                  = NO_UPDATE;
-                context->subtestProgress_callBack   = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
-                require_action( context->subtestProgress_callBack != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" );
+//===========================================================================================================================
 
-                err = systemf( NULL, "ifconfig %s %sexpensive && ifconfig %s %sconstrained", context->ifName, context->isExpensiveNow ? "" : "-", context->ifName, context->isConstrainedNow ? "" : "-" );
-                require_noerr_action( err, test_failed, errorDescription = "systemf failed" );
+static OSStatus
+       _DNSProxyTestFindDomainNameRecord(
+               const uint8_t * inMsgPtr,
+               size_t                  inMsgLen,
+               const uint8_t * inRecordSection,
+               unsigned int    inRecordCount,
+               const uint8_t * inRecordName,
+               uint16_t                inRecordType,
+               const uint8_t * inRDataName );
 
-                gettimeofday( &context->updateTime, NULL );
-            }
-            break;
-        case TEST_FAILED:
-        test_failed:
-            ExpensiveConstrainedSubtestReport( context, errorDescription );
-            ExpensiveConstrainedStopAndCleanTheTest( context );
-            if ( context->numOfRetries > 0 )
-            {
-                context->state = TEST_BEGIN;
-                context->numOfRetries--;
-                break;
-            }
-            ExpensiveConstrainedSubtestParams[context->subtestIndex++].test_passed = 0;
-            if (context->subtestIndex == (int) countof( ExpensiveConstrainedSubtestParams ))
-            {
-                ExpensiveConstrainedFinalResultReport( context, false );
-                exit( 2 );
-            }
-            if (context->timer == NULL)
-            {
-                // If timer is NULL, it means that we encounter error before we set up the test handler, which is unrecoverable.
-                ExpensiveConstrainedFinalResultReport( context, false );
-                exit( 1 );
-            }
-            context->state = TEST_BEGIN;
-            break;
-        case TEST_SUCCEEDED:
-            ExpensiveConstrainedSubtestReport( context, NULL );
-            ExpensiveConstrainedStopAndCleanTheTest( context );
-            ExpensiveConstrainedSubtestParams[context->subtestIndex++].test_passed = 1;
-            if (context->subtestIndex == (int) countof( ExpensiveConstrainedSubtestParams ))
-            {
-                // all the subtests have been run
-                Boolean hasFailed = false;
-                for ( int i = 0; i < (int) countof( ExpensiveConstrainedSubtestParams ) && !hasFailed; i++ )
-                    hasFailed = ( ExpensiveConstrainedSubtestParams[i].test_passed != 1 );
+static OSStatus
+       _DNSProxyTestVerifyReverseIPv6Response(
+               const uint8_t * inMsgPtr,
+               size_t                  inMsgLen,
+               const uint8_t * inQName,
+               int                             inResponseCode,
+               const uint8_t * inCanonicalName,
+               const uint8_t * inAnswerName )
+{
+       OSStatus                                err;
+       const DNSHeader *               hdr;
+       const uint8_t *                 ptr;
+       const uint8_t *                 answerSection;
+       const uint8_t *                 ownerName;
+       unsigned int                    flags, qCount, answerCount, answerCountExpected;
+       int                                             rcode;
+       uint16_t                                qtype, qclass;
+       uint8_t                                 qname[ kDomainNameLengthMax ];
+       
+       require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kMalformedErr );
+       
+       hdr             = (const DNSHeader *) inMsgPtr;
+       flags   = DNSHeaderGetFlags( hdr );
+       rcode   = DNSFlagsGetRCode( flags );
+       require_action_quiet( rcode == inResponseCode, exit, err = kValueErr );
+       
+       qCount = DNSHeaderGetQuestionCount( hdr );
+       require_action_quiet( qCount == 1, exit, err = kCountErr );
+       
+       ptr = (const uint8_t *) &hdr[ 1 ];
+       err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, qname, &qtype, &qclass, &ptr );
+       require_noerr_quiet( err, exit );
+       require_action_quiet( DomainNameEqual( qname, inQName ), exit, err = kNameErr );
+       require_action_quiet( qtype  == kDNSRecordType_PTR, exit, err = kTypeErr );
+       require_action_quiet( qclass == kDNSClassType_IN, exit, kTypeErr );
+       
+       answerCountExpected     = ( inCanonicalName ? 1 : 0 ) + ( inAnswerName ? 1 : 0 );
+       answerCount                     = DNSHeaderGetAnswerCount( hdr );
+       require_action_quiet( answerCount == answerCountExpected, exit, err = kCountErr );
+       
+       answerSection   = ptr;
+       ownerName               = inQName;
+       if( inCanonicalName )
+       {
+               err = _DNSProxyTestFindDomainNameRecord( inMsgPtr, inMsgLen, answerSection, answerCount, ownerName,
+                       kDNSRecordType_CNAME, inCanonicalName );
+               require_noerr( err, exit );
+               
+               ownerName = inCanonicalName;
+       }
+       if( inAnswerName )
+       {
+               err = _DNSProxyTestFindDomainNameRecord( inMsgPtr, inMsgLen, answerSection, answerCount, ownerName,
+                       kDNSRecordType_PTR, inAnswerName );
+               require_noerr( err, exit );
+       }
+       
+exit:
+       return( err );
+}
 
-                ExpensiveConstrainedFinalResultReport( context, !hasFailed );
-                exit( hasFailed ? 2 : 0 );
-            }
-            context->state = TEST_BEGIN;
-            break;
-        default:
-            FPrintF( stdout, "unknown error\n" );
-            exit( 1 );
-    }
+static OSStatus
+       _DNSProxyTestFindDomainNameRecord(
+               const uint8_t * inMsgPtr,
+               size_t                  inMsgLen,
+               const uint8_t * inRecordSection,
+               unsigned int    inRecordCount,
+               const uint8_t * inRecordName,
+               uint16_t                inRecordType,
+               const uint8_t * inRDataName )
+{
+       OSStatus                        err;
+       const uint8_t *         ptr;
+       unsigned int            i;
+       Boolean                         found = false;
+       
+       ptr = inRecordSection;
+       for( i = 0; i < inRecordCount; ++i )
+       {
+               const uint8_t *         rdataPtr;
+               uint16_t                        type;
+               uint16_t                        class;
+               uint8_t                         name[ kDomainNameLengthMax ];
+               
+               err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, name, &type, &class, NULL, &rdataPtr, NULL, &ptr );
+               require_noerr( err, exit );
+               
+               if( type  != inRecordType )                                             continue;
+               if( class != kDNSClassType_IN )                                 continue;
+               if( !DomainNameEqual( name, inRecordName ) )    continue;
+               
+               err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, rdataPtr, name, NULL );
+               require_noerr( err, exit );
+               
+               if( !DomainNameEqual( name, inRDataName ) ) continue;
+               found = true;
+               break;
+       }
+       err = found ? kNoErr : kNotFoundErr;
+       
+exit:
+       return( err );
 }
 
-//===========================================================================================================================
-//    ExpensiveConstrainedCallback
 //===========================================================================================================================
 
 static void DNSSD_API
-    ExpensiveConstrainedCallback(
-        __unused DNSServiceRef  inSDRef,
-        DNSServiceFlags         inFlags,
-        uint32_t                inInterfaceIndex,
-        DNSServiceErrorType     inError,
-        const char *            inHostname,
-        const struct sockaddr * inSockAddr,
-        __unused uint32_t       inTTL,
-        void *                  inContext )
+       _DNSProxyTestProbeGAICallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inHostname,
+               const struct sockaddr * inSockAddr,
+               uint32_t                                inTTL,
+               void *                                  inCtx )
 {
-    ExpensiveConstrainedContext * const   context = (ExpensiveConstrainedContext *)inContext;
-    OSStatus                                    err;
-    const char *                                addrStr;
-    char                                        addrStrBuf[ kSockAddrStringMaxSize ];
-    char                                        inFlagsDescription[ 128 ];
-    NanoTime64                                  now;
-    char                                        nowTimestamp[ 32 ];
+       OSStatus                                        err;
+       const DNSProxyTestRef           me              = (DNSProxyTestRef) inCtx;
+       Boolean                                         done    = false;
+       
+       Unused( inSDRef );
+       Unused( inInterfaceIndex );
+       Unused( inHostname );
+       Unused( inTTL );
+       
+       if( ( inFlags & kDNSServiceFlagsAdd ) && !inError )
+       {
+               DNSServiceForget( &me->probeGAI );
+               dispatch_source_forget( &me->timer );
+               
+               dpt_ulog( kLogLevelInfo, "Probe: Got GAI address %##a for %s\n", inSockAddr, me->probeHostname );
+               
+               err = _DNSProxyTestContinue( me, kNoErr, &done );
+               require_noerr( err, exit );
+       }
+       err = kNoErr;
+       
+exit:
+       if( err || done ) _DNSProxyTestStop( me, err );
+}
 
-    switch ( inError ) {
-        case kDNSServiceErr_NoError:
-        case kDNSServiceErr_NoSuchRecord:
-            break;
+//===========================================================================================================================
 
-        case kDNSServiceErr_Timeout:
-            Exit( kExitReason_Timeout );
+static void    _DNSProxyTestProbeTimerHandler( void *inCtx )
+{
+       const DNSProxyTestRef           me = (DNSProxyTestRef) inCtx;
+       
+       dpt_ulog( kLogLevelInfo, "Probe: GAI request for '%s' timed out.\n", me->probeHostname );
+       _DNSProxyTestStop( me, kNotPreparedErr );
+}
 
-        default:
-            err = inError;
-            goto exit;
-    }
+//===========================================================================================================================
+//     RCodeTestCmd
+//===========================================================================================================================
 
-    if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
-    {
-        dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
-        err = kTypeErr;
-        goto exit;
-    }
+#define kRCodeTestMaxRCodeValue                15
 
-    if( !inError )
-    {
-        err = SockAddrToString( inSockAddr, kSockAddrStringFlagsNone, addrStrBuf );
-        require_noerr( err, exit );
-        addrStr = addrStrBuf;
-    }
-    else
-    {
-        addrStr = ( inSockAddr->sa_family == AF_INET ) ? kNoSuchRecordAStr : kNoSuchRecordAAAAStr;
-    }
+typedef struct RCodeTest *             RCodeTestRef;
+struct RCodeTest
+{
+       dispatch_queue_t                        queue;                          // Serial queue for test events.
+       dispatch_semaphore_t            doneSem;                        // Semaphore to signal when the test is done.
+       dnssd_getaddrinfo_t                     gai;                            // Current subtest's GAI object. (Also used for probing test DNS server.)
+       dispatch_source_t                       timer;                          // Timer for enforcing time limit on current dnssd_getaddrinfo.
+       CFMutableDictionaryRef          report;                         // Test's report, as a plist.
+       CFMutableArrayRef                       subtestResults;         // Pointer to report's subtest results.
+       CFMutableArrayRef                       gaiResults;                     // Array of dnssd_getaddrinfo_result objects for the current GAI.
+       char *                                          hostname;                       // Current subtest's hostname. (Also used for probing test DNS server.)
+       char *                                          description;            // Current subtest description.
+       NanoTime64                                      startTime;                      // Current subtest's start time.
+       pid_t                                           serverPID;                      // PID of spawned test DNS server.
+       OSStatus                                        error;                          // Current test error.
+       int32_t                                         refCount;                       // Test's reference count.
+       int                                                     rcode;                          // Argument to use in current subtest's hostname's RCODE label.
+       int                                                     indexIdx;                       // Index of argument to use in current subtest's hostname's Index label.
+       int                                                     subtestCount;           // Number of subtests that have completed or are in progress.
+       int                                                     subtestPassCount;       // Number of subtests that have passed so far.
+       Boolean                                         hostnameIsAlias;        // True if current subtest's hostname is an alias.
+       Boolean                                         hostnameHasAddr;        // True if current subtest's hostname that has an IPv4 address.
+       Boolean                                         done;                           // True if all subtests have completed.
+};
 
-    now = NanoTimeGetCurrent();
-    _NanoTime64ToTimestamp( now, nowTimestamp, sizeof( nowTimestamp ) );
-    SNPrintF( inFlagsDescription, sizeof( inFlagsDescription ), "%{du:cbflags}", inFlags );
-    err = CFPropertyListAppendFormatted( kCFAllocatorDefault,  context->subtestProgress_callBack,
-        "{"
-            "%kO=%s"
-            "%kO=%s"
-            "%kO=%s"
-            "%kO=%lli"
-            "%kO=%s"
-        "}",
-        EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_TIMESTAMP,  nowTimestamp,
-        EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_NAME,       inHostname,
-        EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_FLAGS,      inFlagsDescription,
-        EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_INTERFACE,  (int64_t) inInterfaceIndex,
-        EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_ADDRESS,    addrStr
-    );
-    require_noerr_quiet( err, exit );
+ulog_define_ex( kDNSSDUtilIdentifier, RCodeTest, kLogLevelInfo, kLogFlags_None, "RCodeTest", NULL );
+#define rct_ulog( LEVEL, ... )         ulog( &log_category_from_name( RCodeTest ), (LEVEL), __VA_ARGS__ )
 
-    if ( inFlags & kDNSServiceFlagsMoreComing )
-        return;
+static OSStatus        _RCodeTestCreate( RCodeTestRef *outTest );
+static OSStatus        _RCodeTestRun( RCodeTestRef inTest, Boolean *outPassed );
+static void            _RCodeTestRetain( RCodeTestRef inTest );
+static void            _RCodeTestRelease( RCodeTestRef inTest );
+
+static void    RCodeTestCmd( void )
+{
+       OSStatus                                err;
+       OutputFormatType                outputFormat;
+       RCodeTestRef                    test    = NULL;
+       Boolean                                 passed  = false;
+       
+       err = CheckRootUser();
+       require_noerr_quiet( err, exit );
+       
+       err = OutputFormatFromArgString( gRCodeTest_OutputFormat, &outputFormat );
+       require_noerr_quiet( err, exit );
+       
+       err = _RCodeTestCreate( &test );
+       require_noerr( err, exit );
+       
+       err = _RCodeTestRun( test, &passed );
+       require_noerr( err, exit );
+       
+       err = OutputPropertyList( test->report, outputFormat, gRCodeTest_OutputFilePath );
+       require_noerr( err, exit );
+       
+exit:
+       if( test ) _RCodeTestRelease( test );
+    gExitCode = err ? 1 : ( passed ? 0 : 2 );
+}
 
-    if ( inFlags & kDNSServiceFlagsAdd )
-        context->operation = RESULT_ADD;
-    else
-        context->operation = RESULT_RMV;
+//===========================================================================================================================
 
-    gettimeofday(&context->notificationTime, NULL);
+static OSStatus        _RCodeTestCreate( RCodeTestRef *outTest )
+{
+       OSStatus                        err;
+       RCodeTestRef            obj;
+       
+       obj = (RCodeTestRef) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
+       
+       obj->refCount   = 1;
+       obj->error              = kInProgressErr;
+       obj->serverPID  = -1;
+       
+       obj->queue = dispatch_queue_create( "com.apple.dnssdutil.rcode-test", DISPATCH_QUEUE_SERIAL );
+       require_action( obj->queue, exit, err = kNoResourcesErr );
+       
+       obj->doneSem = dispatch_semaphore_create( 0 );
+       require_action( obj->doneSem, exit, err = kNoResourcesErr );
+       
+       obj->report = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
+       require_action( obj->report, exit, err = kNoMemoryErr );
+       
+       obj->indexIdx = -1;
+       *outTest = obj;
+       obj = NULL;
+       err = kNoErr;
+       
 exit:
-    if( err ) exit( 1 );
+       if( obj ) _RCodeTestRelease( obj );
+       return( err );
 }
 
-//===========================================================================================================================
-//    ExpensiveConstrainedInitializeContext
 //===========================================================================================================================
 
-static void ExpensiveConstrainedInitializeContext( ExpensiveConstrainedContext *context )
-{
-    // clear the flags of the previous subtest
-    context->flags      = 0;
-    context->protocols  = 0;
+static void            _RCodeTestStart( void *inCtx );
+static void            _RCodeTestStop( RCodeTestRef inTest, OSStatus inError );
+static OSStatus        _RCodeTestStartSubtest( RCodeTestRef inTest );
+static OSStatus        _RCodeTestContinue( RCodeTestRef inType, OSStatus inSubtestError, Boolean *outDone );
+static Boolean _RCodeTestRCodeIsGood( const int inRCode );
 
-    // get the parameter for the current subtest
-    const ExpensiveConstrainedTestParams *param     = &ExpensiveConstrainedSubtestParams[context->subtestIndex];
-    gExpensiveConstrainedTest_Name                  = param->qname;
-    gExpensiveConstrainedTest_DenyExpensive         = param->deny_expensive;
-    gExpensiveConstrainedTest_DenyConstrained       = param->deny_constrained;
-    gExpensiveConstrainedTest_StartFromExpensive    = param->start_from_expensive;
-    gExpensiveConstrainedTest_ProtocolIPv4          = param->ipv4_query;
-    gExpensiveConstrainedTest_ProtocolIPv6          = param->ipv6_query;
+static OSStatus        _RCodeTestRun( RCodeTestRef me, Boolean *outPassed )
+{
+       Boolean         passed;
+       
+       dispatch_async_f( me->queue, me, _RCodeTestStart );
+       dispatch_semaphore_wait( me->doneSem, DISPATCH_TIME_FOREVER );
+       
+       passed = ( !me->error && ( me->subtestPassCount == me->subtestCount ) ) ? true : false;
+       CFDictionarySetBoolean( me->report, CFSTR( "pass" ), passed );
+       rct_ulog( kLogLevelInfo, "Test result: %s\n", passed ? "pass" : "fail" );
+       
+       if( outPassed ) *outPassed = passed;
+       return( me->error );
 }
 
-//===========================================================================================================================
-//    ExpensiveConstrainedStopAndCleanTheTest
 //===========================================================================================================================
 
-static void ExpensiveConstrainedStopAndCleanTheTest( ExpensiveConstrainedContext *context )
+static void    _RCodeTestRetain( const RCodeTestRef me )
 {
-    // Stop the ongoing query
-    if ( context->opRef != NULL )
-        DNSServiceRefDeallocate( context->opRef );
-
-    context->opRef      = NULL;
-    context->flags      = 0;
-    context->protocols  = 0;
+       atomic_add_32( &me->refCount, 1 );
 }
 
-//===========================================================================================================================
-//    ExpensiveConstrainedSubtestProgressReport
 //===========================================================================================================================
 
-static void ExpensiveConstrainedSubtestProgressReport( ExpensiveConstrainedContext *context )
+static void    _RCodeTestRelease( const RCodeTestRef me )
 {
-    OSStatus            err;
-    NanoTime64          now;
-    char                startTime[ 32 ];
-    char                endTime[ 32 ];
-    char                expensive[ 32 ];
-    char                constrained[ 32 ];
+       if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 )
+       {
+               check( !me->gai );
+               check( !me->timer );
+               check( !me->subtestResults );
+               check( !me->gaiResults );
+               check( !me->hostname );
+               check( !me->description );
+               check( me->serverPID < 0 );
+               dispatch_forget( &me->queue );
+               dispatch_forget( &me->doneSem );
+               ForgetCF( &me->report );
+               free( me );
+       }
+}
 
-    now = NanoTimeGetCurrent();
-    _NanoTime64ToTimestamp( context->subtestProgress_startTime, startTime, sizeof( startTime ) );
-    _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) );
+//===========================================================================================================================
 
-    snprintf( expensive, sizeof( expensive ), "%s -> %s", context->isExpensivePrev ? "True" : "False", context->isExpensiveNow ? "True" : "False" );
-    snprintf( constrained, sizeof( constrained ), "%s -> %s", context->isConstrainedPrev ? "True" : "False", context->isConstrainedNow ? "True" : "False" );
+#define kRCodeTestProbeQueryTimeoutSecs                5
 
-    err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->subtestProgress,
-        "{"
-            "%kO=%s"
-            "%kO=%s"
-            "%kO=%s"
-            "%kO=%s"
-            "%kO=%s"
-            "%kO=%s"
-            "%kO=%s"
-            "%kO=%O"
-        "}",
-        EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_START_TIME,              startTime,
-        EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_END_TIME,                endTime,
-        EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_STATE,                   ExpensiveConstrainedStateString(context->state),
-        EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_EXPECT_RESULT,           ExpensiveConstrainedOperationString(context->expectedOperation),
-        EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_ACTUAL_RESULT,           ExpensiveConstrainedOperationString(context->operation),
-        EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_EXPENSIVE_PREV_NOW,      expensive,
-        EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_CONSTRAINED_PREV_NOW,    constrained,
-        EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_CALL_BACK,               context->subtestProgress_callBack
-    );
-    require_noerr( err, exit );
-    ForgetCF( &context->subtestProgress_callBack );
-    return;
+static void    _RCodeTestHandleGAIProbeResults( RCodeTestRef inTest, dnssd_getaddrinfo_result_t *inResults, size_t inCount );
+static void    _RCodeTestProbeQueryTimerHandler( void *inCtx );
 
+static void    _RCodeTestStart( void * const inCtx )
+{
+       OSStatus                                err;
+       const RCodeTestRef              me                      = (RCodeTestRef) inCtx;
+       char *                                  serverCmd       = NULL;
+       dnssd_getaddrinfo_t             gai;
+       NanoTime64                              startTime;
+       char                                    startTimeStr[ 32 ];
+       char                                    tag[ 6 + 1 ];
+       
+       startTime = NanoTimeGetCurrent();
+       rct_ulog( kLogLevelInfo, "Starting test\n" );
+       
+       // The "dnssdutil server" command will create a resolver entry for the server's "d.test." domain containing an array
+       // of the server's IP addresses. Because configd favors IPv6 addresses, when there's a mix of IPv4 and IPv6
+       // addresses, configd may rearrange the array in order to ensure that IPv6 addresses come before the IPv4 addresses.
+       // To preserve the original address order, the server is specified to run in IPv6-only mode. This way,
+       // mDNSResponder's view of the address will be such that address with index value 1 is first, address with index
+       // value 2 is second, etc.
+       
+       serverCmd = NULL;
+       ASPrintF( &serverCmd,
+               "dnssdutil server --loopback --follow %lld --responseDelay 20 --ipv6 --extraIPv6 3", (int64_t) getpid() );
+       require_action_quiet( serverCmd, exit, err = kNoMemoryErr );
+       
+       err = _SpawnCommand( &me->serverPID, "/dev/null", "/dev/null", "%s", serverCmd );
+       require_noerr( err, exit );
+       
+       check( !me->hostname );
+       me->hostname = NULL;
+       ASPrintF( &me->hostname, "tag-rcode-test-probe-%s.ipv4.d.test.",
+               _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
+       require_action( me->hostname, exit, err = kNoMemoryErr );
+       
+       check( !me->gai );
+       me->gai = dnssd_getaddrinfo_create();
+       require_action( me->gai, exit, err = kNoResourcesErr );
+       
+       dnssd_getaddrinfo_set_hostname( me->gai, me->hostname );
+       dnssd_getaddrinfo_set_flags( me->gai, 0 );
+       dnssd_getaddrinfo_set_interface_index( me->gai, kDNSServiceInterfaceIndexAny );
+       dnssd_getaddrinfo_set_protocols( me->gai, kDNSServiceProtocol_IPv4 );
+       dnssd_getaddrinfo_set_queue( me->gai, me->queue );
+       gai = me->gai;
+       dnssd_retain( gai );
+       _RCodeTestRetain( me );
+       dnssd_getaddrinfo_set_result_handler( me->gai,
+       ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+       {
+               require_return( me->gai == gai );
+               _RCodeTestHandleGAIProbeResults( me, inResults, inCount );
+       } );
+       dnssd_getaddrinfo_set_event_handler( me->gai,
+       ^( const dnssd_event_t inEvent, const DNSServiceErrorType inGAIError )
+       {
+               if( inEvent == dnssd_event_invalidated )
+               {
+                       dnssd_release( gai );
+                       _RCodeTestRelease( me );
+               }
+               else if( inEvent == dnssd_event_error )
+               {
+                       require_return( me->gai == gai );
+                       rct_ulog( kLogLevelError, "dnssd_getaddrinfo error: %#m\n", inGAIError );
+                       _RCodeTestStop( me, inGAIError );
+               }
+       } );
+       dnssd_getaddrinfo_activate( me->gai );
+       
+       check( !me->timer );
+       err = DispatchTimerOneShotCreate( dispatch_time_seconds( kRCodeTestProbeQueryTimeoutSecs ),
+               kRCodeTestProbeQueryTimeoutSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 10 ),
+               me->queue, _RCodeTestProbeQueryTimerHandler, me, &me->timer );
+       require_noerr( err, exit );
+       dispatch_resume( me->timer );
+       
+       _NanoTime64ToTimestamp( startTime, startTimeStr, sizeof( startTimeStr ) );
+       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->report,
+               "%kO=%s"        // startTime
+               "%kO=%s"        // serverCmd
+               "%kO=%s"        // probeHostname
+               "%kO=[%@]",     // results
+               CFSTR( "startTime" ),           startTimeStr,
+               CFSTR( "serverCmd" ),           serverCmd,
+               CFSTR( "probeHostname" ),       me->hostname,
+               CFSTR( "results" ),                     &me->subtestResults );
+       require_noerr( err, exit );
+       
 exit:
-    ErrQuit( 1, "error: %#m\n", err );
+       FreeNullSafe( serverCmd );
+       if( err ) _RCodeTestStop( me, err );
+}
+
+static void
+       _RCodeTestHandleGAIProbeResults(
+               const RCodeTestRef                                      me,
+               dnssd_getaddrinfo_result_t * const      inResults,
+               const size_t                                            inCount )
+{
+       size_t          i;
+       Boolean         startSubtests = false;
+       
+       for( i = 0; i < inCount; ++i )
+       {
+               const dnssd_getaddrinfo_result_t                result = inResults[ i ];
+               
+               if( dnssd_getaddrinfo_result_get_type( result ) == dnssd_getaddrinfo_result_type_add )
+               {
+                       rct_ulog( kLogLevelInfo, "Probe GAI got %##a for %s\n",
+                               dnssd_getaddrinfo_result_get_address( result ), me->hostname );
+                       startSubtests = true;
+                       break;
+               }
+       }
+       if( startSubtests )
+       {
+               OSStatus                err;
+               
+               dnssd_getaddrinfo_forget( &me->gai );
+               dispatch_source_forget( &me->timer );
+               err = _RCodeTestStartSubtest( me );
+               if( err ) _RCodeTestStop( me, err );
+       }
+}
+
+static void    _RCodeTestProbeQueryTimerHandler( void * const inCtx )
+{
+       const RCodeTestRef              me = (RCodeTestRef) inCtx;
+       
+       rct_ulog( kLogLevelInfo, "Probe GAI request for '%s' timed out.\n", me->hostname );
+       _RCodeTestStop( me, kNotPreparedErr );
 }
 
 //===========================================================================================================================
-//    ExpensiveConstrainedFinalSubtestReport
+
+static void    _RCodeTestStop( RCodeTestRef me, OSStatus inError )
+{
+       OSStatus                err;
+       NanoTime64              endTime;
+       char                    endTimeStr[ 32 ];
+       
+       endTime = NanoTimeGetCurrent();
+       me->error = inError;
+       rct_ulog( kLogLevelInfo, "Stopping test with error: %#m\n", me->error );
+       
+       dnssd_getaddrinfo_forget( &me->gai );
+       dispatch_source_forget( &me->timer );
+       me->subtestResults = NULL;
+       ForgetCF( &me->gaiResults );
+       ForgetMem( &me->hostname );
+       ForgetMem( &me->description );
+       if( me->serverPID >= 0 )
+       {
+               OSStatus                killErr;
+               
+               killErr = kill( me->serverPID, SIGTERM );
+               killErr = map_global_noerr_errno( killErr );
+               check_noerr( killErr );
+               me->serverPID = -1;
+       }
+       _NanoTime64ToTimestamp( endTime, endTimeStr, sizeof( endTimeStr ) );
+       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->report,
+               "%kO=%s"                // endTime
+               "%kO=%lli"              // subtestCount
+               "%kO=%lli",             // subtestPassCount
+               CFSTR( "endTime" ),                             endTimeStr,
+               CFSTR( "subtestCount" ),                (int64_t) me->subtestCount,
+               CFSTR( "subtestPassCount" ),    (int64_t) me->subtestPassCount );
+       check_noerr( err );
+       if( err && !me->error ) me->error = err;
+       dispatch_semaphore_signal( me->doneSem );
+}
+
 //===========================================================================================================================
 
-static void ExpensiveConstrainedSubtestReport( ExpensiveConstrainedContext *context, const char *error_description )
+#define kRCodeSubtestRegularTimeLimitSecs              4
+#define kRCodeSubtestExtendedTimeLimitSecs             12      // Allow three seconds for each of the four server addresses.
+
+static void    _RCodeSubtestHandleGAIResults( RCodeTestRef inTest, dnssd_getaddrinfo_result_t *inResults, size_t inCount );
+static void    _RCodeSubtestTimerHandler( void *inCtx );
+
+static const void *    _DNSSDObjectCFArrayCallbackRetain( CFAllocatorRef inAllocator, const void *inObject );
+static void                    _DNSSDObjectCFArrayCallbackRelease( CFAllocatorRef inAllocator, const void *inObject );
+
+static const CFArrayCallBacks kDNSSDObjectArrayCallbacks =
 {
-    OSStatus            err;
-    NanoTime64          now;
-    char                startTime[ 32 ];
-    char                endTime[ 32 ];
-    char                flagDescription[ 1024 ];
+       .version        = 0,
+       .retain         = _DNSSDObjectCFArrayCallbackRetain,
+       .release        = _DNSSDObjectCFArrayCallbackRelease
+};
 
-    now = NanoTimeGetCurrent();
-    _NanoTime64ToTimestamp( context->subtestReport_startTime, startTime, sizeof( startTime ) );
-    _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) );
-    SNPrintF( flagDescription, sizeof( flagDescription ), "%#{flags}", context->flags, kDNSServiceFlagsDescriptors );
+// Arguments to use for Index labels. Since the test uses a test DNS server with four IPv6 addresses, which
+// mDNSResponder views as four different servers, this is an arbitrary mix of the four possible index values. An Index
+// label makes it so that only the server specified by the Index label's argument responds with the correct response.
+// The point of the mix is to force mDNSResponder to have to send queries to more than one server before it gets the
+// right response.
 
-    if (error_description != NULL)
-    {
-        err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->subtestReport,
-            "{"
-                "%kO=%s"
-                "%kO=%s"
-                "%kO=%s"
-                "%kO=%s"
-                "%kO=%s"
-                "%kO=%lli"
-                "%kO=%s"
-                "%kO=%O"
-                "%kO=%s"
-                "%kO=%O"
-            "}",
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_START_TIME,        startTime,
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_END_TIME,          endTime,
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_QNAME,             context->name,
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_FLAGS,             flagDescription,
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_PROTOCOLS,         ExpensiveConstrainedProtocolString( context->protocols ),
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_INDEX,   (int64_t) context->ifIndex,
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_NAME,    context->ifName,
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_RESULT,            CFSTR( "Fail" ),
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_ERROR,             error_description,
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_TEST_PROGRESS,     context->subtestProgress
-        );
-    }
-    else
-    {
-        err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->subtestReport,
-            "{"
-                "%kO=%s"
-                "%kO=%s"
-                "%kO=%s"
-                "%kO=%s"
-                "%kO=%s"
-                "%kO=%lli"
-                "%kO=%s"
-                "%kO=%O"
-                "%kO=%O"
-            "}",
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_START_TIME,        startTime,
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_END_TIME,          endTime,
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_QNAME,             context->name,
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_FLAGS,             flagDescription,
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_PROTOCOLS,         ExpensiveConstrainedProtocolString( context->protocols ),
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_INDEX,   (int64_t) context->ifIndex,
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_NAME,    context->ifName,
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_RESULT,            CFSTR( "Pass" ),
-            EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_TEST_PROGRESS,     context->subtestProgress
-        );
-    }
+static const int               kRCodeTestIndexArguments[] = { 2, 4, 1, 3, 2, 1, 4, 3 };
+#define kRCodeTestMaxIndexArgumentIndex                ( countof( kRCodeTestIndexArguments ) - 1 )
 
-    require_noerr( err, exit );
-    ForgetCF( &context->subtestProgress );
-    return;
+static OSStatus        _RCodeTestStartSubtest( const RCodeTestRef me )
+{
+       OSStatus                                err;
+       dnssd_getaddrinfo_t             gai;
+       const char *                    rcodeStr;
+       int                                             index;
+       unsigned int                    timeLimitSecs;
+       char                                    rcodeStrBuf[ 32 ];
+       char                                    indexLabelStr[ 32 ];
+       char                                    rcodeLabelStr[ 32 ];
+       char                                    tag[ 6 + 1 ];
+       
+       require_action_quiet( !me->done, exit, err = kInternalErr );
+       
+       check( !me->gai );
+       check( !me->timer );
+       
+       me->startTime = NanoTimeGetCurrent();
+       
+       if( me->indexIdx < 0 )
+       {
+               index = -1;
+               indexLabelStr[ 0 ] = '\0';
+               require_fatal( ( me->rcode >= 0 ) && ( me->rcode <= kRCodeTestMaxRCodeValue ),
+                       "Unexpected subtest rcode value %d.", me->rcode );
+       }
+       else
+       {
+               index = kRCodeTestIndexArguments[ me->indexIdx ];
+               SNPrintF( indexLabelStr, sizeof( indexLabelStr ), "index-%d.", index );
+               me->hostnameHasAddr = true;
+               me->hostnameIsAlias = true;
+               require_fatal( ( me->rcode >= -1 ) && ( me->rcode <= kRCodeTestMaxRCodeValue ),
+                       "Unexpected subtest rcode value %d.", me->rcode );
+       }
+       if( me->rcode > 0 )
+       {
+               SNPrintF( rcodeLabelStr, sizeof( rcodeLabelStr ), "rcode-%d.", me->rcode );
+       }
+       else
+       {
+               rcodeLabelStr[ 0 ] = '\0';
+       }
+       ForgetMem( &me->hostname );
+       ASPrintF( &me->hostname, "%s%s%scount-%d.tag-rcode-test-%s.d.test.",
+               me->hostnameIsAlias ? "alias." : "", indexLabelStr, rcodeLabelStr, me->hostnameHasAddr ? 1 : 0,
+               _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
+       require_action( me->hostname, exit, err = kNoMemoryErr );
+       
+       CFReleaseNullSafe( me->gaiResults );
+       me->gaiResults = CFArrayCreateMutable( NULL, 0, &kDNSSDObjectArrayCallbacks );
+       require_action( me->gaiResults, exit, err = kNoMemoryErr );
+       
+       me->gai = dnssd_getaddrinfo_create();
+       require_action( me->gai, exit, err = kNoResourcesErr );
+       
+       dnssd_getaddrinfo_set_hostname( me->gai, me->hostname );
+       dnssd_getaddrinfo_set_flags( me->gai, kDNSServiceFlagsReturnIntermediates );
+       dnssd_getaddrinfo_set_interface_index( me->gai, kDNSServiceInterfaceIndexAny );
+       dnssd_getaddrinfo_set_protocols( me->gai, kDNSServiceProtocol_IPv4 );
+       dnssd_getaddrinfo_set_queue( me->gai, me->queue );
+       gai = me->gai;
+       dnssd_retain( gai );
+       _RCodeTestRetain( me );
+       dnssd_getaddrinfo_set_result_handler( me->gai,
+       ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+       {
+               require_return( me->gai == gai );
+               _RCodeSubtestHandleGAIResults( me, inResults, inCount );
+       } );
+       dnssd_getaddrinfo_set_event_handler( gai,
+       ^( const dnssd_event_t inEvent, const DNSServiceErrorType inGAIError )
+       {
+               if( inEvent == dnssd_event_invalidated )
+               {
+                       dnssd_release( gai );
+                       _RCodeTestRelease( me );
+               }
+               else if( inEvent == dnssd_event_error )
+               {
+                       OSStatus                testErr;
+                       Boolean                 done;
+                       
+                       require_return( me->gai == gai );
+                       rct_ulog( kLogLevelError, "dnssd_getaddrinfo error: %#m\n", inGAIError );
+                       testErr = _RCodeTestContinue( me, inGAIError, &done );
+                       if( testErr || done ) _RCodeTestStop( me, testErr );
+               }
+       } );
+       
+       // If an Index label is being used without an RCode label, then only the server specified by the index label will
+       // respond to the query, so we need more time to allow for query retries due to unresponsive servers.
+       
+       if( ( index > 0 ) && ( me->rcode < 0 ) )
+       {
+               timeLimitSecs = kRCodeSubtestExtendedTimeLimitSecs;
+       }
+       else
+       {
+               timeLimitSecs = kRCodeSubtestRegularTimeLimitSecs;
+       }
+       check( !me->timer );
+       err = DispatchTimerOneShotCreate( dispatch_time_seconds( timeLimitSecs ),
+               timeLimitSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 20 ),
+               me->queue, _RCodeSubtestTimerHandler, me, &me->timer );
+       require_noerr( err, exit );
+       
+       rcodeStr = DNSRCodeToString( me->rcode );
+       if( !rcodeStr )
+       {
+               SNPrintF( rcodeStrBuf, sizeof( rcodeStrBuf ), "RCODE%d", me->rcode );
+               rcodeStr = rcodeStrBuf;
+       }
+       ForgetMem( &me->description );
+       if( me->indexIdx < 0 )
+       {
+               ASPrintF( &me->description, "DNS response with RCODE %s (%d), %s CNAME record, and %s A record from all servers",
+                       rcodeStr, me->rcode, me->hostnameIsAlias ? "one" : "no", me->hostnameHasAddr ? "one" : "no" );
+               require_action( me->description, exit, err = kNoMemoryErr );
+       }
+       else
+       {
+               int             n;
+               
+               ASPrintF( &me->description, "DNS response with RCODE NoError (0), %s CNAME record, and %s A record from server #%d.",
+                       me->hostnameIsAlias ? "one" : "no", me->hostnameHasAddr ? "one" : "no", index );
+               require_action( me->description, exit, err = kNoMemoryErr );
+               
+               if( me->rcode < 0 )
+               {
+                       n = AppendPrintF( &me->description, " No DNS respones from all other servers." );
+                       require_action( n >= 0, exit, err = kNoMemoryErr );
+               }
+               else
+               {
+                       n = AppendPrintF( &me->description, " DNS responses with RCODE %s (%d) and no records from all other servers.",
+                               rcodeStr, me->rcode );
+                       require_action( n >= 0, exit, err = kNoMemoryErr );
+               }
+       }
+       ++me->subtestCount;
+       rct_ulog( kLogLevelInfo, "Starting subtest #%d: %s\n", me->subtestCount, me->description );
+       
+       dnssd_getaddrinfo_activate( me->gai );
+       dispatch_resume( me->timer );
+       
 exit:
-    ErrQuit( 1, "error: %#m\n", err );
+       return( err );
 }
 
-//===========================================================================================================================
-//    ExpensiveConstrainedFinalResultReport
-//===========================================================================================================================
-
-static void ExpensiveConstrainedFinalResultReport( ExpensiveConstrainedContext *context, Boolean allPassed )
+static void
+       _RCodeSubtestHandleGAIResults(
+               const RCodeTestRef                                      me,
+               dnssd_getaddrinfo_result_t * const      inResults,
+               const size_t                                            inCount )
 {
-    OSStatus            err;
-    CFPropertyListRef   plist;
-    NanoTime64          now;
-    char                startTime[ 32 ];
-    char                endTime[ 32 ];
-
-    now = NanoTimeGetCurrent();
-    _NanoTime64ToTimestamp( context->testReport_startTime, startTime, sizeof( startTime ) );
-    _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) );
-
-    err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
-        "{"
-            "%kO=%s"
-            "%kO=%s"
-            "%kO=%b"
-            "%kO=%O"
-        "}",
-        EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_START_TIME,       startTime,
-        EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_END_TIME,         endTime,
-        EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_ALL_PASSED,       allPassed,
-        EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_SUBTEST_RESULT,   context->subtestReport
-    );
-    require_noerr( err, exit );
-    ForgetCF( &context->subtestReport );
-
-    err = OutputPropertyList( plist, context->outputFormat, context->outputFilePath );
-    CFRelease( plist );
-    require_noerr( err, exit );
+       size_t                  i;
+       
+       for( i = 0; i < inCount; ++i )
+       {
+               const dnssd_getaddrinfo_result_t                result = inResults[ i ];
+               
+               rct_ulog( kLogLevelInfo, "GAI result -- %@\n", result );
+               CFArrayAppendValue( me->gaiResults, result );
+       }
+}
 
-    return;
+static void    _RCodeSubtestTimerHandler( void * const inCtx )
+{
+       OSStatus                                                        err;
+       const RCodeTestRef                                      me = (RCodeTestRef) inCtx;
+       dnssd_getaddrinfo_result_t                      result;
+       const sockaddr_ip *                                     sip;
+       const uint8_t *                                         targetName;
+       CFIndex                                                         n;
+       dnssd_getaddrinfo_result_type_t         resultType;
+       int                                                                     expectedResultCount;
+       uint8_t                                                         hostname[ kDomainNameLengthMax ];
+       uint8_t                                                         actualHostname[ kDomainNameLengthMax ];
+       Boolean                                                         expectAddress, expectCanonName, done;
+       
+       // mDNSResponder has traditionally ignored responses with RCODEs not equal to NoError, NXDomain, or NotAuth.
+       // Such responses result in a kDNSServiceErr_NoSuchRecord error, or NoAddress in the case of dnssd_getaddrinfo.
+       
+       if( ( me->indexIdx >= 0 ) || _RCodeTestRCodeIsGood( me->rcode ) )
+       {
+               expectAddress           = me->hostnameHasAddr;
+               expectCanonName         = me->hostnameIsAlias;
+               expectedResultCount     = 1;
+       }
+       else
+       {
+               expectAddress           = false;
+               expectCanonName         = false;
+               expectedResultCount     = 1;
+       }
+       n = CFArrayGetCount( me->gaiResults );
+       require_action_quiet( n == expectedResultCount, exit, err = kCountErr );
+       
+       if( n != 0 )
+       {
+               result = (dnssd_getaddrinfo_result_t) CFArrayGetValueAtIndex( me->gaiResults, 0 );
+               resultType = dnssd_getaddrinfo_result_get_type( result );
+               
+               sip = (const sockaddr_ip *) dnssd_getaddrinfo_result_get_address( result );
+               require_action_quiet( sip->sa.sa_family == AF_INET, exit, err = kAddressErr );
+               
+               if( expectAddress )
+               {
+                       require_action_quiet( resultType == dnssd_getaddrinfo_result_type_add, exit, err = kTypeErr );
+                       require_action_quiet( ntohl( sip->v4.sin_addr.s_addr ) == ( kDNSServerBaseAddrV4 + 1 ), exit, err = kAddressErr );
+               }
+               else
+               {
+                       require_action_quiet( resultType == dnssd_getaddrinfo_result_type_no_address, exit, err = kTypeErr );
+               }
+               err = DomainNameFromString( hostname, me->hostname, NULL );
+               require_noerr_fatal( err, "Failed to convert hostname to DNS wire format -- hostname: %s, error: %#m",
+                       me->hostname, err );
+               
+               err = DomainNameFromString( actualHostname, dnssd_getaddrinfo_result_get_actual_hostname( result ), NULL );
+               require_noerr_quiet( err, exit );
+               
+               if( expectCanonName )
+               {
+                       size_t          firstLabelLen;
+                       
+                       firstLabelLen = hostname[ 0 ];
+                       require_fatal( firstLabelLen > 0, "Hostname's first label length is zero -- hostname: %s", me->hostname );
+                       
+                       targetName = &hostname[ 1 + firstLabelLen ];
+               }
+               else
+               {
+                       targetName = hostname;
+               }
+               require_action_quiet( DomainNameEqual( actualHostname, targetName ), exit, err = kNameErr );
+       }
+       err = kNoErr;
+       
 exit:
-    ErrQuit( 1, "error: %#m\n", err );
+       err = _RCodeTestContinue( me, err, &done );
+       if( err || done ) _RCodeTestStop( me, err );
 }
 
-//===========================================================================================================================
-//    ExpensiveConstrainedProtocolString
-//===========================================================================================================================
+static const void *    _DNSSDObjectCFArrayCallbackRetain( __unused const CFAllocatorRef inAllocator, const void *inObject )
+{
+       dnssd_retain( (dnssd_object_t) inObject );
+       return inObject;
+}
 
-static const char *ExpensiveConstrainedProtocolString( DNSServiceProtocol protocol )
+static void    _DNSSDObjectCFArrayCallbackRelease( __unused const CFAllocatorRef inAllocator, const void *inObject )
 {
-    const char *str = NULL;
-    switch ( protocol ) {
-        case kDNSServiceProtocol_IPv4:
-            str = "IPv4";
-            break;
-        case kDNSServiceProtocol_IPv6:
-            str = "IPv6";
-            break;
-        case kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6:
-            str = "IPv4 & IPv6";
-            break;
-        default:
-            break;
-    }
-    return str;
+       dnssd_release( (dnssd_object_t) inObject );
 }
 
-//===========================================================================================================================
-//    ExpensiveConstrainedStateString
 //===========================================================================================================================
 
-static const char *ExpensiveConstrainedStateString( enum ExpensiveConstrainedTestState state )
-{
-    const char *str = NULL;
-    switch ( state ) {
-        case TEST_BEGIN:
-            str = "TEST_BEGIN";
-            break;
-        case TEST_EXPENSIVE_PREPARE:
-            str = "TEST_EXPENSIVE_PREPARE";
-            break;
-        case TEST_EXPENSIVE:
-            str = "TEST_EXPENSIVE";
-            break;
-        case TEST_CONSTRAINED_PREPARE:
-            str = "TEST_CONSTRAINED_PREPARE";
-            break;
-        case TEST_CONSTRAINED:
-            str = "TEST_CONSTRAINED";
-            break;
-        case TEST_EXPENSIVE_CONSTRAINED_PREPARE:
-            str = "TEST_EXPENSIVE_CONSTRAINED_PREPARE";
-            break;
-        case TEST_EXPENSIVE_CONSTRAINED:
-            str = "TEST_EXPENSIVE_CONSTRAINED";
-            break;
-        case TEST_FAILED:
-            str = "TEST_FAILED";
-            break;
-        case TEST_SUCCEEDED:
-            str = "TEST_SUCCEEDED";
-            break;
-        default:
-            str = "UNKNOWN";
-            break;
-    }
+static OSStatus        _RCodeTestStopSubtest( RCodeTestRef inTest, OSStatus inError );
 
-    return str;
+static OSStatus        _RCodeTestContinue( const RCodeTestRef me, OSStatus inSubtestError, Boolean *outDone )
+{
+       OSStatus                err;
+       
+       require_action_quiet( !me->done, exit, err = kNoErr );
+       
+       err = _RCodeTestStopSubtest( me, inSubtestError );
+       require_noerr( err, exit );
+       require_action_quiet( !me->done, exit, err = kNoErr );
+       
+       err = _RCodeTestStartSubtest( me );
+       require_noerr( err, exit );
+       
+exit:
+       if( outDone ) *outDone = me->done;
+       return( err );
 }
 
-//===========================================================================================================================
-//    ExpensiveConstrainedOperationString
 //===========================================================================================================================
 
-static const char *ExpensiveConstrainedOperationString( enum ExpensiveConstrainedTestOperation operation )
+static OSStatus        _RCodeTestStopSubtest( const RCodeTestRef me, const OSStatus inError )
 {
-    const char *str = NULL;
-    switch ( operation ) {
-        case RESULT_ADD:
-            str = "RESULT_ADD";
-            break;
-        case RESULT_RMV:
-            str = "RESULT_RMV";
-            break;
-        case NO_UPDATE:
-            str = "NO_UPDATE";
-            break;
-        default:
-            str = "UNKNOWN";
-            break;
-    }
-    return str;
+       OSStatus                                err;
+       NanoTime64                              endTime;
+       CFMutableArrayRef               gaiResultDescs;
+       char                                    errorStr[ 128 ];
+       char                                    startTimeStr[ 32 ];
+       char                                    endTimeStr[ 32 ];
+       CFIndex                                 i, n;
+       
+       dnssd_getaddrinfo_forget( &me->gai );
+       dispatch_source_forget( &me->timer );
+       
+       endTime = NanoTimeGetCurrent();
+       
+       if( !inError ) ++me->subtestPassCount;
+       
+       rct_ulog( kLogLevelInfo, "Subtest #%d result: %s (pass rate: %d/%d)\n",
+               me->subtestCount, inError ? "fail" : "pass", me->subtestPassCount, me->subtestCount );
+       
+       _NanoTime64ToTimestamp( me->startTime, startTimeStr, sizeof( startTimeStr ) );
+       _NanoTime64ToTimestamp( endTime, endTimeStr, sizeof( endTimeStr ) );
+       SNPrintF( errorStr, sizeof( errorStr ), "%m", inError );
+       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->subtestResults,
+               "{"
+                       "%kO=%s"                // description
+                       "%kO=%s"                // startTime
+                       "%kO=%s"                // endTime
+                       "%kO=%s"                // hostname
+                       "%kO=%b"                // pass
+                       "%kO="                  // error
+                       "{"
+                               "%kO=%lli"      // code
+                               "%kO=%s"        // description
+                       "}"
+                       "%kO=[%@]"              // GAIResults
+               "}",
+               CFSTR( "description" ), me->description,
+               CFSTR( "startTime" ),   startTimeStr,
+               CFSTR( "endTime" ),             endTimeStr,
+               CFSTR( "hostname" ),    me->hostname,
+               CFSTR( "pass" ),                !inError ? true : false,
+               CFSTR( "error" ),
+               CFSTR( "code" ),                (int64_t) inError,
+               CFSTR( "description" ), errorStr,
+               CFSTR( "GAIResults" ),  &gaiResultDescs );
+       require_noerr( err, exit );
+       
+       n = CFArrayGetCount( me->gaiResults );
+       for( i = 0; i < n; ++ i )
+       {
+               dnssd_getaddrinfo_result_t              result = (dnssd_getaddrinfo_result_t) CFArrayGetValueAtIndex( me->gaiResults, i );
+               char *                                                  resultDesc;
+               
+               resultDesc = dnssd_copy_description( result );
+               require_action_quiet( resultDesc, exit, err = kNoMemoryErr );
+               
+               err = CFPropertyListAppendFormatted( NULL, gaiResultDescs, "%s", resultDesc );
+               ForgetMem( &resultDesc );
+               require_noerr( err, exit );
+       }
+       
+       if( me->indexIdx < 0 )
+       {
+               // Each subtest is one of the 64 possible combinations of
+               // rcode ∈ {0, 1, 2, …, 15}, hostnameIsAlias ∈ {false, true}, hostnameHasAddr ∈ {false, true}.
+               
+               if( !me->hostnameHasAddr )
+               {
+                       me->hostnameHasAddr = true;
+               }
+               else
+               {
+                       me->hostnameHasAddr = false;
+                       if( !me->hostnameIsAlias )
+                       {
+                               me->hostnameIsAlias = true;
+                       }
+                       else
+                       {
+                               me->hostnameIsAlias = false;
+                               if( me->rcode < kRCodeTestMaxRCodeValue )
+                               {
+                                       ++me->rcode;
+                               }
+                               else
+                               {
+                                       me->indexIdx    = 0;    // At this point we move on to using Index labels.
+                                       me->rcode               = -1;   // Start with rcode set to -1 to indicate no RCode label.
+                               }
+                       }
+               }
+       }
+       else
+       {
+               if( me->indexIdx < (int) kRCodeTestMaxIndexArgumentIndex )
+               {
+                       ++me->indexIdx;
+               }
+               else
+               {
+                       me->indexIdx = 0;
+                       
+                       // When using Index labels to limit responses to one server, we want the other servers to respond
+                       // with bad RCODEs, so if the current RCODE value is good, try incrementing again.
+                       
+                       do
+                       {
+                               if( me->rcode < kRCodeTestMaxRCodeValue )
+                               {
+                                       ++me->rcode;
+                               }
+                               else
+                               {
+                                       me->done = true;
+                               }
+                               
+                       }       while( !me->done && _RCodeTestRCodeIsGood( me->rcode ) );
+               }
+       }
+       
+exit:
+       return( err );
 }
 
 //===========================================================================================================================
-//    expensiveConstrainedEndsWith
-//===========================================================================================================================
-static Boolean expensiveConstrainedEndsWith( const char *str, const char *suffix )
+
+static Boolean _RCodeTestRCodeIsGood( const int inRCode )
 {
-    if ( !str || !suffix )
-        return false;
-    size_t lenstr = strlen( str );
-    size_t lensuffix = strlen( suffix );
-    if ( lensuffix > lenstr )
-        return false;
-    return strncmp( str + lenstr - lensuffix, suffix, lensuffix ) == 0;
+       return( ( inRCode == kDNSRCode_NoError ) || ( inRCode == kDNSRCode_NXDomain ) || ( inRCode == kDNSRCode_NotAuth ) );
 }
+#endif // MDNSRESPONDER_PROJECT
 
 //===========================================================================================================================
 //     RegistrationTestCmd
@@ -16256,7 +21541,7 @@ static OSStatus _RegistrationTestEndSubtest( RegistrationTest *inTest )
        txtStr                  = NULL;
        if( subtest->txtPtr )
        {
-               err = DNSRecordDataToString( subtest->txtPtr, subtest->txtLen, kDNSServiceType_TXT, NULL, 0, &txtStr );
+               err = DNSRecordDataToString( subtest->txtPtr, subtest->txtLen, kDNSServiceType_TXT, &txtStr );
                require_noerr( err, exit );
        }
        _NanoTime64ToTimestamp( subtest->startTime, startTime, sizeof( startTime ) );
@@ -16926,7 +22211,7 @@ static void DNSSD_API
                require_noerr( err, exit );
                
                rdataStr = NULL;
-               DNSRecordDataToString( inRDataPtr, inRDataLen, inType, NULL, 0, &rdataStr );
+               DNSRecordDataToString( inRDataPtr, inRDataLen, inType, &rdataStr );
                if( rdataStr )
                {
                        rdataKey = kRegistrationTestReportKey_RDataFormatted;
@@ -17098,7 +22383,7 @@ typedef struct
 {
        int                                                     family;                 // TCP connection's address family.
        KeepAliveCallVariant            callVariant;    // Describes which DNSServiceSleepKeepalive* call to use.
-       const char *                            description;
+       const char *                            description;    // Subtest description.
        
 }      KeepAliveSubtestParams;
 
@@ -17139,7 +22424,7 @@ struct KeepAliveTest
        dispatch_queue_t                                                queue;                  // Serial queue for test events.
        dispatch_semaphore_t                                    doneSem;                // Semaphore to signal when the test is done.
        dispatch_source_t                                               readSource;             // Read source for TCP listener socket.
-       DNSServiceRef                                                   keepalive;              // DNSServiceSleepKeepalive{,2} operation.
+       DNSServiceRef                                                   keepalive;              // DNSServiceSleepKeepalive{,sockaddr} operation.
        DNSServiceRef                                                   query;                  // Query to verify registered keepalive record.
        dispatch_source_t                                               timer;                  // Timer to put time limit on query.
        AsyncConnectionRef                                              connection;             // Establishes current subtest's TCP connection.
@@ -17413,7 +22698,7 @@ static OSStatus _KeepAliveTestStartSubtest( KeepAliveTestRef inTest )
        _KeepAliveTestGetSubtestLogPrefix( inTest, prefix, sizeof( prefix ) );
        kat_ulog( kLogLevelInfo, "%s: Will listen for connections on %s\n", prefix, serverAddrStr );
        
-       err = SocketContextCreate( sock, inTest, &sockCtx );
+       sockCtx = SocketContextCreate( sock, inTest, &err );
        require_noerr( err, exit );
        sock = kInvalidSocketRef;
        
@@ -17608,19 +22893,20 @@ static void   _KeepAliveTestHandleConnection( KeepAliveTestRef inTest, SocketRef i
                case kKeepAliveCallVariant_TakesSockAddrs:
                        kat_ulog( kLogLevelInfo,
                                "%s: Will call DNSServiceSleepKeepalive_sockaddr() for local and remote sockaddrs\n", prefix );
-                       if( !SOFT_LINK_HAS_FUNCTION( system_dnssd, DNSServiceSleepKeepalive_sockaddr ) )
+                       check( !inTest->keepalive );
+                       if( __builtin_available( macOS 10.15.4, iOS 13.2.2, watchOS 6.2, tvOS 13.2, * ) )
+                       {
+                               err = DNSServiceSleepKeepalive_sockaddr( &inTest->keepalive, 0, &subtest->local.sa, &subtest->remote.sa,
+                                       subtest->timeoutKA, _KeepAliveTestKeepaliveCallback, inTest );
+                               require_noerr( err, exit );
+                       }
+                       else
                        {
-                               kat_ulog( kLogLevelError,
-                                       "%s: Failed to soft link DNSServiceSleepKeepalive_sockaddr from libsystem_dnssd.\n", prefix );
+                               kat_ulog( kLogLevelError, "DNSServiceSleepKeepalive_sockaddr() is not available on this OS.\n" );
                                subtestFailed = true;
                                err = kUnsupportedErr;
-                               goto exit;
-                       }
-                       check( !inTest->keepalive );
-                       err = soft_DNSServiceSleepKeepalive_sockaddr( &inTest->keepalive, 0, &subtest->local.sa, &subtest->remote.sa,
-                               subtest->timeoutKA, _KeepAliveTestKeepaliveCallback, inTest );
-                       require_noerr( err, exit );
-                       
+                               goto exit;
+                       }
                        err = DNSServiceSetDispatchQueue( inTest->keepalive, inTest->queue );
                        require_noerr( err, exit );
                        break;
@@ -17855,11 +23141,539 @@ static void DNSSD_API
        err = kNoErr;
        
 exit:
-       err = _KeepAliveTestContinue( test, err, &done );
-       check_noerr( err );
-       if( err || done ) _KeepAliveTestStop( test, kNoErr );
+       err = _KeepAliveTestContinue( test, err, &done );
+       check_noerr( err );
+       if( err || done ) _KeepAliveTestStop( test, kNoErr );
+}
+#endif // TARGET_OS_DARWIN
+
+#if ( ENABLE_DNSSDUTIL_DNSSEC_TEST == 1 )
+//===========================================================================================================================
+//     DNSSECTestCmd
+//===========================================================================================================================
+
+#define kDNSSECTestQueryTimeoutSecs 4
+
+typedef struct DNSSECTest_BasicValidationContext
+{
+       uint32_t number_of_answers;             // The number of answers expected to receive in the "basic validation" test
+
+}      DNSSECTest_BasicValidationContext;
+
+//===========================================================================================================================
+
+typedef struct
+{
+       DNSServiceRef           query;
+       dispatch_source_t       queryTimer;                     // Used to setup timeout timer, to prevent from waiting forever.
+       Boolean                         testStarted;
+       const char *            testName;                       // The name of the curreent running test case.
+       const char *            testCaseName;           // The query name in the subtest of the test case.
+       union {
+               DNSSECTest_BasicValidationContext       basicValidation;
+       } testCaseContext;                                              // Contains different customized context pointer, which can be used by different test cases.
+       const char *            subtestQueryName;       // The query name that is passed to mDNSResponder API
+       int                                     subtestIndex;           // The index of the current case in the test input array.
+       pid_t                           localServerPID;         // The pid of the dnssdutil server
+       NanoTime64                      startTime;                      // The time when the DNSSEC test case starts.
+       CFMutableArrayRef       subtestReport;          // The reference to the CFMutableArrayRef, which contains subtest reports for different subtests
+       NanoTime64                      subtestStartTime;       // The time when the subtest starts
+       Boolean                         subtestFailed;          // Indicate if any subtest failed before.
+
+       char *                          outputFilePath;         // File to write test results to. If NULL, then write to stdout. (malloced)
+       OutputFormatType        outputFormat;           // Format of test report output.
+} DNSSECTestContext;
+
+//===========================================================================================================================
+
+typedef struct
+{
+       const char *    testCaseName;                                                                           // The name of the test case that will be run.
+       void                    (*testCaseHandler)( DNSSECTestContext * context );      // The main function of the test case.
+} DNSSECTestTestCase;
+
+//===========================================================================================================================
+
+static void DNSSECTestSetupLocalDNSServer( DNSSECTestContext *context );
+static void DNSSECTestStartTestCase( DNSSECTestContext *context );
+
+//===========================================================================================================================
+
+// DNSSEC test cases
+static void DNSSECTest_BasicValidation( DNSSECTestContext *context );
+
+static DNSSECTestTestCase DNSSECTestTestCases[] =
+{
+       { "basic validation", DNSSECTest_BasicValidation }
+};
+
+//===========================================================================================================================
+
+// The main function to start the DNSSEC test
+static void
+       DNSSECTestCmd( void )
+{
+       OSStatus                        err;
+       dispatch_source_t       signalSource    = NULL;
+       DNSSECTestContext *     context                 = NULL;
+
+       // Set up SIGINT handler.
+    signal( SIGINT, SIG_IGN );
+    err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), Exit, kExitReason_SIGINT, &signalSource );
+    require_noerr( err, exit );
+    dispatch_resume( signalSource );
+
+       // Create the test context.
+       context = (DNSSECTestContext *) calloc( 1, sizeof( *context ) );
+       require_action( context, exit, err = kNoMemoryErr );
+
+       context->testStarted = false;
+
+       // Get the command line option.
+       context->testCaseName = strdup( gDNSSECTest_TestCaseName );
+       require( context->testCaseName , exit );
+
+       // Start the subtest from index 0
+       context->subtestIndex = 0;
+
+       // Get the output format.
+       err = OutputFormatFromArgString( gDNSSECTest_OutputFormat, &context->outputFormat );
+       require_noerr_quiet( err, exit );
+
+       // Get the output file path.
+       if( gDNSSECTest_OutputFilePath )
+       {
+               context->outputFilePath = strdup( gDNSSECTest_OutputFilePath );
+               require_noerr_quiet( context->outputFilePath , exit );
+       }
+
+       // Initialize the CFArray to store the test result.
+       context->subtestReport  = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+       context->startTime              = NanoTimeGetCurrent();
+
+       // Start the local dnssdutil server.
+       DNSSECTestSetupLocalDNSServer( context );
+
+       // Start the test.
+       DNSSECTestStartTestCase( context );
+
+exit:
+       exit( 1 );
+}
+
+//===========================================================================================================================
+
+// Start the local DNS server with dnssdutil server command.
+static void
+       DNSSECTestSetupLocalDNSServer( DNSSECTestContext *context )
+{
+       Unused( context );
+       pid_t pid = getpid();
+
+       OSStatus err = _SpawnCommand( &context->localServerPID, NULL, NULL, "dnssdutil server --loopback --follow %lld",
+               (int64_t) pid );
+       require_noerr_action( err, exit,
+               FPrintF( stderr, "dnssdutil server --loopback --follow %lld failed, error: %d\n", (int64_t) pid, err ) );
+
+       // Wait long enough to allow the DNS server being setup.
+       sleep( 2 );
+
+       return;
+exit:
+       exit( 1 );
+}
+
+//===========================================================================================================================
+
+// Start the test case that user specifies.
+static void
+       DNSSECTestStartTestCase( DNSSECTestContext *context )
+{
+       Boolean findTestCase = false;
+
+       for ( int i = 0; i < (int)countof( DNSSECTestTestCases ); i++ )
+       {
+               if( strcmp( context->testCaseName, DNSSECTestTestCases[i].testCaseName ) == 0 )
+               {
+                       DNSSECTestTestCases[ i ].testCaseHandler( context );
+                       findTestCase = true;
+               }
+       }
+
+       if( !findTestCase )
+       {
+               FPrintF( stdout, "Unknown test case \"%s\"\n", context->testCaseName );
+               exit( 1 );
+       }
+}
+
+//===========================================================================================================================
+
+// Write the current subtest status into the report list. When this function is called, either some error occurs or the
+// subtest finishes.
+static void
+       _DNSSECTestQueryWriteSubtestReport( DNSSECTestContext *inContext, const char *errorDescription )
+{
+       OSStatus        err;
+       NanoTime64      now;
+       char            startTime[ 32 ];
+       char            endTime[ 32 ];
+
+       now = NanoTimeGetCurrent();
+       _NanoTime64ToTimestamp( inContext->subtestStartTime, startTime, sizeof( startTime ) );
+       _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) );
+
+       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, inContext->subtestReport,
+               "{"
+                       "%kO=%s"
+                       "%kO=%s"
+                       "%kO=%s"
+                       "%kO=%s"
+                       "%kO=%s"
+               "}",
+               CFSTR( "Start Time" ),                  startTime,
+               CFSTR( "End Time" ),                    endTime,
+               CFSTR( "Subtest Query Name" ),  inContext->subtestQueryName,
+               CFSTR( "Result" ),                              errorDescription ? "Fail" : "Pass",
+               CFSTR( "Error Description" ),   errorDescription ? errorDescription : "No Error"
+       );
+
+       require_noerr( err, exit );
+       return;
+
+exit:
+       ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+
+// Output the final test result to the file (path specified by the user) in the disk.
+static void
+       _DNSSECTestQueryOutputFinalReport( DNSSECTestContext *inContext )
+{
+       OSStatus                        err;
+       CFPropertyListRef       plist;
+       NanoTime64                      now;
+       char                            startTime[ 32 ];
+       char                            endTime[ 32 ];
+
+       now = NanoTimeGetCurrent();
+       _NanoTime64ToTimestamp( inContext->startTime, startTime, sizeof( startTime ) );
+       _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) );
+
+       err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+               "{"
+                       "%kO=%s"
+                       "%kO=%s"
+                       "%kO=%s"
+                       "%kO=%b"
+                       "%kO=%O"
+               "}",
+               CFSTR( "Start Time" ),          startTime,
+               CFSTR( "End Time" ),            endTime,
+               CFSTR( "Test Case Name" ),      inContext->testCaseName,
+               CFSTR( "All Passed" ),          !inContext->subtestFailed,
+               CFSTR( "Subtest Reports" ),     inContext->subtestReport
+       );
+       require_noerr( err, exit );
+       ForgetCF( &inContext->subtestReport );
+
+       err = OutputPropertyList( plist, inContext->outputFormat, inContext->outputFilePath );
+       CFRelease( plist );
+       require_noerr( err, exit );
+
+       return;
+exit:
+       ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+
+// The handler that will be called when timeout happens. When timeout happens it always means something bad happen, and
+// it might be possible that all the remaning tests timeout too, so the function exits directly instead of continuing.
+static void
+       _DNSSECTestQueryTimeoutHandler( void *inContext )
+{
+       DNSSECTestContext * const context = (DNSSECTestContext *) inContext;
+
+       DNSServiceForget( &context->query );
+       dispatch_source_forget( &context->queryTimer );
+
+       context->subtestFailed = true;
+       _DNSSECTestQueryWriteSubtestReport( context, "Query for DNSSEC-related records timed out" );
+       _DNSSECTestQueryOutputFinalReport( context );
+
+       exit( 1 );
+}
+
+//===========================================================================================================================
+#pragma mark - Test case "basic validation"
+
+//===========================================================================================================================
+
+typedef struct DNSSECTest_BasicValidationTestCase
+{
+       const char *    queryName;              // The query name that controls the behavior of the local DNS server.
+       uint16_t                queryType;              // The DNS record being queried.
+       uint32_t                num_of_answers; // How many anwers are expected to be returned.
+
+}      DNSSECTest_BasicValidationTestCase;
+
+//===========================================================================================================================
+
+DNSSECTest_BasicValidationTestCase DNSSECTest_BasicValidationTestCases[] =
+{
+       // kDNSSECAlgorithm_RSASHA1 5
+       { "count-1.z-5-1.z-5-2.z-5-3.dnssec.test.",             kDNSServiceType_A,              1 },
+       { "count-2.z-5-1.z-5-2.z-5-3.dnssec.test.",             kDNSServiceType_A,              2 },
+       { "count-1.z-5-1.z-5-2.z-5-3.dnssec.test.",             kDNSServiceType_AAAA,   1 },
+       { "count-2.z-5-1.z-5-2.z-5-3.dnssec.test.",             kDNSServiceType_AAAA,   2 },
+       { "count-10.z-5-1.z-5-2.z-5-3.dnssec.test.",    kDNSServiceType_A,              10 },
+       // kDNSSECAlgorithm_RSASHA256 8
+       { "count-1.z-8-1.z-8-2.z-8-3.dnssec.test.",             kDNSServiceType_A,              1 },
+       { "count-2.z-8-1.z-8-2.z-8-3.dnssec.test.",             kDNSServiceType_A,              2 },
+       { "count-1.z-8-1.z-8-2.z-8-3.dnssec.test.",             kDNSServiceType_AAAA,   1 },
+       { "count-2.z-8-1.z-8-2.z-8-3.dnssec.test.",             kDNSServiceType_AAAA,   2 },
+       { "count-10.z-8-1.z-8-2.z-8-3.dnssec.test.",    kDNSServiceType_A,              10 },
+       // kDNSSECAlgorithm_RSASHA512 10
+       { "count-1.z-10-1.z-10-2.z-10-3.dnssec.test.",  kDNSServiceType_A,              1 },
+       { "count-2.z-10-1.z-10-2.z-10-3.dnssec.test.",  kDNSServiceType_A,              2 },
+       { "count-1.z-10-1.z-10-2.z-10-3.dnssec.test.",  kDNSServiceType_AAAA,   1 },
+       { "count-2.z-10-1.z-10-2.z-10-3.dnssec.test.",  kDNSServiceType_AAAA,   2 },
+       { "count-10.z-10-1.z-10-2.z-10-3.dnssec.test.", kDNSServiceType_A,              10 },
+       // kDNSSECAlgorithm_ECDSAP256SHA256 13
+       { "count-1.z-13-1.z-13-2.z-13-3.dnssec.test.",  kDNSServiceType_A,              1 },
+       { "count-2.z-13-1.z-13-2.z-13-3.dnssec.test.",  kDNSServiceType_A,              2 },
+       { "count-1.z-13-1.z-13-2.z-13-3.dnssec.test.",  kDNSServiceType_AAAA,   1 },
+       { "count-2.z-13-1.z-13-2.z-13-3.dnssec.test.",  kDNSServiceType_AAAA,   2 },
+       { "count-10.z-13-1.z-13-2.z-13-3.dnssec.test.", kDNSServiceType_A,              10 },
+       // kDNSSECAlgorithm_ECDSAP384SHA384 14
+       { "count-1.z-14-1.z-14-2.z-14-3.dnssec.test.",  kDNSServiceType_A,              1 },
+       { "count-2.z-14-1.z-14-2.z-14-3.dnssec.test.",  kDNSServiceType_A,              2 },
+       { "count-1.z-14-1.z-14-2.z-14-3.dnssec.test.",  kDNSServiceType_AAAA,   1 },
+       { "count-2.z-14-1.z-14-2.z-14-3.dnssec.test.",  kDNSServiceType_AAAA,   2 },
+       { "count-10.z-14-1.z-14-2.z-14-3.dnssec.test.", kDNSServiceType_A,              10 },
+       // Mixed use of mutiple DNSKEY algorithms
+       { "count-1.z-5-1.z-8-2.z-10-3.dnssec.test.",    kDNSServiceType_A,              1 },
+       { "count-2.z-5-1.z-5-2.z-8-3.dnssec.test.",             kDNSServiceType_A,              2 },
+       { "count-1.z-8-1.z-5-2.z-5-3.dnssec.test.",             kDNSServiceType_AAAA,   1 },
+       { "count-2.z-10-1.z-8-2.z-8-3.dnssec.test.",    kDNSServiceType_AAAA,   2 },
+       { "count-10.z-10-1.z-8-2.z-5-3.dnssec.test.",   kDNSServiceType_A,              10 },
+       { "count-1.z-13-1.z-14-2.z-14-3.dnssec.test.",  kDNSServiceType_A,              1 },
+       { "count-2.z-14-1.z-13-2.z-8-3.dnssec.test.",   kDNSServiceType_A,              2 },
+       { "count-1.z-5-1.z-14-2.z-8-3.dnssec.test.",    kDNSServiceType_AAAA,   1 },
+       { "count-2.z-10-1.z-8-2.z-13-3.dnssec.test.",   kDNSServiceType_AAAA,   2 },
+       { "count-10.z-14-1.z-13-2.z-5-3.dnssec.test.",  kDNSServiceType_A,              10 }
+};
+
+//===========================================================================================================================
+
+static void DNSSD_API
+       _DNSSECTest_BasicValidationCallBack(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inFullName,
+               uint16_t                                inType,
+               uint16_t                                inClass,
+               uint16_t                                inRDataLen,
+               const void *                    inRDataPtr,
+               uint32_t                                inTTL,
+               void *                                  inContext );
+
+//===========================================================================================================================
+
+static Boolean
+       _DNSSECTest_BasicValidationVerifyTheResponse(
+               DNSSECTestContext *     inContext,
+               DNSServiceFlags         inFlags,
+               uint32_t                        inInterfaceIndex,
+               DNSServiceErrorType     inError,
+               const char *            inFullName,
+               uint16_t                        inType,
+               uint16_t                        inClass,
+               uint16_t                        inRDataLen,
+               const void *            inRDataPtr,
+               uint32_t                        inTTL,
+               char *                          inStrBuffer,
+               uint32_t                        inBufferLen,
+               Boolean *                       outExpectMore );
+
+//===========================================================================================================================
+
+static void DNSSECTest_BasicValidation( DNSSECTestContext *inContext )
+{
+       OSStatus                err;
+       const char *    error_description = NULL;
+
+       // Get the current subtest
+       DNSSECTest_BasicValidationTestCase * testCases = &DNSSECTest_BasicValidationTestCases[ inContext->subtestIndex ];
+       inContext->subtestQueryName = testCases->queryName;
+       inContext->testCaseContext.basicValidation.number_of_answers = testCases->num_of_answers;
+
+       // Issue the query.
+       err = DNSServiceQueryRecord( &inContext->query,
+               kDNSServiceFlagsEnableDNSSEC, 0, testCases->queryName, testCases->queryType, kDNSServiceClass_IN,
+               _DNSSECTest_BasicValidationCallBack, inContext );
+       require_noerr_action( err, exit, error_description = "DNSServiceQueryRecord failed" );
+
+       // Set the dispatch queue.
+       err = DNSServiceSetDispatchQueue( inContext->query,
+               dispatch_get_main_queue() );
+       require_noerr_action( err, exit, error_description = "DNSServiceSetDispatchQueue failed" );
+
+       // Create a timer to handle timeout.
+       err = DispatchTimerCreate( dispatch_time_seconds( kDNSSECTestQueryTimeoutSecs ), DISPATCH_TIME_FOREVER,
+               UINT64_C_safe( kDNSSECTestQueryTimeoutSecs ) * kNanosecondsPerSecond / 10, NULL, _DNSSECTestQueryTimeoutHandler,
+               NULL, inContext, &inContext->queryTimer );
+       require_noerr_action( err, exit, error_description = "DispatchTimerCreate failed" );
+       dispatch_resume( inContext->queryTimer );
+
+       // start the current subtest, and record the start time.
+       inContext->subtestStartTime     = NanoTimeGetCurrent();
+       if( !inContext->testStarted )
+       {
+               // Only call dispatch_main once, when we first start the test.
+               inContext->testStarted = true;
+               dispatch_main();
+       }
+
+       return;
+exit:
+       _DNSSECTestQueryWriteSubtestReport( inContext, error_description );
+       _DNSSECTestQueryOutputFinalReport( inContext );
+       exit( 1 );
+}
+
+//===========================================================================================================================
+
+static void DNSSD_API
+       _DNSSECTest_BasicValidationCallBack(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inFullName,
+               uint16_t                                inType,
+               uint16_t                                inClass,
+               uint16_t                                inRDataLen,
+               const void *                    inRDataPtr,
+               uint32_t                                inTTL,
+               void *                                  inContext )
+{
+       char                                            err_desp_str[1024];
+       Boolean                                         valid;
+       Boolean                                         expectMore;
+       DNSSECTestContext * const       context = (DNSSECTestContext *)inContext;
+
+       Unused( inSDRef );
+
+       err_desp_str[0] = '\0';
+
+       // Verify if the response is expected
+       valid = _DNSSECTest_BasicValidationVerifyTheResponse(context, inFlags, inInterfaceIndex, inError, inFullName, inType,
+               inClass, inRDataLen, inRDataPtr, inTTL, err_desp_str, sizeof( err_desp_str ), &expectMore);
+
+       // If more answers are expected to be returned.
+       if( expectMore )
+       {
+               return;
+       }
+
+       // Cancel the current request and its corresponding timer.
+       DNSServiceForget( &context->query );
+       dispatch_source_forget( &context->queryTimer );
+
+       // Check if the test fails.
+       if( !valid )
+       {
+               context->subtestFailed = true;
+       }
+       _DNSSECTestQueryWriteSubtestReport( context, valid ? NULL : err_desp_str );
+
+       // Increment the index.
+       context->subtestIndex++;
+       if( context->subtestIndex == countof( DNSSECTest_BasicValidationTestCases ) )
+       {
+               // All test cases have been run
+               _DNSSECTestQueryOutputFinalReport( context );
+               exit( context->subtestFailed ? 2 : 0 );
+       }
+
+       // Start the next subtest.
+       DNSSECTest_BasicValidation( context );
+}
+
+//===========================================================================================================================
+
+// Check if the response is expected. Return true if the response is expected, otherwise return false.
+static Boolean
+_DNSSECTest_BasicValidationVerifyTheResponse(
+       DNSSECTestContext *     inContext,
+       DNSServiceFlags         inFlags,
+       uint32_t                        inInterfaceIndex,
+       DNSServiceErrorType     inError,
+       const char *            inFullName,
+       uint16_t                        inType,
+       uint16_t                        inClass,
+       uint16_t                        inRDataLen,
+       const void *            inRDataPtr,
+       uint32_t                        inTTL,
+       char *                          inStrBuffer,
+       uint32_t                        inBufferLen,
+       Boolean *                       outExpectMore )
+{
+       const char * const                                              queryName       = inContext->subtestQueryName;
+       DNSSECTest_BasicValidationTestCase *    testCases       = &DNSSECTest_BasicValidationTestCases[ inContext->subtestIndex ];
+       DNSSECTest_BasicValidationContext *             subContext      = &inContext->testCaseContext.basicValidation;
+       Boolean                                                                 expectMore      = false;
+
+       Unused( inClass );
+       Unused( inRDataLen );
+       Unused( inRDataPtr );
+       Unused( inTTL );
+
+       require_noerr_action( inError, exit, SNPrintF( inStrBuffer, inBufferLen, "Unexpected DNSServiceErrorType: %d", inError ) );
+
+       require_action( inFlags & kDNSServiceFlagsAdd, exit,
+               SNPrintF( inStrBuffer, inBufferLen, "Unexpected add/remove event: %#{flags}", inFlags, kDNSServiceFlagsDescriptors )
+       );
+
+       require_action( inInterfaceIndex == kDNSServiceInterfaceIndexAny, exit,
+               SNPrintF( inStrBuffer, inBufferLen, "Non-local-only interface is used: %u", inInterfaceIndex ) );
+
+       require_action( strcmp( queryName, inFullName ) == 0, exit,
+               SNPrintF( inStrBuffer, inBufferLen, "Mismatched name: %s", inFullName ) );
+
+       require_action( inType == testCases->queryType, exit,
+               SNPrintF( inStrBuffer, inBufferLen, "Mismatched query type: %s", RecordTypeToString( inType ) ) );
+
+       require_action( (inFlags & kDNSServiceFlagsSecure) == kDNSServiceFlagsSecure, exit,
+               SNPrintF( inStrBuffer, inBufferLen, "Unexpected DNSSEC result: %#{flags}", (inFlags & kDNSServiceFlagsSecure),
+                       kDNSServiceFlagsDescriptors )
+       );
+
+       if( --subContext->number_of_answers != 0 )
+       {
+               expectMore = true;
+       }
+
+
+exit:
+       if( outExpectMore )
+       {
+               *outExpectMore = expectMore;
+       }
+
+       return inStrBuffer[0] != '\0' ? false : true;
 }
-#endif // TARGET_OS_DARWIN
+#else
+static void DNSSECTestCmd( void )
+{
+       exit( 0 );
+}
+#endif // #if ( ENABLE_DNSSDUTIL_DNSSEC_TEST == 1 )
 
 //===========================================================================================================================
 //     SSDPDiscoverCmd
@@ -17965,11 +23779,7 @@ static void    SSDPDiscoverCmd( void )
        {
                struct sockaddr_in              mcastAddr4;
                
-               memset( &mcastAddr4, 0, sizeof( mcastAddr4 ) );
-               SIN_LEN_SET( &mcastAddr4 );
-               mcastAddr4.sin_family           = AF_INET;
-               mcastAddr4.sin_port                     = htons( kSSDPPort );
-               mcastAddr4.sin_addr.s_addr      = htonl( 0xEFFFFFFA );  // 239.255.255.250
+               _SockAddrInitIPv4( &mcastAddr4, UINT32_C( 0xEFFFFFFA ), kSSDPPort );    // 239.255.255.250
                
                err = WriteSSDPSearchRequest( &context->header, &mcastAddr4, gSSDPDiscover_MX, gSSDPDiscover_ST );
                require_noerr( err, exit );
@@ -18048,7 +23858,7 @@ static void     SSDPDiscoverCmd( void )
        {
                SocketContext *         sockCtx;
                
-               err = SocketContextCreate( sockV4, context, &sockCtx );
+               sockCtx = SocketContextCreate( sockV4, context, &err );
                require_noerr( err, exit );
                sockV4 = kInvalidSocketRef;
                
@@ -18064,7 +23874,7 @@ static void     SSDPDiscoverCmd( void )
        {
                SocketContext *         sockCtx;
                
-               err = SocketContextCreate( sockV6, context, &sockCtx );
+               sockCtx = SocketContextCreate( sockV6, context, &err );
                require_noerr( err, exit );
                sockV6 = kInvalidSocketRef;
                
@@ -18345,7 +24155,7 @@ static void     ResQueryCmd( void )
        
        // Print result.
        
-       FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n );
+       FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}\n", n, answer, (size_t) n );
        
 exit:
        if( err ) exit( 1 );
@@ -18459,7 +24269,7 @@ static void     ResolvDNSQueryCmd( void )
        // Print result.
        
        FPrintF( stdout, "From:         %##a\n", &from );
-       FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n );
+       FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}\n", n, answer, (size_t) n );
        
 exit:
        if( dns ) soft_dns_free( dns );
@@ -18523,713 +24333,1677 @@ static void        _CFHostResolveCallback( CFHostRef inHost, CFHostInfoType inInfoType,
        
        gettimeofday( &now, NULL );
        
-       Unused( inInfoType );
-       Unused( inInfo );
+       Unused( inInfoType );
+       Unused( inInfo );
+       
+       if( inError && ( inError->domain != 0 ) && ( inError->error ) )
+       {
+               err = inError->error;
+               if( inError->domain == kCFStreamErrorDomainNetDB )
+               {
+                       FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
+               }
+               else
+               {
+                       FPrintF( stderr, "Error %#m\n", err );
+               }
+       }
+       else
+       {
+               CFArrayRef                                      addresses;
+               CFIndex                                         count, i;
+               CFDataRef                                       addrData;
+               const struct sockaddr *         sockAddr;
+               Boolean                                         wasResolved = false;
+               
+               addresses = CFHostGetAddressing( inHost, &wasResolved );
+               check( wasResolved );
+               
+               if( addresses )
+               {
+                       count = CFArrayGetCount( addresses );
+                       for( i = 0; i < count; ++i )
+                       {
+                               addrData = CFArrayGetCFDataAtIndex( addresses, i, &err );
+                               require_noerr( err, exit );
+                               
+                               sockAddr = (const struct sockaddr *) CFDataGetBytePtr( addrData );
+                               FPrintF( stdout, "%##a\n", sockAddr );
+                       }
+               }
+               err = kNoErr;
+       }
+       
+       FPrintF( stdout, "---\n" );
+       FPrintF( stdout, "End time:   %{du:time}\n", &now );
+       
+       if( gCFHost_WaitSecs > 0 ) sleep( (unsigned int) gCFHost_WaitSecs );
+       
+exit:
+       exit( err ? 1 : 0 );
+}
+
+//===========================================================================================================================
+//     DNSConfigAddCmd
+//
+//     Note: Based on ajn's supplemental test tool.
+//===========================================================================================================================
+
+static void    DNSConfigAddCmd( void )
+{
+       OSStatus                                        err;
+       CFMutableDictionaryRef          dict    = NULL;
+       CFMutableArrayRef                       array   = NULL;
+       size_t                                          i;
+       SCDynamicStoreRef                       store   = NULL;
+       CFStringRef                                     key             = NULL;
+       Boolean                                         success;
+       
+       // Create dictionary.
+       
+       dict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
+       require_action( dict, exit, err = kNoMemoryErr );
+       
+       // Add DNS server IP addresses.
+       
+       array = CFArrayCreateMutable( NULL, (CFIndex) gDNSConfigAdd_IPAddrCount, &kCFTypeArrayCallBacks );
+       require_action( array, exit, err = kNoMemoryErr );
+       
+       for( i = 0; i < gDNSConfigAdd_IPAddrCount; ++i )
+       {
+               CFStringRef             addrStr;
+               
+               addrStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_IPAddrArray[ i ], kCFStringEncodingUTF8 );
+               require_action( addrStr, exit, err = kUnknownErr );
+               
+               CFArrayAppendValue( array, addrStr );
+               CFRelease( addrStr );
+       }
+       
+       CFDictionarySetValue( dict, kSCPropNetDNSServerAddresses, array );
+       ForgetCF( &array );
+       
+       // Add domains, if any.
+       
+       array = CFArrayCreateMutable( NULL, (CFIndex) Min( gDNSConfigAdd_DomainCount, 1 ), &kCFTypeArrayCallBacks );
+       require_action( array, exit, err = kNoMemoryErr );
+       
+       if( gDNSConfigAdd_DomainCount > 0 )
+       {
+               for( i = 0; i < gDNSConfigAdd_DomainCount; ++i )
+               {
+                       CFStringRef             domainStr;
+                       
+                       domainStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_DomainArray[ i ], kCFStringEncodingUTF8 );
+                       require_action( domainStr, exit, err = kUnknownErr );
+                       
+                       CFArrayAppendValue( array, domainStr );
+                       CFRelease( domainStr );
+               }
+       }
+       else
+       {
+               // There are no domains, but the domain array needs to be non-empty, so add a zero-length string to the array.
+               
+               CFArrayAppendValue( array, CFSTR( "" ) );
+       }
+       CFDictionarySetValue( dict, kSCPropNetDNSSupplementalMatchDomains, array );
+       ForgetCF( &array );
+       
+       // Set search orders.
+       
+       if( gDNSConfigAdd_SearchOrder >= 0 )
+       {
+               const size_t            n = Min( gDNSConfigAdd_DomainCount, 1 );
+               
+               array = CFArrayCreateMutable( NULL, (CFIndex) n, &kCFTypeArrayCallBacks );
+               require_action( array, exit, err = kNoMemoryErr );
+               
+               for( i = 0; i < n; ++i )
+               {
+                       err = CFArrayAppendInt64( array, gDNSConfigAdd_SearchOrder );
+                       require_noerr( err, exit );
+               }
+               CFDictionarySetValue( dict, kSCPropNetDNSSupplementalMatchOrders, array );
+               ForgetCF( &array );
+       }
+       
+       // Add interface, if any.
+       
+       if( gDNSConfigAdd_Interface )
+       {
+               err = CFDictionarySetCString( dict, kSCPropInterfaceName, gDNSConfigAdd_Interface, kSizeCString );
+               require_noerr( err, exit );
+               
+               CFDictionarySetValue( dict, kSCPropNetDNSConfirmedServiceID, gDNSConfigAdd_ID );
+       }
+       
+       // Set dictionary in dynamic store.
+       
+       store = SCDynamicStoreCreate( NULL, CFSTR( kDNSSDUtilIdentifier ), NULL, NULL );
+       err = map_scerror( store );
+       require_noerr( err, exit );
+       
+       key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigAdd_ID, kSCEntNetDNS );
+       require_action( key, exit, err = kUnknownErr );
+       
+       success = SCDynamicStoreSetValue( store, key, dict );
+       require_action( success, exit, err = kUnknownErr );
+       
+exit:
+       CFReleaseNullSafe( dict );
+       CFReleaseNullSafe( array );
+       CFReleaseNullSafe( store );
+       CFReleaseNullSafe( key );
+       gExitCode = err ? 1 : 0;
+}
+
+//===========================================================================================================================
+//     DNSConfigRemoveCmd
+//===========================================================================================================================
+
+static void    DNSConfigRemoveCmd( void )
+{
+       OSStatus                                err;
+       SCDynamicStoreRef               store   = NULL;
+       CFStringRef                             key             = NULL;
+       Boolean                                 success;
+       
+       store = SCDynamicStoreCreate( NULL, CFSTR( kDNSSDUtilIdentifier ), NULL, NULL );
+       err = map_scerror( store );
+       require_noerr( err, exit );
+       
+       key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigRemove_ID, kSCEntNetDNS );
+       require_action( key, exit, err = kUnknownErr );
+       
+       success = SCDynamicStoreRemoveValue( store, key );
+       require_action( success, exit, err = kUnknownErr );
+       
+exit:
+       CFReleaseNullSafe( store );
+       CFReleaseNullSafe( key );
+       gExitCode = err ? 1 : 0;
+}
+
+//===========================================================================================================================
+//     XPCSendCmd
+//===========================================================================================================================
+
+static OSStatus        _XPCDictionaryCreateFromString( const char *inString, xpc_object_t *outDict );
+
+static void    XPCSendCmd( void )
+{
+       OSStatus                        err;
+       xpc_object_t            msg, reply;
+       
+       err = _XPCDictionaryCreateFromString( gXPCSend_MessageStr, &msg );
+       require_noerr_quiet( err, exit );
+       
+       FPrintF( stdout, "Service:    %s\n", gXPCSend_ServiceName );
+       FPrintF( stdout, "Message:    %s\n", gXPCSend_MessageStr );
+       FPrintF( stdout, "Start time: %{du:time}\n", NULL );
+       FPrintF( stdout, "---\n" );
+       FPrintF( stdout, "XPC Message:\n%{xpc}\n", msg );
+       
+       err = xpc_send_message_sync( gXPCSend_ServiceName, 0, 0, msg, &reply );
+       xpc_forget( &msg );
+       require_noerr_quiet( err, exit );
+       
+       FPrintF( stdout, "XPC Reply:\n%{xpc}\n", reply );
+       FPrintF( stdout, "---\n" );
+       FPrintF( stdout, "End time:   %{du:time}\n", NULL );
+       xpc_forget( &reply );
+       
+exit:
+       if( err ) ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//     _XPCDictionaryCreateFromString
+//===========================================================================================================================
+
+#define kXPCObjectPrefix_Bool          "bool:"
+#define kXPCObjectPrefix_Data          "data:"
+#define kXPCObjectPrefix_Int64         "int:"
+#define kXPCObjectPrefix_String                "string:"
+#define kXPCObjectPrefix_UInt64                "uint:"
+#define kXPCObjectPrefix_UUID          "uuid:"
+
+typedef struct XPCListItem             XPCListItem;
+struct XPCListItem
+{
+       XPCListItem *           next;
+       xpc_object_t            obj;
+       char *                          key;
+};
+
+static OSStatus        _XPCListItemCreate( xpc_object_t inObject, const char *inKey, XPCListItem **outItem );
+static void            _XPCListItemFree( XPCListItem *inItem );
+static void            _XPCListFree( XPCListItem *inList );
+
+static OSStatus        _XPCObjectFromString( const char *inString, xpc_object_t *outObject );
+
+static OSStatus        _XPCDictionaryCreateFromString( const char *inString, xpc_object_t *outDict )
+{
+       OSStatus                                err;
+       xpc_object_t                    container;
+       const char *                    ptr             = inString;
+       const char * const              end             = inString + strlen( inString );
+       XPCListItem *                   list    = NULL;
+       
+       container = xpc_dictionary_create( NULL, NULL, 0 );
+       require_action( container, exit, err = kNoMemoryErr );
        
-       if( inError && ( inError->domain != 0 ) && ( inError->error ) )
+       while( *ptr )
        {
-               err = inError->error;
-               if( inError->domain == kCFStreamErrorDomainNetDB )
+               xpc_type_t                              containerType;
+               xpc_object_t                    value;
+               int                                             c;
+               char                                    keyStr[ 256 ];
+               char                                    valStr[ 256 ];
+               
+               // At this point, zero or more of the current container's elements have been parsed.
+               // Skip the white space leading up to the container's next element, if any, or the container's end.
+               
+               while( isspace_safe( *ptr ) ) ++ptr;
+               
+               // Check if we're done with the current container.
+               
+               c = *ptr;
+               if( c == '\0' ) break;
+               
+               containerType = xpc_get_type( container );
+               if( ( ( containerType == XPC_TYPE_DICTIONARY ) && ( c == '}' ) ) ||
+                       ( ( containerType == XPC_TYPE_ARRAY )      && ( c == ']' ) ) )
                {
-                       FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
+                       XPCListItem *           item;
+                       
+                       item = list;
+                       require_action_quiet( item, exit, err = kMalformedErr );
+                       
+                       ++ptr;
+                       
+                       // Add the current container to its parent container.
+                       
+                       if( item->key )
+                       {
+                               xpc_dictionary_set_value( item->obj, item->key, container );
+                       }
+                       else
+                       {
+                               xpc_array_append_value( item->obj, container );
+                       }
+                       
+                       // Continue with the parent container.
+                       
+                       xpc_release( container );
+                       container = xpc_retain( item->obj );
+                       list = item->next;
+                       _XPCListItemFree( item );
+                       continue;
                }
-               else
+               
+               // If the current container is a dictionary, parse the key string.
+               
+               if( containerType == XPC_TYPE_DICTIONARY )
                {
-                       FPrintF( stderr, "Error %#m\n", err );
+                       err = _ParseEscapedString( ptr, end, "={}[]" kWhiteSpaceCharSet, keyStr, sizeof( keyStr ), NULL, NULL, &ptr );
+                       require_noerr_quiet( err, exit );
+                       
+                       c = *ptr;
+                       require_action_quiet( c == '=', exit, err = kMalformedErr );
+                       ++ptr;
                }
-       }
-       else
-       {
-               CFArrayRef                                      addresses;
-               CFIndex                                         count, i;
-               CFDataRef                                       addrData;
-               const struct sockaddr *         sockAddr;
-               Boolean                                         wasResolved = false;
                
-               addresses = CFHostGetAddressing( inHost, &wasResolved );
-               check( wasResolved );
+               // Check if the value is a dictionary ({...}) or an array ([...]).
                
-               if( addresses )
+               c = *ptr;
+               if( ( c == '{' ) || ( c == '[' ) )
                {
-                       count = CFArrayGetCount( addresses );
-                       for( i = 0; i < count; ++i )
+                       XPCListItem *           item;
+                       
+                       ++ptr;
+                       
+                       // Save the current container.
+                       
+                       err = _XPCListItemCreate( container, ( containerType == XPC_TYPE_DICTIONARY ) ? keyStr : NULL, &item );
+                       require_noerr( err, exit );
+                       
+                       item->next = list;
+                       list = item;
+                       item = NULL;
+                       
+                       // Create and continue with the child container.
+                       
+                       xpc_release( container );
+                       if( c == '{' )
                        {
-                               addrData = CFArrayGetCFDataAtIndex( addresses, i, &err );
-                               require_noerr( err, exit );
-                               
-                               sockAddr = (const struct sockaddr *) CFDataGetBytePtr( addrData );
-                               FPrintF( stdout, "%##a\n", sockAddr );
+                               container = xpc_dictionary_create( NULL, NULL, 0 );
+                               require_action( container, exit, err = kNoMemoryErr );
+                       }
+                       else
+                       {
+                               container = xpc_array_create( NULL, 0 );
+                               require_action( container, exit, err = kNoMemoryErr );
                        }
+                       continue;
                }
-               err = kNoErr;
+               
+               // Parse the value string.
+               
+               err = _ParseEscapedString( ptr, end, "{}[]" kWhiteSpaceCharSet, valStr, sizeof( valStr ), NULL, NULL, &ptr );
+               require_noerr_quiet( err, exit );
+               
+               err = _XPCObjectFromString( valStr, &value );
+               require_noerr_quiet( err, exit );
+               
+               if( containerType == XPC_TYPE_DICTIONARY )
+               {
+                       xpc_dictionary_set_value( container, keyStr, value );
+               }
+               else
+               {
+                       xpc_array_append_value( container, value );
+               }
+               xpc_forget( &value );
        }
+       require_action_quiet( !list, exit, err = kMalformedErr );
        
-       FPrintF( stdout, "---\n" );
-       FPrintF( stdout, "End time:   %{du:time}\n", &now );
+       check( container );
+       check( xpc_get_type( container ) == XPC_TYPE_DICTIONARY );
        
-       if( gCFHost_WaitSecs > 0 ) sleep( (unsigned int) gCFHost_WaitSecs );
+       *outDict = container;
+       container = NULL;
+       err = kNoErr;
        
 exit:
-       exit( err ? 1 : 0 );
+       xpc_release_null_safe( container );
+       if( list ) _XPCListFree( list );
+       return( err );
 }
 
-//===========================================================================================================================
-//     DNSConfigAddCmd
-//
-//     Note: Based on ajn's supplemental test tool.
-//===========================================================================================================================
-
-static void    DNSConfigAddCmd( void )
+static OSStatus        _XPCObjectFromString( const char *inString, xpc_object_t *outObject )
 {
-       OSStatus                                        err;
-       CFMutableDictionaryRef          dict    = NULL;
-       CFMutableArrayRef                       array   = NULL;
-       size_t                                          i;
-       SCDynamicStoreRef                       store   = NULL;
-       CFStringRef                                     key             = NULL;
-       Boolean                                         success;
+       OSStatus                        err;
+       xpc_object_t            object;
        
-       // Create dictionary.
+       if( 0 ) {}
        
-       dict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
-       require_action( dict, exit, err = kNoMemoryErr );
+       // Bool
        
-       // Add DNS server IP addresses.
+       else if( stricmp_prefix( inString, kXPCObjectPrefix_Bool ) == 0 )
+       {
+               const char * const              str = inString + sizeof_string( kXPCObjectPrefix_Bool );
+               bool                                    value;
+               
+               if( IsTrueString(  str, kSizeCString ) )
+               {
+                       value = true;
+               }
+               else if( IsFalseString( str, kSizeCString ) )
+               {
+                       value = false;
+               }
+               else
+               {
+                       err = kValueErr;
+                       goto exit;
+               }
+               
+               object = xpc_bool_create( value );
+               require_action( object, exit, err = kNoMemoryErr );
+       }
        
-       array = CFArrayCreateMutable( NULL, (CFIndex) gDNSConfigAdd_IPAddrCount, &kCFTypeArrayCallBacks );
-       require_action( array, exit, err = kNoMemoryErr );
+       // Data
        
-       for( i = 0; i < gDNSConfigAdd_IPAddrCount; ++i )
+       else if( stricmp_prefix( inString, kXPCObjectPrefix_Data ) == 0 )
        {
-               CFStringRef             addrStr;
+               const char * const              str = inString + sizeof_string( kXPCObjectPrefix_Data );
+               uint8_t *                               dataPtr;
+               size_t                                  dataLen;
                
-               addrStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_IPAddrArray[ i ], kCFStringEncodingUTF8 );
-               require_action( addrStr, exit, err = kUnknownErr );
+               err = HexToDataCopy( str, kSizeCString, kHexToData_DefaultFlags, &dataPtr, &dataLen, NULL );
+               require_noerr( err, exit );
                
-               CFArrayAppendValue( array, addrStr );
-               CFRelease( addrStr );
+               object = xpc_data_create( dataPtr, dataLen );
+               free( dataPtr );
+               require_action( object, exit, err = kNoMemoryErr );
        }
        
-       CFDictionarySetValue( dict, kSCPropNetDNSServerAddresses, array );
-       ForgetCF( &array );
+       // Int64
        
-       // Add domains, if any.
+       else if( stricmp_prefix( inString, kXPCObjectPrefix_Int64 ) == 0 )
+       {
+               const char * const              str = inString + sizeof_string( kXPCObjectPrefix_Int64 );
+               int64_t                                 i64;
+               
+               i64 = _StringToInt64( str, &err );
+               require_noerr_quiet( err, exit );
+               
+               object = xpc_int64_create( i64 );
+               require_action( object, exit, err = kNoMemoryErr );
+       }
        
-       array = CFArrayCreateMutable( NULL, (CFIndex) Min( gDNSConfigAdd_DomainCount, 1 ), &kCFTypeArrayCallBacks );
-       require_action( array, exit, err = kNoMemoryErr );
+       // String
        
-       if( gDNSConfigAdd_DomainCount > 0 )
+       else if( stricmp_prefix( inString, kXPCObjectPrefix_String ) == 0 )
        {
-               for( i = 0; i < gDNSConfigAdd_DomainCount; ++i )
-               {
-                       CFStringRef             domainStr;
-                       
-                       domainStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_DomainArray[ i ], kCFStringEncodingUTF8 );
-                       require_action( domainStr, exit, err = kUnknownErr );
-                       
-                       CFArrayAppendValue( array, domainStr );
-                       CFRelease( domainStr );
-               }
+               const char * const              str = inString + sizeof_string( kXPCObjectPrefix_String );
+               
+               object = xpc_string_create( str );
+               require_action( object, exit, err = kNoMemoryErr );
        }
-       else
+       
+       // UInt64
+       
+       else if( stricmp_prefix( inString, kXPCObjectPrefix_UInt64 ) == 0 )
        {
-               // There are no domains, but the domain array needs to be non-empty, so add a zero-length string to the array.
+               const char * const              str = inString + sizeof_string( kXPCObjectPrefix_UInt64 );
+               uint64_t                                u64;
+               
+               u64 = _StringToUInt64( str, &err );
+               require_noerr_quiet( err, exit );
                
-               CFArrayAppendValue( array, CFSTR( "" ) );
+               object = xpc_uint64_create( u64 );
+               require_action( object, exit, err = kNoMemoryErr );
        }
        
-       CFDictionarySetValue( dict, kSCPropNetDNSSupplementalMatchDomains, array );
-       ForgetCF( &array );
-       
-       // Add interface, if any.
+       // UUID
        
-       if( gDNSConfigAdd_Interface )
+       else if( stricmp_prefix( inString, kXPCObjectPrefix_UUID ) == 0 )
        {
-               err = CFDictionarySetCString( dict, kSCPropInterfaceName, gDNSConfigAdd_Interface, kSizeCString );
-               require_noerr( err, exit );
+               const char * const              str = inString + sizeof_string( kXPCObjectPrefix_UUID );
+               uuid_t                                  uuid;
                
-               CFDictionarySetValue( dict, kSCPropNetDNSConfirmedServiceID, gDNSConfigAdd_ID );
+               err = uuid_parse( str, uuid );
+               require_noerr_action_quiet( err, exit, err = kValueErr );
+               
+               object = xpc_uuid_create( uuid );
+               require_action( object, exit, err = kNoMemoryErr );
        }
        
-       // Set dictionary in dynamic store.
-       
-       store = SCDynamicStoreCreate( NULL, CFSTR( kDNSSDUtilIdentifier ), NULL, NULL );
-       err = map_scerror( store );
-       require_noerr( err, exit );
+       // Unsupported prefix
        
-       key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigAdd_ID, kSCEntNetDNS );
-       require_action( key, exit, err = kUnknownErr );
+       else
+       {
+               err = kValueErr;
+               goto exit;
+       }
        
-       success = SCDynamicStoreSetValue( store, key, dict );
-       require_action( success, exit, err = kUnknownErr );
+       *outObject = object;
+       err = kNoErr;
        
 exit:
-       CFReleaseNullSafe( dict );
-       CFReleaseNullSafe( array );
-       CFReleaseNullSafe( store );
-       CFReleaseNullSafe( key );
-       gExitCode = err ? 1 : 0;
+       return( err );
 }
 
-//===========================================================================================================================
-//     DNSConfigRemoveCmd
-//===========================================================================================================================
-
-static void    DNSConfigRemoveCmd( void )
+static OSStatus        _XPCListItemCreate( xpc_object_t inObject, const char *inKey, XPCListItem **outItem )
 {
-       OSStatus                                err;
-       SCDynamicStoreRef               store   = NULL;
-       CFStringRef                             key             = NULL;
-       Boolean                                 success;
+       OSStatus                        err;
+       XPCListItem *           item;
        
-       store = SCDynamicStoreCreate( NULL, CFSTR( kDNSSDUtilIdentifier ), NULL, NULL );
-       err = map_scerror( store );
-       require_noerr( err, exit );
+       item = (XPCListItem *) calloc( 1, sizeof( *item ) );
+       require_action( item, exit, err = kNoMemoryErr );
        
-       key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigRemove_ID, kSCEntNetDNS );
-       require_action( key, exit, err = kUnknownErr );
+       item->obj = xpc_retain( inObject );
+       if( ( xpc_get_type( item->obj ) == XPC_TYPE_DICTIONARY ) && inKey )
+       {
+               item->key = strdup( inKey );
+               require_action( item->key, exit, err = kNoMemoryErr );
+       }
        
-       success = SCDynamicStoreRemoveValue( store, key );
-       require_action( success, exit, err = kUnknownErr );
+       *outItem = item;
+       item = NULL;
+       err = kNoErr;
        
 exit:
-       CFReleaseNullSafe( store );
-       CFReleaseNullSafe( key );
-       gExitCode = err ? 1 : 0;
+       if( item ) _XPCListItemFree( item );
+       return( err );
+}
+
+static void    _XPCListItemFree( XPCListItem *inItem )
+{
+       xpc_forget( &inItem->obj );
+       ForgetMem( &inItem->key );
+       free( inItem );
+}
+
+static void _XPCListFree( XPCListItem *inList )
+{
+       XPCListItem *           item;
+       
+       while( ( item = inList ) != NULL )
+       {
+               inList = item->next;
+               _XPCListItemFree( item );
+       }
 }
+#endif // TARGET_OS_DARWIN
 
+#if( MDNSRESPONDER_PROJECT )
 //===========================================================================================================================
-//     XPCSendCmd
+//     InterfaceMonitorCmd
 //===========================================================================================================================
 
-static OSStatus        _XPCDictionaryCreateFromString( const char *inString, xpc_object_t *outDict );
+static void    _InterfaceMonitorPrint( mdns_interface_monitor_t inMonitor );
+static void    _InterfaceMonitorSignalHandler( void *inContext );
 
-static void    XPCSendCmd( void )
+static void    InterfaceMonitorCmd( void )
 {
-       OSStatus                        err;
-       xpc_object_t            msg, reply;
+       OSStatus                                                err;
+       mdns_interface_monitor_t                monitor;
+       dispatch_source_t                               signalSource = NULL;
+       uint32_t                                                ifIndex;
+       __block int                                             exitCode;
        
-       err = _XPCDictionaryCreateFromString( gXPCSend_MessageStr, &msg );
+       err = InterfaceIndexFromArgString( gInterface, &ifIndex );
        require_noerr_quiet( err, exit );
        
-       FPrintF( stdout, "Service:    %s\n", gXPCSend_ServiceName );
-       FPrintF( stdout, "Message:    %s\n", gXPCSend_MessageStr );
-       FPrintF( stdout, "Start time: %{du:time}\n", NULL );
-       FPrintF( stdout, "---\n" );
-       FPrintF( stdout, "XPC Message:\n%{xpc}\n", msg );
+       monitor = mdns_interface_monitor_create( ifIndex );
+       require_action( monitor, exit, err = kNoResourcesErr );
        
-       err = xpc_send_message_sync( gXPCSend_ServiceName, 0, 0, msg, &reply );
-       xpc_forget( &msg );
-       require_noerr_quiet( err, exit );
+       exitCode = 0;
+       mdns_interface_monitor_set_queue( monitor, dispatch_get_main_queue() );
+       mdns_interface_monitor_set_event_handler( monitor,
+       ^( mdns_event_t inEvent, OSStatus inError )
+       {
+               switch( inEvent )
+               {
+                       case mdns_event_error:
+                               FPrintF( stderr, "error: Interface monitor failed: %#m\n", inError );
+                               mdns_interface_monitor_invalidate( monitor );
+                               exitCode = 1;
+                               break;
+                       
+                       case mdns_event_invalidated:
+                               FPrintF( stdout, "Interface monitor invalidated.\n" );
+                               mdns_release( monitor );
+                               exit( exitCode );
+                       
+                       default:
+                               FPrintF( stdout, "Unhandled event '%s' (%ld)\n", mdns_event_to_string( inEvent ), (long) inEvent );
+                               break;
+               }
+       } );
+       mdns_interface_monitor_set_update_handler( monitor,
+       ^( __unused mdns_interface_flags_t inChangeFlags )
+       {
+               _InterfaceMonitorPrint( monitor );
+       } );
        
-       FPrintF( stdout, "XPC Reply:\n%{xpc}\n", reply );
-       FPrintF( stdout, "---\n" );
-       FPrintF( stdout, "End time:   %{du:time}\n", NULL );
-       xpc_forget( &reply );
+       _InterfaceMonitorPrint( monitor );
+       mdns_interface_monitor_activate( monitor );
+       
+       signal( SIGINT, SIG_IGN );
+       err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), _InterfaceMonitorSignalHandler, monitor,
+               &signalSource );
+       require_noerr( err, exit );
+       dispatch_resume( signalSource );
+       
+       dispatch_main();
        
 exit:
        if( err ) ErrQuit( 1, "error: %#m\n", err );
 }
 
-//===========================================================================================================================
-//     _XPCDictionaryCreateFromString
-//===========================================================================================================================
-
-#define kXPCObjectPrefix_Bool          "bool:"
-#define kXPCObjectPrefix_Data          "data:"
-#define kXPCObjectPrefix_Int64         "int:"
-#define kXPCObjectPrefix_String                "string:"
-#define kXPCObjectPrefix_UInt64                "uint:"
-#define kXPCObjectPrefix_UUID          "uuid:"
-
-typedef struct XPCListItem             XPCListItem;
-struct XPCListItem
+static void    _InterfaceMonitorPrint( mdns_interface_monitor_t inMonitor )
 {
-       XPCListItem *           next;
-       xpc_object_t            obj;
-       char *                          key;
-};
+       FPrintF( stdout, "%{du:time} %@\n", NULL, inMonitor );
+}
 
-static OSStatus        _XPCListItemCreate( xpc_object_t inObject, const char *inKey, XPCListItem **outItem );
-static void            _XPCListItemFree( XPCListItem *inItem );
-static void            _XPCListFree( XPCListItem *inList );
+static void    _InterfaceMonitorSignalHandler( void *inContext )
+{
+       mdns_interface_monitor_invalidate( (mdns_interface_monitor_t) inContext );
+}
 
-static OSStatus        _XPCObjectFromString( const char *inString, xpc_object_t *outObject );
+//===========================================================================================================================
+//     QuerierCommand
+//===========================================================================================================================
 
-static OSStatus        _XPCDictionaryCreateFromString( const char *inString, xpc_object_t *outDict )
+typedef struct
 {
-       OSStatus                                err;
-       xpc_object_t                    container;
-       const char *                    ptr             = inString;
-       const char * const              end             = inString + strlen( inString );
-       XPCListItem *                   list    = NULL;
+       dispatch_queue_t                                queue;                                  // Serial queue for command's events.
+       dispatch_semaphore_t                    doneSem;                                // Semaphore to signal when command is done.
+       uint8_t *                                               qname;                                  // Name of record to query for.
+       mdns_querier_t                                  querier;                                // Querier.
+       dispatch_source_t                               sourceSigInt;                   // Dispatch source for SIGINT.
+       dispatch_source_t                               sourceSigTerm;                  // Dispatch source for SIGTERM.
+       int32_t                                                 refCount;                               // Reference count.
+       OSStatus                                                error;                                  // Command's error.
+       uint32_t                                                ifIndex;                                // Interface index for scoping.
+       uint16_t                                                qtype;                                  // Type of record to query for.
+       uint16_t                                                qclass;                                 // Class of record to query for.
+       pid_t                                                   delegatorPID;                   // Delegator PID.
+       uint8_t                                                 delegatorUUID[ 16 ];    // Delegator UUID.
+       Boolean                                                 haveDelegatorPID;               // True if delegatorPID is set.
+       Boolean                                                 haveDelegatorUUID;              // True if delegatorUUID is set.
+       Boolean                                                 dnssecOK;                               // True if queries need an OPT record with the DO bit set.
+       Boolean                                                 checkingDisabled;               // True if queries need the CD bit set.
+       Boolean                                                 done;                                   // True if the command is done.
+       
+       // Variables for resolver.
+       
+       mdns_resolver_type_t                    resolverType;                   // Type of resolver to use.
+       mdns_resolver_t                                 resolver;                               // Resolver.
+       CFMutableArrayRef                               serverAddrs;                    // Server addresses to use for resolver.
+       char *                                                  providerName;                   // Provider name for resolver.
+       char *                                                  urlPath;                                // URL path for resolver.
+       Boolean                                                 noConnectionReuse;              // True if connection reuse is to be disabled.
+       Boolean                                                 squashCNAMEs;                   // True if CNAMEs should be squashed.
+       
+       // Variables for DNS service manager.
+       
+       mdns_dns_service_manager_t              manager;                                // DNS service manager.
+       mdns_dns_service_t                              service;                                // DNS service for query.
+       
+}      QuerierCmd;
+
+static OSStatus        _QuerierCmdCreate( QuerierCmd **outCmd );
+static void            _QuerierCmdRetain( QuerierCmd *inCmd );
+static void            _QuerierCmdRelease( QuerierCmd *inCmd );
+static OSStatus        _QuerierCmdRun( QuerierCmd *inCmd );
+
+static void    QuerierCommand( void )
+{
+       OSStatus                        err;
+       QuerierCmd *            cmd = NULL;
+       size_t                          i;
+       uint8_t                         qname[ kDomainNameLengthMax ];
        
-       container = xpc_dictionary_create( NULL, NULL, 0 );
-       require_action( container, exit, err = kNoMemoryErr );
+       err = _QuerierCmdCreate( &cmd );
+       require_noerr( err, exit );
        
-       while( *ptr )
+       if( gInterface )
        {
-               xpc_type_t                              containerType;
-               xpc_object_t                    value;
-               int                                             c;
-               char                                    keyStr[ 256 ];
-               char                                    valStr[ 256 ];
-               
-               // At this point, zero or more of the current container's elements have been parsed.
-               // Skip the white space leading up to the container's next element, if any, or the container's end.
-               
-               while( isspace_safe( *ptr ) ) ++ptr;
-               
-               // Check if we're done with the current container.
-               
-               c = *ptr;
-               if( c == '\0' ) break;
-               
-               containerType = xpc_get_type( container );
-               if( ( ( containerType == XPC_TYPE_DICTIONARY ) && ( c == '}' ) ) ||
-                       ( ( containerType == XPC_TYPE_ARRAY )      && ( c == ']' ) ) )
-               {
-                       XPCListItem *           item;
-                       
-                       item = list;
-                       require_action_quiet( item, exit, err = kMalformedErr );
-                       
-                       ++ptr;
-                       
-                       // Add the current container to its parent container.
-                       
-                       if( item->key )
-                       {
-                               xpc_dictionary_set_value( item->obj, item->key, container );
-                       }
-                       else
-                       {
-                               xpc_array_append_value( item->obj, container );
-                       }
-                       
-                       // Continue with the parent container.
-                       
-                       xpc_release( container );
-                       container = xpc_retain( item->obj );
-                       list = item->next;
-                       _XPCListItemFree( item );
-                       continue;
+               err = InterfaceIndexFromArgString( gInterface, &cmd->ifIndex );
+               require_noerr_quiet( err, exit );
+       }
+       else
+       {
+               cmd->ifIndex = 0;
+       }
+       err = DomainNameFromString( qname, gQuerier_Name, NULL );
+       if( err )
+       {
+               FPrintF( stderr, "error: Invalid domain name: '%s'\n", gDNSQuery_Name );
+               goto exit;
+       }
+       err = DomainNameDup( qname, &cmd->qname, NULL );
+       require_noerr( err, exit );
+       
+       err = RecordTypeFromArgString( gQuerier_Type, &cmd->qtype );
+       require_noerr_quiet( err, exit );
+       
+       err = RecordClassFromArgString( gQuerier_Class, &cmd->qclass );
+       require_noerr( err, exit );
+       
+       if( gQuerier_Delegator )
+       {
+               err = StringToUUID( gQuerier_Delegator, kSizeCString, false, cmd->delegatorUUID );
+               if( !err )
+               {
+                       cmd->haveDelegatorUUID = true;
                }
-               
-               // If the current container is a dictionary, parse the key string.
-               
-               if( containerType == XPC_TYPE_DICTIONARY )
+               else
                {
-                       err = _ParseEscapedString( ptr, end, "={}[]" kWhiteSpaceCharSet, keyStr, sizeof( keyStr ), NULL, NULL, &ptr );
-                       require_noerr_quiet( err, exit );
-                       
-                       c = *ptr;
-                       require_action_quiet( c == '=', exit, err = kMalformedErr );
-                       ++ptr;
+                       cmd->delegatorPID = _StringToPID( gQuerier_Delegator, &err );
+                       if( err )
+                       {
+                               FPrintF( stderr, "error: Invalid delegator PID or UUID: %s\n", gQuerier_Delegator );
+                               err = kParamErr;
+                               goto exit;
+                       }
+                       cmd->haveDelegatorPID = true;
                }
+       }
+       if( gQuerier_ResolverType )
+       {
+               cmd->resolverType = (mdns_resolver_type_t) CLIArgToValue( "resolverType", gQuerier_ResolverType, &err,
+                       kMDNSResolverTypeStr_Normal,    (int) mdns_resolver_type_normal,
+                       kMDNSResolverTypeStr_TCPOnly,   (int) mdns_resolver_type_tcp,
+                       kMDNSResolverTypeStr_TLS,               (int) mdns_resolver_type_tls,
+                       kMDNSResolverTypeStr_HTTPS,             (int) mdns_resolver_type_https,
+                       NULL );
+               require_noerr_quiet( err, exit );
                
-               // Check if the value is a dictionary ({...}) or an array ([...]).
-               
-               c = *ptr;
-               if( ( c == '{' ) || ( c == '[' ) )
+               for( i = 0; i < gQuerier_ServerAddrCount; ++i )
                {
-                       XPCListItem *           item;
-                       
-                       ++ptr;
-                       
-                       // Save the current container.
-                       
-                       err = _XPCListItemCreate( container, ( containerType == XPC_TYPE_DICTIONARY ) ? keyStr : NULL, &item );
-                       require_noerr( err, exit );
-                       
-                       item->next = list;
-                       list = item;
-                       item = NULL;
-                       
-                       // Create and continue with the child container.
+                       const char * const              addrStr = gQuerier_ServerAddrs[ i ];
+                       mdns_address_t                  serverAddr;
                        
-                       xpc_release( container );
-                       if( c == '{' )
-                       {
-                               container = xpc_dictionary_create( NULL, NULL, 0 );
-                               require_action( container, exit, err = kNoMemoryErr );
-                       }
-                       else
+                       serverAddr = mdns_address_create_from_ip_address_string( addrStr );
+                       if( !serverAddr )
                        {
-                               container = xpc_array_create( NULL, 0 );
-                               require_action( container, exit, err = kNoMemoryErr );
+                               FPrintF( stderr, "error: Failed to create address for '%s'\n", addrStr );
+                               err = kParamErr;
+                               goto exit;
                        }
-                       continue;
+                       CFArrayAppendValue( cmd->serverAddrs, serverAddr );
+                       mdns_release( serverAddr );
                }
-               
-               // Parse the value string.
-               
-               err = _ParseEscapedString( ptr, end, "{}[]" kWhiteSpaceCharSet, valStr, sizeof( valStr ), NULL, NULL, &ptr );
-               require_noerr_quiet( err, exit );
-               
-               err = _XPCObjectFromString( valStr, &value );
-               require_noerr_quiet( err, exit );
-               
-               if( containerType == XPC_TYPE_DICTIONARY )
+               if( gQuerier_ProviderName )
                {
-                       xpc_dictionary_set_value( container, keyStr, value );
+                       cmd->providerName = strdup( gQuerier_ProviderName );
+                       require_action( cmd->providerName, exit, err = kNoMemoryErr );
                }
-               else
+               if( gQuerier_URLPath )
                {
-                       xpc_array_append_value( container, value );
+                       cmd->urlPath = strdup( gQuerier_URLPath );
+                       require_action( cmd->urlPath, exit, err = kNoMemoryErr );
                }
-               xpc_forget( &value );
+               cmd->noConnectionReuse  = gQuerier_NoConnectionReuse ? true : false;
+               cmd->squashCNAMEs               = gQuerier_SquashCNAMEs          ? true : false;
        }
-       require_action_quiet( !list, exit, err = kMalformedErr );
+       cmd->dnssecOK                   = gQuerier_DNSSECOK                     ? true : false;
+       cmd->checkingDisabled   = gQuerier_CheckingDisabled     ? true : false;
+       err = _QuerierCmdRun( cmd );
+       require_noerr( err, exit );
        
-       check( container );
-       check( xpc_get_type( container ) == XPC_TYPE_DICTIONARY );
+exit:
+       if( cmd ) _QuerierCmdRelease( cmd );
+       gExitCode = err ? 1 : 0;
+}
+
+//===========================================================================================================================
+
+static OSStatus        _QuerierCmdCreate( QuerierCmd **outCmd )
+{
+       OSStatus                        err;
+       QuerierCmd *            cmd;
        
-       *outDict = container;
-       container = NULL;
+       cmd = (QuerierCmd *) calloc( 1, sizeof( *cmd ) );
+       require_action( cmd, exit, err = kNoResourcesErr );
+       
+       cmd->refCount           = 1;
+       cmd->resolverType       = mdns_resolver_type_null;
+       
+       cmd->queue = dispatch_queue_create( "com.apple.dnssdutil.querier-command", DISPATCH_QUEUE_SERIAL );
+       require_action( cmd->queue, exit, err = kNoResourcesErr );
+       
+       cmd->doneSem = dispatch_semaphore_create( 0 );
+       require_action( cmd->doneSem, exit, err = kNoResourcesErr );
+       
+       cmd->serverAddrs = CFArrayCreateMutable( kCFAllocatorDefault, 0, &mdns_cfarray_callbacks );
+       require_action( cmd->serverAddrs, exit, err = kNoResourcesErr );
+       
+       *outCmd = cmd;
+       cmd = NULL;
        err = kNoErr;
        
 exit:
-       xpc_release_null_safe( container );
-       if( list ) _XPCListFree( list );
+       if( cmd ) _QuerierCmdRelease( cmd );
        return( err );
 }
 
-static OSStatus        _XPCObjectFromString( const char *inString, xpc_object_t *outObject )
+//===========================================================================================================================
+
+static void    _QuerierCmdRetain( QuerierCmd *inCmd )
 {
-       OSStatus                        err;
-       xpc_object_t            object;
+       atomic_add_32( &inCmd->refCount, 1 );
+}
+
+//===========================================================================================================================
+
+static void    _QuerierCmdRelease( QuerierCmd *inCmd )
+{
+       if( atomic_add_and_fetch_32( &inCmd->refCount, -1 ) == 0 )
+       {
+               check( !inCmd->sourceSigInt );
+               check( !inCmd->sourceSigTerm );
+               check( !inCmd->resolver );
+               check( !inCmd->manager );
+               check( !inCmd->service );
+               check( !inCmd->querier );
+               dispatch_forget( &inCmd->queue );
+               dispatch_forget( &inCmd->doneSem );
+               ForgetMem( &inCmd->qname );
+               ForgetCF( &inCmd->serverAddrs );
+               ForgetMem( &inCmd->providerName );
+               ForgetMem( &inCmd->urlPath );
+               free( inCmd );
+       }
+}
+
+//===========================================================================================================================
+
+static void    _QuerierCmdStart( void *inCtx );
+static void    _QuerierCmdStop( QuerierCmd *inCmd, OSStatus inError );
+static void    _QuerierCmdSigIntHandler( void *inCtx );
+static void    _QuerierCmdSigTermHandler( void *inCtx );
+
+static OSStatus        _QuerierCmdRun( QuerierCmd *inCmd )
+{
+       dispatch_async_f( inCmd->queue, inCmd, _QuerierCmdStart );
+    dispatch_semaphore_wait( inCmd->doneSem, DISPATCH_TIME_FOREVER );
+       return( inCmd->error );
+}
+
+static void    _QuerierCmdStart( void *inCtx )
+{
+       OSStatus                                err;
+       QuerierCmd * const              cmd             = (QuerierCmd *) inCtx;
+       dns_config_t *                  config  = NULL;
+       mdns_querier_t                  querier = NULL;
+       const char *                    ifNamePtr;
+       char                                    ifNameBuf[ IF_NAMESIZE + 1 ];
        
-       if( 0 ) {}
+       signal( SIGINT, SIG_IGN );
+       err = DispatchSignalSourceCreate( SIGINT, cmd->queue, _QuerierCmdSigIntHandler, cmd, &cmd->sourceSigInt );
+       require_noerr( err, exit );
+       dispatch_resume( cmd->sourceSigInt );
        
-       // Bool
+       signal( SIGTERM, SIG_IGN );
+       err = DispatchSignalSourceCreate( SIGTERM, cmd->queue, _QuerierCmdSigTermHandler, cmd, &cmd->sourceSigTerm );
+       require_noerr( err, exit );
+       dispatch_resume( cmd->sourceSigTerm );
        
-       else if( stricmp_prefix( inString, kXPCObjectPrefix_Bool ) == 0 )
-       {
-               const char * const              str = inString + sizeof_string( kXPCObjectPrefix_Bool );
-               bool                                    value;
+       ifNamePtr = if_indextoname( cmd->ifIndex, ifNameBuf );
+       FPrintF( stdout, "Interface:      %u (%s)\n",           cmd->ifIndex, ifNamePtr ? ifNamePtr : "?" );
+       FPrintF( stdout, "Name:           %{du:dname}\n",       cmd->qname );
+       FPrintF( stdout, "Type:           %s (%u)\n",           RecordTypeToString( cmd->qtype ), cmd->qtype );
+       FPrintF( stdout, "Class:          %s (%u)\n",           RecordClassToString( cmd->qclass ), cmd->qclass );
+       if( cmd->resolverType != mdns_resolver_type_null )
+       {
+               CFIndex         n, i;
+               
+               FPrintF( stdout, "Resolver Type:  %s\n", mdns_resolver_type_to_string( cmd->resolverType ) );
+               if( cmd->providerName ) FPrintF( stdout, "Provider Name:  %s\n", cmd->providerName );
+               if( cmd->urlPath )              FPrintF( stdout, "URL path:       %s\n", cmd->urlPath );
+               FPrintF( stdout, "Server(s):      " );
+               n = CFArrayGetCount( cmd->serverAddrs );
+               for( i = 0; i < n; ++i )
+               {
+                       FPrintF( stdout, "%s%@", ( i == 0 ) ? "" : ", ", CFArrayGetValueAtIndex( cmd->serverAddrs, i ) );
+               }
+               FPrintF( stdout, "\n" );
+               FPrintF( stdout, "Start time:     %{du:time}\n", NULL );
+               FPrintF( stdout, "---\n" );
                
-               if( IsTrueString(  str, kSizeCString ) )
+               cmd->resolver = mdns_resolver_create( cmd->resolverType, cmd->ifIndex, &err );
+               require_noerr( err, exit );
+               
+               if( cmd->providerName )
                {
-                       value = true;
+                       err = mdns_resolver_set_provider_name( cmd->resolver, cmd->providerName );
+                       require_noerr( err, exit );
                }
-               else if( IsFalseString( str, kSizeCString ) )
+               if( cmd->urlPath )
                {
-                       value = false;
+                       err = mdns_resolver_set_url_path( cmd->resolver, cmd->urlPath );
+                       require_noerr( err, exit );
+               }
+               if( cmd->noConnectionReuse ) mdns_resolver_disable_connection_reuse( cmd->resolver, true );
+               if( cmd->squashCNAMEs ) mdns_resolver_set_squash_cnames( cmd->resolver, true );
+               for( i = 0; i < n; ++i )
+               {
+                       const mdns_address_t            addr = (mdns_address_t) CFArrayGetValueAtIndex( cmd->serverAddrs, i );
+                       
+                       err = mdns_resolver_add_server_address( cmd->resolver, addr );
+                       require_noerr( err, exit );
+               }
+               mdns_resolver_activate( cmd->resolver );
+               
+               querier = mdns_resolver_create_querier( cmd->resolver, &err );
+               require_noerr( err, exit );
+       }
+       else
+       {
+               FPrintF( stdout, "Start time:     %{du:time}\n", NULL );
+               FPrintF( stdout, "---\n" );
+               
+               config = dns_configuration_copy();
+               require_action( config, exit, err = kUnknownErr );
+               
+               cmd->manager = mdns_dns_service_manager_create( cmd->queue, &err );
+               require_noerr( err, exit );
+               
+               _QuerierCmdRetain( cmd );
+               mdns_dns_service_manager_set_event_handler( cmd->manager,
+               ^( mdns_event_t inEvent, OSStatus inError )
+               {
+                       switch( inEvent )
+                       {
+                               case mdns_event_error:
+                                       if( !cmd->done )
+                                       {
+                                               FPrintF( stderr, "error: DNS service manager failed: %#m\n", inError );
+                                               _QuerierCmdStop( cmd, inError );
+                                       }
+                                       break;
+                               
+                               case mdns_event_invalidated:
+                                       _QuerierCmdRelease( cmd );
+                                       break;
+                               
+                               default:
+                                       break;
+                       }
+               } );
+               mdns_dns_service_manager_apply_dns_config( cmd->manager, config );
+               
+               if( cmd->ifIndex == 0 )
+               {
+                       cmd->service = mdns_dns_service_manager_get_unscoped_service( cmd->manager, cmd->qname );
                }
                else
                {
-                       err = kValueErr;
+                       cmd->service = mdns_dns_service_manager_get_interface_scoped_service( cmd->manager, cmd->qname, cmd->ifIndex );
+               }
+               if( !cmd->service )
+               {
+                       FPrintF( stderr, "error: Failed to get DNS service for %{du:dname}\n", cmd->qname );
+                       err = kNotFoundErr;
                        goto exit;
                }
+               mdns_retain( cmd->service );
+               FPrintF( stdout, "Using DNS service: %@\n\n", cmd->service );
                
-               object = xpc_bool_create( value );
-               require_action( object, exit, err = kNoMemoryErr );
+               querier = mdns_dns_service_create_querier( cmd->service, &err );
+               require_noerr( err, exit );
        }
+       err = mdns_querier_set_query( querier, cmd->qname, cmd->qtype, cmd->qclass );
+       require_noerr( err, exit );
        
-       // Data
+       if( cmd->dnssecOK )                     mdns_querier_set_dnssec_ok( querier, true );
+       if( cmd->checkingDisabled )     mdns_querier_set_checking_disabled( querier, true );
+       cmd->querier = querier;
+       querier = NULL;
        
-       else if( stricmp_prefix( inString, kXPCObjectPrefix_Data ) == 0 )
+       if( cmd->haveDelegatorPID ) mdns_querier_set_delegator_pid( cmd->querier, cmd->delegatorPID );
+       else if( cmd->haveDelegatorUUID ) mdns_querier_set_delegator_uuid( cmd->querier, cmd->delegatorUUID );
+       
+       _QuerierCmdRetain( cmd );
+       mdns_querier_set_queue( cmd->querier, cmd->queue );
+       mdns_querier_set_result_handler( cmd->querier,
+       ^ {
+               if( !cmd->done )
+               {
+                       const mdns_querier_result_type_t                resultType = mdns_querier_get_result_type( cmd->querier );
+                       
+                       if( resultType == mdns_querier_result_type_response )
+                       {
+                               const uint8_t * const           msgPtr = mdns_querier_get_response_ptr( cmd->querier );
+                               const size_t                            msgLen = mdns_querier_get_response_length( cmd->querier );
+                               
+                               FPrintF( stdout, "Message size:     %zu bytes\n", msgLen );
+                               FPrintF( stdout, "%{du:dnsmsg}\n", msgPtr, msgLen );
+                               _QuerierCmdStop( cmd, kNoErr );
+                       }
+                       else
+                       {
+                               OSStatus                querierErr;
+                               
+                               if( resultType == mdns_querier_result_type_error )
+                               {
+                                       querierErr = mdns_querier_get_error( cmd->querier );
+                                       if( !querierErr ) querierErr = kUnknownErr;
+                               }
+                               else
+                               {
+                                       querierErr = kUnexpectedErr;
+                               }
+                               FPrintF( stderr, "error: Unexpected querier result: %s, error: %#m\n",
+                                       mdns_querier_result_type_to_string( resultType ), querierErr );
+                               _QuerierCmdStop( cmd, querierErr );
+                       }
+               }
+               _QuerierCmdRelease( cmd );
+       } );
+       mdns_querier_activate( cmd->querier );
+       
+exit:
+       if( config ) dns_configuration_free( config );
+       mdns_release_null_safe( querier );
+       if( err ) _QuerierCmdStop( cmd, err );
+}
+
+//===========================================================================================================================
+
+#define mdns_dns_service_manager_forget( X )           ForgetCustomEx( X, mdns_dns_service_manager_invalidate, mdns_release )
+
+static void    _QuerierCmdStop( QuerierCmd *inCmd, OSStatus inError )
+{
+       if( !inCmd->done )
        {
-               const char * const              str = inString + sizeof_string( kXPCObjectPrefix_Data );
-               uint8_t *                               dataPtr;
-               size_t                                  dataLen;
-               
-               err = HexToDataCopy( str, kSizeCString, kHexToData_DefaultFlags, &dataPtr, &dataLen, NULL );
-               require_noerr( err, exit );
-               
-               object = xpc_data_create( dataPtr, dataLen );
-               free( dataPtr );
-               require_action( object, exit, err = kNoMemoryErr );
+               inCmd->done             = true;
+               inCmd->error    = inError;
+               dispatch_source_forget( &inCmd->sourceSigInt );
+               dispatch_source_forget( &inCmd->sourceSigTerm );
+               mdns_querier_forget( &inCmd->querier );
+               mdns_resolver_forget( &inCmd->resolver );
+               mdns_dns_service_manager_forget( &inCmd->manager );
+               mdns_forget( &inCmd->service );
+               FPrintF( stdout, "---\n" );
+               FPrintF( stdout, "End time:       %{du:time}\n", NULL );
+               dispatch_semaphore_signal( inCmd->doneSem );
+       }
+}
+
+//===========================================================================================================================
+
+static void    _QuerierCmdSigIntHandler( void *inCtx )
+{
+       FPrintF( stdout, "*** Got SIGINIT signal ***\n" );
+       _QuerierCmdStop( (QuerierCmd *) inCtx, kCanceledErr );
+}
+
+//===========================================================================================================================
+
+static void    _QuerierCmdSigTermHandler( void *inCtx )
+{
+       FPrintF( stdout, "*** Got SIGTERM signal ***\n" );
+       _QuerierCmdStop( (QuerierCmd *) inCtx, kCanceledErr );
+}
+
+//===========================================================================================================================
+//     DNSProxyCmd
+//===========================================================================================================================
+
+static void    _DNSProxyCallback( DNSXConnRef inConnection, DNSXErrorType inError );
+static void    _DNSProxyCmdSignalHandler( void *inContext );
+
+static void    DNSProxyCmd( void )
+{
+       OSStatus                                err;
+       size_t                                  i;
+       DNSXConnRef                             connection;
+       IfIndex                                 inputIfIndexes[ MaxInputIf ];
+       dispatch_source_t               sigIntSource    = NULL;
+       dispatch_source_t               sigTermSource   = NULL;
+       uint32_t                                outputIfIndex;
+       char                                    ifName[ kInterfaceNameBufLen ];
+       uint8_t                                 dns64Prefix[ 16 ];
+       int                                             dns64PrefixBitLen;
+       
+       if( gDNSProxy_InputInterfaceCount > MaxInputIf )
+       {
+               FPrintF( stderr, "error: Invalid input interface count: %zu > %d (max).\n",
+                       gDNSProxy_InputInterfaceCount, MaxInputIf );
+               err = kRangeErr;
+               goto exit;
        }
        
-       // Int64
-       
-       else if( stricmp_prefix( inString, kXPCObjectPrefix_Int64 ) == 0 )
+       for( i = 0; i < gDNSProxy_InputInterfaceCount; ++i )
        {
-               const char * const              str = inString + sizeof_string( kXPCObjectPrefix_Int64 );
-               int64_t                                 i64;
+               uint32_t                ifIndex;
                
-               i64 = _StringToInt64( str, &err );
+               err = InterfaceIndexFromArgString( gDNSProxy_InputInterfaces[ i ], &ifIndex );
                require_noerr_quiet( err, exit );
                
-               object = xpc_int64_create( i64 );
-               require_action( object, exit, err = kNoMemoryErr );
+               inputIfIndexes[ i ] = ifIndex;
        }
+       while( i < MaxInputIf ) inputIfIndexes[ i++ ] = 0;      // Remaining interface indexes are required to be 0.
        
-       // String
-       
-       else if( stricmp_prefix( inString, kXPCObjectPrefix_String ) == 0 )
+       if( gDNSProxy_OutputInterface )
        {
-               const char * const              str = inString + sizeof_string( kXPCObjectPrefix_String );
-               
-               object = xpc_string_create( str );
-               require_action( object, exit, err = kNoMemoryErr );
+               err = InterfaceIndexFromArgString( gDNSProxy_OutputInterface, &outputIfIndex );
+               require_noerr_quiet( err, exit );
+       }
+       else
+       {
+               outputIfIndex = kDNSIfindexAny;
        }
        
-       // UInt64
-       
-       else if( stricmp_prefix( inString, kXPCObjectPrefix_UInt64 ) == 0 )
+       dns64PrefixBitLen = 0;
+       if( gDNSProxy_DNS64IPv6Prefix )
        {
-               const char * const              str = inString + sizeof_string( kXPCObjectPrefix_UInt64 );
-               uint64_t                                u64;
+               const char *            end;
                
-               u64 = _StringToUInt64( str, &err );
+               err = _StringToIPv6Address( gDNSProxy_DNS64IPv6Prefix,
+                       kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoScope, dns64Prefix, NULL, NULL, &dns64PrefixBitLen,
+                       &end );
+               if( !err && ( *end != '\0' ) ) err = kMalformedErr;
                require_noerr_quiet( err, exit );
-               
-               object = xpc_uint64_create( u64 );
-               require_action( object, exit, err = kNoMemoryErr );
        }
-       
-       // UUID
-       
-       else if( stricmp_prefix( inString, kXPCObjectPrefix_UUID ) == 0 )
+       FPrintF( stdout, "Input Interfaces:" );
+       for( i = 0; i < gDNSProxy_InputInterfaceCount; ++i )
        {
-               const char * const              str = inString + sizeof_string( kXPCObjectPrefix_UUID );
-               uuid_t                                  uuid;
-               
-               err = uuid_parse( str, uuid );
-               require_noerr_action_quiet( err, exit, err = kValueErr );
+               const uint32_t          ifIndex = (uint32_t) inputIfIndexes[ i ];
                
-               object = xpc_uuid_create( uuid );
-               require_action( object, exit, err = kNoMemoryErr );
+               FPrintF( stdout, "%s %u (%s)", ( i == 0 ) ? "" : ",", ifIndex, InterfaceIndexToName( ifIndex, ifName ) );
        }
+       FPrintF( stdout, "\n" );
+       FPrintF( stdout, "Output Interface: %u (%s)\n",         outputIfIndex, InterfaceIndexToName( outputIfIndex, ifName ) );
+       if( gDNSProxy_DNS64IPv6Prefix ) FPrintF( stdout, "DNS64 prefix:     %.16a/%d\n", dns64Prefix, dns64PrefixBitLen );
+       FPrintF( stdout, "Start time:       %{du:time}\n",      NULL );
+       FPrintF( stdout, "---\n" );
        
-       // Unsupported prefix
-       
+       connection = NULL;
+       if( gDNSProxy_DNS64IPv6Prefix )
+       {
+               if( __builtin_available( macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, * ) )
+               {
+                       err = DNSXEnableProxy64( &connection, kDNSProxyEnable, inputIfIndexes, outputIfIndex,
+                dns64Prefix, dns64PrefixBitLen, kDNSXProxyFlagNull, dispatch_get_main_queue(), _DNSProxyCallback );
+                       require_noerr_quiet( err, exit );
+               }
+               else
+               {
+                       FPrintF( stderr, "error: DNSXEnableProxy64() is not available on this OS.\n" );
+                       err = kUnsupportedErr;
+                       goto exit;
+               }
+       }
        else
        {
-               err = kValueErr;
-               goto exit;
+               err = DNSXEnableProxy( &connection, kDNSProxyEnable, inputIfIndexes, outputIfIndex,
+                       dispatch_get_main_queue(), _DNSProxyCallback );
+               require_noerr_quiet( err, exit );
        }
+       signal( SIGINT, SIG_IGN );
+       err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), _DNSProxyCmdSignalHandler, connection,
+               &sigIntSource );
+       require_noerr( err, exit );
+       dispatch_activate( sigIntSource );
        
-       *outObject = object;
-       err = kNoErr;
+       signal( SIGTERM, SIG_IGN );
+       err = DispatchSignalSourceCreate( SIGTERM, dispatch_get_main_queue(), _DNSProxyCmdSignalHandler, connection,
+               &sigTermSource );
+       require_noerr( err, exit );
+       dispatch_activate( sigTermSource );
+       
+       dispatch_main();
        
 exit:
-       return( err );
+       if( err ) ErrQuit( 1, "error: %#m\n", err );
 }
 
-static OSStatus        _XPCListItemCreate( xpc_object_t inObject, const char *inKey, XPCListItem **outItem )
+static void    _DNSProxyCallback( DNSXConnRef inConnection, DNSXErrorType inError )
 {
-       OSStatus                        err;
-       XPCListItem *           item;
+       Unused( inConnection );
        
-       item = (XPCListItem *) calloc( 1, sizeof( *item ) );
-       require_action( item, exit, err = kNoMemoryErr );
+       if( inError ) ErrQuit( 1, "error: DNS proxy failed: %#m\n", inError );
+}
+
+static void    _DNSProxyCmdSignalHandler( void *inContext )
+{
+       DNSXConnRef const               connection = (DNSXConnRef) inContext;
+       struct timeval                  now;
        
-       item->obj = xpc_retain( inObject );
-       if( ( xpc_get_type( item->obj ) == XPC_TYPE_DICTIONARY ) && inKey )
-       {
-               item->key = strdup( inKey );
-               require_action( item->key, exit, err = kNoMemoryErr );
-       }
+       gettimeofday( &now, NULL );
        
-       *outItem = item;
-       item = NULL;
-       err = kNoErr;
+       DNSXRefDeAlloc( connection );
        
-exit:
-       if( item ) _XPCListItemFree( item );
-       return( err );
+       FPrintF( stdout, "---\n" );
+       FPrintF( stdout, "End time:         %{du:time}\n", &now );
+       exit( 0 );
 }
 
-static void    _XPCListItemFree( XPCListItem *inItem )
-{
-       xpc_forget( &inItem->obj );
-       ForgetMem( &inItem->key );
-       free( inItem );
-}
+//===========================================================================================================================
+//     GetAddrInfoNewCommand
+//===========================================================================================================================
 
-static void _XPCListFree( XPCListItem *inList )
+
+typedef enum
 {
-       XPCListItem *           item;
+       kDelegationType_None            = 0,    // No delegation.
+       kDelegationType_PID                     = 1,    // Delegation by PID.
+       kDelegationType_UUID            = 2,    // Delegation by UUID.
+       kDelegationType_AuditToken      = 3             // Delegation by audit token.
        
-       while( ( item = inList ) != NULL )
+}      DelegationType;
+
+typedef struct
+{
+       DelegationType                  type;           // Type of delegation.
+       union
        {
-               inList = item->next;
-               _XPCListItemFree( item );
-       }
-}
-#endif // TARGET_OS_DARWIN
+               pid_t                           pid;            // Delegator's PID if type is kDelegationType_PID.
+               uuid_t                          uuid;           // Delegator's UUID if type is kDelegationType_UUID.
+               audit_token_t           auditToken;     // Delegator's audit token if type is kDelegationType_AuditToken.
+               
+       }       ident;                                                  // Delegator's identifier.
+       
+}      Delegation;
 
-#if( MDNSRESPONDER_PROJECT )
-//===========================================================================================================================
-//     InterfaceMonitorCmd
-//===========================================================================================================================
+typedef struct
+{
+       dispatch_queue_t                queue;                  // Serial queue for command's events.
+       dispatch_group_t                group;                  // GCD group to know when command is done.
+       dnssd_getaddrinfo_t             gai;                    // dnssd_getaddrinfo object.
+       dispatch_source_t               timer;                  // Timer to impose time limit on dnssd_getaddrinfo activity.
+       dispatch_source_t               sigint;                 // Dispatch source for SIGINT.
+       dispatch_source_t               sigterm;                // Dispatch source for SIGTERM.
+       const char *                    hostname;               // dnssd_getaddrinfo's hostname argument.
+       const char *                    serviceScheme;  // dnssd_getaddrinfo's service scheme argument.
+       const char *                    accountID;              // dnssd_getaddrinfo's account ID argument.
+       char *                                  stopReason;             // Reason for stopping the command.
+       Delegation                              delegation;             // Specifies the type of delegation to use for dnssd_getaddrinfo, if any.
+       int32_t                                 refCount;               // Reference count.
+       DNSServiceFlags                 flags;                  // dnssd_getaddrinfo's flags argument.
+       DNSServiceProtocol              protocols;              // dnssd_getaddrinfo's protocols argument.
+       uint32_t                                ifIndex;                // dnssd_getaddrinfo's interface index argument.
+       unsigned int                    timeLimitSecs;  // Time limit in seconds for dnssd_getaddrinfo activity.
+       OSStatus                                error;                  // Command's error.
+       Boolean                                 needAuthTags;   // True if dnssd_getaddrinfo 
+       Boolean                                 stopped;                // True if the command has been stopped.
+       Boolean                                 oneshot;                // True if the command should stop after first set of results.
+       Boolean                                 printedHeader;  // True if the results header has been printed.
+       
+}      GetAddrInfoNewCmd;
 
-static void    _InterfaceMonitorPrint( mdns_interface_monitor_t inMonitor );
-static void    _InterfaceMonitorSignalHandler( void *inContext );
+static GetAddrInfoNewCmd *     _GetAddrInfoNewCmdCreateEx( qos_class_t inQoS, Boolean inUseQoS, OSStatus *outError );
+#define _GetAddrInfoNewCmdCreate( OUT_ERROR )                                          _GetAddrInfoNewCmdCreateEx( 0, false, OUT_ERROR )
+#define _GetAddrInfoNewCmdCreateWithQoS( IN_QOS, OUT_ERROR )           _GetAddrInfoNewCmdCreateEx( IN_QOS, true, OUT_ERROR )
+static OSStatus                                _GetAddrInfoNewCmdRun( GetAddrInfoNewCmd *inCmd );
+static void                                    _GetAddrInfoNewCmdStopF( GetAddrInfoNewCmd *inCmd, const char *inFmt, ... );
+static GetAddrInfoNewCmd *     _GetAddrInfoNewCmdRetain( GetAddrInfoNewCmd *inCmd );
+static void                                    _GetAddrInfoNewCmdRelease( GetAddrInfoNewCmd *inCmd );
 
-static void    InterfaceMonitorCmd( void )
+#define _GetAddrInfoNewCmdForget( X )          ForgetCustom( X, _GetAddrInfoNewCmdRelease )
+
+static void    GetAddrInfoNewCommand( void )
 {
-       OSStatus                                                err;
-       mdns_interface_monitor_t                monitor;
-       dispatch_source_t                               signalSource = NULL;
-       uint32_t                                                ifIndex;
-       __block int                                             exitCode;
+       OSStatus                                err;
+       GetAddrInfoNewCmd *             cmd = NULL;
        
-       err = InterfaceIndexFromArgString( gInterface, &ifIndex );
+       if( gGAINew_QoS )
+       {
+               qos_class_t             qos;
+               
+               qos = (qos_class_t) CLIArgToValue( kQoSArgShortName, gGAINew_QoS, &err,
+                       kQoSTypeStr_Unspecified,                QOS_CLASS_UNSPECIFIED,
+                       kQoSTypeStr_Background,                 QOS_CLASS_BACKGROUND,
+                       kQoSTypeStr_Utility,                    QOS_CLASS_UTILITY,
+                       kQoSTypeStr_Default,                    QOS_CLASS_DEFAULT,
+                       kQoSTypeStr_UserInitiated,              QOS_CLASS_USER_INITIATED,
+                       kQoSTypeStr_UserInteractive,    QOS_CLASS_USER_INTERACTIVE,
+                       NULL );
+               require_noerr_quiet( err, exit );
+               
+               cmd = _GetAddrInfoNewCmdCreateWithQoS( qos, &err );
+               require_noerr( err, exit );
+       }
+       else
+       {
+               cmd = _GetAddrInfoNewCmdCreate( &err );
+               require_noerr( err, exit );
+       }
+       err = CheckIntegerArgument( gGAINew_TimeLimitSecs, "time limit", 0, INT_MAX );
        require_noerr_quiet( err, exit );
        
-       monitor = mdns_interface_monitor_create( ifIndex );
-       require_action( monitor, exit, err = kNoResourcesErr );
+       cmd->timeLimitSecs = (unsigned int) gGAINew_TimeLimitSecs;
        
-       exitCode = 0;
-       mdns_interface_monitor_set_queue( monitor, dispatch_get_main_queue() );
-       mdns_interface_monitor_set_event_handler( monitor,
-       ^( mdns_event_t inEvent, OSStatus inError )
+       cmd->hostname           = gGAINew_Hostname;
+       cmd->flags                      = GetDNSSDFlagsFromOpts();
+       cmd->serviceScheme      = gGAINew_ServiceScheme;
+       cmd->accountID          = gGAINew_AccountID;
+       cmd->needAuthTags       = gGAINew_WantAuthTags  ? true : false;
+       cmd->oneshot            = gGAINew_OneShot               ? true : false;
+       
+       err = InterfaceIndexFromArgString( gInterface, &cmd->ifIndex );
+       require_noerr_quiet( err, exit );
+       
+       cmd->protocols = 0;
+       if( gGAINew_ProtocolIPv4 ) cmd->protocols |= kDNSServiceProtocol_IPv4;
+       if( gGAINew_ProtocolIPv6 ) cmd->protocols |= kDNSServiceProtocol_IPv6;
+       
+       // Get delegate ID.
+       
+       if( gGAINew_DelegatorID )
        {
-               switch( inEvent )
+               pid_t           delegatorPID;
+               
+               delegatorPID = _StringToPID( gGAINew_DelegatorID, &err );
+               if( !err )
                {
-                       case mdns_event_error:
-                               FPrintF( stderr, "error: Interface monitor failed: %#m\n", inError );
-                               mdns_interface_monitor_invalidate( monitor );
-                               exitCode = 1;
-                               break;
-                       
-                       case mdns_event_invalidated:
-                               FPrintF( stdout, "Interface monitor invalidated.\n" );
-                               mdns_release( monitor );
-                               exit( exitCode );
-                       
-                       default:
-                               FPrintF( stdout, "Unhandled event '%s' (%ld)\n", mdns_event_to_string( inEvent ), (long) inEvent );
-                               break;
+                       if( delegatorPID >= 0 )
+                       {
+                               cmd->delegation.ident.pid       = delegatorPID;
+                               cmd->delegation.type            = kDelegationType_PID;
+                       }
+                       else
+                       {
+                               delegatorPID = -delegatorPID;
+                               if( audit_token_for_pid( delegatorPID, &cmd->delegation.ident.auditToken ) )
+                               {
+                                       cmd->delegation.type = kDelegationType_AuditToken;
+                               }
+                               else
+                               {
+                                       FPrintF( stderr, "Failed to get audit token for PID: %d\n", delegatorPID );
+                                       err = kParamErr;
+                                       goto exit;
+                               }
+                       }
                }
-       } );
-       mdns_interface_monitor_set_update_handler( monitor,
-       ^( __unused mdns_interface_flags_t inChangeFlags )
-       {
-               _InterfaceMonitorPrint( monitor );
-       } );
+               else
+               {
+                       err = uuid_parse( gGAINew_DelegatorID, cmd->delegation.ident.uuid );
+                       if( !err )
+                       {
+                               cmd->delegation.type = kDelegationType_UUID;
+                       }
+                       else
+                       {
+                               FPrintF( stderr, "Invalid delegate ID (PID or UUID): %s\n", gGAINew_DelegatorID );
+                               err = kParamErr;
+                               goto exit;
+                       }
+               }
+       }
+       err = _GetAddrInfoNewCmdRun( cmd );
+       require_noerr( err, exit );
        
-       _InterfaceMonitorPrint( monitor );
-       mdns_interface_monitor_activate( monitor );
+exit:
+       _GetAddrInfoNewCmdForget( &cmd );
+       gExitCode = err ? 1 : 0;
+}
+
+//===========================================================================================================================
+
+static GetAddrInfoNewCmd *     _GetAddrInfoNewCmdCreateEx( qos_class_t inQoS, Boolean inUseQoS, OSStatus *outError )
+{
+       OSStatus                                err;
+       GetAddrInfoNewCmd *             obj;
+       GetAddrInfoNewCmd *             cmd = NULL;
        
-       signal( SIGINT, SIG_IGN );
-       err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), _InterfaceMonitorSignalHandler, monitor,
-               &signalSource );
-       require_noerr( err, exit );
-       dispatch_resume( signalSource );
+       obj = (GetAddrInfoNewCmd *) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
        
-       dispatch_main();
+       obj->refCount = 1;
+       if( inUseQoS )
+       {
+               dispatch_queue_attr_t           attr;
+               
+               attr = dispatch_queue_attr_make_with_qos_class( DISPATCH_QUEUE_SERIAL, inQoS, 0 );
+               obj->queue = dispatch_queue_create( "com.apple.dnssdutil.getaddrinfo-new-command", attr );
+               require_action( obj->queue, exit, err = kNoResourcesErr );
+       }
+       else
+       {
+               obj->queue = dispatch_queue_create( "com.apple.dnssdutil.getaddrinfo-new-command", DISPATCH_QUEUE_SERIAL );
+               require_action( obj->queue, exit, err = kNoResourcesErr );
+       }
+       obj->group = dispatch_group_create();
+       require_action( obj->group, exit, err = kNoResourcesErr );
+       
+       cmd = obj;
+       obj = NULL;
+       err = kNoErr;
        
 exit:
-       if( err ) ErrQuit( 1, "error: %#m\n", err );
+       if( outError ) *outError = err;
+       _GetAddrInfoNewCmdForget( &obj );
+       return( cmd );
 }
 
-static void    _InterfaceMonitorPrint( mdns_interface_monitor_t inMonitor )
-{
-       FPrintF( stdout, "%{du:time} %@\n", NULL, inMonitor );
-}
+//===========================================================================================================================
 
-static void    _InterfaceMonitorSignalHandler( void *inContext )
+static void    _GetAddrInfoNewCmdStart( void *inCtx );
+
+static OSStatus        _GetAddrInfoNewCmdRun( GetAddrInfoNewCmd *me )
 {
-       mdns_interface_monitor_invalidate( (mdns_interface_monitor_t) inContext );
+       dispatch_group_enter( me->group );
+       dispatch_async_f( me->queue, me, _GetAddrInfoNewCmdStart );
+    dispatch_group_wait( me->group, DISPATCH_TIME_FOREVER );
+       FPrintF( stdout, "---\n" );
+       FPrintF( stdout, "End time:   %{du:time}\n", NULL );
+       if( me->stopReason ) FPrintF( stdout, "End reason: %s\n", me->stopReason );
+       return( me->error );
 }
 
-//===========================================================================================================================
-//     DNSProxyCmd
-//===========================================================================================================================
-
-static void    _DNSProxyCallback( DNSXConnRef inConnection, DNSXErrorType inError );
-static void    _DNSProxyCmdSignalHandler( void *inContext );
-
-static void    DNSProxyCmd( void )
+static void    _GetAddrInfoNewCmdStart( void *inCtx )
 {
-       OSStatus                                err;
-       size_t                                  i;
-       DNSXConnRef                             connection;
-       IfIndex                                 inputIfIndexes[ MaxInputIf ];
-       dispatch_source_t               sigIntSource    = NULL;
-       dispatch_source_t               sigTermSource   = NULL;
-       uint32_t                                outputIfIndex;
-       char                                    ifName[ kInterfaceNameBufLen ];
+       OSStatus                                                err;
+       GetAddrInfoNewCmd * const               me = (GetAddrInfoNewCmd *) inCtx;
+       char                                                    ifName[ kInterfaceNameBufLen ];
        
-       if( gDNSProxy_InputInterfaceCount > MaxInputIf )
-       {
-               FPrintF( stderr, "error: Invalid input interface count: %zu > %d (max).\n",
-                       gDNSProxy_InputInterfaceCount, MaxInputIf );
-               err = kRangeErr;
-               goto exit;
-       }
+       me->gai = dnssd_getaddrinfo_create();
+       require_action( me->gai, exit, err = kNoResourcesErr );
        
-       for( i = 0; i < gDNSProxy_InputInterfaceCount; ++i )
+       dnssd_getaddrinfo_set_hostname( me->gai, me->hostname );
+       dnssd_getaddrinfo_set_flags( me->gai, me->flags );
+       dnssd_getaddrinfo_set_interface_index( me->gai, me->ifIndex );
+       dnssd_getaddrinfo_set_protocols( me->gai, me->protocols );
+       dnssd_getaddrinfo_set_need_authenticated_results( me->gai, me->needAuthTags ? true : false );
+       switch( me->delegation.type )
        {
-               uint32_t                ifIndex;
+               case kDelegationType_PID:
+                       dnssd_getaddrinfo_set_delegate_pid( me->gai, me->delegation.ident.pid );
+                       break;
                
-               err = InterfaceIndexFromArgString( gDNSProxy_InputInterfaces[ i ], &ifIndex );
-               require_noerr_quiet( err, exit );
+               case kDelegationType_UUID:
+                       dnssd_getaddrinfo_set_delegate_uuid( me->gai, me->delegation.ident.uuid );
+                       break;
                
-               inputIfIndexes[ i ] = ifIndex;
+               case kDelegationType_AuditToken:
+                       if( __builtin_available( macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, * ) )
+                       {
+                               dnssd_getaddrinfo_set_delegate_audit_token( me->gai, me->delegation.ident.auditToken );
+                       }
+                       else
+                       {
+                               FPrintF( stderr, "error: dnssd_getaddrinfo_set_delegate_audit_token() is not available on this OS.\n" );
+                               err = kUnsupportedErr;
+                               goto exit;
+                       }
+                       break;
+               
+               case kDelegationType_None:
+               default:
+                       break;
        }
-       while( i < MaxInputIf ) inputIfIndexes[ i++ ] = 0;      // Remaining interface indexes are required to be 0.
-       
-       if( gDNSProxy_OutputInterface )
+       if( me->serviceScheme )
        {
-               err = InterfaceIndexFromArgString( gDNSProxy_OutputInterface, &outputIfIndex );
-               require_noerr_quiet( err, exit );
+               if( __builtin_available( macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, * ) )
+               {
+                       dnssd_getaddrinfo_set_service_scheme( me->gai, me->serviceScheme );
+               }
+               else
+               {
+                       FPrintF( stderr, "error: dnssd_getaddrinfo_set_service_scheme() is not available on this OS.\n" );
+                       err = kUnsupportedErr;
+                       goto exit;
+               }
        }
-       else
+       if( me->accountID )
        {
-               outputIfIndex = kDNSIfindexAny;
+               if( __builtin_available( macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, * ) )
+               {
+                       dnssd_getaddrinfo_set_account_id( me->gai, me->accountID );
+               }
+               else
+               {
+                       FPrintF( stderr, "error: dnssd_getaddrinfo_set_account_id() is not available on this OS.\n" );
+                       err = kUnsupportedErr;
+                       goto exit;
+               }
        }
-       
-       FPrintF( stdout, "Input Interfaces:" );
-       for( i = 0; i < gDNSProxy_InputInterfaceCount; ++i )
+       dnssd_getaddrinfo_set_queue( me->gai, me->queue );
+       dnssd_getaddrinfo_set_result_handler( me->gai,
+       ^( dnssd_getaddrinfo_result_t *inResultArray, size_t inResultCount )
        {
-               const uint32_t          ifIndex = (uint32_t) inputIfIndexes[ i ];
-               
-               FPrintF( stdout, "%s %u (%s)", ( i == 0 ) ? "" : ",", ifIndex, InterfaceIndexToName( ifIndex, ifName ) );
-       }
-       FPrintF( stdout, "\n" );
-       FPrintF( stdout, "Output Interface: %u (%s)\n",         outputIfIndex, InterfaceIndexToName( outputIfIndex, ifName ) );
-       FPrintF( stdout, "Start time:       %{du:time}\n",      NULL );
-       FPrintF( stdout, "---\n" );
+               if( !me->gai ) return;
+               if( inResultCount > 0 )
+               {
+                       size_t          i;
+                       
+                       for( i = 0; i < inResultCount; ++i )
+                       {
+                               const dnssd_getaddrinfo_result_t                        result  = inResultArray[ i ];
+                               const dnssd_getaddrinfo_result_type_t           type    = dnssd_getaddrinfo_result_get_type( result );
+                               const char *                                                            typeStr;
+                               const char *                                                            cacheStr;
+                               
+                               if( !me->printedHeader )
+                               {
+                                       FPrintF( stdout, "%-26s  Type C? IF %-30s Address\n", "Timestamp", "Hostname" );
+                                       me->printedHeader = true;
+                               }
+                               switch( type )
+                               {
+                                       case dnssd_getaddrinfo_result_type_add:                         typeStr = "Add"; break;
+                                       case dnssd_getaddrinfo_result_type_remove:                      typeStr = "Rmv"; break;
+                                       case dnssd_getaddrinfo_result_type_no_address:          typeStr = "NoA"; break;
+                                       case dnssd_getaddrinfo_result_type_expired:                     typeStr = "Exp"; break;
+                                       case dnssd_getaddrinfo_result_type_service_binding:     typeStr = "SvB"; break;
+                                       default:                                                                                        typeStr = "???"; break;
+                               }
+                               cacheStr = "-";
+                               if( __builtin_available( macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, * ) )
+                               {
+                                       if( type != dnssd_getaddrinfo_result_type_remove )
+                                       {
+                                               cacheStr = dnssd_getaddrinfo_result_is_from_cache( result ) ? "Y" : "N";
+                                       }
+                               }
+                               FPrintF( stdout, "%{du:time}  %-4s %-2s %2d %-30s %##a\n",
+                                       NULL, typeStr, cacheStr, dnssd_getaddrinfo_result_get_interface_index( result ),
+                                       dnssd_getaddrinfo_result_get_hostname( result ), dnssd_getaddrinfo_result_get_address( result ) );
+                               if( __builtin_available( macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, * ) )
+                               {
+                                       if( me->flags & kDNSServiceFlagsReturnIntermediates )
+                                       {
+                                               const dnssd_cname_array_t               cnames = dnssd_getaddrinfo_result_get_cnames( result );
+                                               size_t                                                  j, n;
+                                               
+                                               FPrintF( stdout, "    Canonical Names: [" );
+                                               n = dnssd_cname_array_get_count( cnames );
+                                               for( j = 0; j < n; ++j )
+                                               {
+                                                       FPrintF( stdout, "%s%s", ( j == 0 ) ? "" : ", ", dnssd_cname_array_get_cname( cnames, j ) );
+                                               }
+                                               FPrintF( stdout, "]\n" );
+                                       }
+                               }
+                               if( me->needAuthTags )
+                               {
+                                       if( ( type == dnssd_getaddrinfo_result_type_add ) || ( type == dnssd_getaddrinfo_result_type_expired ) )
+                                       {
+                                               const void *            tagPtr;
+                                               size_t                          tagLen;
+                                               
+                                               tagPtr = dnssd_getaddrinfo_result_get_authentication_tag( result, &tagLen );
+                                               FPrintF( stdout, "    Auth Tag: " );
+                                               if( tagPtr )    FPrintF( stdout, "%.4H (%zu bytes)\n", tagPtr, (int) tagLen, (int) tagLen, tagLen );
+                                               else                    FPrintF( stdout, "<NO AUTH TAG!>\n" );
+                                       }
+                               }
+                       }
+                       if( me->oneshot ) _GetAddrInfoNewCmdStopF( me, "one-shot done" );
+               }
+       } );
+       _GetAddrInfoNewCmdRetain( me );
+       dispatch_group_enter( me->group );
+       dnssd_getaddrinfo_set_event_handler( me->gai,
+       ^( dnssd_event_t inEvent, DNSServiceErrorType inError )
+       {
+               switch( inEvent )
+               {
+                       case dnssd_event_invalidated:
+                               dispatch_group_leave( me->group );
+                               _GetAddrInfoNewCmdRelease( me );
+                               break;
+                       
+                       case dnssd_event_error:
+                               if( !me->error ) me->error = inError;
+                               _GetAddrInfoNewCmdStopF( me, "error %#m", inError );
+                               break;
+                       
+                       default:
+                               break;
+               }
+       } );
        
-       connection = NULL;
-       err = DNSXEnableProxy( &connection, kDNSProxyEnable, inputIfIndexes, outputIfIndex, dispatch_get_main_queue(),
-               _DNSProxyCallback );
-       require_noerr_quiet( err, exit );
+       // Set up signal handlers.
        
        signal( SIGINT, SIG_IGN );
-       err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), _DNSProxyCmdSignalHandler, connection,
-               &sigIntSource );
-       require_noerr( err, exit );
-       dispatch_activate( sigIntSource );
+       me->sigint = dispatch_source_create( DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) SIGINT, 0, me->queue );
+       require_action( me->sigint, exit, err = kNoResourcesErr );
+       
+       dispatch_source_set_event_handler( me->sigint, ^{ _GetAddrInfoNewCmdStopF( me, "interrupt signal" ); } );
+       dispatch_activate( me->sigint );
        
        signal( SIGTERM, SIG_IGN );
-       err = DispatchSignalSourceCreate( SIGTERM, dispatch_get_main_queue(), _DNSProxyCmdSignalHandler, connection,
-               &sigTermSource );
-       require_noerr( err, exit );
-       dispatch_activate( sigTermSource );
+       me->sigterm = dispatch_source_create( DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) SIGTERM, 0, me->queue );
+       require_action( me->sigterm, exit, err = kNoResourcesErr );
+       
+       dispatch_source_set_event_handler( me->sigterm, ^{ _GetAddrInfoNewCmdStopF( me, "termination signal" ); } );
+       dispatch_activate( me->sigterm );
+       
+       // Start getaddrinfo operation.
+       
+       InterfaceIndexToName( me->ifIndex, ifName );
+       FPrintF( stdout, "Service Scheme: %s\n", me->serviceScheme );
+       FPrintF( stdout, "Flags:          %#{flags}\n", me->flags, kDNSServiceFlagsDescriptors );
+       FPrintF( stdout, "Interface:      %d (%s)\n",   (int32_t) me->ifIndex, ifName );
+       FPrintF( stdout, "Protocols:      %#{flags}\n", me->protocols, kDNSServiceProtocolDescriptors );
+       FPrintF( stdout, "Name:           %s\n",                me->hostname );
+       FPrintF( stdout, "Mode:           %s\n",                me->oneshot ? "one-shot" : "continuous" );
+       if( me->serviceScheme ) FPrintF( stdout, "Service Scheme: %s\n", me->serviceScheme );
+       if( me->accountID )             FPrintF( stdout, "Account ID:     %s\n", me->accountID );
+       FPrintF( stdout, "Time limit:     " );
+       if( me->timeLimitSecs > 0 )     FPrintF( stdout, "%d second%?c\n", me->timeLimitSecs, me->timeLimitSecs != 1, 's' );
+       else                                            FPrintF( stdout, "∞\n" );
+       FPrintF( stdout, "Start time: %{du:time}\n",    NULL );
+       FPrintF( stdout, "---\n" );
        
-       dispatch_main();
+       if( me->timeLimitSecs > 0 )
+       {
+               me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+               require_action( me->timer, exit, err = kNoResourcesErr );
+               
+               dispatch_source_set_timer( me->timer, dispatch_time_seconds( me->timeLimitSecs ), DISPATCH_TIME_FOREVER,
+                       me->timeLimitSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 20 ) );
+               dispatch_source_set_event_handler( me->timer, ^{ _GetAddrInfoNewCmdStopF( me, "time limit" ); } );
+               dispatch_activate( me->timer );
+       }
+       dnssd_getaddrinfo_activate( me->gai );
+       err = kNoErr;
        
 exit:
-       if( err ) ErrQuit( 1, "error: %#m\n", err );
+       if( err ) _GetAddrInfoNewCmdStopF( me, "error %#m", err );
 }
 
-static void    _DNSProxyCallback( DNSXConnRef inConnection, DNSXErrorType inError )
+//===========================================================================================================================
+
+static void    _GetAddrInfoNewCmdStopF( GetAddrInfoNewCmd *me, const char *inFmt, ... )
 {
-       Unused( inConnection );
-       
-       if( inError ) ErrQuit( 1, "error: DNS proxy failed: %#m\n", inError );
+       if( !me->stopped )
+       {
+               me->stopped = true;
+               dnssd_getaddrinfo_forget( &me->gai );
+               dispatch_source_forget( &me->timer );
+               dispatch_source_forget( &me->sigint );
+               dispatch_source_forget( &me->sigterm );
+               if( inFmt )
+               {
+                       va_list         args;
+                       
+                       check( !me->stopReason );
+                       va_start( args, inFmt );
+                       VASPrintF( &me->stopReason, inFmt, args );
+                       va_end( args );
+                       check( me->stopReason );
+               }
+               dispatch_group_leave( me->group );
+       }
 }
 
-static void    _DNSProxyCmdSignalHandler( void *inContext )
+//===========================================================================================================================
+
+static GetAddrInfoNewCmd *     _GetAddrInfoNewCmdRetain( GetAddrInfoNewCmd *me )
 {
-       DNSXConnRef const               connection = (DNSXConnRef) inContext;
-       struct timeval                  now;
-       
-       gettimeofday( &now, NULL );
-       
-       DNSXRefDeAlloc( connection );
-       
-       FPrintF( stdout, "---\n" );
-       FPrintF( stdout, "End time:         %{du:time}\n", &now );
-       exit( 0 );
+       atomic_add_32( &me->refCount, 1 );
+       return( me );
+}
+
+//===========================================================================================================================
+
+static void    _GetAddrInfoNewCmdRelease( GetAddrInfoNewCmd *me )
+{
+       if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 )
+       {
+               check( !me->gai );
+               check( !me->timer );
+               check( !me->sigint );
+               check( !me->sigterm );
+               dispatch_forget( &me->queue );
+               dispatch_forget( &me->group );
+               ForgetMem( &me->stopReason );
+               free( me );
+       }
 }
 
 //===========================================================================================================================
@@ -19240,13 +26014,56 @@ static void    XCTestCmd( void )
 {
     int result = 0;
     setenv(DNSSDUTIL_XCTEST, DNSSDUTIL_XCTEST, 0);
-    if(!TestUtilsRunXCTestNamed(gXCTest_Classname)) {
+    if(!run_xctest_named(gXCTest_Classname)) {
         result = 1;
     }
     unsetenv(DNSSDUTIL_XCTEST);
     exit( result );
 }
 
+//===========================================================================================================================
+//    MultiConnectTestCmd
+//===========================================================================================================================
+
+static void    MultiConnectTestCmd( void )
+{
+    int result = 0;
+       dispatch_group_t group = dispatch_group_create();
+       int count = gMultiConnectTest_ConnectionCount;
+       DNSServiceRef * refs = calloc( (uint32_t)count, sizeof(DNSServiceRef) );
+
+       // Create count connections on async queue
+       // rdar://problem/59861422
+       __block int goodcount = 0;
+       for ( int i = 0; i < count; i++ ) {
+               char label[256];
+               sprintf( label, "multi-connect queue %d", i+1 );
+               dispatch_group_async( group, dispatch_queue_create( label, DISPATCH_QUEUE_SERIAL ), ^{
+                       DNSServiceErrorType err = DNSServiceCreateConnection( &refs[i] );
+                       if ( err ) {
+                               fprintf( stderr, "%s Failed to create connection # %d err(%d)\n", __FUNCTION__, i, err );
+            } else {
+                goodcount++;
+            }
+               });
+       }
+       dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
+       dispatch_release( group );
+       fprintf( stderr, "%s Created %d connections of %d\n", __FUNCTION__, goodcount, count );
+
+       // Free them
+       goodcount = 0;
+       for ( int i = 0; i < count; i++ ) {
+               if ( refs[i] ) {
+                       DNSServiceRefDeallocate( refs[i] );
+                       goodcount++;
+               }
+       }
+       fprintf( stderr, "%s Stopped %d connections of %d\n", __FUNCTION__, goodcount, count );
+       result = (goodcount == count) ? 0 : 1;
+    exit( result );
+}
+
 #endif // MDNSRESPONDER_PROJECT
 
 //===========================================================================================================================
@@ -19284,11 +26101,11 @@ static void   Exit( void *inContext )
 }
 
 //===========================================================================================================================
-//     _PrintFExtensionTimestampHandler
+//     _PrintFExtensionHandler_Timestamp
 //===========================================================================================================================
 
 static int
-       _PrintFExtensionTimestampHandler(
+       _PrintFExtensionHandler_Timestamp(
                PrintFContext * inContext,
                PrintFFormat *  inFormat,
                PrintFVAList *  inArgs,
@@ -19322,23 +26139,41 @@ exit:
 }
 
 //===========================================================================================================================
-//     _PrintFExtensionDNSMessageHandler
+//     _PrintFExtensionHandler_DNSMessage
 //===========================================================================================================================
 
 static int
-       _PrintFExtensionDNSMessageHandler(
+       _PrintFExtensionHandler_DNSMessageCommon(
+               PrintFContext * inContext,
+               PrintFFormat *  inFormat,
+               PrintFVAList *  inArgs,
+               void *                  inUserContext,
+               Boolean                 inRawRData );
+
+static int
+       _PrintFExtensionHandler_DNSMessage(
                PrintFContext * inContext,
                PrintFFormat *  inFormat,
                PrintFVAList *  inArgs,
                void *                  inUserContext )
 {
-       OSStatus                        err;
-       const void *            msgPtr;
-       size_t                          msgLen;
-       char *                          text;
-       int                                     n;
-       Boolean                         isMDNS;
-       Boolean                         printRawRData;
+       return( _PrintFExtensionHandler_DNSMessageCommon( inContext, inFormat, inArgs, inUserContext, false ) );
+}
+
+static int
+       _PrintFExtensionHandler_DNSMessageCommon(
+               PrintFContext * inContext,
+               PrintFFormat *  inFormat,
+               PrintFVAList *  inArgs,
+               void *                  inUserContext,
+               Boolean                 inRawRData )
+{
+       OSStatus                                        err;
+       const void *                            msgPtr;
+       size_t                                          msgLen;
+       char *                                          msgStr;
+       DNSMessageToStringFlags         flags;
+       int                                                     n;
        
        Unused( inUserContext );
        
@@ -19346,13 +26181,15 @@ static int
        msgLen = va_arg( inArgs->args, size_t );
        require_action_quiet( !inFormat->suppress, exit, n = 0 );
        
-       isMDNS                  = ( inFormat->altForm   > 0 ) ? true : false;
-       printRawRData   = ( inFormat->precision > 0 ) ? true : false;
-       err = DNSMessageToText( msgPtr, msgLen, isMDNS, printRawRData, &text );
+       flags = kDNSMessageToStringFlags_None;
+       if( inRawRData )                                flags |= kDNSMessageToStringFlag_RawRData;
+       if( inFormat->altForm   == 1 )  flags |= kDNSMessageToStringFlag_MDNS;
+       if( inFormat->precision == 1 )  flags |= kDNSMessageToStringFlag_OneLine;
+       err = DNSMessageToString( msgPtr, msgLen, flags, &msgStr );
        if( !err )
        {
-               n = PrintFCore( inContext, "%*{text}", inFormat->fieldWidth, text, kSizeCString );
-               free( text );
+               n = PrintFCore( inContext, "%*{text}", inFormat->fieldWidth, msgStr, kSizeCString );
+               free( msgStr );
        }
        else
        {
@@ -19364,11 +26201,25 @@ exit:
 }
 
 //===========================================================================================================================
-//     _PrintFExtensionCallbackFlagsHandler
+//     _PrintFExtensionHandler_RawDNSMessage
+//===========================================================================================================================
+
+static int
+       _PrintFExtensionHandler_RawDNSMessage(
+               PrintFContext * inContext,
+               PrintFFormat *  inFormat,
+               PrintFVAList *  inArgs,
+               void *                  inUserContext )
+{
+       return( _PrintFExtensionHandler_DNSMessageCommon( inContext, inFormat, inArgs, inUserContext, true ) );
+}
+
+//===========================================================================================================================
+//     _PrintFExtensionHandler_CallbackFlags
 //===========================================================================================================================
 
 static int
-       _PrintFExtensionCallbackFlagsHandler(
+       _PrintFExtensionHandler_CallbackFlags(
                PrintFContext * inContext,
                PrintFFormat *  inFormat,
                PrintFVAList *  inArgs,
@@ -19393,54 +26244,94 @@ exit:
 }
 
 //===========================================================================================================================
-//     _PrintFExtensionDNSRecordDataHandler
+//     _PrintFExtensionHandler_DNSRecordData
+//===========================================================================================================================
+
+static int
+       _PrintFExtensionHandler_DNSRecordData(
+               PrintFContext * inContext,
+               PrintFFormat *  inFormat,
+               PrintFVAList *  inArgs,
+               void *                  inUserContext )
+{
+       const void *            rdataPtr;
+       int                                     recordType, n, fieldWidth;
+       unsigned int            rdataLen;
+       
+       Unused( inUserContext );
+       
+       recordType      = va_arg( inArgs->args, int );
+       rdataPtr        = va_arg( inArgs->args, const void * );
+       rdataLen        = va_arg( inArgs->args, unsigned int );
+       require_action_quiet( !inFormat->suppress, exit, n = 0 );
+       
+       check( inFormat->fieldWidth < INT_MAX );
+       fieldWidth = inFormat->leftJustify ? -( (int) inFormat->fieldWidth ) : ( (int) inFormat->fieldWidth );
+       
+       if( rdataLen > 0 )
+       {
+               char *          rdataStr = NULL;
+               
+               DNSRecordDataToString( rdataPtr, rdataLen, recordType, &rdataStr );
+               if( rdataStr )
+               {
+                       n = PrintFCore( inContext, "%*s", fieldWidth, rdataStr );
+                       free( rdataStr );
+               }
+               else
+               {
+                       n = PrintFCore( inContext, "%*H", fieldWidth, rdataPtr, rdataLen, rdataLen );
+               }
+       }
+       else
+       {
+               n = PrintFCore( inContext, "%*s", fieldWidth, "<< ZERO-LENGTH RDATA >>" );
+       }
+       
+exit:
+       return( n );
+}
+
+//===========================================================================================================================
+//     _PrintFExtensionHandler_DomainName
 //===========================================================================================================================
 
 static int
-       _PrintFExtensionDNSRecordDataHandler(
+       _PrintFExtensionHandler_DomainName(
                PrintFContext * inContext,
                PrintFFormat *  inFormat,
                PrintFVAList *  inArgs,
                void *                  inUserContext )
 {
-       const void *            rdataPtr;
-       unsigned int            rdataLen, rdataType;
+       OSStatus                        err;
+       const uint8_t *         namePtr;
        int                                     n, fieldWidth;
+       char                            nameStr[ kDNSServiceMaxDomainName ];
        
        Unused( inUserContext );
        
-       rdataType       = va_arg( inArgs->args, unsigned int );
-       rdataPtr        = va_arg( inArgs->args, const void * );
-       rdataLen        = va_arg( inArgs->args, unsigned int );
+       namePtr = va_arg( inArgs->args, const uint8_t * );
        require_action_quiet( !inFormat->suppress, exit, n = 0 );
        
        check( inFormat->fieldWidth < INT_MAX );
        fieldWidth = inFormat->leftJustify ? -( (int) inFormat->fieldWidth ) : ( (int) inFormat->fieldWidth );
        
-       if( rdataLen > 0 )
+       err = DomainNameToString( namePtr, NULL, nameStr, NULL );
+       check_noerr( err );
+       if( !err )
        {
-               char *          rdataStr = NULL;
-               
-               DNSRecordDataToString( rdataPtr, rdataLen, rdataType, NULL, 0, &rdataStr );
-               if( rdataStr )
-               {
-                       n = PrintFCore( inContext, "%*s", fieldWidth, rdataStr );
-                       free( rdataStr );
-               }
-               else
-               {
-                       n = PrintFCore( inContext, "%*H", fieldWidth, rdataPtr, rdataLen, rdataLen );
-               }
+               n = PrintFCore( inContext, "%*s", fieldWidth, nameStr );
        }
        else
        {
-               n = PrintFCore( inContext, "%*s", fieldWidth, "<< ZERO-LENGTH RDATA >>" );
+               n = PrintFCore( inContext, "%*s", fieldWidth, "<< ERROR: domain name conversion failed >>" );
        }
        
 exit:
        return( n );
 }
 
+
 //===========================================================================================================================
 //     GetDNSSDFlagsFromOpts
 //===========================================================================================================================
@@ -19474,6 +26365,7 @@ static DNSServiceFlags  GetDNSSDFlagsFromOpts( void )
        if( gDNSSDFlag_UnicastResponse )                flags |= kDNSServiceFlagsUnicastResponse;
        if( gDNSSDFlag_Unique )                                 flags |= kDNSServiceFlagsUnique;
        if( gDNSSDFlag_WakeOnResolve )                  flags |= kDNSServiceFlagsWakeOnResolve;
+       if( gDNSSDFlag_EnableDNSSEC )                   flags |= kDNSServiceFlagsValidate;
        
        return( flags );
 }
@@ -19741,109 +26633,23 @@ exit:
 //     RecordTypeFromArgString
 //===========================================================================================================================
 
-typedef struct
-{
-       uint16_t                        value;  // Record type's numeric value.
-       const char *            name;   // Record type's name as a string (e.g., "A", "PTR", "SRV").
-       
-}      RecordType;
-
-static const RecordType                kRecordTypes[] =
-{
-       // Common types.
-       
-       { kDNSServiceType_A,                    "A" },
-       { kDNSServiceType_AAAA,                 "AAAA" },
-       { kDNSServiceType_PTR,                  "PTR" },
-       { kDNSServiceType_SRV,                  "SRV" },
-       { kDNSServiceType_TXT,                  "TXT" },
-       { kDNSServiceType_CNAME,                "CNAME" },
-       { kDNSServiceType_SOA,                  "SOA" },
-       { kDNSServiceType_NSEC,                 "NSEC" },
-       { kDNSServiceType_NS,                   "NS" },
-       { kDNSServiceType_MX,                   "MX" },
-       { kDNSServiceType_ANY,                  "ANY" },
-       { kDNSServiceType_OPT,                  "OPT" },
-       
-       // Less common types.
-       
-       { kDNSServiceType_MD,                   "MD" },
-       { kDNSServiceType_NS,                   "NS" },
-       { kDNSServiceType_MD,                   "MD" },
-       { kDNSServiceType_MF,                   "MF" },
-       { kDNSServiceType_MB,                   "MB" },
-       { kDNSServiceType_MG,                   "MG" },
-       { kDNSServiceType_MR,                   "MR" },
-       { kDNSServiceType_NULL,                 "NULL" },
-       { kDNSServiceType_WKS,                  "WKS" },
-       { kDNSServiceType_HINFO,                "HINFO" },
-       { kDNSServiceType_MINFO,                "MINFO" },
-       { kDNSServiceType_RP,                   "RP" },
-       { kDNSServiceType_AFSDB,                "AFSDB" },
-       { kDNSServiceType_X25,                  "X25" },
-       { kDNSServiceType_ISDN,                 "ISDN" },
-       { kDNSServiceType_RT,                   "RT" },
-       { kDNSServiceType_NSAP,                 "NSAP" },
-       { kDNSServiceType_NSAP_PTR,             "NSAP_PTR" },
-       { kDNSServiceType_SIG,                  "SIG" },
-       { kDNSServiceType_KEY,                  "KEY" },
-       { kDNSServiceType_PX,                   "PX" },
-       { kDNSServiceType_GPOS,                 "GPOS" },
-       { kDNSServiceType_LOC,                  "LOC" },
-       { kDNSServiceType_NXT,                  "NXT" },
-       { kDNSServiceType_EID,                  "EID" },
-       { kDNSServiceType_NIMLOC,               "NIMLOC" },
-       { kDNSServiceType_ATMA,                 "ATMA" },
-       { kDNSServiceType_NAPTR,                "NAPTR" },
-       { kDNSServiceType_KX,                   "KX" },
-       { kDNSServiceType_CERT,                 "CERT" },
-       { kDNSServiceType_A6,                   "A6" },
-       { kDNSServiceType_DNAME,                "DNAME" },
-       { kDNSServiceType_SINK,                 "SINK" },
-       { kDNSServiceType_APL,                  "APL" },
-       { kDNSServiceType_DS,                   "DS" },
-       { kDNSServiceType_SSHFP,                "SSHFP" },
-       { kDNSServiceType_IPSECKEY,             "IPSECKEY" },
-       { kDNSServiceType_RRSIG,                "RRSIG" },
-       { kDNSServiceType_DNSKEY,               "DNSKEY" },
-       { kDNSServiceType_DHCID,                "DHCID" },
-       { kDNSServiceType_NSEC3,                "NSEC3" },
-       { kDNSServiceType_NSEC3PARAM,   "NSEC3PARAM" },
-       { kDNSServiceType_HIP,                  "HIP" },
-       { kDNSServiceType_SPF,                  "SPF" },
-       { kDNSServiceType_UINFO,                "UINFO" },
-       { kDNSServiceType_UID,                  "UID" },
-       { kDNSServiceType_GID,                  "GID" },
-       { kDNSServiceType_UNSPEC,               "UNSPEC" },
-       { kDNSServiceType_TKEY,                 "TKEY" },
-       { kDNSServiceType_TSIG,                 "TSIG" },
-       { kDNSServiceType_IXFR,                 "IXFR" },
-       { kDNSServiceType_AXFR,                 "AXFR" },
-       { kDNSServiceType_MAILB,                "MAILB" },
-       { kDNSServiceType_MAILA,                "MAILA" }
-};
-
 static OSStatus        RecordTypeFromArgString( const char *inString, uint16_t *outValue )
 {
-       OSStatus                                                err;
-       int32_t                                                 i32;
-       const RecordType *                              type;
-       const RecordType * const                end = kRecordTypes + countof( kRecordTypes );
+       OSStatus                err;
+       uint16_t                value;
        
-       for( type = kRecordTypes; type < end; ++type )
+       value = DNSRecordTypeStringToValue( inString );
+       if( value == 0 )
        {
-               if( strcasecmp( type->name, inString ) == 0 )
-               {
-                       *outValue = type->value;
-                       return( kNoErr );
-               }
+               int32_t         i32;
+               
+               err = StringToInt32( inString, &i32 );
+               require_noerr_quiet( err, exit );
+               require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr );
+               value = (uint16_t) i32;
        }
-       
-       err = StringToInt32( inString, &i32 );
-       require_noerr_quiet( err, exit );
-       require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr );
-       
-       *outValue = (uint16_t) i32;
+       *outValue = value;
+       err = kNoErr;
        
 exit:
        return( err );
@@ -19922,409 +26728,25 @@ static char * InterfaceIndexToName( uint32_t inIfIndex, char inNameBuf[ kInterfa
 //     RecordTypeToString
 //===========================================================================================================================
 
-static const char *    RecordTypeToString( unsigned int inValue )
-{
-       const RecordType *                              type;
-       const RecordType * const                end = kRecordTypes + countof( kRecordTypes );
-       
-       for( type = kRecordTypes; type < end; ++type )
-       {
-               if( type->value == inValue ) return( type->name );
-       }
-       return( "???" );
-}
-
-//===========================================================================================================================
-//     DNSRecordDataToString
-//===========================================================================================================================
-
-static OSStatus
-       DNSRecordDataToString(
-               const void *    inRDataPtr,
-               size_t                  inRDataLen,
-               unsigned int    inRDataType,
-               const void *    inMsgPtr,
-               size_t                  inMsgLen,
-               char **                 outString )
+static const char *    RecordTypeToString( int inValue )
 {
-       OSStatus                                        err;
-       const uint8_t * const           rdataPtr = (uint8_t *) inRDataPtr;
-       const uint8_t * const           rdataEnd = rdataPtr + inRDataLen;
-       char *                                          rdataStr;
-       const uint8_t *                         ptr;
-       int                                                     n;
-       char                                            domainNameStr[ kDNSServiceMaxDomainName ];
-       
-       rdataStr = NULL;
-       
-       // A Record
-       
-       if( inRDataType == kDNSServiceType_A )
-       {
-               require_action_quiet( inRDataLen == 4, exit, err = kMalformedErr );
-               
-               ASPrintF( &rdataStr, "%.4a", rdataPtr );
-               require_action( rdataStr, exit, err = kNoMemoryErr );
-       }
-       
-       // AAAA Record
-       
-       else if( inRDataType == kDNSServiceType_AAAA )
-       {
-               require_action_quiet( inRDataLen == 16, exit, err = kMalformedErr );
-               
-               ASPrintF( &rdataStr, "%.16a", rdataPtr );
-               require_action( rdataStr, exit, err = kNoMemoryErr );
-       }
-       
-       // PTR, CNAME, or NS Record
-       
-       else if( ( inRDataType == kDNSServiceType_PTR )   ||
-                        ( inRDataType == kDNSServiceType_CNAME ) ||
-                        ( inRDataType == kDNSServiceType_NS ) )
-       {
-               if( inMsgPtr )
-               {
-                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, NULL );
-                       require_noerr( err, exit );
-               }
-               else
-               {
-                       err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, NULL );
-                       require_noerr( err, exit );
-               }
-               
-               rdataStr = strdup( domainNameStr );
-               require_action( rdataStr, exit, err = kNoMemoryErr );
-       }
-       
-       // SRV Record
-       
-       else if( inRDataType == kDNSServiceType_SRV )
-       {
-               const dns_fixed_fields_srv *            fields;
-               const uint8_t *                                         target;
-               unsigned int                                            priority, weight, port;
-               
-               require_action_quiet( inRDataLen > sizeof( dns_fixed_fields_srv ), exit, err = kMalformedErr );
-               
-               fields          = (const dns_fixed_fields_srv *) rdataPtr;
-               priority        = dns_fixed_fields_srv_get_priority( fields );
-               weight          = dns_fixed_fields_srv_get_weight( fields );
-               port            = dns_fixed_fields_srv_get_port( fields );
-               target          = (const uint8_t *) &fields[ 1 ];
-               
-               if( inMsgPtr )
-               {
-                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, target, domainNameStr, NULL );
-                       require_noerr( err, exit );
-               }
-               else
-               {
-                       err = DomainNameToString( target, rdataEnd, domainNameStr, NULL );
-                       require_noerr( err, exit );
-               }
-               
-               ASPrintF( &rdataStr, "%u %u %u %s", priority, weight, port, domainNameStr );
-               require_action( rdataStr, exit, err = kNoMemoryErr );
-       }
-       
-       // TXT Record
-       
-       else if( inRDataType == kDNSServiceType_TXT )
-       {
-               require_action_quiet( inRDataLen > 0, exit, err = kMalformedErr );
-               
-               if( inRDataLen == 1 )
-               {
-                       ASPrintF( &rdataStr, "%#H", rdataPtr, (int) inRDataLen, INT_MAX );
-                       require_action( rdataStr, exit, err = kNoMemoryErr );
-               }
-               else
-               {
-                       ASPrintF( &rdataStr, "%#{txt}", rdataPtr, inRDataLen );
-                       require_action( rdataStr, exit, err = kNoMemoryErr );
-               }
-       }
-       
-       // SOA Record
-       
-       else if( inRDataType == kDNSServiceType_SOA )
-       {
-               const dns_fixed_fields_soa *            fields;
-               uint32_t                                                        serial, refresh, retry, expire, minimum;
-               
-               if( inMsgPtr )
-               {
-                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
-                       require_noerr( err, exit );
-                       
-                       require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
-                       
-                       rdataStr = strdup( domainNameStr );
-                       require_action( rdataStr, exit, err = kNoMemoryErr );
-                       
-                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, ptr, domainNameStr, &ptr );
-                       require_noerr( err, exit );
-               }
-               else
-               {
-                       err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
-                       require_noerr( err, exit );
-                       
-                       rdataStr = strdup( domainNameStr );
-                       require_action( rdataStr, exit, err = kNoMemoryErr );
-                       
-                       err = DomainNameToString( ptr, rdataEnd, domainNameStr, &ptr );
-                       require_noerr( err, exit );
-               }
-               
-               require_action_quiet( ( rdataEnd - ptr ) == sizeof( dns_fixed_fields_soa ), exit, err = kMalformedErr );
-               
-               fields  = (const dns_fixed_fields_soa *) ptr;
-               serial  = dns_fixed_fields_soa_get_serial( fields );
-               refresh = dns_fixed_fields_soa_get_refresh( fields );
-               retry   = dns_fixed_fields_soa_get_retry( fields );
-               expire  = dns_fixed_fields_soa_get_expire( fields );
-               minimum = dns_fixed_fields_soa_get_minimum( fields );
-               
-               n = AppendPrintF( &rdataStr, " %s %u %u %u %u %u\n", domainNameStr, serial, refresh, retry, expire, minimum );
-               require_action( n > 0, exit, err = kUnknownErr );
-       }
-       
-       // NSEC Record
-       
-       else if( inRDataType == kDNSServiceType_NSEC )
-       {
-               unsigned int            windowBlock, bitmapLen, i, recordType;
-               const uint8_t *         bitmapPtr;
-               
-               if( inMsgPtr )
-               {
-                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
-                       require_noerr( err, exit );
-               }
-               else
-               {
-                       err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
-                       require_noerr( err, exit );
-               }
-               
-               require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
-               
-               rdataStr = strdup( domainNameStr );
-               require_action( rdataStr, exit, err = kNoMemoryErr );
-               
-               for( ; ptr < rdataEnd; ptr += ( 2 + bitmapLen ) )
-               {
-                       require_action_quiet( ( ptr + 2 ) < rdataEnd, exit, err = kMalformedErr );
-                       
-                       windowBlock     =  ptr[ 0 ];
-                       bitmapLen       =  ptr[ 1 ];
-                       bitmapPtr       = &ptr[ 2 ];
-                       
-                       require_action_quiet( ( bitmapLen >= 1 ) && ( bitmapLen <= 32 ) , exit, err = kMalformedErr );
-                       require_action_quiet( ( bitmapPtr + bitmapLen ) <= rdataEnd, exit, err = kMalformedErr );
-                       
-                       for( i = 0; i < BitArray_MaxBits( bitmapLen ); ++i )
-                       {
-                               if( BitArray_GetBit( bitmapPtr, bitmapLen, i ) )
-                               {
-                                       recordType = ( windowBlock * 256 ) + i;
-                                       n = AppendPrintF( &rdataStr, " %s", RecordTypeToString( recordType ) );
-                                       require_action( n > 0, exit, err = kUnknownErr );
-                               }
-                       }
-               }
-       }
-       
-       // MX Record
+       const char *            string;
        
-       else if( inRDataType == kDNSServiceType_MX )
-       {
-               uint16_t                        preference;
-               const uint8_t *         exchange;
-               
-               require_action_quiet( ( rdataPtr + 2 ) < rdataEnd, exit, err = kMalformedErr );
-               
-               preference      = ReadBig16( rdataPtr );
-               exchange        = &rdataPtr[ 2 ];
-               
-               if( inMsgPtr )
-               {
-                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, exchange, domainNameStr, NULL );
-                       require_noerr( err, exit );
-               }
-               else
-               {
-                       err = DomainNameToString( exchange, rdataEnd, domainNameStr, NULL );
-                       require_noerr( err, exit );
-               }
-               
-               n = ASPrintF( &rdataStr, "%u %s", preference, domainNameStr );
-               require_action( n > 0, exit, err = kUnknownErr );
-       }
-       
-       // Unhandled record type
-       
-       else
-       {
-               err = kNotHandledErr;
-               goto exit;
-       }
-       
-       check( rdataStr );
-       *outString = rdataStr;
-       rdataStr = NULL;
-       err = kNoErr;
-       
-exit:
-       FreeNullSafe( rdataStr );
-       return( err );
+       string = DNSRecordTypeValueToString( inValue );
+       if( !string ) string = "???";
+       return( string );
 }
 
+#if( MDNSRESPONDER_PROJECT )
 //===========================================================================================================================
-//     DNSMessageToText
+//     RecordClassToString
 //===========================================================================================================================
 
-#define DNSFlagsOpCodeToString( X ) (                                  \
-       ( (X) == kDNSOpCode_Query )                     ? "Query"       :       \
-       ( (X) == kDNSOpCode_InverseQuery )      ? "IQuery"      :       \
-       ( (X) == kDNSOpCode_Status )            ? "Status"      :       \
-       ( (X) == kDNSOpCode_Notify )            ? "Notify"      :       \
-       ( (X) == kDNSOpCode_Update )            ? "Update"      :       \
-                                                                                 "Unassigned" )
-
-#define DNSFlagsRCodeToString( X ) (                                           \
-       ( (X) == kDNSRCode_NoError )            ? "NoError"             :       \
-       ( (X) == kDNSRCode_FormatError )        ? "FormErr"             :       \
-       ( (X) == kDNSRCode_ServerFailure )      ? "ServFail"    :       \
-       ( (X) == kDNSRCode_NXDomain )           ? "NXDomain"    :       \
-       ( (X) == kDNSRCode_NotImplemented )     ? "NotImp"              :       \
-       ( (X) == kDNSRCode_Refused )            ? "Refused"             :       \
-                                                                                 "???" )
-
-static OSStatus
-       DNSMessageToText(
-               const uint8_t * inMsgPtr,
-               size_t                  inMsgLen,
-               const Boolean   inMDNS,
-               const Boolean   inPrintRaw,
-               char **                 outText )
+static const char *    RecordClassToString( int inValue )
 {
-       OSStatus                                err;
-       DataBuffer                              dataBuf;
-       size_t                                  len;
-       const DNSHeader *               hdr;
-       const uint8_t *                 ptr;
-       unsigned int                    id, flags, opcode, rcode;
-       unsigned int                    questionCount, answerCount, authorityCount, additionalCount, i, totalRRCount;
-       uint8_t                                 name[ kDomainNameLengthMax ];
-       char                                    nameStr[ kDNSServiceMaxDomainName ];
-       
-       DataBuffer_Init( &dataBuf, NULL, 0, SIZE_MAX );
-       #define _Append( ... )          do { err = DataBuffer_AppendF( &dataBuf, __VA_ARGS__ ); require_noerr( err, exit ); } while( 0 )
-       
-       require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr );
-       
-       hdr                             = (DNSHeader *) inMsgPtr;
-       id                              = DNSHeaderGetID( hdr );
-       flags                   = DNSHeaderGetFlags( hdr );
-       questionCount   = DNSHeaderGetQuestionCount( hdr );
-       answerCount             = DNSHeaderGetAnswerCount( hdr );
-       authorityCount  = DNSHeaderGetAuthorityCount( hdr );
-       additionalCount = DNSHeaderGetAdditionalCount( hdr );
-       opcode                  = DNSFlagsGetOpCode( flags );
-       rcode                   = DNSFlagsGetRCode( flags );
-       
-       _Append( "ID:               0x%04X (%u)\n", id, id );
-       _Append( "Flags:            0x%04X %c/%s %cAA%cTC%cRD%cRA%?s%?s %s\n",
-               flags,
-               ( flags & kDNSHeaderFlag_Response )                             ? 'R' : 'Q', DNSFlagsOpCodeToString( opcode ),
-               ( flags & kDNSHeaderFlag_AuthAnswer )                   ? ' ' : '!',
-               ( flags & kDNSHeaderFlag_Truncation )                   ? ' ' : '!',
-               ( flags & kDNSHeaderFlag_RecursionDesired )             ? ' ' : '!',
-               ( flags & kDNSHeaderFlag_RecursionAvailable )   ? ' ' : '!',
-               !inMDNS, ( flags & kDNSHeaderFlag_AuthenticData )               ? " AD" : "!AD",
-               !inMDNS, ( flags & kDNSHeaderFlag_CheckingDisabled )    ? " CD" : "!CD",
-               DNSFlagsRCodeToString( rcode ) );
-       _Append( "Question count:   %u\n", questionCount );
-       _Append( "Answer count:     %u\n", answerCount );
-       _Append( "Authority count:  %u\n", authorityCount );
-       _Append( "Additional count: %u\n", additionalCount );
-       
-       ptr = (const uint8_t *) &hdr[ 1 ];
-       for( i = 0; i < questionCount; ++i )
-       {
-               uint16_t                qtype, qclass;
-               Boolean                 isQU;
-               
-               err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, name, &qtype, &qclass, &ptr );
-               require_noerr( err, exit );
-               
-               err = DomainNameToString( name, NULL, nameStr, NULL );
-               require_noerr( err, exit );
-               
-               isQU = ( inMDNS && ( qclass & kQClassUnicastResponseBit ) ) ? true : false;
-               if( inMDNS ) qclass &= ~kQClassUnicastResponseBit;
-               
-               if( i == 0 ) _Append( "\nQUESTION SECTION\n" );
-               
-               _Append( "%-30s %2s %?2s%?2u %-5s\n",
-                       nameStr, inMDNS ? ( isQU ? "QU" : "QM" ) : "",
-                       ( qclass == kDNSServiceClass_IN ), "IN", ( qclass != kDNSServiceClass_IN ), qclass, RecordTypeToString( qtype ) );
-       }
-       
-       totalRRCount = answerCount + authorityCount + additionalCount;
-       for( i = 0; i < totalRRCount; ++i )
-       {
-               uint16_t                        type;
-               uint16_t                        class;
-               uint32_t                        ttl;
-               const uint8_t *         rdataPtr;
-               size_t                          rdataLen;
-               char *                          rdataStr;
-               Boolean                         cacheFlush;
-               
-               err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, name, &type, &class, &ttl, &rdataPtr, &rdataLen, &ptr );
-               require_noerr( err, exit );
-               
-               err = DomainNameToString( name, NULL, nameStr, NULL );
-               require_noerr( err, exit );
-               
-               cacheFlush = ( inMDNS && ( class & kRRClassCacheFlushBit ) ) ? true : false;
-               if( inMDNS ) class &= ~kRRClassCacheFlushBit;
-               
-               rdataStr = NULL;
-               if( !inPrintRaw ) DNSRecordDataToString( rdataPtr, rdataLen, type, inMsgPtr, inMsgLen, &rdataStr );
-               if( !rdataStr )
-               {
-                       ASPrintF( &rdataStr, "%#H", rdataPtr, (int) rdataLen, (int) rdataLen );
-                       require_action( rdataStr, exit, err = kNoMemoryErr );
-               }
-               
-               if(      answerCount     && ( i ==   0                              ) ) _Append( "\nANSWER SECTION\n" );
-               else if( authorityCount  && ( i ==   answerCount                    ) ) _Append( "\nAUTHORITY SECTION\n" );
-               else if( additionalCount && ( i == ( answerCount + authorityCount ) ) ) _Append( "\nADDITIONAL SECTION\n" );
-               
-               _Append( "%-42s %6u %2s %?2s%?2u %-5s %s\n",
-                       nameStr, ttl, cacheFlush ? "CF" : "",
-                       ( class == kDNSServiceClass_IN ), "IN", ( class != kDNSServiceClass_IN ), class,
-                       RecordTypeToString( type ), rdataStr );
-               free( rdataStr );
-       }
-       _Append( "\n" );
-       
-       err = DataBuffer_Append( &dataBuf, "", 1 );
-       require_noerr( err, exit );
-       
-       err = DataBuffer_Detach( &dataBuf, (uint8_t **) outText, &len );
-       require_noerr( err, exit );
-       
-exit:
-       DataBuffer_Free( &dataBuf );
-       return( err );
+       return( ( inValue == kDNSServiceClass_IN ) ? "IN" : "???" );
 }
+#endif
 
 //===========================================================================================================================
 //     WriteDNSQueryMessage
@@ -20399,7 +26821,7 @@ static OSStatus
        dispatch_source_t               source;
        
        source = dispatch_source_create( inType, (uintptr_t) inSock, 0, inQueue ? inQueue : dispatch_get_main_queue() );
-       require_action( source, exit, err = kUnknownErr );
+       require_action( source, exit, err = kNoResourcesErr );
        
        dispatch_set_context( source, inContext );
        dispatch_source_set_event_handler_f( source, inEventHandler );
@@ -20431,7 +26853,7 @@ static OSStatus
        dispatch_source_t               timer;
        
        timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, inQueue ? inQueue : dispatch_get_main_queue() );
-       require_action( timer, exit, err = kUnknownErr );
+       require_action( timer, exit, err = kNoResourcesErr );
        
        dispatch_source_set_timer( timer, inStart, inIntervalNs, inLeewayNs );
        dispatch_set_context( timer, inContext );
@@ -20445,6 +26867,7 @@ exit:
        return( err );
 }
 
+#if( TARGET_OS_DARWIN )
 //===========================================================================================================================
 //     DispatchProcessMonitorCreate
 //===========================================================================================================================
@@ -20476,6 +26899,7 @@ static OSStatus
 exit:
        return( err );
 }
+#endif
 
 //===========================================================================================================================
 //     ServiceTypeDescription
@@ -20551,7 +26975,21 @@ static const char *    ServiceTypeDescription( const char *inName )
 //     SocketContextCreate
 //===========================================================================================================================
 
-static OSStatus        SocketContextCreate( SocketRef inSock, void * inUserContext, SocketContext **outContext )
+static SocketContext * SocketContextCreate( SocketRef inSock, void *inUserContext, OSStatus *outError )
+{
+       return( SocketContextCreateEx( inSock, inUserContext, NULL, outError ) );
+}
+
+//===========================================================================================================================
+//     SocketContextCreateEx
+//===========================================================================================================================
+
+static SocketContext *
+       SocketContextCreateEx(
+               SocketRef                                       inSock,
+               void *                                          inUserContext,
+               SocketContextFinalizer_f        inUserFinalizer,
+               OSStatus *                                      outError )
 {
        OSStatus                        err;
        SocketContext *         context;
@@ -20562,12 +27000,12 @@ static OSStatus       SocketContextCreate( SocketRef inSock, void * inUserContext, Soc
        context->refCount               = 1;
        context->sock                   = inSock;
        context->userContext    = inUserContext;
-       
-       *outContext = context;
+       context->userFinalizer  = inUserFinalizer;
        err = kNoErr;
        
 exit:
-       return( err );
+       if( outError ) *outError = err;
+       return( context );
 }
 
 //===========================================================================================================================
@@ -20576,7 +27014,7 @@ exit:
 
 static SocketContext * SocketContextRetain( SocketContext *inContext )
 {
-       ++inContext->refCount;
+       atomic_add_32( &inContext->refCount, 1 );
        return( inContext );
 }
 
@@ -20584,12 +27022,18 @@ static SocketContext *        SocketContextRetain( SocketContext *inContext )
 //     SocketContextRelease
 //===========================================================================================================================
 
-static void    SocketContextRelease( SocketContext *inContext )
+static void    SocketContextRelease( SocketContext *me )
 {
-       if( --inContext->refCount == 0 )
+       if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 )
        {
-               ForgetSocket( &inContext->sock );
-               free( inContext );
+               ForgetSocket( &me->sock );
+               if( me->userFinalizer )
+               {
+                       me->userFinalizer( me->userContext );
+                       me->userFinalizer = NULL;
+               }
+               me->userContext = NULL;
+               free( me );
        }
 }
 
@@ -20602,6 +27046,15 @@ static void    SocketContextCancelHandler( void *inContext )
        SocketContextRelease( (SocketContext *) inContext );
 }
 
+//===========================================================================================================================
+//     SocketContextFinalizerCF
+//===========================================================================================================================
+
+static void    SocketContextFinalizerCF( void *inUserCtx )
+{
+       CFRelease( (CFTypeRef) inUserCtx );
+}
+
 //===========================================================================================================================
 //     StringToInt32
 //===========================================================================================================================
@@ -20651,22 +27104,23 @@ exit:
 static int64_t _StringToInt64( const char *inString, OSStatus *outError )
 {
        OSStatus                err;
-       long long               val;
+       long long               ll;
        char *                  end;
+       int64_t                 i64 = 0;
        int                             errnoVal;
        
        set_errno_compat( 0 );
-       val = strtoll( inString, &end, 0 );
+       ll = strtoll( inString, &end, 0 );
        errnoVal = errno_compat();
-       
        require_action_quiet( ( *end == '\0' ) && ( end != inString ), exit, err = kMalformedErr );
-       require_action_quiet( ( ( val != LLONG_MIN ) && ( val != LLONG_MAX ) ) || ( errnoVal != ERANGE ), exit, err = kRangeErr );
-       require_action_quiet( ( val >= INT64_MIN ) && ( val <= INT64_MAX ), exit, err = kRangeErr );
+       require_action_quiet( ( ( ll != LLONG_MIN ) && ( ll != LLONG_MAX ) ) || ( errnoVal != ERANGE ), exit, err = kRangeErr );
+       require_action_quiet( ( ll >= INT64_MIN ) && ( ll <= INT64_MAX ), exit, err = kRangeErr );
+       i64 = (int64_t) ll;
        err = kNoErr;
        
 exit:
        if( outError ) *outError = err;
-       return( (int64_t)val );
+       return( i64 );
 }
 
 //===========================================================================================================================
@@ -20701,16 +27155,18 @@ exit:
 static pid_t   _StringToPID( const char *inString, OSStatus *outError )
 {
        OSStatus                err;
-       int64_t                 val;
+       int64_t                 i64;
+       pid_t                   pid = 0;
        
-       val = _StringToInt64( inString, &err );
+       i64 = _StringToInt64( inString, &err );
        require_noerr_quiet( err, exit );
-       require_action_quiet( val == (pid_t) val, exit, err = kRangeErr );
+       require_action_quiet( i64 == (pid_t) i64, exit, err = kRangeErr );
+       pid = (pid_t) i64;
        err = kNoErr;
        
 exit:
        if( outError ) *outError = err;
-       return( (pid_t) val );
+       return( pid );
 }
 
 //===========================================================================================================================
@@ -20912,11 +27368,7 @@ static void    _MDNSMulticastAddrV4Init( void *inContext )
 {
        struct sockaddr_in * const              addr = (struct sockaddr_in *) inContext;
        
-       memset( addr, 0, sizeof( *addr ) );
-       SIN_LEN_SET( addr );
-       addr->sin_family                = AF_INET;
-       addr->sin_port                  = htons( kMDNSPort );
-       addr->sin_addr.s_addr   = htonl( 0xE00000FB );  // The mDNS IPv4 multicast address is 224.0.0.251
+       _SockAddrInitIPv4( addr, UINT32_C( 0xE00000FB ), kMDNSPort );   // The mDNS IPv4 multicast address is 224.0.0.251.
 }
 
 //===========================================================================================================================
@@ -21077,38 +27529,129 @@ static OSStatus      CheckRootUser( void )
 }
 
 //===========================================================================================================================
-//     SpawnCommand
-//
-//     Note: Based on systemf() from CoreUtils framework.
+//     _SpawnCommand
 //===========================================================================================================================
 
 extern char **         environ;
 
-static OSStatus        SpawnCommand( pid_t *outPID, const char *inFormat, ... )
+static OSStatus
+       _SpawnCommand(
+               pid_t *                 outPID,
+               const char *    inStdOutRedirect,
+               const char *    inStdErrRedirect,
+               const char *    inFormat,
+               ... )
 {
-       OSStatus                err;
-       va_list                 args;
-       char *                  command;
-       char *                  argv[ 4 ];
-       pid_t                   pid;
+       OSStatus                                                        err;
+       va_list                                                         args;
+       char *                                                          cmdStr          = NULL;
+       size_t                                                          cmdLen;
+       const char *                                            cmdEnd;
+       char *                                                          bufPtr          = NULL;
+       size_t                                                          bufLen;
+       const char *                                            bufLim;
+       pid_t                                                           pid;
+       const char *                                            src;
+       char *                                                          dst;
+       char **                                                         argArray        = NULL;
+       size_t                                                          argCapacity, argCount;
+       posix_spawn_file_actions_t                      actions;
+       posix_spawn_file_actions_t *            actionsPtr      = NULL;
+       
+       // Create command string from format string and its arguments.
        
-       command = NULL;
        va_start( args, inFormat );
-       VASPrintF( &command, inFormat, args );
+       VASPrintF( &cmdStr, inFormat, args );
        va_end( args );
-       require_action( command, exit, err = kUnknownErr );
-       
-       argv[ 0 ] = "/bin/sh";
-       argv[ 1 ] = "-c";
-       argv[ 2 ] = command;
-       argv[ 3 ] = NULL;
-       err = posix_spawn( &pid, argv[ 0 ], NULL, NULL, argv, environ );
-       free( command );
+       require_action( cmdStr, exit, err = kUnknownErr );
+       
+       cmdLen = strlen( cmdStr );
+       cmdEnd = &cmdStr[ cmdLen ];
+       
+       // Allocate buffer for argument strings.
+       // In the worst-case scenario in terms of memory requirements, the only non-escaped white space is one non-escaped
+       // white space character between each pair of adjacent arguments. The amount of required memory is equal to the size
+       // of cmdStr.
+       
+       bufLen = cmdLen + 1; // +1 for NUL terminator.
+       bufPtr = (char *) malloc( bufLen );
+       require_action( bufPtr, exit, err = kNoMemoryErr );
+       bufLim = &bufPtr[ bufLen ];
+       
+       // Allocate initial argument array.
+       
+       argCount        = 0;
+       argCapacity     = 8;
+       argArray = (char **) malloc( ( argCapacity + 1 ) * sizeof( *argArray ) ); // +1 for NULL arg.
+       require_action( argArray, exit, err = kNoMemoryErr );
+       
+       // Extract argument strings from command string.
+       
+       src = cmdStr;
+       dst = bufPtr;
+       for( ;; )
+       {
+               size_t          maxLen, copiedLen, totalLen;
+               Boolean         more;
+               
+               maxLen = (size_t)( bufLim - dst );
+               if( maxLen > 0 ) --maxLen; // -1 for NUL terminator.
+               more = _ParseQuotedEscapedString( src, cmdEnd, kWhiteSpaceCharSet, dst, maxLen, &copiedLen, &totalLen, &src );
+               if( !more ) break;
+               require_fatal( copiedLen == totalLen, "Incorrect assumption about maximum required buffer space." );
+               
+               if( argCount >= argCapacity )
+               {
+                       size_t          newCapactiy;
+                       char **         newArray;
+                       
+                       newCapactiy = 2 * argCapacity;
+                       newArray = (char **) realloc( argArray, ( newCapactiy + 1 ) * sizeof( *newArray ) ); // +1 for NULL arg.
+                       require_action( newArray, exit, err = kNoMemoryErr );
+                       
+                       argArray        = newArray;
+                       argCapacity     = newCapactiy;
+               }
+               argArray[ argCount++ ] = dst;
+               dst += copiedLen;
+               *dst++ = '\0';
+               check( dst <= bufLim );
+       }
+       require_action_quiet( argCount > 0, exit, err = kCommandErr );
+       
+       argArray[ argCount ] = NULL;
+       
+       // Set up stdout and stderr redirections, if any.
+       
+       if( inStdOutRedirect || inStdErrRedirect )
+       {
+               err = posix_spawn_file_actions_init( &actions );
+               require_noerr( err, exit );
+               
+               actionsPtr = &actions;
+               if( inStdOutRedirect )
+               {
+                       err = posix_spawn_file_actions_addopen( actionsPtr, STDOUT_FILENO, inStdOutRedirect, O_WRONLY, 0 );
+                       require_noerr( err, exit );
+               }
+               if( inStdErrRedirect )
+               {
+                       err = posix_spawn_file_actions_addopen( actionsPtr, STDERR_FILENO, inStdErrRedirect, O_WRONLY, 0 );
+                       require_noerr( err, exit );
+               }
+       }
+       // Spawn command.
+       
+       err = posix_spawnp( &pid, argArray[ 0 ], actionsPtr, NULL, argArray, environ );
        require_noerr_quiet( err, exit );
        
        if( outPID ) *outPID = pid;
        
 exit:
+       FreeNullSafe( cmdStr );
+       FreeNullSafe( bufPtr );
+       FreeNullSafe( argArray );
+       if( actionsPtr ) posix_spawn_file_actions_destroy( actionsPtr );
        return( err );
 }
 
@@ -21688,7 +28231,7 @@ static Boolean  _MDNSInterfaceIsBlacklisted( SocketRef inInfoSock, const char *in
 {
        OSStatus                                                err;
        int                                                             i;
-       static const char * const               kMDNSInterfacePrefixBlacklist[] = { "llw" };
+       static const char * const               kMDNSInterfacePrefixBlacklist[] = { "llw", "nan" };
        struct ifreq                                    ifr;
        
        // Check if the interface name's prefix matches the prefix blacklist.
@@ -21863,6 +28406,244 @@ exit:
        return( err );
 }
 
+#if( TARGET_OS_DARWIN )
+//===========================================================================================================================
+//     _InterfaceIPv6AddressAdd
+//===========================================================================================================================
+
+static OSStatus        _InterfaceIPv6AddressAdd( const char *inIfName, uint8_t inAddr[ STATIC_PARAM 16 ], int inMaskBitLen )
+{
+       OSStatus                                        err;
+       SocketRef                                       infoSock = kInvalidSocketRef;
+       struct in6_aliasreq                     ifra;
+       size_t                                          len;
+       struct sockaddr_in6 *           sin6;
+       int                                                     wholeBytes, remainingBits;
+       
+       require_action_quiet( ( inMaskBitLen >= 0 ) &&  ( inMaskBitLen <= 128 ), exit, err = kSizeErr );
+       
+       infoSock = socket( AF_INET6, SOCK_DGRAM, 0 );
+       err = map_socket_creation_errno( infoSock );
+       require_noerr( err, exit );
+       
+       // Set interface name.
+       
+       memset( &ifra, 0, sizeof( ifra ) );
+       len = strlcpy( ifra.ifra_name, inIfName, sizeof( ifra.ifra_name ) );
+       require_action_quiet( len < sizeof( ifra.ifra_name ), exit, err = kSizeErr );
+       
+       // Set IPv6 address.
+       
+       sin6 = &ifra.ifra_addr;
+       SIN6_LEN_SET( sin6 );
+       sin6->sin6_family = AF_INET6;
+       memcpy( sin6->sin6_addr.s6_addr, inAddr, 16 );
+       
+       // Set prefix mask.
+       
+       sin6 = &ifra.ifra_prefixmask;
+       SIN6_LEN_SET( sin6 );
+       sin6->sin6_family = AF_INET6;
+       wholeBytes = inMaskBitLen / 8;
+       if( wholeBytes > 0 ) memset( sin6->sin6_addr.s6_addr, 0xFF, (size_t) wholeBytes );
+       remainingBits = inMaskBitLen % 8;
+       if( remainingBits > 0 ) sin6->sin6_addr.s6_addr[ wholeBytes ] = ( 0xFFU << ( 8 - remainingBits ) ) & 0xFFU;
+       
+       ifra.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME;
+       ifra.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME;
+       
+       err = ioctl( infoSock, SIOCAIFADDR_IN6, &ifra );
+       err = map_global_value_errno( err != -1, err );
+       require_noerr_quiet( err, exit );
+       
+exit:
+       ForgetSocket( &infoSock );
+       return( err );
+}
+
+//===========================================================================================================================
+//     _InterfaceIPv6AddressRemove
+//===========================================================================================================================
+
+static OSStatus        _InterfaceIPv6AddressRemove( const char *inIfName, const uint8_t inAddr[ STATIC_PARAM 16 ] )
+{
+       OSStatus                                        err;
+       SocketRef                                       infoSock = kInvalidSocketRef;
+       struct in6_ifreq                        ifr;
+       size_t                                          len;
+       struct sockaddr_in6 *           sin6;
+       
+       infoSock = socket( AF_INET6, SOCK_DGRAM, 0 );
+       err = map_socket_creation_errno( infoSock );
+       require_noerr( err, exit );
+       
+       memset( &ifr, 0, sizeof( ifr ) );
+       len = strlcpy( ifr.ifr_name, inIfName, sizeof( ifr.ifr_name ) );
+       require_action_quiet( len < sizeof( ifr.ifr_name ), exit, err = kSizeErr );
+       
+       sin6 = &ifr.ifr_ifru.ifru_addr;
+       SIN6_LEN_SET( sin6 );
+       sin6->sin6_family = AF_INET6;
+       memcpy( sin6->sin6_addr.s6_addr, inAddr, 16 );
+       
+       err = ioctl( infoSock, SIOCDIFADDR_IN6, &ifr );
+       err = map_global_value_errno( err != -1, err );
+       require_noerr_quiet( err, exit );
+       
+exit:
+       ForgetSocket( &infoSock );
+       return( err );
+}
+#endif // TARGET_OS_DARWIN
+
+//===========================================================================================================================
+//     _TicksDiff
+//===========================================================================================================================
+
+static int64_t _TicksDiff( uint64_t inT1, uint64_t inT2 )
+{
+       return( (int64_t)( inT1 - inT2 ) );
+}
+
+//===========================================================================================================================
+//     _SockAddrInitIPv4
+//===========================================================================================================================
+
+static void    _SockAddrInitIPv4( struct sockaddr_in *inSA, uint32_t inIPv4, uint16_t inPort )
+{
+       memset( inSA, 0, sizeof( *inSA ) );
+       SIN_LEN_SET( inSA );
+       inSA->sin_family                = AF_INET;
+       inSA->sin_port                  = htons( inPort );
+       inSA->sin_addr.s_addr   = htonl( inIPv4 );
+}
+
+//===========================================================================================================================
+//     _SockAddrInitIPv6
+//===========================================================================================================================
+
+static void
+       _SockAddrInitIPv6(
+               struct sockaddr_in6 *   inSA,
+               const uint8_t                   inIPv6[ STATIC_PARAM 16 ],
+               uint32_t                                inScope,
+               uint16_t                                inPort )
+{
+       check_compile_time_code( sizeof( inSA->sin6_addr.s6_addr ) == 16 );
+       
+       memset( inSA, 0, sizeof( *inSA ) );
+       SIN6_LEN_SET( inSA );
+       inSA->sin6_family       = AF_INET6;
+       inSA->sin6_port         = htons( inPort );
+       memcpy( inSA->sin6_addr.s6_addr, inIPv6, 16 );
+       inSA->sin6_scope_id     = inScope;
+}
+
+//===========================================================================================================================
+//     _WriteReverseIPv6DomainNameString
+//===========================================================================================================================
+
+static void
+       _WriteReverseIPv6DomainNameString(
+               const uint8_t   inIPv6Addr[ STATIC_PARAM 16 ],
+               char                    outBuffer[ STATIC_PARAM kReverseIPv6DomainNameBufLen ] )
+{
+       char *          dst;
+       int                     i;
+       
+       dst = outBuffer;
+       for( i = 0; i < 16; ++i )
+       {
+               const unsigned int              octet = inIPv6Addr[ 15 - i ];
+               
+               *dst++ = kHexDigitsLowercase[ octet & 0x0F ];
+               *dst++ = '.';
+               *dst++ = kHexDigitsLowercase[ octet >> 4 ];
+               *dst++ = '.';
+       }
+       memcpy( dst, kIP6ArpaDomainStr, sizeof( kIP6ArpaDomainStr ) );
+       dst += sizeof( kIP6ArpaDomainStr );
+       check( ( dst - outBuffer ) == kReverseIPv6DomainNameBufLen );
+}
+
+//===========================================================================================================================
+//     _WriteReverseIPv4DomainNameString
+//===========================================================================================================================
+
+static void
+       _WriteReverseIPv4DomainNameString(
+               uint32_t        inIPv4Addr,
+               char            outBuffer[ STATIC_PARAM kReverseIPv4DomainNameBufLen ] )
+{
+       SNPrintF( outBuffer, kReverseIPv4DomainNameBufLen, "%u.%u.%u.%u.%s",
+                 inIPv4Addr         & 0xFF,
+               ( inIPv4Addr >>  8 ) & 0xFF,
+               ( inIPv4Addr >> 16 ) & 0xFF,
+               ( inIPv4Addr >> 24 ) & 0xFF, kInAddrArpaDomainStr );
+}
+
+#if( MDNSRESPONDER_PROJECT )
+//===========================================================================================================================
+//     _SetDefaultFallbackDNSService
+//===========================================================================================================================
+
+static OSStatus        _SetDefaultFallbackDNSService( const char *inFallbackDNSServiceStr )
+{
+       OSStatus                err;
+       
+       if( __builtin_available( macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, * ) )
+       {
+               nw_resolver_config_t            resolverConfig;
+               CFDataRef                                       plistData;
+               
+               if( stricmp_prefix( inFallbackDNSServiceStr, kFallbackDNSServiceArgPrefix_DoH ) == 0 )
+               {
+                       nw_endpoint_t                   endpoint;
+                       const char * const              url = inFallbackDNSServiceStr + sizeof_string( kFallbackDNSServiceArgPrefix_DoH );
+                       
+                       endpoint = nw_endpoint_create_url( url );
+                       require_action( endpoint, exit, err = kUnknownErr );
+                       
+                       resolverConfig = nw_resolver_config_create_https( endpoint );
+                       nw_forget( &endpoint );
+                       require_action( resolverConfig, exit, err = kUnknownErr );
+               }
+               else if( stricmp_prefix( inFallbackDNSServiceStr, kFallbackDNSServiceArgPrefix_DoT ) == 0 )
+               {
+                       nw_endpoint_t                   endpoint;
+                       const char * const              hostname = inFallbackDNSServiceStr + sizeof_string( kFallbackDNSServiceArgPrefix_DoT );
+                       
+                       endpoint = nw_endpoint_create_host( hostname, "0" );
+                       require_action( endpoint, exit, err = kUnknownErr );
+                       
+                       resolverConfig = nw_resolver_config_create_tls( endpoint );
+                       nw_forget( &endpoint );
+                       require_action( resolverConfig, exit, err = kUnknownErr );
+               }
+               else
+               {
+                       FPrintF( stderr, "error: Unrecognized fallback DNS service string: \"%s\"\n", inFallbackDNSServiceStr );
+                       err = kParamErr;
+                       goto exit;
+               }
+               plistData = nw_resolver_config_copy_plist_data_ref( resolverConfig );
+               require_action( plistData, exit, err = kUnknownErr );
+               
+               err = DNSServiceSetResolverDefaults( CFDataGetBytePtr( plistData ), (size_t) CFDataGetLength( plistData ), true );
+               ForgetCF( &plistData );
+               require_noerr( err, exit );
+       }
+       else
+       {
+               FPrintF( stderr, "error: Setting a default fallback DNS service is unsupported on this OS.\n" );
+               err = kUnsupportedErr;
+       }
+       
+exit:
+       return( err );
+}
+#endif
+
 //===========================================================================================================================
 //     MDNSColliderCreate
 //===========================================================================================================================
@@ -22002,7 +28783,7 @@ static void     _MDNSColliderStart( void *inContext )
                err = CreateMulticastSocket( GetMDNSMulticastAddrV4(), kMDNSPort, NULL, me->interfaceIndex, true, NULL, &sock );
                require_noerr( err, exit );
                
-               err = SocketContextCreate( sock, me, &sockCtx );
+               sockCtx = SocketContextCreate( sock, me, &err );
                require_noerr( err, exit );
                sock = kInvalidSocketRef;
                
@@ -22020,7 +28801,7 @@ static void     _MDNSColliderStart( void *inContext )
                err = CreateMulticastSocket( GetMDNSMulticastAddrV6(), kMDNSPort, NULL, me->interfaceIndex, true, NULL, &sock );
                require_noerr( err, exit );
                
-               err = SocketContextCreate( sock, me, &sockCtx );
+               sockCtx = SocketContextCreate( sock, me, &err );
                require_noerr( err, exit );
                sock = kInvalidSocketRef;
                
@@ -22366,7 +29147,7 @@ static OSStatus
        err = DataBuffer_Append( &msgDB, &header, sizeof( header ) );
        require_noerr( err, exit );
        
-       err = _DataBuffer_AppendDNSRecord( &msgDB, targetPtr, targetLen, inType, kDNSServiceClass_IN | kRRClassCacheFlushBit,
+       err = _DataBuffer_AppendDNSRecord( &msgDB, targetPtr, targetLen, inType, kDNSServiceClass_IN | kMDNSClassCacheFlushBit,
                1976, inRDataPtr, inRDataLen );
        require_noerr( err, exit );
        
@@ -22489,13 +29270,13 @@ static void   _MDNSColliderReadHandler( void *inContext )
                }
                
                if( qtype != kDNSServiceType_ANY ) continue;
-               if( ( qclass & ~kQClassUnicastResponseBit ) != kDNSServiceClass_IN ) continue;
+               if( ( qclass & ~kMDNSClassUnicastResponseBit ) != kDNSServiceClass_IN ) continue;
                if( !DomainNameEqual( qname, me->target ) ) continue;
                
                if( !probeFound )
                {
                        probeFound      = true;
-                       probeIsQU       = ( qclass & kQClassUnicastResponseBit ) ? true : false;
+                       probeIsQU       = ( qclass & kMDNSClassUnicastResponseBit ) ? true : false;
                }
        }
        require_quiet( probeFound, exit );
@@ -22503,7 +29284,7 @@ static void     _MDNSColliderReadHandler( void *inContext )
        ++me->probeCount;
        action = _MDNSColliderGetProbeAction( me->probeActionMap, me->probeCount );
        
-       mc_ulog( kLogLevelInfo, "Received probe from %##a at %{du:time} (action: %s):\n\n%#1{du:dnsmsg}",
+       mc_ulog( kLogLevelInfo, "Received probe from %##a at %{du:time} (action: %s) -- %#.1{du:dnsmsg}\n",
                &sender, &now, _MDNSColliderProbeActionToString( action ), me->msgBuf, msgLen );
        
        if( ( action == kMDNSColliderProbeAction_Respond )                      ||
@@ -22700,6 +29481,9 @@ struct ServiceBrowserPrivate
        dispatch_source_t                               stopTimer;                      // Timer to stop browsing after browseTimeSecs.
        uint32_t                                                ifIndex;                        // If non-zero, then browsing is limited to this interface.
        unsigned int                                    browseTimeSecs;         // Amount of time to spend browsing in seconds.
+#if( MDNSRESPONDER_PROJECT )
+       Boolean                                                 useNewGAI;                      // Use dnssd_getaddrinfo_* instead of DNSServiceGetAddrInfo().
+#endif
        Boolean                                                 includeAWDL;            // True if the IncludeAWDL flag should be used for DNS-SD ops that
                                                                                                                // use the "any" interface.
 };
@@ -22745,9 +29529,13 @@ struct SBServiceInstance
        uint16_t                                port;                           // Service instance's port number. Result of DNSServiceResolve.
        uint8_t *                               txtPtr;                         // Service instance's TXT record data. Result of DNSServiceResolve.
        size_t                                  txtLen;                         // Length of service instance's TXT record data.
-       DNSServiceRef                   getAddrInfo;            // Reference to DNSServiceGetAddrInfo op for service instance's hostname.
+       DNSServiceRef                   gai;                            // Reference to DNSServiceGetAddrInfo op for service instance's hostname.
+#if( MDNSRESPONDER_PROJECT )
+       dnssd_getaddrinfo_t             newGAI;                         // Reference to dnssd_getaddrinfo object for service instance's hostname.
+#endif
        uint64_t                                gaiStartTicks;          // Value of UpTicks() when the DNSServiceGetAddrInfo op began.
        SBIPAddress *                   ipaddrList;                     // List of IP addresses that the hostname resolved to.
+       int32_t                                 refCount;                       // This object's reference count.
 };
 
 struct SBIPAddress
@@ -22816,6 +29604,14 @@ static void DNSSD_API
                uint16_t                                inTXTLen,
                const unsigned char *   inTXTPtr,
                void *                                  inContext );
+#if( MDNSRESPONDER_PROJECT )
+static void
+       _ServiceBrowserGAIResultHandler(
+               ServiceBrowserRef                               inBrowser,
+               SBServiceInstance *                             inInstance,
+               dnssd_getaddrinfo_result_t *    inResultArray,
+               size_t                                                  inResultCount );
+#endif
 static void DNSSD_API
        _ServiceBrowserGAICallback(
                DNSServiceRef                   inSDRef,
@@ -22880,7 +29676,12 @@ static OSStatus
                uint64_t                                inDiscoverTimeUs,
                ServiceBrowserRef               inBrowser,
                SBServiceInstance **    outInstance );
-static void            _SBServiceInstanceFree( SBServiceInstance *inInstance );
+#if( MDNSRESPONDER_PROJECT )
+static void            _SBServiceInstanceRetain( SBServiceInstance *inInstance );
+#endif
+static void            _SBServiceInstanceStop( SBServiceInstance *inInstance );
+static void            _SBServiceInstanceRelease( SBServiceInstance *inInstance );
+#define _SBServiceInstanceForget( X )          ForgetCustomEx( X, _SBServiceInstanceStop, _SBServiceInstanceRelease )
 static OSStatus
        _SBIPAddressCreate(
                const struct sockaddr * inSockAddr,
@@ -22974,6 +29775,17 @@ static void    _ServiceBrowserFinalize( CFTypeRef inObj )
        check( !me->stopTimer );
 }
 
+#if( MDNSRESPONDER_PROJECT )
+//===========================================================================================================================
+//     ServiceBrowserSetUseNewGAI
+//===========================================================================================================================
+
+static void    ServiceBrowserSetUseNewGAI( ServiceBrowserRef me, Boolean inUseNewGAI )
+{
+       me->useNewGAI = inUseNewGAI;
+}
+#endif
+
 //===========================================================================================================================
 //     ServiceBrowserStart
 //===========================================================================================================================
@@ -23110,26 +29922,28 @@ static void   ServiceBrowserResultsRelease( ServiceBrowserResults *inResults )
 
 static void    _ServiceBrowserStop( ServiceBrowserRef me, OSStatus inError )
 {
-       OSStatus                                err;
-       SBDomain *                              d;
-       SBServiceType *                 t;
-       SBServiceBrowse *               b;
-       SBServiceInstance *             i;
+       OSStatus                err;
+       SBDomain *              d;
        
        dispatch_source_forget( &me->stopTimer );
        DNSServiceForget( &me->domainsQuery );
        for( d = me->domainList; d; d = d->next )
        {
+               SBServiceType *         t;
+               
                DNSServiceForget( &d->servicesQuery );
                for( t = d->typeList; t; t = t->next )
                {
+                       SBServiceBrowse *               b;
+                       
                        for( b = t->browseList; b; b = b->next )
                        {
+                               SBServiceInstance *             i;
+                               
                                DNSServiceForget( &b->browse );
                                for( i = b->instanceList; i; i = i->next )
                                {
-                                       DNSServiceForget( &i->resolve );
-                                       DNSServiceForget( &i->getAddrInfo );
+                                       _SBServiceInstanceStop( i );
                                }
                        }
                }
@@ -23493,24 +30307,125 @@ static void DNSSD_API
                err = ReplaceString( &instance->hostname, NULL, inHostname, kSizeCString );
                require_noerr( err, exit );
                
-               DNSServiceForget( &instance->getAddrInfo );
+               sb_ulog( kLogLevelTrace,
+                       "Starting GetAddrInfo on interface %d for %s", (int32_t) instance->ifIndex, instance->hostname );
+               
                ForgetSBIPAddressList( &instance->ipaddrList );
                
-               sb_ulog( kLogLevelTrace, "Starting GetAddrInfo on interface %d for %s",
-                       (int32_t) instance->ifIndex, instance->hostname );
+       #if( MDNSRESPONDER_PROJECT )
+               if( me->useNewGAI )
+               {
+                       dnssd_getaddrinfo_t             gai;
+                       
+                       dnssd_getaddrinfo_forget( &instance->newGAI );
+                       
+                       gai = dnssd_getaddrinfo_create();
+                       require_action( gai, exit, err = kNoResourcesErr );
+                       
+                       dnssd_getaddrinfo_set_hostname( gai, instance->hostname );
+                       dnssd_getaddrinfo_set_flags( gai, 0 );
+                       dnssd_getaddrinfo_set_interface_index( gai, instance->ifIndex );
+                       dnssd_getaddrinfo_set_protocols( gai, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6 );
+                       dnssd_getaddrinfo_set_queue( gai, me->queue );
+                       _SBServiceInstanceRetain( instance );
+                       CFRetain( me );
+                       dnssd_getaddrinfo_set_result_handler( gai,
+                       ^( dnssd_getaddrinfo_result_t * const inResultArray, const size_t inResultCount )
+                       {
+                               if( instance->newGAI == gai )
+                               {
+                                       _ServiceBrowserGAIResultHandler( me, instance, inResultArray, inResultCount );
+                               }
+                       } );
+                       dnssd_getaddrinfo_set_event_handler( gai,
+                       ^( dnssd_event_t inEvent, DNSServiceErrorType inGAIError )
+                       {
+                               switch( inEvent )
+                               {
+                                       case dnssd_event_invalidated:
+                                               dnssd_release( gai );
+                                               _SBServiceInstanceRelease( instance );
+                                               CFRelease( me );
+                                               break;
+                                       
+                                       case dnssd_event_error:
+                                               if( instance->newGAI == gai )
+                                               {
+                                                       sb_ulog( kLogLevelError, "dnssd_getaddrinfo error %#m\n", inGAIError );
+                                                       dnssd_getaddrinfo_forget( &instance->newGAI );
+                                               }
+                                               break;
+                                       
+                                       default:
+                                               break;
+                               }
+                       } );
+                       instance->newGAI = gai;
+                       dnssd_retain( instance->newGAI );
+                       dnssd_getaddrinfo_activate( instance->newGAI );
+               }
+               else
+       #endif
+               {
+                       DNSServiceForget( &instance->gai );
+                       
+                       sdRef = me->connection;
+                       instance->gaiStartTicks = UpTicks();
+                       err = DNSServiceGetAddrInfo( &sdRef, kDNSServiceFlagsShareConnection, instance->ifIndex,
+                               kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, instance->hostname, _ServiceBrowserGAICallback,
+                               instance );
+                       require_noerr( err, exit );
+                       
+                       instance->gai = sdRef;
+               }
+       }
+       
+exit:
+       return;
+}
+
+#if( MDNSRESPONDER_PROJECT )
+//===========================================================================================================================
+//     _ServiceBrowserGAIResultHandler
+//===========================================================================================================================
+
+static void
+       _ServiceBrowserGAIResultHandler(
+               ServiceBrowserRef                               me,
+               SBServiceInstance *                             inInstance,
+               dnssd_getaddrinfo_result_t *    inResultArray,
+               size_t                                                  inResultCount )
+{
+       OSStatus                        err;
+       size_t                          i;
+       const uint64_t          nowTicks = UpTicks();
+       
+       for( i = 0; i < inResultCount; ++i )
+       {
+               const dnssd_getaddrinfo_result_t                        result  = inResultArray[ i ];
+               const dnssd_getaddrinfo_result_type_t           type    = dnssd_getaddrinfo_result_get_type( result );
                
-               sdRef = me->connection;
-               instance->gaiStartTicks = UpTicks();
-               err = DNSServiceGetAddrInfo( &sdRef, kDNSServiceFlagsShareConnection, instance->ifIndex,
-                       kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, instance->hostname, _ServiceBrowserGAICallback, instance );
-               require_noerr( err, exit );
+               sb_ulog( kLogLevelTrace, "dnssd_getaddrinfo result: %@\n", result );
                
-               instance->getAddrInfo = sdRef;
+               if( type == dnssd_getaddrinfo_result_type_add )
+               {
+                       err = _ServiceBrowserAddIPAddress( me, inInstance, dnssd_getaddrinfo_result_get_address( result ),
+                               UpTicksToMicroseconds( nowTicks - inInstance->gaiStartTicks ) );
+                       if( err == kDuplicateErr ) err = kNoErr;
+                       require_noerr( err, exit );
+               }
+               else if( type == dnssd_getaddrinfo_result_type_remove )
+               {
+                       err = _ServiceBrowserRemoveIPAddress( me, inInstance, dnssd_getaddrinfo_result_get_address( result ) );
+                       if( err == kNotFoundErr ) err = kNoErr;
+                       require_noerr( err, exit );
+               }
        }
        
 exit:
        return;
 }
+#endif
 
 //===========================================================================================================================
 //     _ServiceBrowserGAICallback
@@ -23722,7 +30637,7 @@ static OSStatus
        newInstance             = NULL;
        
 exit:
-       if( newInstance ) _SBServiceInstanceFree( newInstance );
+       if( newInstance ) _SBServiceInstanceRelease( newInstance );
        return( err );
 }
 
@@ -23750,7 +30665,7 @@ static OSStatus
        require_action_quiet( instance, exit, err = kNotFoundErr );
        
        *ptr = instance->next;
-       _SBServiceInstanceFree( instance );
+       _SBServiceInstanceForget( &instance );
        err = kNoErr;
        
 exit:
@@ -24029,7 +30944,7 @@ static void     _SBServiceBrowseFree( SBServiceBrowse *inBrowse )
        while( ( instance = inBrowse->instanceList ) != NULL )
        {
                inBrowse->instanceList = instance->next;
-               _SBServiceInstanceFree( instance );
+               _SBServiceInstanceForget( &instance );
        }
        free( inBrowse );
 }
@@ -24054,6 +30969,8 @@ static OSStatus
        obj = (SBServiceInstance *) calloc( 1, sizeof( *obj ) );
        require_action( obj, exit, err = kNoMemoryErr );
        
+       obj->refCount = 1;
+       
        obj->name = strdup( inName );
        require_action( obj->name, exit, err = kNoMemoryErr );
        
@@ -24069,24 +30986,54 @@ static OSStatus
        err = kNoErr;
        
 exit:
-       if( obj ) _SBServiceInstanceFree( obj );
+       if( obj ) _SBServiceInstanceRelease( obj );
        return( err );
 }
 
+#if( MDNSRESPONDER_PROJECT )
 //===========================================================================================================================
-//     _SBServiceInstanceFree
+//     _SBServiceInstanceRetain
 //===========================================================================================================================
 
-static void    _SBServiceInstanceFree( SBServiceInstance *inInstance )
+static void    _SBServiceInstanceRetain( SBServiceInstance *inInstance )
+{
+       atomic_add_32( &inInstance->refCount, 1 );
+}
+#endif
+
+//===========================================================================================================================
+//     _SBServiceInstanceStop
+//===========================================================================================================================
+
+static void    _SBServiceInstanceStop( SBServiceInstance *inInstance )
 {
-       ForgetMem( &inInstance->name );
-       ForgetMem( &inInstance->fqdn );
        DNSServiceForget( &inInstance->resolve );
-       ForgetMem( &inInstance->hostname );
-       ForgetMem( &inInstance->txtPtr );
-       DNSServiceForget( &inInstance->getAddrInfo );
-       ForgetSBIPAddressList( &inInstance->ipaddrList );
-       free( inInstance );
+       DNSServiceForget( &inInstance->gai );
+#if( MDNSRESPONDER_PROJECT )
+       dnssd_getaddrinfo_forget( &inInstance->newGAI );
+#endif
+}
+
+//===========================================================================================================================
+//     _SBServiceInstanceRelease
+//===========================================================================================================================
+
+static void    _SBServiceInstanceRelease( SBServiceInstance *inInstance )
+{
+       if( atomic_add_and_fetch_32( &inInstance->refCount, -1 ) == 0 )
+       {
+               check( !inInstance->resolve );
+               check( !inInstance->gai );
+       #if( MDNSRESPONDER_PROJECT )
+               check( !inInstance->newGAI );
+       #endif
+               ForgetMem( &inInstance->name );
+               ForgetMem( &inInstance->fqdn );
+               ForgetMem( &inInstance->hostname );
+               ForgetMem( &inInstance->txtPtr );
+               ForgetSBIPAddressList( &inInstance->ipaddrList );
+               free( inInstance );
+       }
 }
 
 //===========================================================================================================================
old mode 100755 (executable)
new mode 100644 (file)
index 89a27a4..c2498e1
@@ -1,6 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>\r
 <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
   <ItemGroup Label="ProjectConfigurations">\r
+    <ProjectConfiguration Include="Debug|ARM64">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Debug|Win32">\r
       <Configuration>Debug</Configuration>\r
       <Platform>Win32</Platform>\r
@@ -9,6 +13,10 @@
       <Configuration>Debug</Configuration>\r
       <Platform>x64</Platform>\r
     </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|ARM64">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>ARM64</Platform>\r
+    </ProjectConfiguration>\r
     <ProjectConfiguration Include="Release|Win32">\r
       <Configuration>Release</Configuration>\r
       <Platform>Win32</Platform>\r
     <ProjectGuid>{AF35C285-528D-46A1-8A0E-47B0733DC718}</ProjectGuid>\r
     <RootNamespace>mDNSNetMonitor</RootNamespace>\r
     <Keyword>Win32Proj</Keyword>\r
+    <WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>\r
   </PropertyGroup>\r
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
     <CharacterSet>Unicode</CharacterSet>\r
     <WholeProgramOptimization>true</WholeProgramOptimization>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
     <CharacterSet>Unicode</CharacterSet>\r
     <WholeProgramOptimization>true</WholeProgramOptimization>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+    <WholeProgramOptimization>true</WholeProgramOptimization>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
     <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
     <ConfigurationType>Application</ConfigurationType>\r
     <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="Configuration">\r
+    <ConfigurationType>Application</ConfigurationType>\r
+    <CharacterSet>Unicode</CharacterSet>\r
+    <PlatformToolset>v142</PlatformToolset>\r
   </PropertyGroup>\r
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
   <ImportGroup Label="ExtensionSettings">\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
   </ImportGroup>\r
   <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">\r
     <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
   </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+  </ImportGroup>\r
   <PropertyGroup Label="UserMacros" />\r
   <PropertyGroup>\r
     <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\</OutDir>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</LinkIncremental>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</LinkIncremental>\r
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</LinkIncremental>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\</OutDir>\r
     <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\</OutDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\</IntDir>\r
-    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\</IntDir>\r
+    <OutDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(Platform)\$(Configuration)\</OutDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
+    <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</LinkIncremental>\r
     <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>\r
+    <LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">false</LinkIncremental>\r
   </PropertyGroup>\r
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
     <ClCompile>\r
       <Optimization>Disabled</Optimization>\r
       <AdditionalIncludeDirectories>../../mDNSWindows/SystemService;../../mDNSWindows;../../mDNSShared;../../mDNSCore;$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;C:/Program Files/Microsoft SDKs/Windows/v6.1/Include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
-      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_WIN32_WINNT=0x0501;MDNS_DEBUGMSGS=0;TARGET_OS_WIN32;WIN32_LEAN_AND_MEAN;USE_TCP_LOOPBACK;PLATFORM_NO_STRSEP;PLATFORM_NO_EPIPE;PLATFORM_NO_RLIMIT;PID_FILE=;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_USE_32BIT_TIME_T;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
-      <MinimalRebuild>true</MinimalRebuild>\r
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_WIN32_WINNT=0x0501;MDNS_DEBUGMSGS=0;TARGET_OS_WIN32;WIN32_LEAN_AND_MEAN;USE_TCP_LOOPBACK;PLATFORM_NO_STRSEP;PLATFORM_NO_EPIPE;PLATFORM_NO_RLIMIT;PID_FILE=;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;_USE_32BIT_TIME_T;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r
       <PrecompiledHeader>\r
     <ClCompile>\r
       <Optimization>Disabled</Optimization>\r
       <AdditionalIncludeDirectories>../../mDNSWindows/SystemService;../../mDNSWindows;../../mDNSShared;../../mDNSCore;$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;C:/Program Files/Microsoft SDKs/Windows/v6.1/Include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
-      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_WIN32_WINNT=0x0501;MDNS_DEBUGMSGS=0;TARGET_OS_WIN32;WIN32_LEAN_AND_MEAN;USE_TCP_LOOPBACK;PLATFORM_NO_STRSEP;PLATFORM_NO_EPIPE;PLATFORM_NO_RLIMIT;PID_FILE=;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_WIN32_WINNT=0x0501;MDNS_DEBUGMSGS=0;TARGET_OS_WIN32;WIN32_LEAN_AND_MEAN;USE_TCP_LOOPBACK;PLATFORM_NO_STRSEP;PLATFORM_NO_EPIPE;PLATFORM_NO_RLIMIT;PID_FILE=;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r
+      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r
+    </ClCompile>\r
+    <ResourceCompile>\r
+      <AdditionalIncludeDirectories>../../mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ResourceCompile>\r
+    <Link>\r
+      <AdditionalOptions>/NXCOMPAT /DYNAMICBASE %(AdditionalOptions)</AdditionalOptions>\r
+      <AdditionalDependencies>ws2_32.lib;iphlpapi.lib;crypt32.lib;netapi32.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+      <SubSystem>Console</SubSystem>\r
+      <UACExecutionLevel>RequireAdministrator</UACExecutionLevel>\r
+    </Link>\r
+    <Manifest>\r
+      <AdditionalManifestFiles>mDNSNetMonitor.manifest;%(AdditionalManifestFiles)</AdditionalManifestFiles>\r
+    </Manifest>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">\r
+    <ClCompile>\r
+      <Optimization>Disabled</Optimization>\r
+      <AdditionalIncludeDirectories>../../mDNSWindows/SystemService;../../mDNSWindows;../../mDNSShared;../../mDNSCore;$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;C:/Program Files/Microsoft SDKs/Windows/v6.1/Include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_WIN32_WINNT=0x0501;MDNS_DEBUGMSGS=0;TARGET_OS_WIN32;WIN32_LEAN_AND_MEAN;USE_TCP_LOOPBACK;PLATFORM_NO_STRSEP;PLATFORM_NO_EPIPE;PLATFORM_NO_RLIMIT;PID_FILE=;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
       <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>\r
       <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r
       <PrecompiledHeader>\r
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
     <ClCompile>\r
       <AdditionalIncludeDirectories>../../mDNSWindows/SystemService;../../mDNSWindows;../../mDNSShared;../../mDNSCore;$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;C:/Program Files/Microsoft SDKs/Windows/v6.1/Include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
-      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_WIN32_WINNT=0x0501;MDNS_DEBUGMSGS=0;TARGET_OS_WIN32;WIN32_LEAN_AND_MEAN;USE_TCP_LOOPBACK;PLATFORM_NO_STRSEP;PLATFORM_NO_EPIPE;PLATFORM_NO_RLIMIT;PID_FILE=;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_USE_32BIT_TIME_T;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_WIN32_WINNT=0x0501;MDNS_DEBUGMSGS=0;TARGET_OS_WIN32;WIN32_LEAN_AND_MEAN;USE_TCP_LOOPBACK;PLATFORM_NO_STRSEP;PLATFORM_NO_EPIPE;PLATFORM_NO_RLIMIT;PID_FILE=;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;_USE_32BIT_TIME_T;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
       <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r
       <PrecompiledHeader>\r
       </PrecompiledHeader>\r
@@ -164,7 +225,40 @@ xcopy /I/Y "$(TargetPath)"
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
     <ClCompile>\r
       <AdditionalIncludeDirectories>../../mDNSWindows/SystemService;../../mDNSWindows;../../mDNSShared;../../mDNSCore;$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;C:/Program Files/Microsoft SDKs/Windows/v6.1/Include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
-      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_WIN32_WINNT=0x0501;MDNS_DEBUGMSGS=0;TARGET_OS_WIN32;WIN32_LEAN_AND_MEAN;USE_TCP_LOOPBACK;PLATFORM_NO_STRSEP;PLATFORM_NO_EPIPE;PLATFORM_NO_RLIMIT;PID_FILE=;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_WIN32_WINNT=0x0501;MDNS_DEBUGMSGS=0;TARGET_OS_WIN32;WIN32_LEAN_AND_MEAN;USE_TCP_LOOPBACK;PLATFORM_NO_STRSEP;PLATFORM_NO_EPIPE;PLATFORM_NO_RLIMIT;PID_FILE=;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <WarningLevel>Level3</WarningLevel>\r
+      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r
+    </ClCompile>\r
+    <ResourceCompile>\r
+      <AdditionalIncludeDirectories>../../mDNSWindows;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+    </ResourceCompile>\r
+    <Link>\r
+      <AdditionalOptions>/NXCOMPAT /DYNAMICBASE %(AdditionalOptions)</AdditionalOptions>\r
+      <AdditionalDependencies>ws2_32.lib;iphlpapi.lib;crypt32.lib;netapi32.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>\r
+      <GenerateDebugInformation>true</GenerateDebugInformation>\r
+      <SubSystem>Console</SubSystem>\r
+      <OptimizeReferences>true</OptimizeReferences>\r
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r
+      <UACExecutionLevel>RequireAdministrator</UACExecutionLevel>\r
+    </Link>\r
+    <Manifest>\r
+      <AdditionalManifestFiles>mDNSNetMonitor.manifest;%(AdditionalManifestFiles)</AdditionalManifestFiles>\r
+    </Manifest>\r
+    <PostBuildEvent>\r
+      <Command>if not "%RC_XBS%" == "YES" goto END\r
+if not exist "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)" mkdir "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)"\r
+xcopy /I/Y "$(TargetPath)"                                                          "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)"\r
+:END\r
+</Command>\r
+    </PostBuildEvent>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">\r
+    <ClCompile>\r
+      <AdditionalIncludeDirectories>../../mDNSWindows/SystemService;../../mDNSWindows;../../mDNSShared;../../mDNSCore;$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;C:/Program Files/Microsoft SDKs/Windows/v6.1/Include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_WIN32_WINNT=0x0501;MDNS_DEBUGMSGS=0;TARGET_OS_WIN32;WIN32_LEAN_AND_MEAN;USE_TCP_LOOPBACK;PLATFORM_NO_STRSEP;PLATFORM_NO_EPIPE;PLATFORM_NO_RLIMIT;PID_FILE=;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
       <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r
       <PrecompiledHeader>\r
       </PrecompiledHeader>\r
@@ -195,8 +289,6 @@ xcopy /I/Y "$(TargetPath)"
     </PostBuildEvent>\r
   </ItemDefinitionGroup>\r
   <ItemGroup>\r
-    <ClCompile Include="..\..\mDNSCore\anonymous.c" />\r
-    <ClCompile Include="..\..\mDNSCore\CryptoAlg.c" />\r
     <ClCompile Include="..\..\mDNSCore\DNSCommon.c" />\r
     <ClCompile Include="..\..\mDNSCore\DNSDigest.c" />\r
     <ClCompile Include="..\..\mDNSShared\dnssd_ipc.c" />\r
old mode 100755 (executable)
new mode 100644 (file)
index b9a25be..1a95a4d
     <ClCompile Include="..\..\mDNSWindows\Poll.c">\r
       <Filter>Source Files</Filter>\r
     </ClCompile>\r
-    <ClCompile Include="..\..\mDNSCore\anonymous.c">\r
-      <Filter>Source Files</Filter>\r
-    </ClCompile>\r
-    <ClCompile Include="..\..\mDNSCore\CryptoAlg.c">\r
-      <Filter>Source Files</Filter>\r
-    </ClCompile>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <ClInclude Include="..\..\mDNSCore\DNSCommon.h">\r
diff --git a/Clients/srputil/srputil-entitlements.plist b/Clients/srputil/srputil-entitlements.plist
new file mode 100644 (file)
index 0000000..bdfa30d
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>com.apple.srp-mdns-proxy.proxy</key>
+       <true/>
+       <key>com.apple.security.network.client</key>
+       <true/>
+       <key>com.apple.security.network.server</key>
+       <true/>
+</dict>
+</plist>
diff --git a/Clients/srputil/srputil.c b/Clients/srputil/srputil.c
new file mode 100644 (file)
index 0000000..c59fb33
--- /dev/null
@@ -0,0 +1,246 @@
+/* srputil.c
+ *
+ * Copyright (c) 2020 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SRP Advertising Proxy utility program, allows:
+ *   start/stop advertising proxy
+ *   get/track list of service types
+ *   get/track list of services of a particular type
+ *   get/track list of hosts
+ *   get/track information about a particular host
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <dns_sd.h>
+#include <net/if.h>
+#include <os/log.h>
+
+#include "srp.h"
+#include "advertising_proxy_services.h"
+
+dispatch_queue_t main_queue;
+
+static void
+started_callback(advertising_proxy_conn_ref cref, xpc_object_t response, advertising_proxy_error_type err)
+{
+    INFO("started: cref %p  response %p   err %d.", cref, response, err);
+    if (err != kDNSSDAdvertisingProxyStatus_NoError) {
+        exit(1);
+    }
+}
+
+static void
+flushed_callback(advertising_proxy_conn_ref cref, xpc_object_t response, advertising_proxy_error_type err)
+{
+    INFO("flushed: cref %p  response %p   err %d.", cref, response, err);
+    if (err != kDNSSDAdvertisingProxyStatus_NoError) {
+        exit(1);
+    }
+    // We don't need to wait around after flushing.
+    exit(0);
+}
+
+static void
+block_callback(advertising_proxy_conn_ref cref, xpc_object_t response, advertising_proxy_error_type err)
+{
+    INFO("blocked: cref %p  response %p   err %d.", cref, response, err);
+    if (err != kDNSSDAdvertisingProxyStatus_NoError) {
+        exit(1);
+    }
+    // We don't need to wait around after blocking.
+    exit(0);
+}
+
+static void
+unblock_callback(advertising_proxy_conn_ref cref, xpc_object_t response, advertising_proxy_error_type err)
+{
+    INFO("unblocked: cref %p  response %p   err %d.", cref, response, err);
+    if (err != kDNSSDAdvertisingProxyStatus_NoError) {
+        exit(1);
+    }
+    // We don't need to wait around after unblocking.
+    exit(0);
+}
+
+static void
+regenerate_callback(advertising_proxy_conn_ref cref, xpc_object_t response, advertising_proxy_error_type err)
+{
+    INFO("regenerated ula: cref %p  response %p   err %d.", cref, response, err);
+    if (err != kDNSSDAdvertisingProxyStatus_NoError) {
+        exit(1);
+    }
+    // We don't need to wait around after unblocking.
+    exit(0);
+}
+
+static void
+services_callback(advertising_proxy_conn_ref cref, xpc_object_t response, advertising_proxy_error_type err)
+{
+    size_t i, num;
+    int64_t lease, hours, minutes, seconds;
+    INFO("services: cref %p  response %p   err %d.", cref, response, err);
+    if (err != kDNSSDAdvertisingProxyStatus_NoError) {
+        exit(1);
+    }
+    xpc_object_t services = xpc_dictionary_get_value(response, "instances");
+    if (services == NULL) {
+        INFO("Non-error response doesn't contain an instances key");
+        exit(1);
+    }
+    if (xpc_get_type(services) != XPC_TYPE_ARRAY) {
+        INFO("Non-error response instances value is not an array");
+        exit(1);
+    }
+    num = xpc_array_get_count(services);
+    if (num == 0) {
+        INFO("No registered services.");
+    }
+    for (i = 0; i < num; i++) {
+        xpc_object_t dict = xpc_array_get_value(services, i);
+        if (dict == NULL || xpc_get_type(dict) != XPC_TYPE_DICTIONARY) {
+            INFO("services array[%d] is not a dictionary", i);
+            exit(1);
+        }
+        const char *hostname, *instance_name, *service_type, *port, *address, *regname;
+        regname = xpc_dictionary_get_string(dict, "regname");
+        hostname = xpc_dictionary_get_string(dict, "hostname");
+        instance_name = xpc_dictionary_get_string(dict, "name");
+        if (instance_name == NULL) {
+            instance_name = "<no instances>";
+            service_type = "";
+            port = "";
+        } else {
+            service_type = xpc_dictionary_get_string(dict, "type");
+            port = xpc_dictionary_get_string(dict, "port");
+        }
+        address = xpc_dictionary_get_string(dict, "address");
+        lease = xpc_dictionary_get_int64(dict, "lease");
+        hours = lease / 3600 / 1000;
+        lease -= hours * 3600 * 1000;
+        minutes = lease / 60 / 1000;
+        lease -= minutes * 60 * 1000;
+        seconds = lease / 1000;
+        lease -= seconds * 1000;
+
+        printf("\"%s\" \"%s\" %s %s %s %qd:%qd:%qd.%qd \"%s\"\n", regname, instance_name, service_type, port,
+               address == NULL ? "" : address, hours, minutes, seconds, lease, hostname);
+    }
+    exit(0);
+}
+
+static void
+usage(void)
+{
+    ERROR("srputil start          -- start the SRP MDNS Proxy through launchd");
+    ERROR("        services       -- get the list of services currently being advertised");
+    ERROR("        block          -- block the SRP listener");
+    ERROR("        unblock        -- unblock the SRP listener");
+    ERROR("        regenerate-ula -- generate a new ULA and restart the network");
+#ifdef NOTYET
+    ERROR("        flush -- flush all entries from the SRP proxy (for testing only)");
+#endif
+}
+
+int
+main(int argc, char **argv)
+{
+    int i;
+       bool start_proxy = false;
+    bool flush_entries = false;
+    bool list_services = false;
+    bool block = false;
+    bool unblock = false;
+    bool regenerate_ula = false;
+#ifdef NOTYET
+       bool watch = false;
+       bool get = false;
+#endif
+
+    for (i = 1; i < argc; i++) {
+        if (!strcmp(argv[i], "start")) {
+                       start_proxy = true;
+               } else if (!strcmp(argv[i], "flush")) {
+            flush_entries = true;
+               } else if (!strcmp(argv[i], "services")) {
+            list_services = true;
+               } else if (!strcmp(argv[i], "block")) {
+            block = true;
+               } else if (!strcmp(argv[i], "unblock")) {
+            unblock = true;
+               } else if (!strcmp(argv[i], "regenerate-ula")) {
+            regenerate_ula = true;
+#ifdef NOTYET
+               } else if (!strcmp(argv[i], "watch")) {
+            ERROR("Watching not implemented yet.");
+            exit(1);
+               } else if (!strcmp(argv[i], "get")) {
+            ERROR("Getting not implemented yet.");
+            exit(1);
+#endif
+        } else {
+            usage();
+        }
+    }
+
+    main_queue = dispatch_get_main_queue();
+    dispatch_retain(main_queue);
+
+    // Start the queue, //then// do the work
+    dispatch_async(main_queue, ^{
+            advertising_proxy_error_type err = kDNSSDAdvertisingProxyStatus_NoError;;
+            advertising_proxy_conn_ref cref = NULL;
+
+            if (start_proxy) {
+                err = advertising_proxy_enable(&cref, main_queue, started_callback);
+            }
+            if (err == kDNSSDAdvertisingProxyStatus_NoError && flush_entries) {
+                err = advertising_proxy_flush_entries(&cref, main_queue, flushed_callback);
+            }
+            if (err == kDNSSDAdvertisingProxyStatus_NoError && list_services) {
+                err = advertising_proxy_get_service_list(&cref, main_queue, services_callback);
+            }
+            if (err == kDNSSDAdvertisingProxyStatus_NoError && block) {
+                err = advertising_proxy_block_service(&cref, main_queue, block_callback);
+            }
+            if (err == kDNSSDAdvertisingProxyStatus_NoError && unblock) {
+                err = advertising_proxy_unblock_service(&cref, main_queue, unblock_callback);
+            }
+            if (err == kDNSSDAdvertisingProxyStatus_NoError && regenerate_ula) {
+                err = advertising_proxy_regenerate_ula(&cref, main_queue, regenerate_callback);
+            }
+            if (err != kDNSSDAdvertisingProxyStatus_NoError) {
+                exit(1);
+            }
+        });
+    dispatch_main();
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
index ab0be62add21993156aa4897054320b98e90e46b..562905b8a90ef507102abeaed72015bea6321f3f 100644 (file)
@@ -120,7 +120,7 @@ dso_transport_finalize(dso_transport_t *transport)
 //
 // If there is a finalize function, that function MUST either free its own state that references the
 // DSO state, or else must NULL out the pointer to the DSO state.
-int64_t dso_transport_idle(void *context, int64_t now_in, int64_t next_timer_event)
+int32_t dso_transport_idle(void *context, int64_t now_in, int64_t next_timer_event)
 {
     dso_connect_state_t *cs, *cnext;
     mDNS *m = context;
@@ -248,7 +248,7 @@ bool dso_write_finish(dso_transport_t *transport)
 {
 #ifdef DSO_USES_NETWORK_FRAMEWORK
     uint32_t serial = transport->dso->serial;
-    int bytes_to_write = transport->bytes_to_write;
+    size_t bytes_to_write = transport->bytes_to_write;
     transport->bytes_to_write = 0;
     if (transport->write_failed) {
         dso_drop(transport->dso);
@@ -428,7 +428,7 @@ bool dso_send_no_error(dso_state_t *dso, const DNSMessageHeader *header)
 }
 
 #ifdef DSO_USES_NETWORK_FRAMEWORK
-static void dso_read_message(dso_transport_t *transport, size_t length);
+static void dso_read_message(dso_transport_t *transport, uint32_t length);
 
 static void dso_read_message_length(dso_transport_t *transport)
 {
@@ -457,24 +457,28 @@ static void dso_read_message_length(dso_transport_t *transport)
                                   LogMsg("dso_read_message_length: remote end closed connection.");
                                   goto fail;
                               } else {
-                                  size_t length;
+                                  uint32_t length;
+                                  size_t length_length;
                                   const uint8_t *lenbuf;
-                                  dispatch_data_t map = dispatch_data_create_map(content, (const void **)&lenbuf, &length);
+                                  dispatch_data_t map = dispatch_data_create_map(content, (const void **)&lenbuf,
+                                                                                 &length_length);
                                   if (map == NULL) {
                                       LogMsg("dso_read_message_length: map create failed");
                                       goto fail;
-                                  } else if (length != 2) {
-                                      LogMsg("dso_read_message_length: invalid length = %d", length);
+                                  } else if (length_length != 2) {
+                                      LogMsg("dso_read_message_length: invalid length = %d", length_length);
+                                      dispatch_release(map);
                                       goto fail;
                                   }
                                   length = ((unsigned)(lenbuf[0]) << 8) | ((unsigned)lenbuf[1]);
+                                  dispatch_release(map);
                                   dso_read_message(transport, length);
                               }
                               KQueueUnlock("dso_read_message_length completion routine");
                           });
 }
 
-void dso_read_message(dso_transport_t *transport, size_t length)
+void dso_read_message(dso_transport_t *transport, uint32_t length)
 {
     const uint32_t serial = transport->dso->serial;
     if (transport->connection == NULL) {
@@ -508,6 +512,7 @@ void dso_read_message(dso_transport_t *transport, size_t length)
                                       goto fail;
                                   } else if (bytes_read != length) {
                                       LogMsg("dso_read_message_length: only %d of %d bytes read", bytes_read, length);
+                                      dispatch_release(map);
                                       goto fail;
                                   }
                                   // Process the message.
@@ -515,6 +520,9 @@ void dso_read_message(dso_transport_t *transport, size_t length)
                                   dns_message_received(dso, message, length);
                                   mDNS_Unlock(&mDNSStorage);
 
+                                  // Release the map object now that we no longer need its buffers.
+                                  dispatch_release(map);
+
                                   // Now read the next message length.
                                   dso_read_message_length(transport);
                               }
@@ -732,9 +740,9 @@ dso_connect_state_t *dso_connect_state_create(const char *hostname, mDNSAddr *ad
                                               int max_outstanding_queries, size_t inbuf_size, size_t outbuf_size,
                                               dso_event_callback_t callback, dso_state_t *dso, void *context, const char *detail)
 {
-    int detlen = strlen(detail) + 1;
-    int hostlen = hostname == NULL ? 0 : strlen(hostname) + 1;
-    int len;
+    size_t detlen = strlen(detail) + 1;
+    size_t hostlen = hostname == NULL ? 0 : strlen(hostname) + 1;
+    size_t len;
     dso_connect_state_t *cs;
     char *csp;
     char nbuf[INET6_ADDRSTRLEN + 1];
index eeef345b130114d86c2a4c9c4b6b4248585a01f4..871d1d59c553b3a5baa4f715a4bac2e891ed3f90 100644 (file)
--- a/DSO/dso.c
+++ b/DSO/dso.c
@@ -23,6 +23,7 @@
 #include <signal.h>
 #include <stdlib.h>
 #include <stdbool.h>
+#include <stdlib.h>
 #include <unistd.h>
 #include <string.h>
 #include <assert.h>
@@ -45,7 +46,8 @@
 #ifdef STANDALONE
 #undef LogMsg
 #define LogMsg(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__)
-#define mDNSRandom(x) arc4random_uniform(x)
+extern uint16_t srp_random16(void);
+#define mDNSRandom(x) srp_random16()
 #define mDNSPlatformMemAllocateClear(length) calloc(1, length)
 #endif // STANDALONE
 
@@ -104,7 +106,7 @@ void dso_drop(dso_state_t *dso)
     dso_connections_needing_cleanup = dso;
 }
 
-int64_t dso_idle(void *context, int64_t now, int64_t next_timer_event)
+int32_t dso_idle(void *context, int64_t now, int64_t next_timer_event)
 {
     dso_state_t *dso, *dnext;
     dso_activity_t *ap, *anext;
@@ -171,7 +173,7 @@ dso_state_t *dso_create(bool is_server, int max_outstanding_queries, const char
                         dso_event_callback_t callback, void *context, dso_transport_t *transport)
 {
     dso_state_t *dso;
-    int namelen = strlen(remote_name) + 1;
+    int namelen = (int)strlen(remote_name) + 1;
     int outsize = (sizeof (dso_outstanding_query_state_t)) + max_outstanding_queries * sizeof (dso_outstanding_query_t);
 
     // We allocate everything in a single hunk so that we can free it together as well.
@@ -361,7 +363,7 @@ dso_activity_t *dso_add_activity(dso_state_t *dso, const char *name, const char
     }
 
     len = namelen + sizeof *activity;
-    ap = mDNSPlatformMemAllocateClear(len);
+    ap = mDNSPlatformMemAllocateClear((mDNSu32)len);
     if (ap == NULL) {
         return NULL;
     }
index 13ad08a476138ad2c4a4fb1a6a937a50ea73e22b..fc2b4e151918d45b47af429dfd63abbcc386b97b 100644 (file)
--- a/DSO/dso.h
+++ b/DSO/dso.h
@@ -168,7 +168,7 @@ dso_state_t *dso_create(bool is_server, int max_outstanding_queries, const char
                         dso_event_callback_t callback, void *context, dso_transport_t *transport);
 dso_state_t *dso_find_by_serial(uint32_t serial);
 void dso_drop(dso_state_t *dso);
-int64_t dso_idle(void *context, int64_t now, int64_t next_timer_event);
+int32_t dso_idle(void *context, int64_t now, int64_t next_timer_event);
 void dso_release(dso_state_t **dsop);
 void dso_start_tlv(dso_message_t *state, int opcode);
 void dso_add_tlv_bytes(dso_message_t *state, const uint8_t *bytes, size_t len);
@@ -191,7 +191,7 @@ void dso_message_received(dso_state_t *dso, const uint8_t *message, size_t messa
 void dns_message_received(dso_state_t *dso, const uint8_t *message, size_t message_length);
 
 // Provided by DSO transport implementation for use by dso.c:
-int64_t dso_transport_idle(void *context, int64_t now, int64_t next_timer_event);
+int32_t dso_transport_idle(void *context, int64_t now, int64_t next_timer_event);
 bool dso_send_simple_response(dso_state_t *dso, int rcode, const DNSMessageHeader *header, const char *pres);
 bool dso_send_not_implemented(dso_state_t *dso, const DNSMessageHeader *header);
 bool dso_send_refused(dso_state_t *dso, const DNSMessageHeader *header);
index ad4b4ca96e60ec806757d5cdeaf822c2ec3f36e4..69c06bd3aadaf4be22c6f14ddd34f6d8771eec43 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -17,7 +17,7 @@
 
 include $(MAKEFILEPATH)/pb_makefiles/platform.make
 
-MVERS = "mDNSResponder-1096.100.3"
+MVERS = "mDNSResponder-1310.40.42"
 
 VER =
 ifneq ($(strip $(GCC_VERSION)),)
@@ -30,6 +30,38 @@ buildsettings        := OBJROOT=$(OBJROOT) SYMROOT=$(SYMROOT) DSTROOT=$(DSTROOT) MVERS=
 
 .PHONY: install installSome installEmpty installExtras SystemLibraries installhdrs installapi installsrc java clean
 
+# Sanitizer support
+# Disable Sanitizer instrumentation in LibSystem contributors. See rdar://problem/29952210.
+UNSUPPORTED_SANITIZER_PROJECTS := mDNSResponderSystemLibraries mDNSResponderSystemLibraries_Sim
+PROJECT_SUPPORTS_SANITIZERS := 1
+ifneq ($(words $(filter $(UNSUPPORTED_SANITIZER_PROJECTS), $(RC_ProjectName))), 0)
+  PROJECT_SUPPORTS_SANITIZERS := 0
+endif
+ifeq ($(RC_ENABLE_ADDRESS_SANITIZATION),1)
+  ifeq ($(PROJECT_SUPPORTS_SANITIZERS),1)
+    $(info Enabling Address Sanitizer)
+    buildsettings += -enableAddressSanitizer YES
+  else
+    $(warning WARNING: Address Sanitizer not supported for project $(RC_ProjectName))
+  endif
+endif
+ifeq ($(RC_ENABLE_THREAD_SANITIZATION),1)
+  ifeq ($(PROJECT_SUPPORTS_SANITIZERS),1)
+    $(info Enabling Thread Sanitizer)
+    buildsettings += -enableThreadSanitizer YES
+  else
+    $(warning WARNING: Thread Sanitizer not supported for project $(RC_ProjectName))
+  endif
+endif
+ifeq ($(RC_ENABLE_UNDEFINED_BEHAVIOR_SANITIZATION),1)
+  ifeq ($(PROJECT_SUPPORTS_SANITIZERS),1)
+    $(info Enabling Undefined Behavior Sanitizer)
+    buildsettings += -enableUndefinedBehaviorSanitizer YES
+  else
+    $(warning WARNING: Undefined Behavior Sanitizer not supported for project $(RC_ProjectName))
+  endif
+endif
+
 # B&I install build targets
 #
 # For the mDNSResponder build alias, the make target used by B&I depends on the platform:
@@ -46,17 +78,19 @@ buildsettings       := OBJROOT=$(OBJROOT) SYMROOT=$(SYMROOT) DSTROOT=$(DSTROOT) MVERS=
 
 install:
 ifeq ($(RC_ProjectName), mDNSResponderServices)
+ifeq ($(RC_PROJECT_COMPILATION_PLATFORM), osx)
+       cd '$(projectdir)'; xcodebuild install $(buildsettings) -target 'Build Services-macOS' $(VER)
+else
        cd '$(projectdir)'; xcodebuild install $(buildsettings) -target 'Build Services' $(VER)
+endif
+else ifeq ($(RC_ProjectName), mDNSResponderServices_Sim)
+       mkdir -p $(DSTROOT)/AppleInternal
 else
        cd '$(projectdir)'; xcodebuild install $(buildsettings) $(VER)
 endif
 
 installSome:
-ifeq ($(RC_ProjectName), mDNSResponderServices)
-       cd '$(projectdir)'; xcodebuild install $(buildsettings) -target 'Build Services' $(VER)
-else
        cd '$(projectdir)'; xcodebuild install $(buildsettings) $(VER)
-endif
 
 installEmpty:
        mkdir -p $(DSTROOT)/AppleInternal
@@ -66,6 +100,8 @@ ifeq ($(RC_PROJECT_COMPILATION_PLATFORM), osx)
        cd '$(projectdir)'; xcodebuild install $(buildsettings) -target 'Build Extras-macOS' $(VER)
 else ifeq ($(RC_PROJECT_COMPILATION_PLATFORM), ios)
        cd '$(projectdir)'; xcodebuild install $(buildsettings) -target 'Build Extras-iOS' $(VER)
+else ifeq ($(RC_PROJECT_COMPILATION_PLATFORM), atv)
+       cd '$(projectdir)'; xcodebuild install $(buildsettings) -target 'Build Extras-tvOS' $(VER)
 else
        cd '$(projectdir)'; xcodebuild install $(buildsettings) -target 'Build Extras' $(VER)
 endif
@@ -77,7 +113,13 @@ SystemLibraries:
 
 installhdrs::
 ifeq ($(RC_ProjectName), mDNSResponderServices)
+ifeq ($(RC_PROJECT_COMPILATION_PLATFORM), osx)
+       cd '$(projectdir)'; xcodebuild installhdrs $(buildsettings) -target 'Build Services-macOS' $(VER)
+else
        cd '$(projectdir)'; xcodebuild installhdrs $(buildsettings) -target 'Build Services' $(VER)
+endif
+else ifeq ($(RC_ProjectName), mDNSResponderServices_Sim)
+       mkdir -p $(DSTROOT)/AppleInternal
 else ifneq ($(findstring SystemLibraries,$(RC_ProjectName)),)
        cd '$(projectdir)'; xcodebuild installhdrs $(buildsettings) -target SystemLibraries $(VER)
 endif
@@ -86,7 +128,13 @@ endif
 
 installapi:
 ifeq ($(RC_ProjectName), mDNSResponderServices)
+ifeq ($(RC_PROJECT_COMPILATION_PLATFORM), osx)
+       cd '$(projectdir)'; xcodebuild installapi $(buildsettings) -target 'Build Services-macOS' $(VER)
+else
        cd '$(projectdir)'; xcodebuild installapi $(buildsettings) -target 'Build Services' $(VER)
+endif
+else ifeq ($(RC_ProjectName), mDNSResponderServices_Sim)
+       mkdir -p $(DSTROOT)/AppleInternal
 else ifneq ($(findstring SystemLibraries,$(RC_ProjectName)),)
        cd '$(projectdir)'; xcodebuild installapi $(buildsettings) -target SystemLibrariesDynamic $(VER)
 endif
diff --git a/Platforms/ADK/Thread/Makefile b/Platforms/ADK/Thread/Makefile
new file mode 100644 (file)
index 0000000..1e7adf9
--- /dev/null
@@ -0,0 +1,21 @@
+all:   ADK
+       (cd ADK/External/mdnsresponder; \
+        for file in `find . -name \*.\[ch\] -print`; do \
+            if ! cmp -s ../../../../../../$${file} $${file}; then \
+                 cp ../../../../../../$${file} $${file}; \
+                 echo cp ../../../../../../$${file} $${file}; \
+             fi; \
+        done)
+       (cd ADK; make TARGET=nRF52 PROTOCOLS=THREAD USE_STATIC_COMMISSIONING=1 USE_BLE=0 apps)
+
+clean:
+       (cd ADK; make TARGET=nRF52 PROTOCOLS=THREAD USE_STATIC_COMMISSIONING=1 USE_BLE=0 clean)
+
+ADK:
+       git clone git@github.pie.apple.com:Home-ADK/ADK.git
+
+update:
+       (cd ADK; git pull)
+
+install:
+       (cd ADK; USE_STATIC_COMMISSIONING=1 ./Tools/install.sh -d nRF52 -a Output/nRF52-arm-none-eabi-gcc/Debug/THREAD/Applications/Lightbulb.Oberon -k -t thread)
diff --git a/Platforms/ADK/Thread/adk-mem-parse.py b/Platforms/ADK/Thread/adk-mem-parse.py
new file mode 100644 (file)
index 0000000..2059195
--- /dev/null
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# This code parses an ADK log with MALLOC_DEBUG_LOGGING and eliminates matching alloc/free pairs.
+# It then prints a list of all allocations that have not yet been freed, and the file and line
+# number where they were allocated.  This can be useful for detecting leaks, although not everything
+# printed will be a leak: it could just be live data.
+
+import sys
+import re
+
+allocations = {}
+
+alloc_re = re.compile(r"^([0-9][0-9]*\.[0-9][0-9]*)\s+[A-Za-z][A-Za-z]*\s+(0x[0-9a-f][0-9a-f]*): (malloc|strdup|calloc)\((.*)\) at (.*)$")
+free_re = re.compile(r"^([0-9][0-9]*\.[0-9][0-9]*)\s+[A-Za-z][A-Za-z]*\s+(0x[0-9a-f][0-9a-f]*): (free)\((.*)\) at (.*)$")
+
+for line in sys.stdin:
+    line = line.strip()
+    matches = alloc_re.match(line)
+    if matches != None:
+        allocations[matches.group(2)] = matches
+    else:
+        matches = free_re.match(line)
+        if matches != None:
+            if matches.group(2) in allocations:
+                del allocations[matches.group(2)]
+            else:
+                print("mismatched free: ", line);
+
+leaks = {}
+for key, value in allocations.items():
+    if value.group(5) in leaks:
+        leaks[value.group(5)].append(value)
+    else:
+        leaks[value.group(5)] = [value]
+
+for key in sorted(leaks.keys()):
+    print("\nPossible leaks at: ", key)
+    for match in leaks[key]:
+        print("  ", match.group(2), " ", match.group(3), " ", match.group(4))
index 34ba93108d9aa1f858af2bccc9ba684223fa2d81..957377d62d2ba9a10d536c6080e0b838cc0c46f0 100644 (file)
@@ -1,10 +1,79 @@
-SRPCFLAGS = -O0 -g -Wall -Werror -DSTANDALONE -I../mDNSCore -I/usr/local/include -I. -I../mDNSShared -I../DSO -MMD -MF .depfile-${notdir $<}
-LINKOPTS = -lmbedcrypto
-
 BUILDDIR = build
 OBJDIR = objects
+GENKEY=\"/usr/local/bin/mbedtls_gen_key\"
+CERTWRITE=\"/usr/local/bin/mbedtls_cert_write\"
+
+ifndef os
+  SYSTEM := $(shell uname -s)
+  ifeq ($(SYSTEM), Darwin)
+    os=x
+  else ifeq ($(SYSTEM), Linux)
+    os_id := $(shell sed -n -e 's/^ID=//p' < /etc/os-release)
+    ifeq ($(os_id), raspbian)
+      os=raspbian
+    else
+      os=linux
+    endif
+  endif
+endif
+
+ifdef INSTBASE
+  INSTALL_PREFIX=$(INSTBASE)
+else
+  INSTALL_PREFIX=$(DESTDIR)/usr
+endif
 
-all:   setup $(BUILDDIR)/srp-simple $(BUILDDIR)/srp-gw $(BUILDDIR)/keydump $(BUILDDIR)/dnssd-proxy
+CP?=/bin/cp
+
+ifeq ($(os),x)
+SRPCFLAGS = -O0 -g -Wall -Werror -I../mDNSCore -I/usr/local/include -I. -I../mDNSShared -I../DSO -MMD -MF .depfile-${notdir $<} -DUSE_KQUEUE -DHAVE_SOCKADDR_DL -DGENKEY_PROGRAM=$(GENKEY) -DCERTWRITE_PROGRAM=$(CERTWRITE) -DEXCLUDE_TLS -D__APPLE_USE_RFC_3542 -DIOLOOP_MACOS
+SRPLDOPTS = -framework CoreServices -framework Security -framework CoreFoundation -framework Network
+HMACOBJS     = $(OBJDIR)/hmac-macos.o
+SIGNOBJS     = $(OBJDIR)/sign-macos.o 
+VERIFYOBJS   = $(OBJDIR)/verify-macos.o
+TLSOBJS      =
+IOOBJS       = $(OBJDIR)/macos-ioloop.o $(OBJDIR)/posix.o
+IOWOTLSOBJS  = $(OBJDIR)/macos-ioloop.o $(OBJDIR)/posix.o
+else ifeq ($(os), linux)
+SRPCFLAGS = -DMDNS_UDS_SERVERPATH=\"/var/run/mdnsd\" -O0 -g -Wall -Werror -DSTANDALONE -I../mDNSCore -I/usr/local/include -I. -I../mDNSShared -I../DSO -MMD -MF .depfile-${notdir $<} -DNOT_HAVE_SA_LEN -DUSE_SELECT -DUSE_INOTIFY -DGENKEY_PROGRAM=$(GENKEY) -DCERTWRITE_PROGRAM=$(CERTWRITE) -DNO_KEYCHAIN
+SRPLDOPTS = /usr/local/lib/libmbedtls.a /usr/local/lib/libmbedx509.a /usr/local/lib/libmbedcrypto.a 
+#SRPLDOPTS = -lmbedcrypto -lmbedtls -lmbedx509
+HMACOBJS     = $(OBJDIR)/hmac-mbedtls.o
+SIGNOBJS     = $(OBJDIR)/sign-mbedtls.o 
+VERIFYOBJS   = $(OBJDIR)/verify-mbedtls.o
+TLSOBJS      = $(OBJDIR)/tls-mbedtls.o
+IOOBJS       = $(OBJDIR)/ioloop.o $(TLSOBJS)
+IOWOTLSOBJS  = $(OBJDIR)/ioloop-notls.o
+else ifeq ($(os), linux-uclibc)
+SRPCFLAGS = -DMDNS_UDS_SERVERPATH=\"/var/run/mdnsd\" -O0 -g -Wall -Werror -DSTANDALONE -I../mDNSCore -I/usr/local/include -I. -I../mDNSShared -I../DSO -MMD -MF .depfile-${notdir $<} -DNOT_HAVE_SA_LEN -DUSE_SELECT -DLINUX_GETENTROPY -DGENKEY_PROGRAM=$(GENKEY) -DCERTWRITE_PROGRAM=$(CERTWRITE) -DNO_KEYCHAIN
+SRPLDOPTS = -lmbedcrypto -lmbedtls -lmbedx509
+HMACOBJS     = $(OBJDIR)/hmac-mbedtls.o
+SIGNOBJS     = $(OBJDIR)/sign-mbedtls.o 
+VERIFYOBJS   = $(OBJDIR)/verify-mbedtls.o
+TLSOBJS      = $(OBJDIR)/tls-mbedtls.o
+IOOBJS       = $(OBJDIR)/ioloop.o $(TLSOBJS)
+IOWOTLSOBJS  = $(OBJDIR)/ioloop-notls.o
+else ifeq ($(os), raspbian)
+SRPCFLAGS = -DMDNS_UDS_SERVERPATH=\"/var/run/mdnsd\" -O0 -g -Wall -Werror -DSTANDALONE -I../mDNSCore -I/usr/local/include -I. -I../mDNSShared -I../DSO -MMD -MF .depfile-${notdir $<} -DNOT_HAVE_SA_LEN -DUSE_SELECT -DLINUX_GETENTROPY -DGENKEY_PROGRAM=$(GENKEY) -DCERTWRITE_PROGRAM=$(CERTWRITE) -DNO_KEYCHAIN
+SRPLDOPTS = /usr/local/lib/libmbedtls.a /usr/local/lib/libmbedx509.a /usr/local/lib/libmbedcrypto.a 
+HMACOBJS     = $(OBJDIR)/hmac-mbedtls.o
+SIGNOBJS     = $(OBJDIR)/sign-mbedtls.o 
+VERIFYOBJS   = $(OBJDIR)/verify-mbedtls.o
+TLSOBJS      = $(OBJDIR)/tls-mbedtls.o
+IOOBJS       = $(OBJDIR)/ioloop.o $(TLSOBJS)
+IOWOTLSOBJS  = $(OBJDIR)/ioloop-notls.o
+else
+SRPCFLAGS=$(os) $(os_id)
+endif
+
+all:   setup $(BUILDDIR)/srp-client $(BUILDDIR)/srp-mdns-proxy $(BUILDDIR)/keydump $(BUILDDIR)/dnssd-proxy $(BUILDDIR)/srp-dns-proxy # $(BUILDDIR)/dnssd-relay
+
+install: all
+       $(CP) $(BUILDDIR)/srp-client $(INSTALL_PREFIX)/sbin
+       $(CP) $(BUILDDIR)/srp-dns-proxy $(INSTALL_PREFIX)/sbin
+       $(CP) $(BUILDDIR)/srp-mdns-proxy $(INSTALL_PREFIX)/sbin
+       $(CP) $(BUILDDIR)/dnssd-proxy $(INSTALL_PREFIX)/sbin
+       $(CP) $(BUILDDIR)/keydump $(INSTALL_PREFIX)/bin
 
 # 'setup' sets up the build directory structure the way we want
 setup:
@@ -16,45 +85,64 @@ clean:
        @if test -d $(OBJDIR)   ; then rm -r $(OBJDIR)   ; fi
        @if test -d $(BUILDDIR) ; then rm -r $(BUILDDIR) ; fi
 
-SIGNOBJS     = $(OBJDIR)/sign-mbedtls.o 
 SIMPLEOBJS   = $(OBJDIR)/towire.o $(SIGNOBJS)
 DSOOBJS      = $(OBJDIR)/dso.o
 MDNSOBJS     = $(OBJDIR)/dnssd_clientstub.o $(OBJDIR)/dnssd_ipc.o
-VERIFYOBJS   = $(OBJDIR)/verify-mbedtls.o
-FROMWIREOBJS = $(OBJDIR)/fromwire.o $(VERIFYOBJS)
-IOOBJS       = $(OBJDIR)/ioloop.o
+FROMWIREOBJS = $(OBJDIR)/fromwire.o $(VERIFYOBJS) $(OBJDIR)/wireutils.o
+CFOBJS       = $(OBJDIR)/config-parse.o
+
+$(BUILDDIR)/dnssd-relay:  $(OBJDIR)/dnssd-relay.o $(DSOOBJS) $(IOOBJS) $(CFOBJS)
+       $(CC) -o $@ $+ $(SRPLDOPTS)
 
-$(BUILDDIR)/dnssd-proxy:  $(OBJDIR)/dnssd-proxy.o $(SIMPLEOBJS) $(DSOOBJS) $(MDNSOBJS) $(FROMWIREOBJS) $(IOOBJS)
-       $(CC) -o $@ $+ $(LINKOPTS)
+$(BUILDDIR)/dnssd-proxy:  $(OBJDIR)/dnssd-proxy.o $(SIMPLEOBJS) $(DSOOBJS) $(MDNSOBJS) $(FROMWIREOBJS) $(IOOBJS) $(CFOBJS)
+       $(CC) -o $@ $+ $(SRPLDOPTS)
 
-$(BUILDDIR)/srp-simple:        $(OBJDIR)/srp-simple.o $(SIMPLEOBJS)
-       $(CC) -o $@ $+ $(LINKOPTS)
+$(BUILDDIR)/srp-client:        $(OBJDIR)/srp-ioloop.o $(OBJDIR)/srp-client.o $(SIMPLEOBJS) $(IOWOTLSOBJS) $(CFOBJS)
+       $(CC) -o $@ $+ $(SRPLDOPTS)
 
-$(BUILDDIR)/srp-gw:    $(OBJDIR)/srp-gw.o $(SIMPLEOBJS) $(FROMWIREOBJS) $(IOOBJS)
-       $(CC) -o $@ $+ $(LINKOPTS)
+$(BUILDDIR)/srp-dns-proxy:     $(OBJDIR)/srp-dns-proxy.o $(OBJDIR)/srp-parse.o $(SIMPLEOBJS) $(FROMWIREOBJS) $(IOOBJS) $(HMACOBJS) $(CFOBJS)
+       $(CC) -o $@ $+ $(SRPLDOPTS)
 
-$(BUILDDIR)/keydump:   $(OBJDIR)/keydump.o $(SIMPLEOBJS) $(FROMWIREOBJS)
-       $(CC) -o $@ $+ $(LINKOPTS)
+$(BUILDDIR)/srp-mdns-proxy:    $(OBJDIR)/srp-mdns-proxy.o $(OBJDIR)/srp-parse.o $(MDNSOBJS) $(SIMPLEOBJS) $(FROMWIREOBJS) $(IOOBJS) $(HMACOBJS) $(CFOBJS)
+       $(CC) -o $@ $+ $(SRPLDOPTS)
+
+$(BUILDDIR)/keydump:   $(OBJDIR)/keydump.o $(SIMPLEOBJS) $(FROMWIREOBJS) $(IOOBJS)
+       $(CC) -o $@ $+ $(SRPLDOPTS)
 
 $(OBJDIR)/dso.o:       ../DSO/dso.c
-       $(CC) -o $@ $(SRPCFLAGS) -c -I. -I../mDNSShared $<
+       $(CC) -o $@ $(SRPCFLAGS) $(CFLAGS) -c -I. -I../mDNSShared $<
 
 $(OBJDIR)/dnssd_clientstub.o:  ../mDNSShared/dnssd_clientstub.c
-       $(CC) -o $@ $(SRPCFLAGS) -c -I. -I../mDNSShared $<
+       $(CC) -o $@ $(SRPCFLAGS) $(CFLAGS) -c -I. -I../mDNSShared $<
 
 $(OBJDIR)/dnssd_ipc.o:  ../mDNSShared/dnssd_ipc.c
-       $(CC) -o $@ $(SRPCFLAGS) -c -I. -I../mDNSShared $<
+       $(CC) -o $@ $(SRPCFLAGS) $(CFLAGS) -c -I. -I../mDNSShared $<
+
+$(OBJDIR)/ioloop-notls.o: ioloop.c
+       $(CC) -o $@ $(SRPCFLAGS) $(CFLAGS) -DEXCLUDE_TLS -c  $<
 
 $(OBJDIR)/%.o: %.c
-       $(CC) -o $@ $(SRPCFLAGS) -c  $<
+       $(CC) -o $@ $(SRPCFLAGS) $(CFLAGS) -c  $<
 
+-include .depfile-config-parse.c
 -include .depfile-dnssd-proxy.c
+-include .depfile-dnssd_clientstub.c
+-include .depfile-dnssd_ipc.c
+-include .depfile-dso.c
 -include .depfile-fromwire.c
+-include .depfile-hmac-mbedtls.c
+-include .depfile-hmac-macos.c
 -include .depfile-ioloop.c
 -include .depfile-keydump.c
 -include .depfile-sign-mbedtls.c
--include .depfile-srp-gw.c
--include .depfile-srp-simple.c
+-include .depfile-sign-macos.c
+-include .depfile-srp-client.c
+-include .depfile-srp-dns-proxy.c
+-include .depfile-srp-ioloop.c
+-include .depfile-srp-mdns-proxy.c
+-include .depfile-srp-parse.c
+-include .depfile-tls-mbedtls.c
 -include .depfile-towire.c
 -include .depfile-verify-mbedtls.c
--include .depfile-dso.c
+-include .depfile-verify-macos.c
+-include .depfile-wireutils.c
diff --git a/ServiceRegistration/config-parse.c b/ServiceRegistration/config-parse.c
new file mode 100644 (file)
index 0000000..344def7
--- /dev/null
@@ -0,0 +1,191 @@
+/* -*- Mode: C; tab-width: 4; c-file-style: "bsd"; c-basic-offset: 4; fill-column: 108 -*-
+ *
+ * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2018 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+//*************************************************************************************************************
+//
+// General purpose stupid little parser, currently used by dnssd-proxy for its configuration file
+//
+//*************************************************************************************************************
+// Headers
+
+#include <stdio.h>          // For printf()
+#include <stdlib.h>         // For malloc()
+#include <string.h>         // For strrchr(), strcmp()
+#include <time.h>           // For "struct tm" etc.
+#include <signal.h>         // For SIGINT, SIGTERM
+#include <assert.h>
+#include <netdb.h>           // For gethostbyname()
+#include <sys/socket.h>      // For AF_INET, AF_INET6, etc.
+#include <net/if.h>          // For IF_NAMESIZE
+#include <netinet/in.h>      // For INADDR_NONE
+#include <netinet/tcp.h>     // For SOL_TCP, TCP_NOTSENT_LOWAT
+#include <arpa/inet.h>       // For inet_addr()
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+
+#include "config-parse.h"
+
+#ifdef STANDALONE
+#undef LogMsg
+#define LogMsg(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__)
+#endif // STANDALONE
+
+// Parse one line of a config file.
+// A line consists of a verb followed by one or more hunks of text.
+// We parse the verb first, then that tells us how many hunks of text to expect.
+// Each hunk is space-delineated; the last hunk can contain spaces.
+static bool config_parse_line(void *context, const char *filename, char *line, int lineno,
+                                                         config_file_verb_t *verbs, int num_verbs)
+{
+       char *sp;
+#define MAXCFHUNKS 10
+       char *hunks[MAXCFHUNKS];
+       int num_hunks = 0;
+       config_file_verb_t *config_file_verb = NULL;
+       int i;
+
+       sp = line;
+       do {
+               // Skip leading spaces.
+               while (*sp && (*sp == ' ' || *sp == '\t'))
+                       sp++;
+               if (num_hunks == 0) {
+                       // If this is a blank line with spaces on it or a comment line, we ignore it.
+                       if (!*sp || *sp == '#')
+                               return true;
+               }
+               hunks[num_hunks++] = sp;
+               // Find EOL or hunk
+               while (*sp && (*sp != ' ' && *sp != '\t')) {
+                       sp++;
+               }
+               if (*sp) {
+                       *sp++ = 0;
+               }
+               if (num_hunks == 1) {
+                       for (i = 0; i < num_verbs; i++) {
+                               // If the verb name matches, or the verb name is NULL (meaning whatever doesn't
+                               // match a preceding verb), we've found our verb.
+                               if (verbs[i].name == NULL || !strcmp(verbs[i].name, hunks[0])) {
+                                       config_file_verb = &verbs[i];
+                                       break;
+                               }
+                       }
+                       if (config_file_verb == NULL) {
+                               LogMsg("cfParseLine: unknown verb %s at line %d", hunks[0], lineno);
+                               return false;
+                       }
+               }                               
+       } while (*sp && num_hunks < MAXCFHUNKS && config_file_verb->max_hunks > num_hunks);
+       
+       // If we didn't get the hunks we needed, bail.
+       if (config_file_verb->min_hunks > num_hunks) {
+               LogMsg("config_file_parse_line: error: verb %s requires between %d and %d modifiers; %d given at line %d",
+                          hunks[0], config_file_verb->min_hunks, config_file_verb->max_hunks, num_hunks, lineno);
+               return false;
+       }
+
+       return config_file_verb->handler(context, filename, hunks, num_hunks, lineno);
+}
+
+// Parse a configuration file
+bool config_parse(void *context, const char *filename, config_file_verb_t *verbs, int num_verbs)
+{
+       int file;
+       char *buf, *line, *eof, *eol, *nextCR, *nextNL;
+       off_t flen, have;
+    ssize_t len;
+    int lineno;
+       bool success = true;
+
+       file = open(filename, O_RDONLY);
+       if (file < 0) {
+               LogMsg("cfParse: fatal: %s: %s", filename, strerror(errno));
+               return false;
+       }
+
+       // Get the length of the file.
+       flen = lseek(file, 0, SEEK_END);
+       lseek(file, 0, SEEK_SET);
+       buf = malloc(flen + 1);
+       if (buf == NULL) {
+               LogMsg("cfParse: fatal: not enough memory for %s", filename);
+               goto outclose;
+       }
+       
+       // Just in case we have a read() syscall that doesn't always read the whole file at once
+       have = 0;
+       while (have < flen) {
+               len = read(file, &buf[have], flen - have);
+               if (len < 0) {
+                       LogMsg("cfParse: fatal: read of %s at %lld len %lld: %s",
+                                  filename, (long long)have, (long long)(flen - have), strerror(errno));
+                       goto outfree;
+               }
+               if (len == 0) {
+                       LogMsg("cfParse: fatal: read of %s at %lld len %lld: zero bytes read",
+                                  filename, (long long)have, (long long)(flen - have));
+               outfree:
+                       free(buf);
+               outclose:
+                       close(file);
+                       return false;
+               }
+               have += len;
+       }
+       close(file);
+       buf[flen] = 0; // NUL terminate.
+       eof = buf + flen;
+       
+       // Parse through the file line by line.
+       line = buf;
+       lineno = 1;
+       while (line < eof) { // < because NUL at eof could be last eol.
+               nextCR = strchr(line, '\r');
+               nextNL = strchr(line, '\n');
+
+               // Added complexity for CR/LF agnostic line endings.   Necessary?
+               if (nextNL != NULL) {
+                       if (nextCR != NULL && nextCR < nextNL)
+                               eol = nextCR;
+                       else
+                               eol = nextNL;
+               } else {
+                       if (nextCR != NULL)
+                               eol = nextCR;
+                       else
+                               eol = buf + flen;
+               }
+
+               // If this isn't a blank line or a comment line, parse it.
+               if (eol - line != 1 && line[0] != '#') {
+                       *eol = 0;
+                       // If we get a bad config line, we're going to return failure later, but continue parsing now.
+                       if (!config_parse_line(context, filename, line, lineno, verbs, num_verbs))
+                               success = false;
+        }
+        line = eol + 1;
+        lineno++;
+       }               
+       free(buf);
+       return success;
+}
+
diff --git a/ServiceRegistration/config-parse.h b/ServiceRegistration/config-parse.h
new file mode 100644 (file)
index 0000000..69161df
--- /dev/null
@@ -0,0 +1,33 @@
+/* -*- Mode: C; tab-width: 4; c-file-style: "bsd"; c-basic-offset: 4; fill-column: 108 -*-
+ *
+ * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2018 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef __CONFIG_PARSE_H
+#define __CONFIG_PARSE_H
+
+// Structure of a configuration file verb
+typedef struct {
+       char *name;
+       int min_hunks, max_hunks;
+       bool (*handler)(void *context, const char *config_file_name, char **hunks, int num_hunks, int lineno);
+} config_file_verb_t;
+
+// Provided by RelayParse.c
+bool config_parse(void *context, const char *config_file_name, config_file_verb_t *verbs, int num_verbs);
+
+#endif // __CONFIG_PARSE_H
index 93d5d96abb6c2ad023500702f547f0b14addb86d..6c3bc8208a89d38f828663e7f7335f37541364d4 100644 (file)
@@ -1,6 +1,6 @@
 /* dns-msg.h
  *
- * Copyright (c) 2018 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2018, 2019 Apple Computer, Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #ifndef __DNS_MSG_H
 #define __DNS_MSG_H
 
+#include "srp.h"
+
 #ifndef DNS_MAX_UDP_PAYLOAD
 #define DNS_MAX_UDP_PAYLOAD 1410
 #endif
 
-#define DNS_HEADER_SIZE           12
-#define DNS_DATA_SIZE             (DNS_MAX_UDP_PAYLOAD - DNS_HEADER_SIZE)
-#define DNS_MAX_POINTER           ((2 << 14) - 1)
-#define DNS_MAX_LABEL_SIZE        63
-#define DNS_MAX_NAME_SIZE            255
-#define DNS_MAX_NAME_SIZE_ESCAPED 1009
+#define DNS_HEADER_SIZE            12
+#define DNS_DATA_SIZE              (DNS_MAX_UDP_PAYLOAD - DNS_HEADER_SIZE)
+#define DNS_MAX_POINTER            ((2 << 14) - 1)
+#define DNS_MAX_LABEL_SIZE         63
+#define DNS_MAX_LABEL_SIZE_ESCAPED 252
+#define DNS_MAX_NAME_SIZE          255
+#define DNS_MAX_NAME_SIZE_ESCAPED  1009
+#define DNS_MAX_LABELS             128
+
+typedef struct message message_t;
 
 typedef struct dns_wire dns_wire_t;
 struct dns_wire {
@@ -43,14 +49,25 @@ struct dns_wire {
     uint8_t data[DNS_DATA_SIZE];
 };
 
+typedef struct dns_name_pointer dns_name_pointer_t;
+struct dns_name_pointer {
+    dns_name_pointer_t *NULLABLE next;
+    uint8_t *NONNULL message_start;
+    uint8_t *NONNULL name_start;
+    int num_labels;
+    int length;
+};
+
 typedef struct dns_towire_state dns_towire_state_t;
 struct dns_towire_state {
-       dns_wire_t *NULLABLE message;
+    dns_wire_t *NULLABLE message;
     uint8_t *NONNULL p;
     uint8_t *NONNULL lim;
     uint8_t *NULLABLE p_rdlength;
     uint8_t *NULLABLE p_opt;
-    int error;
+    uint16_t line, outer_line;
+    bool truncated : 1;
+    unsigned int error : 31;
 };
 
 typedef struct dns_transaction dns_transaction_t;
@@ -60,14 +77,6 @@ struct dns_transaction {
     dns_wire_t *NULLABLE response;
     int response_length;
     int sock;
-};    
-
-typedef struct dns_name_pointer dns_name_pointer_t;
-struct dns_name_pointer {
-    uint8_t *NONNULL message_start;
-    uint8_t *NONNULL name_start;
-    int num_labels;
-    int length;
 };
 
 typedef void (*dns_response_callback_t)(dns_transaction_t *NONNULL txn);
@@ -80,11 +89,10 @@ struct dns_label {
     char data[DNS_MAX_LABEL_SIZE];
 };
 
-typedef struct dns_txt_element dns_txt_element_t;
-struct dns_txt_element {
-    dns_txt_element_t *NULLABLE next;
+typedef struct dns_rdata_txt dns_rdata_txt_t;
+struct dns_rdata_txt {
     uint8_t len;
-    char data[0];
+    char *NONNULL data;
 };
 
 typedef struct dns_rdata_unparsed dns_rdata_unparsed_t;
@@ -99,25 +107,13 @@ struct dns_rdata_single_name {
     dns_label_t *NONNULL name;
 };
 
-typedef struct dns_rdata_a dns_rdata_a_t;
-struct dns_rdata_a {
-    struct in_addr *NONNULL addrs;
-    int num;
-};
-
-typedef struct dns_rdata_aaaa dns_rdata_aaaa_t;
-struct dns_rdata_aaaa {
-    struct in6_addr *NONNULL addrs;
-    int num;
-} aaaa;
-
 typedef struct dns_rdata_srv dns_rdata_srv_t;
 struct dns_rdata_srv {
     dns_label_t *NONNULL name;
     uint16_t priority;
     uint16_t weight;
     uint16_t port;
-} srv;
+};
 
 typedef struct dns_rdata_sig dns_rdata_sig_t;
 struct dns_rdata_sig {
@@ -132,7 +128,7 @@ struct dns_rdata_sig {
     int start;
     int len;
     uint8_t *NONNULL signature;
-} sig;
+};
 
 typedef struct dns_rdata_key dns_rdata_key_t;
 struct dns_rdata_key {
@@ -141,7 +137,7 @@ struct dns_rdata_key {
     uint8_t algorithm;
     int len;
     uint8_t *NONNULL key;
-} key;
+};
 
 typedef struct dns_rr dns_rr_t;
 struct dns_rr {
@@ -153,10 +149,10 @@ struct dns_rr {
         dns_rdata_unparsed_t unparsed;
         dns_rdata_ptr_t ptr;
         dns_rdata_cname_t cname;
-        dns_rdata_a_t a;
-        dns_rdata_aaaa_t aaaa;
+        struct in_addr a;
+        struct in6_addr aaaa;
         dns_rdata_srv_t srv;
-        dns_txt_element_t *NONNULL txt;
+        dns_rdata_txt_t txt;
         dns_rdata_sig_t sig;
         dns_rdata_key_t key;
     } data;
@@ -166,12 +162,13 @@ typedef struct dns_edns0 dns_edns0_t;
 struct dns_edns0 {
     dns_edns0_t *NULLABLE next;
     uint16_t length;
+    uint16_t type;
     uint8_t data[0];
 };
 
 typedef struct dns_message dns_message_t;
 struct dns_message {
-    dns_wire_t *NULLABLE wire;
+    int ref_count;
     int qdcount, ancount, nscount, arcount;
     dns_rr_t *NULLABLE questions;
     dns_rr_t *NULLABLE answers;
@@ -200,17 +197,17 @@ struct dns_message {
 #define dns_flags_cd 0x0010
 
 // Getters
-#define dns_qr_get(w) ((ntohs((w)->bitfield) & dns_qr_mask) >> dns_qr_shift)
+#define dns_qr_get(w)     ((ntohs((w)->bitfield) & dns_qr_mask) >> dns_qr_shift)
 #define dns_opcode_get(w) ((ntohs((w)->bitfield) & dns_opcode_mask) >> dns_opcode_shift)
-#define dns_rcode_get(w) ((ntohs((w)->bitfield) & dns_rcode_mask) >> dns_rcode_shift)
+#define dns_rcode_get(w)  ((ntohs((w)->bitfield) & dns_rcode_mask) >> dns_rcode_shift)
 
 // Setters
-#define dns_qr_set(w, value) ((w)->bitfield = htons(((ntohs((w)->bitfield) & ~dns_qr_mask) | \
-                                                     ((value) << dns_qr_shift))))
-#define dns_opcode_set(w, value) ((w)->bitfield = htons(((ntohs((w)->bitfield) & ~dns_opcode_mask) | \
-                                                         ((value) << dns_opcode_shift))))
-#define dns_rcode_set(w, value) ((w)->bitfield = htons(((ntohs((w)->bitfield) & ~dns_rcode_mask) | \
-                                                        ((value) << dns_rcode_shift))))
+#define dns_qr_set(w, value) \
+    ((w)->bitfield = htons(((ntohs((w)->bitfield) & ~dns_qr_mask) | ((value) << dns_qr_shift))))
+#define dns_opcode_set(w, value) \
+    ((w)->bitfield = htons(((ntohs((w)->bitfield) & ~dns_opcode_mask) | ((value) << dns_opcode_shift))))
+#define dns_rcode_set(w, value) \
+    ((w)->bitfield = htons(((ntohs((w)->bitfield) & ~dns_rcode_mask) | ((value) << dns_rcode_shift))))
 
 // Query/Response
 #define dns_qr_query           0
@@ -261,12 +258,12 @@ struct dns_message {
 #define dns_rrtype_mb          7 // [RFC1035] a mailbox domain name (EXPERIMENTAL)
 #define dns_rrtype_mg          8 // [RFC1035] a mail group member (EXPERIMENTAL)
 #define dns_rrtype_mr          9 // [RFC1035] a mail rename domain name (EXPERIMENTAL)
-#define dns_rrtype_null       10 // [RFC1035]   a null RR (EXPERIMENTAL)
-#define dns_rrtype_wks        11 // [RFC1035]   a well known service description
-#define dns_rrtype_ptr        12 // [RFC1035]   a domain name pointer
-#define dns_rrtype_hinfo      13 // [RFC1035]   host information
-#define dns_rrtype_minfo      14 // [RFC1035]   mailbox or mail list information
-#define dns_rrtype_mx         15 // [RFC1035]   mail exchange
+#define dns_rrtype_null       10 // [RFC1035]    a null RR (EXPERIMENTAL)
+#define dns_rrtype_wks        11 // [RFC1035]    a well known service description
+#define dns_rrtype_ptr        12 // [RFC1035]    a domain name pointer
+#define dns_rrtype_hinfo      13 // [RFC1035]    host information
+#define dns_rrtype_minfo      14 // [RFC1035]    mailbox or mail list information
+#define dns_rrtype_mx         15 // [RFC1035]    mail exchange
 #define dns_rrtype_txt        16 // [RFC1035] text strings
 #define dns_rrtype_rp         17 // [RFC1183] for Responsible Person
 #define dns_rrtype_afsdb      18 // [RFC1183,RFC5864] for AFS Data Base location
@@ -289,7 +286,7 @@ struct dns_message {
 #define dns_rrtype_naptr      35 // [RFC2915] [RFC2168] [RFC3403] Naming Authority Pointer
 #define dns_rrtype_kx         36 // [RFC2230] Key Exchanger
 #define dns_rrtype_cert       37 // [RFC4398] CERT
-#define dns_rrtype_a6         38 // [RFC3226] [RFC2874] [RFC6563]       A6 (OBSOLETE - use AAAA)
+#define dns_rrtype_a6         38 // [RFC3226] [RFC2874] [RFC6563]    A6 (OBSOLETE - use AAAA)
 #define dns_rrtype_dname      39 // [RFC6672]
 #define dns_rrtype_sink       40 // [http://tools.ietf.org/html/draft-eastlake-kitchen-sink]
 #define dns_rrtype_opt        41 // [RFC6891] [RFC3225]
@@ -310,34 +307,34 @@ struct dns_message {
 #define dns_rrtype_rkey       57 // [Jim_Reid] RKEY/rkey-completed-template
 #define dns_rrtype_talink     58 // [Wouter_Wijngaards] Trust Anchor LINK
 #define dns_rrtype_cds        59 // [RFC7344] Child DS
-#define dns_rrtype_cdnskey    60 // [RFC7344]  DNSKEY(s) the Child wants reflected in DS
-#define dns_rrtype_openpgpkey 61 // [RFC7929]  OpenPGP Key
+#define dns_rrtype_cdnskey    60 // [RFC7344]   DNSKEY(s) the Child wants reflected in DS
+#define dns_rrtype_openpgpkey 61 // [RFC7929]   OpenPGP Key
 #define dns_rrtype_csync      62 // [RFC7477] Child-To-Parent Synchronization
 #define dns_rrtype_spf        99 // [RFC7208]
-#define dns_rrtype_uinfo     100 // [IANA-Reserved]            
-#define dns_rrtype_uid       101 // [IANA-Reserved]            
-#define dns_rrtype_gid       102 // [IANA-Reserved]            
-#define dns_rrtype_unspec    103 // [IANA-Reserved]            
+#define dns_rrtype_uinfo     100 // [IANA-Reserved]
+#define dns_rrtype_uid       101 // [IANA-Reserved]
+#define dns_rrtype_gid       102 // [IANA-Reserved]
+#define dns_rrtype_unspec    103 // [IANA-Reserved]
 #define dns_rrtype_nid       104 // [RFC6742]
 #define dns_rrtype_l32       105 // [RFC6742]
 #define dns_rrtype_l64       106 // [RFC6742]
 #define dns_rrtype_lp        107 // [RFC6742]
 #define dns_rrtype_eui48     108 // an EUI-48 address [RFC7043]
 #define dns_rrtype_eui64     109 // an EUI-64 address [RFC7043]
-#define dns_rrtype_tkey      249 // Transaction Key    [RFC2930]               
-#define dns_rrtype_tsig      250 // Transaction Signature [RFC2845]            
-#define dns_rrtype_ixfr      251 // incremental transfer       [RFC1995]               
-#define dns_rrtype_axfr      252 // transfer of an entire zone [RFC1035][RFC5936]              
-#define dns_rrtype_mailb     253 // mailbox-related RRs (MB, MG or MR) [RFC1035]               
-#define dns_rrtype_maila     254 // mail agent RRs (OBSOLETE - see MX) [RFC1035]               
+#define dns_rrtype_tkey      249 // Transaction Key [RFC2930]
+#define dns_rrtype_tsig      250 // Transaction Signature [RFC2845]
+#define dns_rrtype_ixfr      251 // incremental transfer    [RFC1995]
+#define dns_rrtype_axfr      252 // transfer of an entire zone [RFC1035][RFC5936]
+#define dns_rrtype_mailb     253 // mailbox-related RRs (MB, MG or MR) [RFC1035]
+#define dns_rrtype_maila     254 // mail agent RRs (OBSOLETE - see MX) [RFC1035]
 #define dns_rrtype_any       255 // A request for some or all records the server has available
-#define dns_rrtype_uri       256 // URI        [RFC7553]       URI/uri-completed-template
+#define dns_rrtype_uri       256 // URI [RFC7553]   URI/uri-completed-template
 #define dns_rrtype_caa       257 // Certification Authority Restriction [RFC6844]
 #define dns_rrtype_avc       258 // Application Visibility and Control [Wolfgang_Riedel]
 #define dns_rrtype_doa       259 // Digital Object Architecture [draft-durand-doa-over-dns]
 
 #define dns_opt_llq            1 // On-hold [http://files.dns-sd.org/draft-sekar-dns-llq.txt]
-#define dns_opt_update_lease   2 // On-hold    [http://files.dns-sd.org/draft-sekar-dns-ul.txt]
+#define dns_opt_update_lease   2 // On-hold [http://files.dns-sd.org/draft-sekar-dns-ul.txt]
 #define dns_opt_nsid           3 // [RFC5001]
 #define dns_opt_owner          4 // [draft-cheshire-edns0-owner-option]
 #define dns_opt_dau            5 // [RFC6975]
@@ -354,48 +351,78 @@ struct dns_message {
 // towire.c:
 
 uint16_t srp_random16(void);
-void dns_name_to_wire(dns_name_pointer_t *NULLABLE r_pointer,
-                      dns_towire_state_t *NONNULL txn,
-                      const char *NONNULL name);
-void dns_full_name_to_wire(dns_name_pointer_t *NULLABLE r_pointer,
-                           dns_towire_state_t *NONNULL txn,
-                           const char *NONNULL name);
-void dns_pointer_to_wire(dns_name_pointer_t *NULLABLE r_pointer,
-                         dns_towire_state_t *NONNULL txn,
-                         dns_name_pointer_t *NONNULL pointer);
-void dns_u8_to_wire(dns_towire_state_t *NONNULL txn,
-                    uint8_t val);
-void dns_u16_to_wire(dns_towire_state_t *NONNULL txn,
-                      uint16_t val);
-void dns_u32_to_wire(dns_towire_state_t *NONNULL txn,
-                      uint32_t val);
-void dns_ttl_to_wire(dns_towire_state_t *NONNULL txn,
-                     int32_t val);
-void dns_rdlength_begin(dns_towire_state_t *NONNULL txn);
-void dns_rdlength_end(dns_towire_state_t *NONNULL txn);
-void dns_rdata_a_to_wire(dns_towire_state_t *NONNULL txn,
-                         const char *NONNULL ip_address);
-void dns_rdata_aaaa_to_wire(dns_towire_state_t *NONNULL txn,
-                            const char *NONNULL ip_address);
-uint16_t dns_rdata_key_to_wire(dns_towire_state_t *NONNULL txn,
-                               unsigned key_type,
-                               unsigned name_type,
-                               unsigned signatory,
-                               srp_key_t *NONNULL key);
-void dns_rdata_txt_to_wire(dns_towire_state_t *NONNULL txn,
-                           const char *NONNULL txt_record);
-void dns_rdata_raw_data_to_wire(dns_towire_state_t *NONNULL txn, const void *NONNULL raw_data, size_t length);
-void dns_edns0_header_to_wire(dns_towire_state_t *NONNULL txn,
-                              int mtu,
-                              int xrcode,
-                              int version,
-                              int DO);
-void dns_edns0_option_begin(dns_towire_state_t *NONNULL txn);
-void dns_edns0_option_end(dns_towire_state_t *NONNULL txn);
-void dns_sig0_signature_to_wire(dns_towire_state_t *NONNULL txn,
-                                srp_key_t *NONNULL key, uint16_t key_tag,
-                                dns_name_pointer_t *NONNULL signer,
-                                const char *NONNULL signer_fqdn);
+void dns_name_to_wire_(dns_name_pointer_t *NULLABLE r_pointer,
+                       dns_towire_state_t *NONNULL txn,
+                       const char *NONNULL name, int line);
+#define dns_name_to_wire(r_pointer, txn, name) dns_name_to_wire_(r_pointer, txn, name, __LINE__)
+
+void dns_full_name_to_wire_(dns_name_pointer_t *NULLABLE r_pointer,
+                            dns_towire_state_t *NONNULL txn,
+                            const char *NONNULL name, int line);
+#define dns_full_name_to_wire(r_pointer, txn, name) dns_full_name_to_wire_(r_pointer, txn, name, __LINE__)
+
+void dns_pointer_to_wire_(dns_name_pointer_t *NULLABLE r_pointer,
+                          dns_towire_state_t *NONNULL txn,
+                          dns_name_pointer_t *NONNULL pointer, int line);
+#define dns_pointer_to_wire(r_pointer, txn, pointer) dns_pointer_to_wire_(r_pointer, txn, pointer, __LINE__)
+
+void dns_u8_to_wire_(dns_towire_state_t *NONNULL txn, uint8_t val, int line);
+#define dns_u8_to_wire(txn, val) dns_u8_to_wire_(txn, val, __LINE__)
+
+void dns_u16_to_wire_(dns_towire_state_t *NONNULL txn, uint16_t val, int line);
+#define dns_u16_to_wire(txn, val) dns_u16_to_wire_(txn, val, __LINE__)
+
+void dns_u32_to_wire_(dns_towire_state_t *NONNULL txn, uint32_t val, int line);
+#define dns_u32_to_wire(txn, val) dns_u32_to_wire_(txn, val, __LINE__)
+
+void dns_ttl_to_wire_(dns_towire_state_t *NONNULL txn, int32_t val, int line);
+#define dns_ttl_to_wire(txn, val) dns_ttl_to_wire_(txn, val, __LINE__)
+
+void dns_rdlength_begin_(dns_towire_state_t *NONNULL txn, int line);
+#define dns_rdlength_begin(txn) dns_rdlength_begin_(txn, __LINE__)
+
+void dns_rdlength_end_(dns_towire_state_t *NONNULL txn, int line);
+#define dns_rdlength_end(txn) dns_rdlength_end_(txn, __LINE__)
+
+void dns_rdata_a_to_wire_(dns_towire_state_t *NONNULL txn, const char *NONNULL ip_address, int line);
+#define dns_rdata_a_to_wire(txn, ip_address) dns_rdata_a_to_wire_(txn, ip_address, __LINE__)
+
+void dns_rdata_aaaa_to_wire_(dns_towire_state_t *NONNULL txn, const char *NONNULL ip_address, int line);
+#define dns_rdata_aaaa_to_wire(txn, ip_address) dns_rdata_aaaa_to_wire_(txn, ip_address, __LINE__)
+
+uint16_t dns_rdata_key_to_wire_(dns_towire_state_t *NONNULL txn,
+                                unsigned key_type,
+                                unsigned name_type,
+                                unsigned signatory,
+                                srp_key_t *NONNULL key, int line);
+#define dns_rdata_key_to_wire(txn, key_type, name_type, signatory, key) \
+    dns_rdata_key_to_wire_(txn, key_type, name_type, signatory, key, __LINE__)
+
+void dns_rdata_txt_to_wire_(dns_towire_state_t *NONNULL txn, const char *NONNULL txt_record, int line);
+#define dns_rdata_txt_to_wire(txn, txt_record) dns_rdata_txt_to_wire_(txn, txt_record, __LINE__)
+
+void dns_rdata_raw_data_to_wire_(dns_towire_state_t *NONNULL txn,
+                                 const void *NONNULL raw_data, size_t length, int line);
+#define dns_rdata_raw_data_to_wire(txn, raw_data, length) dns_rdata_raw_data_to_wire_(txn, raw_data, length, __LINE__)
+
+void dns_edns0_header_to_wire_(dns_towire_state_t *NONNULL txn,
+                               int mtu, int xrcode, int version, int DO, int line);
+#define dns_edns0_header_to_wire(txn, mtu, xrcode, version, DO) \
+    dns_edns0_header_to_wire_(txn, mtu, xrcode, version, DO, __LINE__)
+
+void dns_edns0_option_begin_(dns_towire_state_t *NONNULL txn, int line);
+#define dns_edns0_option_begin(txn) dns_edns0_option_begin_(txn, __LINE__)
+
+void dns_edns0_option_end_(dns_towire_state_t *NONNULL txn, int line);
+#define dns_edns0_option_end(txn) dns_edns0_option_end_(txn, __LINE__)
+
+void dns_sig0_signature_to_wire_(dns_towire_state_t *NONNULL txn,
+                                 srp_key_t *NONNULL key, uint16_t key_tag,
+                                 dns_name_pointer_t *NONNULL signer, const char *NONNULL signer_hostname,
+                                 const char *NONNULL signer_domain, int line);
+#define dns_sig0_signature_to_wire(txn, key, key_tag, signer, signer_hostname, signer_domain) \
+    dns_sig0_signature_to_wire_(txn, key, key_tag, signer, signer_hostname, signer_domain, __LINE__)
+
 int dns_send_to_server(dns_transaction_t *NONNULL txn,
                        const char *NONNULL anycast_address, uint16_t port,
                        dns_response_callback_t NONNULL callback);
@@ -419,10 +446,30 @@ bool dns_rdata_parse_data(dns_rr_t *NONNULL rr, const uint8_t *NONNULL buf, unsi
                           unsigned target, unsigned rdlen, unsigned rrstart);
 bool dns_wire_parse(dns_message_t *NONNULL *NULLABLE ret, dns_wire_t *NONNULL message, unsigned len);
 bool dns_names_equal(dns_label_t *NONNULL name1, dns_label_t *NONNULL name2);
+
+// wireutils.c
+dns_name_t *NULLABLE dns_name_copy(dns_name_t *NONNULL original);
+void dns_u48_to_wire_(dns_towire_state_t *NONNULL txn, uint64_t val, int line);
+#define dns_u48_to_wire(txn, val) dns_u48_to_wire_(txn, val, __LINE__)
+
+void dns_concatenate_name_to_wire_(dns_towire_state_t *NONNULL towire,
+                                   dns_name_t *NULLABLE labels_prefix,
+                                   const char *NULLABLE prefix, const char *NULLABLE suffix, int line);
+#define dns_concatenate_name_to_wire(txn, labels_prefix, prefix, suffix) \
+    dns_concatenate_name_to_wire_(txn, labels_prefix, prefix, suffix, __LINE__)
+
+const char *NONNULL dns_name_print_to_limit(dns_name_t *NONNULL name, dns_name_t *NULLABLE limit, char *NULLABLE buf,
+                                            int bufmax);
 const char *NONNULL dns_name_print(dns_name_t *NONNULL name, char *NONNULL buf, int bufmax);
+bool dns_labels_equal(const char *NONNULL label1, const char *NONNULL label2, size_t len);
 bool dns_names_equal_text(dns_label_t *NONNULL name1, const char *NONNULL name2);
 size_t dns_name_wire_length(dns_label_t *NONNULL name);
 size_t dns_name_to_wire_canonical(uint8_t *NONNULL buf, size_t max, dns_label_t *NONNULL name);
+dns_name_t *NULLABLE dns_pres_name_parse(const char *NONNULL pname);
+dns_name_t *NULLABLE dns_name_subdomain_of(dns_name_t *NONNULL name, dns_name_t *NONNULL domain);
+const char *NONNULL dns_rcode_name(int rcode);
+bool dns_keys_rdata_equal(dns_rr_t *NONNULL key1, dns_rr_t *NONNULL key2);
+
 #endif // _DNS_MSG_H
 
 // Local Variables:
index 589d1dde9857eee728a8c45a1ad3b2c100d09902..480487800ef9ddb91cf22c21847a4562016268a2 100644 (file)
@@ -21,7 +21,7 @@
  * name resolution, we need a DNS proxy that implements DNSSD Discovery Proxy for local queries, but
  * forwards other queries to an ISP resolver.  The SRP gateway is already expecting to do this.
  * This module implements the functions required to allow the SRP gateway to also do Discovery Relay.
- * 
+ *
  * The Discovery Proxy relies on Apple's DNS-SD library and the mDNSResponder DNSSD server, which is included
  * in Apple's open source mDNSResponder package, available here:
  *
 #include <string.h>
 #include <stdio.h>
 #include <unistd.h>
-#include <sys/errno.h>
+#include <errno.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
-#include <sys/event.h>
 #include <fcntl.h>
 #include <sys/time.h>
 #include <ctype.h>
+#include <sys/types.h>
+#include <ifaddrs.h>
+#include <net/if.h>
+#include <stdarg.h>
 
 #include "dns_sd.h"
 #include "srp.h"
 #include "dns-msg.h"
 #include "srp-crypto.h"
+#define DNSMessageHeader dns_wire_t
 #include "dso.h"
 #include "ioloop.h"
+#include "srp-tls.h"
+#include "config-parse.h"
 
 // Enumerate the list of interfaces, map them to interface indexes, give each one a name
 // Have a tree of subdomains for matching
 
+// Configuration file settings
+uint16_t udp_port;
+uint16_t tcp_port;
+uint16_t tls_port;
+char *my_name = "discoveryproxy.home.arpa.";
+#define MAX_ADDRS 10
+char *listen_addrs[MAX_ADDRS];
+int num_listen_addrs = 0;
+char *publish_addrs[MAX_ADDRS];
+int num_publish_addrs = 0;
+char *tls_cacert_filename = NULL;
+char *tls_cert_filename = "/etc/dnssd-proxy/server.crt";
+char *tls_key_filename = "/etc/dnssd-proxy/server.key";
+comm_t *listener[4 + MAX_ADDRS];
+int num_listeners = 0;
+
+typedef struct hardwired hardwired_t;
+struct hardwired {
+    hardwired_t *next;
+    uint16_t type;
+    char *name;
+    char *fullname;
+    uint8_t *rdata;
+    uint16_t rdlen;
+};
+
+typedef struct interface_addr interface_addr_t;
+struct interface_addr {
+    interface_addr_t *next;
+    addr_t addr, mask;
+};
+
+typedef struct interface interface_t;
+struct interface {
+    int ifindex;                        // The interface index (for use with sendmsg() and recvmsg().
+    bool no_push;                       // If true, don't set up DNS Push for this domain
+    char *name;                         // The name of the interface
+    interface_addr_t *addresses;        // Addresses on this interface.
+};
+
+typedef struct served_domain served_domain_t;
+struct served_domain {
+    served_domain_t *next;              // Active configurations, used for identifying a domain that matches
+    char *domain;                       // The domain name of the interface, represented as a text string.
+    char *domain_ld;                    // The same name, with a leading dot (if_domain_lp == if_domain + 1)
+    dns_name_t *domain_name;            // The domain name, parsed into labels.
+    hardwired_t *hardwired_responses;   // Hardwired responses for this interface
+    struct interface *interface;        // Interface to which this domain applies (may be NULL).
+} *served_domains;
+
 typedef struct dnssd_query {
-    io_t io;
-    DNSServiceRef ref;
-    char *name;                                                // The name we are looking up.
-    const char *enclosing_domain;      // The domain the name is in, or NULL if not ours; if null, name is an FQDN.
+    dnssd_txn_t *txn;
+    wakeup_t *wakeup;
+    char *name;                     // The name we are looking up.
+    served_domain_t *served_domain; // If this query matches an enclosing domain, the domain that matched.
+
+                                    // If we've already copied out the enclosing domain once in a DNS message.
     dns_name_pointer_t enclosing_domain_pointer;
+
     message_t *question;
     comm_t *connection;
     dso_activity_t *activity;
-    int serviceFlags;                          // Service flags to use with this query.
+    int serviceFlags;               // Service flags to use with this query.
     bool is_dns_push;
     bool is_edns0;
-    uint16_t type, qclass;                     // Original query type and class.
+    uint16_t type, qclass;          // Original query type and class.
     dns_towire_state_t towire;
-    uint8_t *p_dso_length;                     // Where to store the DSO length just before we write out a push notification.
-    dns_wire_t response;                       // This has to be at the end because we don't zero the RRdata buffer.
+    uint8_t *p_dso_length;          // Where to store the DSO length just before we write out a push notification.
+    dns_wire_t *response;
+    size_t data_size;                  // Size of the data payload of the response
 } dnssd_query_t;
 
 const char push_subscription_activity_type[] = "push subscription";
 
 const char local_suffix[] = ".local.";
-#define PROXIED_DOMAIN "proxy.home.arpa."
-const char proxied_domain[] = PROXIED_DOMAIN;
-const char proxied_domain_ld[] = "." PROXIED_DOMAIN;
-#define MY_NAME "proxy.example.com."
-#define MY_IPV4_ADDR "192.0.2.1"
-// #define MY_IPV6_ADDR "2001:db8::1" // for example
 
 #define TOWIRE_CHECK(note, towire, func) { func; if ((towire)->error != 0 && failnote == NULL) failnote = (note); }
 
-int64_t dso_transport_idle(void *context, int64_t next_event)
+// Forward references
+static served_domain_t *NULLABLE new_served_domain(interface_t *NULLABLE interface, char *NONNULL domain);
+
+// Code
+
+int64_t dso_transport_idle(void *context, int64_t now, int64_t next_event)
 {
     return next_event;
 }
 
-void dnssd_query_cancel(io_t *io)
+void dnssd_query_cancel(dnssd_query_t *query)
 {
-    dnssd_query_t *query = (dnssd_query_t *)io;
-    if (query->io.sock != -1) {
-        DNSServiceRefDeallocate(query->ref);
-        query->io.sock = -1;
+    if (query->txn != NULL) {
+        ioloop_dnssd_txn_cancel(query->txn);
+        ioloop_dnssd_txn_release(query->txn);
+        query->txn = NULL;
     }
     query->connection = NULL;
 }
 
+void dnssd_query_close_callback(void *context, int status)
+{
+    dnssd_query_t *query = context;
+
+    ERROR("DNSServiceProcessResult on %s%s returned %d",
+          query->name, (query->served_domain != NULL
+                      ? (query->served_domain->interface != NULL ? ".local" : query->served_domain->domain_ld)
+                      : ""), status);
+    if (query->activity != NULL && query->connection != NULL) {
+        dso_drop_activity(query->connection->dso, query->activity);
+    } else {
+        dnssd_query_cancel(query);
+    }
+}
+
 void
 dns_push_finalize(dso_activity_t *activity)
 {
     dnssd_query_t *query = (dnssd_query_t *)activity->context;
-    INFO("dnssd_push_finalize: %s", activity->name);
-    dnssd_query_cancel(&query->io);
+    INFO("dnssd_push_finalize: " PUB_S_SRP, activity->name);
+    dnssd_query_cancel(query);
 }
 
 void
-dnssd_query_finalize(io_t *io)
+dnssd_query_finalize_callback(void *context)
 {
-    dnssd_query_t *query = (dnssd_query_t *)io;
-    INFO("dnssd_query_finalize on %s%s", query->name, query->enclosing_domain ? ".local" : "");
+    dnssd_query_t *query = context;
+    INFO("dnssd_query_finalize on " PRI_S_SRP PUB_S_SRP,
+         query->name, (query->served_domain
+                       ? (query->served_domain->interface ? ".local" : query->served_domain->domain_ld)
+                       : ""));
+    if (query->txn) {
+        ioloop_dnssd_txn_cancel(query->txn);
+        ioloop_dnssd_txn_release(query->txn);
+        query->txn = NULL;
+    }
     if (query->question) {
         message_free(query->question);
     }
@@ -118,100 +200,6 @@ dnssd_query_finalize(io_t *io)
     free(query);
 }
 
-static void
-dnssd_query_callback(io_t *io)
-{
-    dnssd_query_t *query = (dnssd_query_t *)io;
-    int status = DNSServiceProcessResult(query->ref);
-    if (status != kDNSServiceErr_NoError) {
-        ERROR("DNSServiceProcessResult on %s%s returned %d", query->name, query->enclosing_domain ? ".local" : "", status);
-        if (query->activity != NULL && query->connection != NULL) {
-            dso_drop_activity(query->connection->dso, query->activity);
-        } else {
-            dnssd_query_cancel(&query->io);
-        }
-    }
-}
-
-static void
-add_dnssd_query(dnssd_query_t *query)
-{
-    io_t *io = &query->io;
-    io->sock = DNSServiceRefSockFD(query->ref);
-    io->cancel = dnssd_query_cancel;
-    io->cancel_on_close = &query->connection->io;
-    add_reader(io, dnssd_query_callback, dnssd_query_finalize);
-}
-
-// Parse a NUL-terminated text string into a sequence of labels.
-dns_name_t *
-dns_pres_name_parse(const char *pname)
-{
-    const char *dot = strchr(pname, '.');
-    dns_label_t *ret;
-    int len;
-    if (dot == NULL) {
-        dot = pname + strlen(pname);
-    }
-    len = (dot - pname) + 1 + (sizeof *ret) - DNS_MAX_LABEL_SIZE;
-    ret = calloc(len, 1);
-    if (ret == NULL) {
-        return NULL;
-    }
-    ret->len = dot - pname;
-    if (ret->len > 0) {
-        memcpy(ret->data, pname, ret->len);
-    }
-    ret->data[ret->len] = 0;
-    if (dot[0] == '.') {
-        ret->next = dns_pres_name_parse(dot + 1);
-    }
-    return ret;
-}
-
-bool
-dns_subdomain_of(dns_name_t *name, dns_name_t *domain, char *buf, size_t buflen)
-{
-    int dnum = 0, nnum = 0;
-    dns_name_t *np, *dp;
-    char *bufp = buf;
-    size_t bytesleft = buflen;
-
-    for (dp = domain; dp; dp = dp->next) {
-        dnum++;
-    }
-    for (np = name; np; np = np->next) {
-        nnum++;
-    }
-    if (nnum < dnum) {
-        return false;
-    }
-    for (np = name; np; np = np->next) {
-        if (nnum-- == dnum) {
-            break;
-        }
-    }
-    if (dns_names_equal(np, domain)) {
-        for (dp = name; dp != np; dp = dp->next) {
-            if (dp->len + 1 > bytesleft) {
-                // It's okay to return false here because a name that overflows the buffer isn't valid.
-                ERROR("dns_subdomain_of: out of buffer space!");
-                return false;
-            }
-            memcpy(bufp, dp->data, dp->len);
-            bufp += dp->len;
-            bytesleft = bytesleft - dp->len;
-            if (dp->next != np) {
-                *bufp++ = '.';
-                bytesleft -= dp->len;
-            }
-        }
-        *bufp = 0;
-        return true;
-    }
-    return false;
-}
-
 void
 dp_simple_response(comm_t *comm, int rcode)
 {
@@ -234,19 +222,27 @@ dp_simple_response(comm_t *comm, int rcode)
 }
 
 bool
+dso_send_formerr(dso_state_t *dso, const dns_wire_t *header)
+{
+    comm_t *transport = dso->transport;
+    (void)header;
+    dp_simple_response(transport, dns_rcode_formerr);
+    return true;
+}
+
+served_domain_t *
 dp_served(dns_name_t *name, char *buf, size_t bufsize)
 {
-    static dns_name_t *home_dot_arpa = NULL;
-    if (home_dot_arpa == NULL) {
-        home_dot_arpa = dns_pres_name_parse(proxied_domain);
-        if (home_dot_arpa == NULL) {
-            ERROR("Unable to parse %s!", proxied_domain);
-            return false;
+    served_domain_t *sdt;
+    dns_label_t *lim;
+
+    for (sdt = served_domains; sdt; sdt = sdt->next) {
+        if ((lim = dns_name_subdomain_of(name, sdt->domain_name))) {
+            dns_name_print_to_limit(name, lim, buf, bufsize);
+            return sdt;
         }
     }
-
-     // For now we treat any query to home.arpa as local.
-    return dns_subdomain_of(name, home_dot_arpa, buf, bufsize);
+    return NULL;
 }
 
 // Utility function to find "local" on the end of a string of labels.
@@ -254,7 +250,7 @@ bool
 truncate_local(dns_name_t *name)
 {
     dns_label_t *lp, *prev, *prevprev;
-    
+
     prevprev = prev = NULL;
     // Find the root label.
     for (lp = name; lp && lp->len; lp = lp->next) {
@@ -262,40 +258,74 @@ truncate_local(dns_name_t *name)
         prev = lp;
     }
     if (lp && prev && prevprev) {
-        if (prev->len == 5 && !strncasecmp(prev->data, "local", 5)) {
+        if (prev->len == 5 && dns_labels_equal(prev->data, "local", 5)) {
             dns_name_free(prev);
             prevprev->next = NULL;
             return true;
         }
     }
-    dns_name_free(name);
     return false;
-}    
+}
 
 void
 dp_query_add_data_to_response(dnssd_query_t *query, const char *fullname,
-                              uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata, uint32_t ttl)
+                              uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata, int32_t ttl)
 {
     dns_towire_state_t *towire = &query->towire;
     const char *failnote = NULL;
+    const uint8_t *rd = rdata;
+    char pbuf[DNS_MAX_NAME_SIZE + 1];
+    char rbuf[DNS_MAX_NAME_SIZE + 1];
+    uint8_t *revert = query->towire.p; // Remember where we were in case there's no room.
+    bool local;
+
+    if (rdlen == 0) {
+        INFO("Eliding zero-length response for " PRI_S_SRP " %d %d", fullname, rrtype, rrclass);
+        return;
+    }
+    // Don't send A records for 127.* nor AAAA records for ::1
+    if (rrtype == dns_rrtype_a && rdlen == 4) {
+        // Should use IN_LINKLOCAL and IN_LOOPBACK macros here, but for some reason they are not present on
+        // OpenWRT.
+        if (rd[0] == 127) {
+            IPv4_ADDR_GEN_SRP(rd, rd_buf);
+            INFO("Eliding localhost response for " PRI_S_SRP ": " PRI_IPv4_ADDR_SRP, fullname,
+                  IPv4_ADDR_PARAM_SRP(rd, rd_buf));
+            return;
+        }
+        if (rd[0] == 169 && rd[1] == 254) {
+            IPv4_ADDR_GEN_SRP(rd, rd_buf);
+            INFO("Eliding link-local response for " PRI_S_SRP ": " PRI_IPv4_ADDR_SRP, fullname,
+                 IPv4_ADDR_PARAM_SRP(rd, rd_buf));
+            return;
+        }
+    } else if (rrtype == dns_rrtype_aaaa && rdlen == 16) {
+        struct in6_addr addr = *(struct in6_addr *)rdata;
+        if (IN6_IS_ADDR_LOOPBACK(&addr)) {
+            SEGMENTED_IPv6_ADDR_GEN_SRP(rdata, rdata_buf);
+            INFO("Eliding localhost response for " PRI_S_SRP ": " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                 fullname, SEGMENTED_IPv6_ADDR_PARAM_SRP(rdata, rdata_buf));
+            return;
+        }
+        if (IN6_IS_ADDR_LINKLOCAL(&addr)) {
+            SEGMENTED_IPv6_ADDR_GEN_SRP(rdata, rdata_buf);
+            INFO("Eliding link-local response for " PRI_S_SRP ": " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                 fullname, SEGMENTED_IPv6_ADDR_PARAM_SRP(rdata, rdata_buf));
+            return;
+        }
+    }
+    INFO("dp_query_add_data_to_response: survived for rrtype %d rdlen %d", rrtype, rdlen);
 
     // Rewrite the domain if it's .local.
-    if (query->enclosing_domain != NULL) {
-        TOWIRE_CHECK("query name", towire, dns_name_to_wire(NULL, towire, query->name));
-        if (query->enclosing_domain_pointer.message_start != NULL) {
-            // This happens if we are sending a DNS response, because we can always point back to the question.
-            TOWIRE_CHECK("enclosing_domain_pointer", towire,
-                         dns_pointer_to_wire(NULL, towire, &query->enclosing_domain_pointer));
-            INFO(" dns answer:  type %02d class %02d %s.%s (p)", rrtype, rrclass, query->name, query->enclosing_domain);
-        } else {
-            // This happens if we are sending a DNS Push notification.
-            TOWIRE_CHECK("enclosing_domain", towire, dns_full_name_to_wire(NULL, towire, query->enclosing_domain));
-            INFO("push answer:  type %02d class %02d %s.%s", rrtype, rrclass, query->name, query->enclosing_domain);
-        }
+    if (query->served_domain != NULL) {
+        TOWIRE_CHECK("concatenate_name_to_wire", towire,
+                     dns_concatenate_name_to_wire(towire, NULL, query->name, query->served_domain->domain));
+        INFO(PUB_S_SRP " answer:  type %02d class %02d " PRI_S_SRP "." PRI_S_SRP, query->is_dns_push ? "PUSH" : "DNS ",
+             rrtype, rrclass, query->name, query->served_domain->domain);
     } else {
-        TOWIRE_CHECK("query->name", towire, dns_full_name_to_wire(NULL, towire, query->name));
-        INFO("%s answer:  type %02d class %02d %s.%s (p)",
-             query->is_dns_push ? "push" : " dns", rrtype, rrclass, query->name, query->enclosing_domain);
+        TOWIRE_CHECK("compress_name_to_wire", towire, dns_concatenate_name_to_wire(towire, NULL, NULL, query->name));
+        INFO(PUB_S_SRP " answer:  type %02d class %02d " PRI_S_SRP " (p)",
+             query->is_dns_push ? "push" : " dns", rrtype, rrclass, query->name);
     }
     TOWIRE_CHECK("rrtype", towire, dns_u16_to_wire(towire, rrtype));
     TOWIRE_CHECK("rrclass", towire, dns_u16_to_wire(towire, rrclass));
@@ -303,54 +333,61 @@ dp_query_add_data_to_response(dnssd_query_t *query, const char *fullname,
 
     if (rdlen > 0) {
         // If necessary, correct domain names inside of rrdata.
-        if (rrclass == dns_qclass_in && (rrtype == dns_rrtype_srv ||
-                                         rrtype == dns_rrtype_ptr ||
-                                         rrtype == dns_rrtype_cname)) {
-            dns_rr_t answer;
-            dns_name_t *name;
-            unsigned offp = 0;
-            answer.type = rrtype;
-            answer.qclass = rrclass;
-            if (!dns_rdata_parse_data(&answer, rdata, &offp, rdlen, rdlen, 0)) {
-                ERROR("dp_query_add_data_to_response: rdata from mDNSResponder didn't parse!!");
-                goto raw;
-            }
+        dns_rr_t answer;
+        dns_name_t *name;
+        unsigned offp = 0;
+
+        answer.type = rrtype;
+        answer.qclass = rrclass;
+        if (dns_rdata_parse_data(&answer, rdata, &offp, rdlen, rdlen, 0)) {
             switch(rrtype) {
             case dns_rrtype_cname:
             case dns_rrtype_ptr:
+            case dns_rrtype_ns:
+            case dns_rrtype_md:
+            case dns_rrtype_mf:
+            case dns_rrtype_mb:
+            case dns_rrtype_mg:
+            case dns_rrtype_mr:
+            case dns_rrtype_nsap_ptr:
+            case dns_rrtype_dname:
                 name = answer.data.ptr.name;
-                if (!truncate_local(name)) {
-                    goto raw;
-                }
                 TOWIRE_CHECK("rdlength begin", towire, dns_rdlength_begin(towire));
                 break;
             case dns_rrtype_srv:
                 name = answer.data.srv.name;
-                if (!truncate_local(name)) {
-                    goto raw;
-                }
                 TOWIRE_CHECK("rdlength begin", towire, dns_rdlength_begin(towire));
                 TOWIRE_CHECK("answer.data.srv.priority", towire, dns_u16_to_wire(towire, answer.data.srv.priority));
                 TOWIRE_CHECK("answer.data.srv.weight", towire, dns_u16_to_wire(towire, answer.data.srv.weight));
                 TOWIRE_CHECK("answer.data.srv.port", towire, dns_u16_to_wire(towire, answer.data.srv.port));
                 break;
             default:
-                ERROR("dp_query_add_data_to_response: can't get here.");
+                INFO("record type %d not translated", rrtype);
                 goto raw;
-                break;
             }
-            // If we get here, the name ended in "local."
-            int bytes_written = dns_name_to_wire_canonical(towire->p, towire->lim - towire->p, name);
-            towire->p += bytes_written;
-            if (query->enclosing_domain_pointer.message_start != NULL) {
-                TOWIRE_CHECK("enclosing_domain_pointer internal", towire,
-                             dns_pointer_to_wire(NULL, towire, &query->enclosing_domain_pointer));
+
+            dns_name_print(name, rbuf, sizeof rbuf);
+
+            // If the name ends in .local, truncate it.
+            if ((local = truncate_local(name))) {
+                dns_name_print(name, pbuf, sizeof pbuf);
+            }
+
+            // If the name ended in .local, concatenate the interface domain name to the end.
+            if (local) {
+                TOWIRE_CHECK("concatenate_name_to_wire 2", towire,
+                             dns_concatenate_name_to_wire(towire, name, NULL, query->served_domain->domain));
+                INFO("translating " PRI_S_SRP " to " PRI_S_SRP " . " PRI_S_SRP, rbuf, pbuf,
+                     query->served_domain->domain);
             } else {
-                TOWIRE_CHECK("enclosing_domain internal", towire,
-                             dns_full_name_to_wire(NULL, towire, query->enclosing_domain));
+                TOWIRE_CHECK("concatenate_name_to_wire 2", towire,
+                             dns_concatenate_name_to_wire(towire, name, NULL, NULL));
+                INFO("compressing " PRI_S_SRP, rbuf);
             }
+            dns_name_free(name);
             dns_rdlength_end(towire);
         } else {
+            ERROR("dp_query_add_data_to_response: rdata from mDNSResponder didn't parse!!");
         raw:
             TOWIRE_CHECK("rdlen", towire, dns_u16_to_wire(towire, rdlen));
             TOWIRE_CHECK("rdata", towire, dns_rdata_raw_data_to_wire(towire, rdata, rdlen));
@@ -358,45 +395,194 @@ dp_query_add_data_to_response(dnssd_query_t *query, const char *fullname,
     } else {
         TOWIRE_CHECK("rdlen", towire, dns_u16_to_wire(towire, rdlen));
     }
-    if (failnote) {
-        ERROR("dp_query_add_data_to_response: %s", failnote);
+    if (towire->truncated || failnote) {
+        ERROR("RR ADD FAIL: dp_query_add_data_to_response: " PUB_S_SRP, failnote);
+        query->towire.p = revert;
     }
 }
 
-typedef struct hardwired hardwired_t;
-struct hardwired {
-    hardwired_t *next;
-    uint16_t type;
-    char *name;
-    char *fullname;
-    uint8_t *rdata;
-    uint16_t rdlen;
-} *hardwired_responses;
-
 void
-dnssd_hardwired_add(const char *name, const char *domain, size_t rdlen, uint8_t *rdata, uint16_t type)
+dnssd_hardwired_add(served_domain_t *sdt,
+                    const char *name, const char *domain, size_t rdlen, uint8_t *rdata, uint16_t type)
 {
-    hardwired_t *hp;
+    hardwired_t *hp, **hrp;
     int namelen = strlen(name);
-    size_t total = (sizeof *hp) + rdlen + namelen * 2 + strlen(proxied_domain_ld) + 2;
-
-    hp = calloc(1, (sizeof *hp) + rdlen + namelen * 2 + strlen(proxied_domain_ld) + 2);
+    size_t total = sizeof *hp;
+    uint8_t *trailer;
+    total += rdlen; // Space for RDATA
+    total += namelen; // Space for name
+    total += 1; // NUL
+    total += namelen;// space for FQDN
+    total += strlen(domain);
+    total += 1; // NUL
+
+    hp = calloc(1, total + 4);
+    if (hp == NULL) {
+        ERROR("no memory for %s %s", name, domain);
+        return;
+    }
+    trailer = ((uint8_t *)hp) + total;
+    memcpy(trailer, "abcd", 4);
     hp->rdata = (uint8_t *)(hp + 1);
     hp->rdlen = rdlen;
     memcpy(hp->rdata, rdata, rdlen);
     hp->name = (char *)hp->rdata + rdlen;
     strcpy(hp->name, name);
     hp->fullname = hp->name + namelen + 1;
-    strcpy(hp->fullname, name);
-    strcpy(hp->fullname + namelen, proxied_domain_ld);
+    if (namelen != 0) {
+        strcpy(hp->fullname, name);
+        strcpy(hp->fullname + namelen, domain);
+    } else {
+        strcpy(hp->fullname, domain);
+    }
     if (hp->fullname + strlen(hp->fullname) + 1 != (char *)hp + total) {
         ERROR("%p != %p", hp->fullname + strlen(hp->fullname) + 1, ((char *)hp) + total);
+        return;
+    }
+    if (memcmp(trailer, "abcd", 4)) {
+        ERROR("ran off the end.");
+        return;
     }
     hp->type = type;
-    hp->next = hardwired_responses;
-    hardwired_responses = hp;
+    hp->next = NULL;
+
+    // Store this new hardwired_t at the end of the list unless a hardwired_t with the same name
+    // is already on the list.   If it is, splice it in.
+    for (hrp = &sdt->hardwired_responses; *hrp != NULL; hrp = &(*hrp)->next) {
+        hardwired_t *old = *hrp;
+        if (!strcmp(old->fullname, hp->name) && old->type == hp->type) {
+            INFO("hardwired_add: superseding " PRI_S_SRP " name " PRI_S_SRP " type %d rdlen %d", old->fullname,
+                 old->name, old->type, old->rdlen);
+            hp->next = old->next;
+            free(old);
+            break;
+        }
+    }
+    *hrp = hp;
 
-    INFO("hardwired_add: fullname %s name %s type %d rdlen %d", hp->fullname, hp->name, hp->type, hp->rdlen);
+    INFO("hardwired_add: fullname " PRI_S_SRP " name " PRI_S_SRP " type %d rdlen %d",
+         hp->fullname, hp->name, hp->type, hp->rdlen);
+}
+
+void dnssd_hardwired_lbdomains_setup(dns_towire_state_t *towire, dns_wire_t *wire)
+{
+    served_domain_t *ip6 = NULL, *ipv4 = NULL, *addr_domain, *interface_domain;
+    char name[DNS_MAX_NAME_SIZE + 1];
+
+#define RESET \
+    memset(towire, 0, sizeof *towire); \
+    towire->message = wire; \
+    towire->p = wire->data; \
+    towire->lim = towire->p + sizeof wire->data
+
+    for (addr_domain = served_domains; addr_domain; addr_domain = addr_domain->next) {
+        interface_t *interface = addr_domain->interface;
+        interface_addr_t *ifaddr;
+        if (interface == NULL) {
+            INFO("Domain " PRI_S_SRP " has no interface", addr_domain->domain);
+            continue;
+        }
+        INFO("Interface " PUB_S_SRP, interface->name);
+        // Add lb domain support for link domain
+        for (ifaddr = interface->addresses; ifaddr != NULL; ifaddr = ifaddr->next) {
+            if (ifaddr->addr.sa.sa_family == AF_INET) {
+                uint8_t *address = (uint8_t *)&(ifaddr->addr.sin.sin_addr);
+                uint8_t *mask = (uint8_t *)&(ifaddr->mask.sin.sin_addr);
+                char *bp;
+                int space = sizeof name;
+                int i;
+
+                if (address[0] == 127) {
+                    INFO("Skipping IPv4 loopback address on " PRI_S_SRP " (" PUB_S_SRP ")",
+                          addr_domain->domain, interface->name);
+                    continue;
+                }
+
+                if (address[0] == 169 && address[1] == 254) {
+                    INFO("Skipping IPv4 link local address on " PRI_S_SRP " (" PUB_S_SRP ")",
+                         addr_domain->domain, interface->name);
+                    continue;
+                }
+
+                snprintf(name, space, "lb._dns-sd._udp");
+                bp = name + strlen(name);
+                for (i = 3; i >= 0; i--) {
+                    snprintf(bp, space - (bp - name), ".%d", address[i] & mask[i]);
+                    bp += strlen(bp);
+                }
+                if (ipv4 == NULL) {
+                    ipv4 = new_served_domain(NULL, "in-addr.arpa.");
+                    if (ipv4 == NULL) {
+                        ERROR("No space for in-addr.arpa.");
+                        return;
+                    }
+                }
+
+                INFO("Adding PTRs for " PRI_S_SRP, name);
+                for (interface_domain = served_domains; interface_domain != NULL;
+                     interface_domain = interface_domain->next) {
+                    if (interface_domain->interface == NULL || interface_domain->interface->ifindex == 0) {
+                        continue;
+                    }
+                    RESET;
+                    INFO("Adding PTR from " PRI_S_SRP " to " PRI_S_SRP, name, interface_domain->domain);
+                    dns_full_name_to_wire(NULL, towire, interface_domain->domain);
+                    dnssd_hardwired_add(ipv4, name, ipv4->domain_ld, towire->p - wire->data, wire->data,
+                                        dns_rrtype_ptr);
+                }
+            } else if (ifaddr->addr.sa.sa_family == AF_INET6) {
+                uint8_t *address = (uint8_t *)&(ifaddr->addr.sin6.sin6_addr);
+                uint8_t *mask = (uint8_t *)&(ifaddr->mask.sin6.sin6_addr);
+                char *bp;
+                int space = sizeof name;
+                int i, word, shift;
+
+                if (IN6_IS_ADDR_LOOPBACK(&ifaddr->addr.sin6.sin6_addr)) {
+                    INFO("Skipping IPv6 link local address on " PRI_S_SRP " (" PUB_S_SRP ")", addr_domain->domain,
+                         interface->name);
+                    continue;
+                }
+                if (IN6_IS_ADDR_LINKLOCAL(&ifaddr->addr.sin6.sin6_addr)) {
+                    INFO("Skipping IPv6 link local address on " PRI_S_SRP " (" PUB_S_SRP ")", addr_domain->domain,
+                         interface->name);
+                    continue;
+                }
+                snprintf(name, space, "lb._dns-sd._udp");
+                bp = name + strlen(name);
+                for (i = 16; i >= 0; i--) {
+                    word = i;
+                    for (shift = 0; shift < 8; shift += 4) {
+                        snprintf(bp, (sizeof name) - (bp - name), ".%x",
+                                (address[word] >> shift) & (mask[word] >> shift) & 15);
+                        bp += strlen(bp);
+                    }
+                }
+                if (ip6 == NULL) {
+                    ip6 = new_served_domain(NULL, "ip6.arpa.");
+                    if (ip6 == NULL) {
+                        ERROR("No space for ip6.arpa.");
+                        return;
+                    }
+                }
+                INFO("Adding PTRs for " PRI_S_SRP, name);
+                for (interface_domain = served_domains; interface_domain != NULL;
+                     interface_domain = interface_domain->next) {
+                    if (interface_domain->interface == NULL || interface_domain->interface->ifindex == 0) {
+                        continue;
+                    }
+                    INFO("Adding PTR from " PRI_S_SRP " to " PRI_S_SRP, name, interface_domain->domain);
+                    RESET;
+                    dns_full_name_to_wire(NULL, towire, interface_domain->domain);
+                    dnssd_hardwired_add(ip6, name, ip6->domain_ld, towire->p - wire->data, wire->data, dns_rrtype_ptr);
+                }
+            } else {
+                char buf[INET6_ADDRSTRLEN];
+                IOLOOP_NTOP(&ifaddr->addr, buf);
+                INFO("Skipping " PRI_S_SRP, buf);
+            }
+        }
+    }
+#undef RESET
 }
 
 void
@@ -404,6 +590,12 @@ dnssd_hardwired_setup(void)
 {
     dns_wire_t wire;
     dns_towire_state_t towire;
+    served_domain_t *sdt;
+    int i;
+    dns_name_t *my_name_parsed = my_name == NULL ? NULL : dns_pres_name_parse(my_name);
+    char namebuf[DNS_MAX_NAME_SIZE + 1];
+    const char *local_name = my_name;
+    addr_t addr;
 
 #define RESET \
     memset(&towire, 0, sizeof towire); \
@@ -411,63 +603,187 @@ dnssd_hardwired_setup(void)
     towire.p = wire.data; \
     towire.lim = towire.p + sizeof wire.data
 
-    // Browsing pointers...
-    RESET;
-    dns_full_name_to_wire(NULL, &towire, proxied_domain);
-    dnssd_hardwired_add("b._dns-sd._udp", proxied_domain_ld, towire.p - wire.data, wire.data, dns_rrtype_ptr);
-    dnssd_hardwired_add("lb._dns-sd._udp", proxied_domain_ld, towire.p - wire.data, wire.data, dns_rrtype_ptr);
-    
-    // SRV
-    // _dns-push-tls._tcp
-    RESET;
-    dns_u16_to_wire(&towire, 0); // priority
-    dns_u16_to_wire(&towire, 0); // weight
-    dns_u16_to_wire(&towire, 53); // port
-    // Define MY_NAME to reference a name for this server in a different zone.
-#ifndef MY_NAME
-    dns_name_to_wire(NULL, &towire, "ns");
-    dns_full_name_to_wire(NULL, &towire, proxied_domain);
-#else
-    dns_full_name_to_wire(NULL, &towire, MY_NAME);
-#endif
-    dnssd_hardwired_add("_dns-push-tls._tcp", proxied_domain_ld, towire.p - wire.data, wire.data, dns_rrtype_srv);
-    
-    // A
-#ifndef MY_NAME
-    // ns
-#ifdef MY_IPV4_ADDR
-    RESET;
-    dns_rdata_a_to_wire(&towire, MY_IPV4_ADDR);
-    dnssd_hardwired_add("ns", proxied_domain_ld, towire.p - wire.data, wire.data, dns_rrtype_a);
-#endif
+    // For each interface, set up the hardwired names.
+    for (sdt = served_domains; sdt; sdt = sdt->next) {
+        if (sdt->interface == NULL) {
+            continue;
+        }
 
-    // AAAA
-#ifdef MY_IPV6_ADDR
-    RESET;
-    dns_rdata_aaaa_to_wire(&towire, MY_IPV6_ADDR);
-    dnssd_hardwired_add("ns", proxied_domain_ld, towire.p - wire.data, wire.data, dns_rrtype_aaaa);
-#endif
-#endif
+        // SRV
+        // _dns-llq._udp
+        // _dns-llq-tls._tcp
+        // _dns-update._udp
+        // _dns-update-tls._udp
+        // We deny the presence of support for LLQ, because we only support DNS Push
+        RESET;
+        dnssd_hardwired_add(sdt, "_dns-llq._udp", sdt->domain_ld, towire.p - wire.data, wire.data, dns_rrtype_srv);
+        dnssd_hardwired_add(sdt, "_dns-llq-tls._tcp", sdt->domain_ld, towire.p - wire.data, wire.data, dns_rrtype_srv);
+
+        // We deny the presence of support for DNS Update, because a Discovery Proxy zone is stateless.
+        dnssd_hardwired_add(sdt, "_dns-update._udp", sdt->domain_ld, towire.p - wire.data, wire.data, dns_rrtype_srv);
+        dnssd_hardwired_add(sdt, "_dns-update-tls._tcp", sdt->domain_ld, towire.p - wire.data, wire.data,
+                            dns_rrtype_srv);
+
+        // Until we set up the DNS Push listener, we deny its existence.   If TLS is ready to go, this will be
+        // overwritten immediately; otherwise it will be overwritten when the TLS key has been generated and signed.
+        dnssd_hardwired_add(sdt, "_dns-push-tls._tcp", sdt->domain_ld, towire.p - wire.data, wire.data, dns_rrtype_srv);
+
+        // If my_name wasn't set, or if my_name is in this interface's domain, we need to answer
+        // for it when queried.
+        if (my_name == NULL || my_name_parsed != NULL) {
+            const char *local_domain = NULL;
+            if (my_name == NULL) {
+                local_name = "ns";
+                local_domain = sdt->domain_ld;
+            } else {
+                dns_name_t *lim;
+                local_name = NULL;
+
+                // See if my_name is a subdomain of this interface's domain
+                if ((lim = dns_name_subdomain_of(my_name_parsed, sdt->domain_name)) != NULL) {
+                    dns_name_print_to_limit(my_name_parsed, lim, namebuf, sizeof namebuf);
+                    local_name = namebuf;
+                    dns_name_free(my_name_parsed);
+                    my_name_parsed = NULL;
+                    if (local_name[0] == '\0') {
+                        local_domain = sdt->domain;
+                    } else {
+                        local_domain = sdt->domain_ld;
+                    }
+                }
+            }
+            if (local_name != NULL) {
+                for (i = 0; i < num_publish_addrs; i++) {
+                    RESET;
+                    memset(&addr, 0, sizeof addr);
+                    getipaddr(&addr, publish_addrs[i]);
+                    if (addr.sa.sa_family == AF_INET) {
+                        // A
+                        // ns
+                        dns_rdata_raw_data_to_wire(&towire, &addr.sin.sin_addr, sizeof addr.sin.sin_addr);
+                        dnssd_hardwired_add(sdt, local_name, local_domain, towire.p - wire.data, wire.data,
+                                            dns_rrtype_a);
+                    } else {
+                        // AAAA
+                        RESET;
+                        dns_rdata_raw_data_to_wire(&towire, &addr.sin6.sin6_addr, sizeof addr.sin6.sin6_addr);
+                        dnssd_hardwired_add(sdt, local_name, local_domain, towire.p - wire.data, wire.data,
+                                            dns_rrtype_aaaa);
+                    }
+                }
+            }
+        }
 
-    // NS
-    RESET;
-#ifdef MY_NAME
-    dns_full_name_to_wire(NULL, &towire, MY_NAME);
-#else
-    dns_name_to_wire(NULL, &towire, "ns");
-    dns_full_name_to_wire(NULL, &towire, proxied_domain);
-#endif
-    dnssd_hardwired_add("", proxied_domain, towire.p - wire.data, wire.data, dns_rrtype_ns);
-
-    // SOA (piggybacking on what we already did for NS, which starts the same.
-    dns_name_to_wire(NULL, &towire, "postmaster");
-    dns_full_name_to_wire(NULL, &towire, proxied_domain);
-    dns_u32_to_wire(&towire, 0);     // serial 
-    dns_ttl_to_wire(&towire, 7200);  // refresh
-    dns_ttl_to_wire(&towire, 3600);  // retry
-    dns_ttl_to_wire(&towire, 86400); // expire
-    dns_ttl_to_wire(&towire, 120);    // minimum
-    dnssd_hardwired_add("", proxied_domain, towire.p - wire.data, wire.data, dns_rrtype_soa);
+        // NS
+        RESET;
+        if (my_name != NULL) {
+            dns_full_name_to_wire(NULL, &towire, my_name);
+        } else {
+            dns_name_to_wire(NULL, &towire, "ns");
+            dns_full_name_to_wire(NULL, &towire, sdt->domain);
+        }
+        dnssd_hardwired_add(sdt, "", sdt->domain, towire.p - wire.data, wire.data, dns_rrtype_ns);
+
+        // SOA (piggybacking on what we already did for NS, which starts the same.
+        dns_name_to_wire(NULL, &towire, "postmaster");
+        dns_full_name_to_wire(NULL, &towire, sdt->domain);
+        dns_u32_to_wire(&towire, 0);     // serial
+        dns_ttl_to_wire(&towire, 7200);  // refresh
+        dns_ttl_to_wire(&towire, 3600);  // retry
+        dns_ttl_to_wire(&towire, 86400); // expire
+        dns_ttl_to_wire(&towire, 120);    // minimum
+        dnssd_hardwired_add(sdt, "", sdt->domain, towire.p - wire.data, wire.data, dns_rrtype_soa);
+    }
+
+    if (my_name_parsed != NULL) {
+        dns_name_free(my_name_parsed);
+        my_name_parsed = NULL;
+
+        sdt = new_served_domain(NULL, my_name);
+        if (sdt == NULL) {
+            ERROR("Unable to allocate domain for %s", my_name);
+        } else {
+            for (i = 0; i < num_publish_addrs; i++) {
+                // AAAA
+                // A
+                RESET;
+                memset(&addr, 0, sizeof addr);
+                getipaddr(&addr, publish_addrs[i]);
+                if (addr.sa.sa_family == AF_INET) {
+                    dns_rdata_raw_data_to_wire(&towire, &addr.sin.sin_addr, sizeof addr.sin.sin_addr);
+                    dnssd_hardwired_add(sdt, "", sdt->domain, towire.p - wire.data, wire.data, dns_rrtype_a);
+                } else {
+                    dns_rdata_raw_data_to_wire(&towire, &addr.sin6.sin6_addr, sizeof addr.sin6.sin6_addr);
+                    dnssd_hardwired_add(sdt, "", sdt->domain, towire.p - wire.data, wire.data, dns_rrtype_aaaa);
+                }
+            }
+        }
+    }
+    dnssd_hardwired_lbdomains_setup(&towire, &wire);
+}
+
+void
+dnssd_hardwired_push_setup(void)
+{
+    dns_wire_t wire;
+    dns_towire_state_t towire;
+    served_domain_t *sdt;
+
+#define RESET \
+    memset(&towire, 0, sizeof towire); \
+    towire.message = &wire; \
+    towire.p = wire.data; \
+    towire.lim = towire.p + sizeof wire.data
+
+    // For each interface, set up the hardwired names.
+    for (sdt = served_domains; sdt; sdt = sdt->next) {
+        if (sdt->interface == NULL) {
+            continue;
+        }
+
+        if (!sdt->interface->no_push) {
+            // SRV
+            // _dns-push-tls._tcp
+            RESET;
+            dns_u16_to_wire(&towire, 0); // priority
+            dns_u16_to_wire(&towire, 0); // weight
+            dns_u16_to_wire(&towire, 853); // port
+            // Define my_name in the config file to reference a name for this server in a different zone.
+            if (my_name == NULL) {
+                dns_name_to_wire(NULL, &towire, "ns");
+                dns_full_name_to_wire(NULL, &towire, sdt->domain);
+            } else {
+                dns_full_name_to_wire(NULL, &towire, my_name);
+            }
+            dnssd_hardwired_add(sdt, "_dns-push-tls._tcp", sdt->domain_ld, towire.p - wire.data, wire.data,
+                                dns_rrtype_srv);
+            // This will probably never be used, but existing open source mDNSResponder code can be
+            // configured to do DNS queries over TLS for specific domains, so we might as well support it,
+            // since we do have TLS support.
+            dnssd_hardwired_add(sdt, "_dns-query-tls._udp", sdt->domain_ld, towire.p - wire.data, wire.data,
+                                dns_rrtype_srv);
+        }
+    }
+}
+
+bool
+embiggen(dnssd_query_t *query)
+{
+    dns_wire_t *nr = malloc(query->data_size + sizeof *nr); // increments wire size by DNS_DATA_SIZE
+    if (nr == NULL) {
+        return false;
+    }
+    memcpy(nr, query->response, DNS_HEADER_SIZE + query->data_size);
+    query->data_size += DNS_DATA_SIZE;
+#define RELOCATE(x) (x) = &nr->data[0] + ((x) - &query->response->data[0])
+    RELOCATE(query->towire.p);
+    query->towire.lim = &nr->data[0] + query->data_size;
+    query->towire.p_rdlength = NULL;
+    query->towire.p_opt = NULL;
+    query->towire.message = nr;
+    free(query->response);
+    query->response = nr;
+    return true;
 }
 
 void
@@ -476,11 +792,16 @@ dp_query_send_dns_response(dnssd_query_t *query)
     struct iovec iov;
     dns_towire_state_t *towire = &query->towire;
     const char *failnote = NULL;
+    uint8_t *revert = towire->p;
+    uint16_t tc = towire->truncated ? dns_flags_tc : 0;
+    uint16_t bitfield = ntohs(query->response->bitfield);
+    uint16_t mask = 0;
 
     // Send an SOA record if it's a .local query.
-    if (query->enclosing_domain != NULL) {
+    if (query->served_domain != NULL && query->served_domain->interface != NULL && !towire->truncated) {
+    redo:
         // DNSSD Hybrid, Section 6.1.
-        TOWIRE_CHECK("&query->enclosing_domain_pointer", towire,
+        TOWIRE_CHECK("&query->enclosing_domain_pointer 1", towire,
                      dns_pointer_to_wire(NULL, towire, &query->enclosing_domain_pointer));
         TOWIRE_CHECK("dns_rrtype_soa", towire,
                      dns_u16_to_wire(towire, dns_rrtype_soa));
@@ -488,41 +809,60 @@ dp_query_send_dns_response(dnssd_query_t *query)
                      dns_u16_to_wire(towire, dns_qclass_in));
         TOWIRE_CHECK("ttl", towire, dns_ttl_to_wire(towire, 3600));
         TOWIRE_CHECK("rdlength_begin ", towire, dns_rdlength_begin(towire));
-#ifdef MY_NAME
-        TOWIRE_CHECK(MY_NAME, towire, dns_full_name_to_wire(NULL, towire, MY_NAME));
-#else
-        TOWIRE_CHECK("\"ns\"", towire, dns_name_to_wire(NULL, towire, "ns"));
-        TOWIRE_CHECK("&query->enclosing_domain_pointer", towire,
-                     dns_pointer_to_wire(NULL, towire, &query->enclosing_domain_pointer));
-#endif
+        if (my_name != NULL) {
+            TOWIRE_CHECK(my_name, towire, dns_full_name_to_wire(NULL, towire, my_name));
+        } else {
+            TOWIRE_CHECK("\"ns\"", towire, dns_name_to_wire(NULL, towire, "ns"));
+            TOWIRE_CHECK("&query->enclosing_domain_pointer 2", towire,
+                         dns_pointer_to_wire(NULL, towire, &query->enclosing_domain_pointer));
+        }
         TOWIRE_CHECK("\"postmaster\"", towire,
                      dns_name_to_wire(NULL, towire, "postmaster"));
-        TOWIRE_CHECK("&query->enclosing_domain_pointer", towire,
+        TOWIRE_CHECK("&query->enclosing_domain_pointer 3", towire,
                      dns_pointer_to_wire(NULL, towire, &query->enclosing_domain_pointer));
-        TOWIRE_CHECK("serial", towire,dns_u32_to_wire(towire, 0));     // serial 
+        TOWIRE_CHECK("serial", towire,dns_u32_to_wire(towire, 0));     // serial
         TOWIRE_CHECK("refresh", towire, dns_ttl_to_wire(towire, 7200));  // refresh
         TOWIRE_CHECK("retry", towire, dns_ttl_to_wire(towire, 3600));  // retry
         TOWIRE_CHECK("expire", towire, dns_ttl_to_wire(towire, 86400)); // expire
         TOWIRE_CHECK("minimum", towire, dns_ttl_to_wire(towire, 120));    // minimum
         dns_rdlength_end(towire);
-        query->response.nscount = htons(1);
+        if (towire->truncated) {
+            query->towire.p = revert;
+            if (query->connection->tcp_stream) {
+                if (embiggen(query)) {
+                    query->towire.error = 0;
+                    towire->truncated = false;
+                    goto redo;
+                }
+            } else {
+                tc = dns_flags_tc;
+            }
+        } else {
+            query->response->nscount = htons(1);
+        }
 
         // Response is authoritative and not recursive.
-        query->response.bitfield = htons((ntohs(query->response.bitfield) | dns_flags_aa) & ~dns_flags_ra);
+        mask = ~dns_flags_ra;
+        bitfield = bitfield | dns_flags_aa | tc;
+        bitfield = bitfield & mask;
     } else {
         // Response is recursive and not authoritative.
-        query->response.bitfield = htons((ntohs(query->response.bitfield) | dns_flags_ra) & ~dns_flags_aa);
+        mask = ~dns_flags_aa;
+        bitfield = bitfield | dns_flags_ra | tc;
+        bitfield = bitfield & mask;
     }
-    // Not truncated, not authentic, checking not disabled.
-    query->response.bitfield = htons(ntohs(query->response.bitfield) & ~(dns_flags_rd | dns_flags_tc | dns_flags_ad | dns_flags_cd));
+    // Not authentic, checking not disabled.
+    mask = ~(dns_flags_rd | dns_flags_ad | dns_flags_cd);
+    bitfield = bitfield & mask;
+    query->response->bitfield = htons(bitfield);
 
     // This is a response
-    dns_qr_set(&query->response, dns_qr_response);
-    // No error.
-    dns_rcode_set(&query->response, dns_rcode_noerror);
-    
+    dns_qr_set(query->response, dns_qr_response);
+
     // Send an OPT RR if we got one
+    // XXX reserve space so we can always send an OPT RR?
     if (query->is_edns0) {
+    redo_edns0:
         TOWIRE_CHECK("Root label", towire, dns_u8_to_wire(towire, 0));     // Root label
         TOWIRE_CHECK("dns_rrtype_opt", towire, dns_u16_to_wire(towire, dns_rrtype_opt));
         TOWIRE_CHECK("UDP Payload size", towire, dns_u16_to_wire(towire, 4096)); // UDP Payload size
@@ -530,32 +870,53 @@ dp_query_send_dns_response(dnssd_query_t *query)
         TOWIRE_CHECK("EDNS version 0", towire, dns_u8_to_wire(towire, 0));     // EDNS version 0
         TOWIRE_CHECK("No extended flags", towire, dns_u16_to_wire(towire, 0));    // No extended flags
         TOWIRE_CHECK("No payload", towire, dns_u16_to_wire(towire, 0));    // No payload
-        query->response.arcount = htons(1);
+        if (towire->truncated) {
+            query->towire.p = revert;
+            if (query->connection->tcp_stream) {
+                if (embiggen(query)) {
+                    query->towire.error = false;
+                    query->towire.truncated = false;
+                    goto redo_edns0;
+                }
+            }
+        } else {
+            query->response->arcount = htons(1);
+        }
     }
 
     if (towire->error) {
         ERROR("dp_query_send_dns_response failed on %s", failnote);
+        if (tc == dns_flags_tc) {
+            dns_rcode_set(query->response, dns_rcode_noerror);
+        } else {
+            dns_rcode_set(query->response, dns_rcode_servfail);
+        }
+    } else {
+        // No error.
+        dns_rcode_set(query->response, dns_rcode_noerror);
     }
 
-    iov.iov_len = (query->towire.p - (uint8_t *)&query->response);
-    iov.iov_base = &query->response;
-    INFO("dp_query_send_dns_response: %s (len %zd)", query->name, iov.iov_len);
+    iov.iov_len = (query->towire.p - (uint8_t *)query->response);
+    iov.iov_base = query->response;
+    INFO("dp_query_send_dns_response: " PRI_S_SRP " (len %zd)", query->name, iov.iov_len);
 
     if (query->connection != NULL) {
         query->connection->send_response(query->connection, query->question, &iov, 1);
     }
 
     // Free up state
-    dnssd_query_cancel(&query->io);
     // Query will be freed automatically next time through the io loop.
+    dnssd_query_cancel(query);
 }
 
 void
 dp_query_towire_reset(dnssd_query_t *query)
 {
-    query->towire.p = &query->response.data[0];  // We start storing RR data here.
-    query->towire.lim = &query->response.data[DNS_DATA_SIZE]; // This is the limit to how much we can store.
-    query->towire.message = &query->response;
+    query->towire.p = &query->response->data[0];  // We start storing RR data here.
+    query->towire.lim = &query->response->data[0] + query->data_size; // This is the limit to how much we can store.
+    query->towire.message = query->response;
+    query->towire.p_rdlength = NULL;
+    query->towire.p_opt = NULL;
     query->p_dso_length = NULL;
 }
 
@@ -563,15 +924,15 @@ void
 dns_push_start(dnssd_query_t *query)
 {
     const char *failnote = NULL;
-    
+
     // If we don't have a dso header yet, start one.
     if (query->p_dso_length == NULL) {
-        memset(&query->response, 0, (sizeof query->response) - DNS_DATA_SIZE);
-        dns_opcode_set(&query->response, dns_opcode_dso);
+        memset(query->response, 0, (sizeof *query->response) - DNS_DATA_SIZE);
+        dns_opcode_set(query->response, dns_opcode_dso);
         // This is a unidirectional DSO message, which is marked as a query
-        dns_qr_set(&query->response, dns_qr_query);
+        dns_qr_set(query->response, dns_qr_query);
         // No error cuz not a response.
-        dns_rcode_set(&query->response, dns_rcode_noerror);
+        dns_rcode_set(query->response, dns_rcode_noerror);
 
         TOWIRE_CHECK("kDSOType_DNSPushUpdate", &query->towire,
                      dns_u16_to_wire(&query->towire, kDSOType_DNSPushUpdate));
@@ -595,9 +956,9 @@ dp_push_response(dnssd_query_t *query)
 
     if (query->p_dso_length != NULL) {
         int16_t dso_length = query->towire.p - query->p_dso_length - 2;
-        iov.iov_len = (query->towire.p - (uint8_t *)&query->response);
-        iov.iov_base = &query->response;
-        INFO("dp_push_response: %s (len %zd)", query->name, iov.iov_len);
+        iov.iov_len = (query->towire.p - (uint8_t *)query->response);
+        iov.iov_base = query->response;
+        INFO("dp_push_response: " PRI_S_SRP " (len %zd)", query->name, iov.iov_len);
 
         query->towire.p = query->p_dso_length;
         dns_u16_to_wire(&query->towire, dso_length);
@@ -614,7 +975,7 @@ dnssd_hardwired_response(dnssd_query_t *query, DNSServiceQueryRecordReply callba
     hardwired_t *hp;
     bool got_response = false;
 
-    for (hp = hardwired_responses; hp; hp = hp->next) {
+    for (hp = query->served_domain->hardwired_responses; hp; hp = hp->next) {
         if ((query->type == hp->type || query->type == dns_rrtype_any) &&
             query->qclass == dns_qclass_in && !strcasecmp(hp->name, query->name)) {
             if (query->is_dns_push) {
@@ -622,8 +983,13 @@ dnssd_hardwired_response(dnssd_query_t *query, DNSServiceQueryRecordReply callba
                 dp_query_add_data_to_response(query, hp->fullname, hp->type, dns_qclass_in, hp->rdlen, hp->rdata, 3600);
             } else {
                 // Store the response
-                dp_query_add_data_to_response(query, hp->fullname, hp->type, dns_qclass_in, hp->rdlen, hp->rdata, 3600);
-                query->response.ancount = htons(ntohs(query->response.ancount) + 1);
+                if (!query->towire.truncated) {
+                    dp_query_add_data_to_response(query, hp->fullname, hp->type, dns_qclass_in, hp->rdlen, hp->rdata,
+                                                  3600);
+                    if (!query->towire.truncated) {
+                        query->response->ancount = htons(ntohs(query->response->ancount) + 1);
+                    }
+                }
             }
             got_response = true;
         }
@@ -650,41 +1016,65 @@ dns_query_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfac
                    uint32_t ttl, void *context)
 {
     dnssd_query_t *query = context;
-    
-    INFO("%s %d %d %x %d", fullname, rrtype, rrclass, rdlen, errorCode);
+
+    INFO(PRI_S_SRP " %d %d %x %d", fullname, rrtype, rrclass, rdlen, errorCode);
 
     if (errorCode == kDNSServiceErr_NoError) {
+    re_add:
         dp_query_add_data_to_response(query, fullname, rrtype, rrclass, rdlen, rdata,
                                       ttl > 10 ? 10 : ttl); // Per dnssd-hybrid 5.5.1, limit ttl to 10 seconds
-        query->response.ancount = htons(ntohs(query->response.ancount) + 1);
+        if (query->towire.truncated) {
+            if (query->connection->tcp_stream) {
+                if (embiggen(query)) {
+                    query->towire.truncated = false;
+                    query->towire.error = false;
+                    goto re_add;
+                } else {
+                    dns_rcode_set(query->response, dns_rcode_servfail);
+                    dp_query_send_dns_response(query);
+                    return;
+                }
+            }
+        } else {
+            query->response->ancount = htons(ntohs(query->response->ancount) + 1);
+        }
         // If there isn't more coming, send the response now
-        if (!(flags & kDNSServiceFlagsMoreComing)) {
-            dp_query_send_dns_response(query);
+        if (!(flags & kDNSServiceFlagsMoreComing) || query->towire.truncated) {
+            // When we get a CNAME response, we may not get the record it points to with the MoreComing
+            // flag set, so don't respond yet.
+            if (query->type != dns_rrtype_cname && rrtype == dns_rrtype_cname) {
+            } else {
+                dp_query_send_dns_response(query);
+            }
         }
     } else if (errorCode == kDNSServiceErr_NoSuchRecord) {
         // If we get "no such record," we can't really do much except return the answer.
         dp_query_send_dns_response(query);
     } else {
-        dns_rcode_set(&query->response, dns_rcode_servfail);
+        dns_rcode_set(query->response, dns_rcode_servfail);
         dp_query_send_dns_response(query);
     }
 }
 
 void
-dp_query_wakeup(io_t *io)
+dp_query_wakeup(void *context)
 {
-    dnssd_query_t *query = (dnssd_query_t *)io;
+    dnssd_query_t *query = context;
     char name[DNS_MAX_NAME_SIZE + 1];
     int namelen = strlen(query->name);
 
     // Should never happen.
-    if ((namelen + query->enclosing_domain != NULL ? sizeof local_suffix : 0) > sizeof name) {
+    if (namelen + (query->served_domain
+                   ? (query->served_domain->interface != NULL
+                      ? sizeof local_suffix
+                      : strlen(query->served_domain->domain_ld))
+                   : 0) > sizeof name) {
         ERROR("db_query_wakeup: no space to construct name.");
-        dnssd_query_cancel(&query->io);
+        dnssd_query_cancel(query);
     }
 
     strcpy(name, query->name);
-    if (query->enclosing_domain != NULL) {
+    if (query->served_domain != NULL) {
         strcpy(name + namelen, local_suffix);
     }
     dp_query_send_dns_response(query);
@@ -695,30 +1085,61 @@ dp_query_start(comm_t *comm, dnssd_query_t *query, int *rcode, DNSServiceQueryRe
 {
     char name[DNS_MAX_NAME_SIZE + 1];
     char *np;
+    bool local = false;
+    int len;
+    DNSServiceRef sdref;
 
-    if (query->enclosing_domain != NULL) {
+    // If a query has a served domain, query->name is the subdomain of the served domain that is
+    // being queried; otherwise query->name is the whole name.
+    if (query->served_domain != NULL) {
         if (dnssd_hardwired_response(query, callback)) {
             *rcode = dns_rcode_noerror;
             return true;
         }
-
-        int len = strlen(query->name);
-        if (len + sizeof local_suffix > sizeof name) {
-            *rcode = dns_rcode_servfail;
-            free(query->name);
-            free(query);
-            ERROR("question name %s is too long for .local.", name);
-            return false;
+        len = strlen(query->name);
+        if (query->served_domain->interface != NULL) {
+            if (len + sizeof local_suffix > sizeof name) {
+                *rcode = dns_rcode_servfail;
+                free(query->name);
+                free(query);
+                ERROR("question name %s is too long for .local.", name);
+                return false;
+            }
+            memcpy(name, query->name, len);
+            memcpy(&name[len], local_suffix, sizeof local_suffix);
+        } else {
+            int dlen = strlen(query->served_domain->domain_ld) + 1;
+            if (len + dlen > sizeof name) {
+                *rcode = dns_rcode_servfail;
+                free(query->name);
+                free(query);
+                ERROR("question name %s is too long for %s.", name, query->served_domain->domain);
+                return false;
+            }
+            memcpy(name, query->name, len);
+            memcpy(&name[len], query->served_domain->domain_ld, dlen);
         }
-        memcpy(name, query->name, len);
-        memcpy(&name[len], local_suffix, sizeof local_suffix);
         np = name;
+        local = true;
     } else {
         np = query->name;
     }
-        
+
+    // If we get an SOA query for record that's under a zone cut we're authoritative for, which
+    // is the case of query->served_domain->interface != NULL, then answer with a negative response that includes
+    // our authority records, rather than waiting for the query to time out.
+    if (query->served_domain != NULL && query->served_domain->interface != NULL &&
+        (query->type == dns_rrtype_soa ||
+         query->type == dns_rrtype_ns ||
+         query->type == dns_rrtype_ds) && query->qclass == dns_qclass_in && query->is_dns_push == false) {
+        query->question = comm->message;
+        comm->message = NULL;
+        dp_query_send_dns_response(query);
+        return true;
+    }
+
     // Issue a DNSServiceQueryRecord call
-    int err = DNSServiceQueryRecord(&query->ref, query->serviceFlags,
+    int err = DNSServiceQueryRecord(&sdref, query->serviceFlags,
                                     kDNSServiceInterfaceIndexAny, np, query->type,
                                     query->qclass, callback, query);
     if (err != kDNSServiceErr_NoError) {
@@ -726,18 +1147,27 @@ dp_query_start(comm_t *comm, dnssd_query_t *query, int *rcode, DNSServiceQueryRe
         *rcode = dns_rcode_servfail;
         return false;
     } else {
-        INFO("dp_query_start: DNSServiceQueryRecord started for '%s': %d", np, err);
+        query->txn = ioloop_dnssd_txn_add(sdref, dnssd_query_finalize_callback, dnssd_query_close_callback);
+        if (query->txn == NULL) {
+            return false;
+        }
+        INFO("dp_query_start: DNSServiceQueryRecord started for '" PRI_S_SRP "': %d", np, err);
     }
-    
+
     // If this isn't a DNS Push subscription, we need to respond quickly with as much data as we have.  It
     // turns out that dig gives us a second, but also that responses seem to come back in on the order of a
     // millisecond, so we'll wait 100ms.
-    if (!query->is_dns_push && query->enclosing_domain) {
-        query->io.wakeup_time = ioloop_now + IOLOOP_SECOND / 10;
-        query->io.wakeup = dp_query_wakeup;
+    if (!query->is_dns_push && local) {
+        // [mDNSDP 5.6 p. 25]
+        if (query->wakeup == NULL) {
+            query->wakeup = ioloop_wakeup_create();
+            if (query->wakeup == NULL) {
+                *rcode = dns_rcode_servfail;
+                return false;
+            }
+        }
+        ioloop_add_wake_event(query->wakeup, query, dp_query_wakeup, ioloop_timenow() + IOLOOP_SECOND * 6);
     }
-
-    add_dnssd_query(query);
     return true;
 }
 
@@ -745,32 +1175,37 @@ dnssd_query_t *
 dp_query_generate(comm_t *comm, dns_rr_t *question, bool dns_push, int *rcode)
 {
     char name[DNS_MAX_NAME_SIZE + 1];
-    const char *enclosing_domain;
+    served_domain_t *sdt = dp_served(question->name, name, sizeof name);
 
     // If it's a query for a name served by the local discovery proxy, do an mDNS lookup.
-    if ((dp_served(question->name, name, sizeof name))) {
-        enclosing_domain = proxied_domain;
-        INFO("%s question: type %d class %d %s%s -> %s.local", dns_push ? "push" : " dns",
-             question->type, question->qclass, name, proxied_domain, name);
+    if (sdt) {
+        INFO(PUB_S_SRP " question: type %d class %d " PRI_S_SRP "." PRI_S_SRP " -> " PRI_S_SRP ".local",
+             dns_push ? "push" : " dns", question->type, question->qclass, name, sdt->domain, name);
     } else {
         dns_name_print(question->name, name, sizeof name);
-        enclosing_domain = NULL;
-        INFO("%s question: type %d class %d %s",
+        INFO(PUB_S_SRP " question: type %d class %d " PRI_S_SRP,
              dns_push ? "push" : " dns", question->type, question->qclass, name);
     }
 
-    dnssd_query_t *query = malloc(sizeof *query);
+    dnssd_query_t *query = calloc(1,sizeof *query);
     if (query == NULL) {
+    nomem:
         ERROR("Unable to allocate memory for query on %s", name);
         *rcode = dns_rcode_servfail;
         return NULL;
     }
-    // Zero out everything except the message data buffer, which is large and doesn't need it.
-    memset(query, 0, (sizeof *query) - (sizeof query->response) + DNS_HEADER_SIZE);
+    query->response = malloc(sizeof *query->response);
+    if (query->response == NULL) {
+        goto nomem;
+    }
+    query->data_size = DNS_DATA_SIZE;
+
+    // Zero out the DNS header, but not the data.
+    memset(query->response, 0, DNS_HEADER_SIZE);
 
     // Steal the data from the question.   If subdomain is not null, this is a local mDNS query; otherwise
     // we are recursing.
-    INFO("name = %s", name);
+    INFO("name = " PRI_S_SRP, name);
     query->name = strdup(name);
     if (!query->name) {
         *rcode = dns_rcode_servfail;
@@ -779,11 +1214,11 @@ dp_query_generate(comm_t *comm, dns_rr_t *question, bool dns_push, int *rcode)
         return NULL;
     }
     // It is safe to assume that enclosing domain will not be freed out from under us.
-    query->enclosing_domain = enclosing_domain;
+    query->served_domain = sdt;
     query->serviceFlags = 0;
 
     // If this is a local query, add ".local" to the end of the name and require multicast.
-    if (enclosing_domain != NULL) {
+    if (sdt != NULL && sdt->interface) {
         query->serviceFlags |= kDNSServiceFlagsForceMulticast;
     } else {
         query->serviceFlags |= kDNSServiceFlagsReturnIntermediates;
@@ -802,21 +1237,19 @@ dp_query_generate(comm_t *comm, dns_rr_t *question, bool dns_push, int *rcode)
     query->type = question->type;
     query->qclass = question->qclass;
 
-    // Just in case we don't need to do a DNSServiceQueryRecord query to satisfy it.
-    query->io.sock = -1;
-
     *rcode = dns_rcode_noerror;
     return query;
 }
 
 // This is the callback for DNS push query results, as opposed to push updates.
 void
-dns_push_query_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
-                        const char *fullname, uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata,
-                        uint32_t ttl, void *context)
+dns_push_query_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
+                        DNSServiceErrorType errorCode,const char *fullname, uint16_t rrtype, uint16_t rrclass,
+                        uint16_t rdlen, const void *rdata, uint32_t ttl, void *context)
 {
     dnssd_query_t *query = context;
-    
+    uint8_t *revert = query->towire.p;
+
     // From DNSSD-Hybrid, for mDNS queries:
     // If we have cached answers, respond immediately, because we probably have all the answers.
     // If we don't have cached answers, respond as soon as we get an answer (presumably more-coming will be false).
@@ -827,23 +1260,47 @@ dns_push_query_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t int
     // all outstanding queries that aren't waiting on a time trigger.   This is because more-coming isn't
     // query-specific
 
-    INFO("PUSH %s %d %d %x %d", fullname, rrtype, rrclass, rdlen, errorCode);
+    INFO("PUSH " PRI_S_SRP " %d %d %x %d", fullname, rrtype, rrclass, rdlen, errorCode);
 
     // query_state_waiting means that we're answering a regular DNS question
     if (errorCode == kDNSServiceErr_NoError) {
         dns_push_start(query);
 
         // If kDNSServiceFlagsAdd is set, it's an add, otherwise a delete.
+        re_add:
         if (flags & kDNSServiceFlagsAdd) {
             dp_query_add_data_to_response(query, fullname, rrtype, rrclass, rdlen, rdata, ttl);
         } else {
-            // I think if this happens it means delete all RRs of this type.
-            if (rdlen == 0) {
-                dp_query_add_data_to_response(query, fullname, rrtype, dns_qclass_any, rdlen, rdata, 0);
+           // There was a verion of the code that used different semantics, we use those semantics on non-tls
+           // connections for now, but should delete this soon.
+           if (query->connection->tls_context != NULL) {
+                // I think if this happens it means delete all RRs of this type.
+                if (rdlen == 0) {
+                    dp_query_add_data_to_response(query, fullname, rrtype, dns_qclass_any, rdlen, rdata, -2);
+                } else {
+                    if (rdlen == 0) {
+                       dp_query_add_data_to_response(query, fullname, rrtype, dns_qclass_none, rdlen, rdata, -2);
+                   } else {
+                        dp_query_add_data_to_response(query, fullname, rrtype, rrclass, rdlen, rdata, -1);
+                   }
+                }
             } else {
-                dp_query_add_data_to_response(query, fullname, rrtype, dns_qclass_none, rdlen, rdata, 0);
+                if (rdlen == 0) {
+                    dp_query_add_data_to_response(query, fullname, rrtype, dns_qclass_any, rdlen, rdata, 0);
+                } else {
+                    dp_query_add_data_to_response(query, fullname, rrtype, dns_qclass_none, rdlen, rdata, 0);
+                }
             }
         }
+        if (query->towire.truncated) {
+            query->towire.truncated = false;
+            query->towire.p = revert;
+            query->towire.error = 0;
+            dp_push_response(query);
+            dns_push_start(query);
+            goto re_add;
+        }
+
         // If there isn't more coming, send a DNS Push notification now.
         // XXX If enough comes to fill the response, send the message.
         if (!(flags & kDNSServiceFlagsMoreComing)) {
@@ -858,18 +1315,19 @@ dns_push_query_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t int
 }
 
 void
-dns_push_subscribe(comm_t *comm, dns_wire_t *header, dso_state_t *dso, dns_rr_t *question,
+dns_push_subscribe(comm_t *comm, const dns_wire_t *header, dso_state_t *dso, dns_rr_t *question,
                    const char *activity_name, const char *opcode_name)
 {
     int rcode;
     dnssd_query_t *query = dp_query_generate(comm, question, true, &rcode);
-    
+
     if (!query) {
         dp_simple_response(comm, rcode);
         return;
     }
 
-    dso_activity_t *activity = dso_add_activity(dso, activity_name, push_subscription_activity_type, query, dns_push_finalize);
+    dso_activity_t *activity = dso_add_activity(dso, activity_name, push_subscription_activity_type, query,
+                                                dns_push_finalize);
     query->activity = activity;
     if (!dp_query_start(comm, query, &rcode, dns_push_query_callback)) {
         dso_drop_activity(dso, activity);
@@ -880,7 +1338,7 @@ dns_push_subscribe(comm_t *comm, dns_wire_t *header, dso_state_t *dso, dns_rr_t
 }
 
 void
-dns_push_reconfirm(comm_t *comm, dns_wire_t *header, dso_state_t *dso)
+dns_push_reconfirm(comm_t *comm, const dns_wire_t *header, dso_state_t *dso)
 {
     dns_rr_t question;
     char name[DNS_MAX_NAME_SIZE + 1];
@@ -889,7 +1347,7 @@ dns_push_reconfirm(comm_t *comm, dns_wire_t *header, dso_state_t *dso)
     // The TLV offset should always be pointing into the message.
     unsigned offp = dso->primary.payload - &header->data[0];
     int len = offp + dso->primary.length;
-    
+
     // Parse the name, rrtype and class.   We say there's no rdata even though there is
     // because there's no ttl and also we want the raw rdata, not parsed rdata.
     if (!dns_rr_parse(&question, header->data, len, &offp, false) ||
@@ -923,7 +1381,7 @@ dns_push_reconfirm(comm_t *comm, dns_wire_t *header, dso_state_t *dso)
 }
 
 void
-dns_push_unsubscribe(comm_t *comm, dns_wire_t *header, dso_state_t *dso, dns_rr_t *question,
+dns_push_unsubscribe(comm_t *comm, const dns_wire_t *header, dso_state_t *dso, dns_rr_t *question,
                    dso_activity_t *activity, const char *opcode_name)
 {
     dso_drop_activity(dso, activity);
@@ -931,12 +1389,12 @@ dns_push_unsubscribe(comm_t *comm, dns_wire_t *header, dso_state_t *dso, dns_rr_
 }
 
 void
-dns_push_subscription_change(const char *opcode_name, comm_t *comm, dns_wire_t *header, dso_state_t *dso)
+dns_push_subscription_change(const char *opcode_name, comm_t *comm, const dns_wire_t *header, dso_state_t *dso)
 {
     // type-in-hex/class-in-hex/name-to-subscribe
     char activity_name[DNS_MAX_NAME_SIZE_ESCAPED + 3 + 4 + 4];
     dso_activity_t *activity;
-    
+
     // The TLV offset should always be pointing into the message.
     unsigned offp = dso->primary.payload - &header->data[0];
     // Get the question
@@ -959,11 +1417,12 @@ dns_push_subscription_change(const char *opcode_name, comm_t *comm, dns_wire_t *
             ERROR("activity name overflow for %s", activity_name);
             return;
         }
-        strncpy(&activity_name[len], local_suffix, sizeof local_suffix);
+        const int lslen = sizeof local_suffix;
+        strncpy(&activity_name[len], local_suffix, lslen);
     } else {
         dns_name_print(question.name, &activity_name[8], (sizeof activity_name) - 8);
     }
-    
+
     activity = dso_find_activity(dso, activity_name, push_subscription_activity_type, NULL);
     if (activity == NULL) {
         // Unsubscribe with no activity means no work to do; just return noerror.
@@ -980,7 +1439,7 @@ dns_push_subscription_change(const char *opcode_name, comm_t *comm, dns_wire_t *
         // Subscribe with a matching activity means no work to do; just return noerror.
         if (dso->primary.opcode == kDSOType_DNSPushSubscribe) {
             dp_simple_response(comm, dns_rcode_noerror);
-        }            
+        }
         // Otherwise cancel the subscription.
         else {
             dns_push_unsubscribe(comm, header, dso, &question, activity, opcode_name);
@@ -988,7 +1447,7 @@ dns_push_subscription_change(const char *opcode_name, comm_t *comm, dns_wire_t *
     }
 }
 
-static void dso_message(comm_t *comm, dns_wire_t *header, dso_state_t *dso)
+static void dso_message(comm_t *comm, const dns_wire_t *header, dso_state_t *dso)
 {
     switch(dso->primary.opcode) {
     case kDSOType_DNSPushSubscribe:
@@ -1001,7 +1460,7 @@ static void dso_message(comm_t *comm, dns_wire_t *header, dso_state_t *dso)
     case kDSOType_DNSPushReconfirm:
         dns_push_reconfirm(comm, header, dso);
         break;
-        
+
     case kDSOType_DNSPushUpdate:
         INFO("dso_message: bogus push update message %d", dso->primary.opcode);
         dso_drop(dso);
@@ -1015,46 +1474,66 @@ static void dso_message(comm_t *comm, dns_wire_t *header, dso_state_t *dso)
     // XXX free the message if we didn't consume it.
 }
 
-static void dns_push_callback(void *context, void *header_context,
+static void dns_push_callback(void *context, const void *event_context,
                               dso_state_t *dso, dso_event_type_t eventType)
 {
-    dns_wire_t *header = header_context;
-       switch(eventType)
+    const dns_wire_t *message;
+    switch(eventType)
     {
-       case kDSOEventType_DNSMessage:
+    case kDSOEventType_DNSMessage:
         // We shouldn't get here because we already handled any DNS messages
-               INFO("dns_push_callback: DNS Message (opcode=%d) received from %s", dns_opcode_get(header), dso->remote_name);
-               break;
-       case kDSOEventType_DNSResponse:
+        message = event_context;
+        INFO("dns_push_callback: DNS Message (opcode=%d) received from " PRI_S_SRP, dns_opcode_get(message),
+             dso->remote_name);
+        break;
+    case kDSOEventType_DNSResponse:
         // We shouldn't get here because we already handled any DNS messages
-               INFO("dns_push_callback: DNS Response (opcode=%d) received from %s", dns_opcode_get(header), dso->remote_name);
-               break;
-       case kDSOEventType_DSOMessage:
-               INFO("dns_push_callback: DSO Message (Primary TLV=%d) received from %s",
+        message = event_context;
+        INFO("dns_push_callback: DNS Response (opcode=%d) received from " PRI_S_SRP, dns_opcode_get(message),
+             dso->remote_name);
+        break;
+    case kDSOEventType_DSOMessage:
+        INFO("dns_push_callback: DSO Message (Primary TLV=%d) received from " PRI_S_SRP,
                dso->primary.opcode, dso->remote_name);
-        dso_message((comm_t *)context, (dns_wire_t *)header, dso);
-               break;
-       case kDSOEventType_DSOResponse:
-               INFO("dns_push_callback: DSO Response (Primary TLV=%d) received from %s",
+        message = event_context;
+        dso_message((comm_t *)context, message, dso);
+        break;
+    case kDSOEventType_DSOResponse:
+        INFO("dns_push_callback: DSO Response (Primary TLV=%d) received from " PRI_S_SRP,
                dso->primary.opcode, dso->remote_name);
-               break;
+        break;
 
-       case kDSOEventType_Finalize:
-               INFO("dns_push_callback: Finalize");
-               break;
+    case kDSOEventType_Finalize:
+        INFO("dns_push_callback: Finalize");
+        break;
 
-       case kDSOEventType_Connected:
-               INFO("dns_push_callback: Connected to %s", dso->remote_name);
-               break;
+    case kDSOEventType_Connected:
+        INFO("dns_push_callback: Connected to " PRI_S_SRP, dso->remote_name);
+        break;
 
-       case kDSOEventType_ConnectFailed:
-               INFO("dns_push_callback: Connection to %s failed", dso->remote_name);
-               break;
+    case kDSOEventType_ConnectFailed:
+        INFO("dns_push_callback: Connection to " PRI_S_SRP " failed", dso->remote_name);
+        break;
 
-       case kDSOEventType_Disconnected:
-               INFO("dns_push_callback: Connection to %s disconnected", dso->remote_name);
-               break;
-       }
+    case kDSOEventType_Disconnected:
+        INFO("dns_push_callback: Connection to " PRI_S_SRP " disconnected", dso->remote_name);
+        break;
+    case kDSOEventType_ShouldReconnect:
+        INFO("dns_push_callback: Connection to " PRI_S_SRP " should reconnect (not for a server)", dso->remote_name);
+        break;
+    case kDSOEventType_Inactive:
+        INFO("dns_push_callback: Inactivity timer went off, closing connection.");
+        break;
+    case kDSOEventType_Keepalive:
+        INFO("dns_push_callback: should send a keepalive now.");
+        break;
+    case kDSOEventType_KeepaliveRcvd:
+        INFO("dns_push_callback: keepalive received.");
+        break;
+    case kDSOEventType_RetryDelay:
+        INFO("dns_push_callback: keepalive received.");
+        break;
+    }
 }
 
 void
@@ -1069,27 +1548,27 @@ dp_dns_query(comm_t *comm, dns_rr_t *question)
     }
 
     // For regular DNS queries, copy the ID, etc.
-    query->response.id = comm->message->wire.id;
-    query->response.bitfield = comm->message->wire.bitfield;
-    dns_rcode_set(&query->response, dns_rcode_noerror);
+    query->response->id = comm->message->wire.id;
+    query->response->bitfield = comm->message->wire.bitfield;
+    dns_rcode_set(query->response, dns_rcode_noerror);
 
     // For DNS queries, we need to return the question.
-    query->response.qdcount = htons(1);
-    if (query->enclosing_domain != NULL) {
+    query->response->qdcount = htons(1);
+    if (query->served_domain != NULL) {
         TOWIRE_CHECK("name", &query->towire, dns_name_to_wire(NULL, &query->towire, query->name));
         TOWIRE_CHECK("enclosing_domain", &query->towire,
                      dns_full_name_to_wire(&query->enclosing_domain_pointer,
-                                           &query->towire, query->enclosing_domain));
+                                           &query->towire, query->served_domain->domain));
     } else {
         TOWIRE_CHECK("full name", &query->towire, dns_full_name_to_wire(NULL, &query->towire, query->name));
-    }        
+    }
     TOWIRE_CHECK("TYPE", &query->towire, dns_u16_to_wire(&query->towire, question->type));    // TYPE
     TOWIRE_CHECK("CLASS", &query->towire, dns_u16_to_wire(&query->towire, question->qclass));  // CLASS
     if (failnote != NULL) {
         ERROR("dp_dns_query: failure encoding question: %s", failnote);
         goto fail;
     }
-    
+
     // We should check for OPT RR, but for now assume it's there.
     query->is_edns0 = true;
 
@@ -1100,16 +1579,18 @@ dp_dns_query(comm_t *comm, dns_rr_t *question)
         free(query);
         return;
     }
-    
+
     // XXX make sure that finalize frees this.
-    query->question = comm->message;
-    comm->message = NULL;
+    if (comm->message) {
+        query->question = comm->message;
+        comm->message = NULL;
+    }
 }
 
 void dso_transport_finalize(comm_t *comm)
 {
     dso_state_t *dso = comm->dso;
-    INFO("dso_transport_finalize: %s", dso->remote_name);
+    INFO("dso_transport_finalize: " PRI_S_SRP, dso->remote_name);
     if (comm) {
         ioloop_close(&comm->io);
     }
@@ -1135,7 +1616,7 @@ void dns_evaluate(comm_t *comm)
             dp_simple_response(comm, dns_rcode_notimp);
             return;
         }
-        
+
         if (!comm->dso) {
             comm->dso = dso_create(true, 0, comm->name, dns_push_callback, comm, comm);
             if (!comm->dso) {
@@ -1192,50 +1673,495 @@ usage(const char *progname)
 void
 connected(comm_t *comm)
 {
-    INFO("connection from %s", comm->name);
+    INFO("connection from " PRI_S_SRP, comm->name);
     return;
 }
 
+static bool config_string_handler(char **ret, const char *filename, const char *string, int lineno, bool tdot,
+                                  bool ldot)
+{
+    char *s;
+    int add_trailing_dot = 0;
+    int add_leading_dot = ldot ? 1 : 0;
+    int len = strlen(string);
+
+    // Space for NUL and leading dot.
+    if (tdot && len > 0 && string[len - 1] != '.') {
+        add_trailing_dot = 1;
+    }
+    s = malloc(strlen(string) + add_leading_dot + add_trailing_dot + 1);
+    if (s == NULL) {
+        ERROR("Unable to allocate domain name %s", string);
+        return false;
+    }
+    *ret = s;
+    if (ldot) {
+        *s++ = '.';
+    }
+    strcpy(s, string);
+    if (add_trailing_dot) {
+        s[len] = '.';
+        s[len + 1] = 0;
+    }
+    return true;
+}
+
+static served_domain_t *
+new_served_domain(interface_t *interface, char *domain)
+{
+    served_domain_t *sdt = calloc(1, sizeof *sdt);
+    if (sdt == NULL) {
+        ERROR("Unable to allocate served domain %s", domain);
+        return NULL;
+    }
+    sdt->domain_ld = malloc(strlen(domain) + 2);
+    if (sdt->domain_ld == NULL) {
+        ERROR("Unable to allocate served domain name %s", domain);
+        free(sdt);
+        return NULL;
+    }
+    sdt->domain_ld[0] = '.';
+    sdt->domain = sdt->domain_ld + 1;
+    strcpy(sdt->domain, domain);
+    sdt->domain_name = dns_pres_name_parse(sdt->domain);
+    sdt->interface = interface;
+    if (sdt->domain_name == NULL) {
+        if (interface != NULL) {
+            ERROR("invalid domain name for interface %s: %s", interface->name, sdt->domain);
+        } else {
+            ERROR("invalid domain name: %s", sdt->domain);
+        }
+        free(sdt);
+        return NULL;
+    }
+    sdt->next = served_domains;
+    served_domains = sdt;
+    return sdt;
+}
+
+// Dynamic interface detection...
+// This is called whenever a new interface address is encountered.  At present, this is only called
+// once for each interface address, on startup, but in principle it _could_ be called whenever an
+// interface is added or deleted, or is assigned or loses an address.
+void
+ifaddr_callback(void *context, const char *name, const addr_t *address, const addr_t *mask,
+                int ifindex, enum interface_address_change event_type)
+{
+    served_domain_t *sd;
+
+    if (address->sa.sa_family == AF_INET) {
+        IPv4_ADDR_GEN_SRP((const uint8_t *)&address->sin.sin_addr, addr_buf);
+        IPv4_ADDR_GEN_SRP((const uint8_t *)&mask->sin.sin_addr, mask_buf);
+        INFO("Interface " PUB_S_SRP " address " PRI_IPv4_ADDR_SRP " mask " PRI_IPv4_ADDR_SRP " index %d " PUB_S_SRP,
+             name, IPv4_ADDR_PARAM_SRP((const uint8_t *)&address->sin.sin_addr, addr_buf),
+             IPv4_ADDR_PARAM_SRP((const uint8_t *)&mask->sin.sin_addr, mask_buf), ifindex,
+             event_type == interface_address_added ? "added" : "removed");
+    } else if (address->sa.sa_family == AF_INET6) {
+        IPv6_ADDR_GEN_SRP((const uint8_t *)&address->sin.sin_addr, addr_buf);
+        IPv6_ADDR_GEN_SRP((const uint8_t *)&mask->sin.sin_addr, mask_buf);
+        INFO("Interface " PUB_S_SRP " address " PRI_IPv6_ADDR_SRP " mask " PRI_IPv6_ADDR_SRP " index %d " PUB_S_SRP,
+             name, IPv6_ADDR_PARAM_SRP((const uint8_t *)&address->sin.sin_addr, addr_buf),
+             IPv6_ADDR_PARAM_SRP((const uint8_t *)&mask->sin.sin_addr, mask_buf), ifindex,
+             event_type == interface_address_added ? "added" : "removed");
+    } else {
+        INFO("Interface " PUB_S_SRP " address type %d index %d " PUB_S_SRP, name, address->sa.sa_family, ifindex,
+             event_type == interface_address_added ? "added" : "removed");
+    }
+
+    for (sd = *((served_domain_t **)context); sd; sd = sd->next) {
+        if (sd->interface != NULL && !strcmp(sd->interface->name, name)) {
+            interface_addr_t **app, *ifaddr;
+            if (event_type == interface_address_added) {
+                for (app = &sd->interface->addresses; *app; app = &(*app)->next)
+                    ;
+                ifaddr = calloc(1, sizeof *ifaddr);
+                sd->interface->ifindex = ifindex;
+                if (ifaddr != NULL) {
+                    ifaddr->addr = *address;
+                    ifaddr->mask = *mask;
+                    *app = ifaddr;
+                }
+            } else if (event_type == interface_address_deleted) {
+                for (app = &sd->interface->addresses; *app; ) {
+                    ifaddr = *app;
+                    if (ifaddr->addr.sa.sa_family == address->sa.sa_family &&
+                        ((address->sa.sa_family == AF_INET &&
+                          ifaddr->addr.sin.sin_addr.s_addr == address->sin.sin_addr.s_addr &&
+                          ifaddr->mask.sin.sin_addr.s_addr == address->sin.sin_addr.s_addr) ||
+                         (address->sa.sa_family == AF_INET6 &&
+                          !memcmp(&ifaddr->addr.sin6.sin6_addr, &address->sin6.sin6_addr, sizeof address->sin6.sin6_addr) &&
+                          !memcmp(&ifaddr->mask.sin6.sin6_addr, &mask->sin6.sin6_addr, sizeof mask->sin6.sin6_addr))))
+                    {
+                        *app = ifaddr->next;
+                        free(ifaddr);
+                    } else {
+                        app = &ifaddr->next;
+                    }
+                }
+                if (sd->interface->addresses == NULL) {
+                    sd->interface->ifindex = 0;
+                }
+            }
+        }
+    }
+}
+
+// Config file parsing...
+static bool
+interface_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
+{
+    interface_t *interface = calloc(1, sizeof *interface);
+    if (interface == NULL) {
+        ERROR("Unable to allocate interface %s", hunks[1]);
+        return false;
+    }
+
+    interface->name = strdup(hunks[1]);
+    if (interface->name == NULL) {
+        ERROR("Unable to allocate interface name %s", hunks[1]);
+        free(interface);
+        return false;
+    }
+
+    if (!strcmp(hunks[0], "nopush")) {
+        interface->no_push = true;
+    }
+
+    if (new_served_domain(interface, hunks[2]) == NULL) {
+        free(interface->name);
+        free(interface);
+        return false;
+    }
+    return true;
+}
+
+static bool port_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
+{
+    char *ep = NULL;
+    long port = strtol(hunks[1], &ep, 10);
+    if (port < 0 || port > 65535 || *ep != 0) {
+        ERROR("Invalid port number: %s", hunks[1]);
+        return false;
+    }
+    if (!strcmp(hunks[0], "udp-port")) {
+        udp_port = port;
+    } else if (!strcmp(hunks[0], "tcp-port")) {
+        tcp_port = port;
+    } else if (!strcmp(hunks[0], "tls-port")) {
+        tls_port = port;
+    }
+    return true;
+}
+
+static bool my_name_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
+{
+    return config_string_handler(&my_name, filename, hunks[1], lineno, true, false);
+}
+
+static bool listen_addr_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
+{
+    if (num_listen_addrs == MAX_ADDRS) {
+        ERROR("Only %d IPv4 listen addresses can be configured.", MAX_ADDRS);
+        return false;
+    }
+    return config_string_handler(&listen_addrs[num_listen_addrs++], filename, hunks[1], lineno, false, false);
+}
+
+static bool publish_addr_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
+{
+    if (num_publish_addrs == MAX_ADDRS) {
+        ERROR("Only %d addresses can be published.", MAX_ADDRS);
+        return false;
+    }
+    return config_string_handler(&publish_addrs[num_publish_addrs++], filename, hunks[1], lineno, false, false);
+}
+
+static bool tls_key_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
+{
+    return config_string_handler(&tls_key_filename, filename, hunks[1], lineno, false, false);
+}
+
+static bool tls_cert_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
+{
+    return config_string_handler(&tls_cert_filename, filename, hunks[1], lineno, false, false);
+}
+
+static bool tls_cacert_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
+{
+    return config_string_handler(&tls_cacert_filename, filename, hunks[1], lineno, false, false);
+}
+
+config_file_verb_t dp_verbs[] = {
+    { "interface",    3, 3, interface_handler },    // interface <name> <domain>
+    { "nopush",       3, 3, interface_handler },    // nopush <name> <domain>
+    { "udp-port",     2, 2, port_handler },         // udp-port <number>
+    { "tcp-port",     2, 2, port_handler },         // tcp-port <number>
+    { "tls-port",     2, 2, port_handler },         // tls-port <number>
+    { "my-name",      2, 2, my_name_handler },      // my-name <domain name>
+    { "tls-key",      2, 2, tls_key_handler },      // tls-key <filename>
+    { "tls-cert",     2, 2, tls_cert_handler },     // tls-cert <filename>
+    { "tls-cacert",   2, 2, tls_cacert_handler },   // tls-cacert <filename>
+    { "listen-addr",  2, 2, listen_addr_handler },  // listen-addr <IP address>
+    { "publish-addr", 2, 2, publish_addr_handler }  // publish-addr <IP address>
+};
+#define NUMCFVERBS ((sizeof dp_verbs) / sizeof (config_file_verb_t))
+
+void
+dnssd_push_setup()
+{
+    listener[num_listeners] = ioloop_setup_listener(AF_INET, true, true, tls_port, NULL, NULL,
+                                                    "IPv4 DNS Push Listener", dns_input, NULL, NULL, NULL, NULL, NULL);
+    if (listener[num_listeners] == NULL) {
+        ERROR("IPv4 DNS Push listener: fail.");
+        return;
+    }
+    num_listeners++;
+
+    listener[num_listeners] = ioloop_setup_listener(AF_INET6, true, true,
+                                                    "IPv6 DNS Push Listener", dns_input, NULL, NULL, NULL, NULL, NULL);
+    if (listener[num_listeners] == NULL) {
+        ERROR("IPv6 DNS Push listener: fail.");
+        return;
+    }
+    num_listeners++;
+
+    dnssd_hardwired_push_setup();
+}
+
+// Start a key generation or cert signing program.   Arguments are key=value pairs.
+// Arguments that can be constant should be <"key=value", NULL>.   Arguments that
+// have a variable component should be <"key", value">.  References to arguments
+// will be held, except that if the rhs of the pair is variable, memory is allocated
+// to store the key=value pair, so the neither the key nor the value is retained.
+// The callback is called when the program exits.
+
+void
+keyprogram_start(const char *program, subproc_callback_t callback, ...)
+{
+#define MAX_SUBPROC_VARS 3
+    size_t lens[MAX_SUBPROC_VARS];
+    char *vars[MAX_SUBPROC_VARS];
+    int num_vars = 0;
+    char *argv[MAX_SUBPROC_ARGS + 1];
+    int argc = 0;
+    va_list vl;
+    int i;
+
+    va_start(vl, callback);
+    while (true) {
+        char *vname, *value;
+        char *arg;
+
+        vname = va_arg(vl, char *);
+        if (vname == NULL) {
+            break;
+        }
+        value = va_arg(vl, char *);
+
+        if (argc >= MAX_SUBPROC_ARGS) {
+            ERROR("keyprogram_start: too many arguments.");
+        }
+
+        if (value == NULL) {
+            arg = vname;
+        } else {
+            if (num_vars >= MAX_SUBPROC_VARS) {
+                ERROR("Too many variable args: %s %s", vname, value);
+                goto out;
+            }
+            lens[num_vars] = strlen(vname) + strlen(value) + 2;
+            vars[num_vars] = malloc(lens[num_vars]);
+            if (vars[num_vars] == NULL) {
+                ERROR("No memory for variable key=value %s %s", vname, value);
+                goto out;
+            }
+            snprintf(vars[num_vars], lens[num_vars], "%s=%s", vname, value);
+            arg = vars[num_vars];
+            num_vars++;
+        }
+        argv[argc++] = arg;
+    }
+    argv[argc] = NULL;
+    ioloop_subproc(program, argv, argc, callback);
+out:
+    for (i = 0; i < num_vars; i++) {
+        free(vars[i]);
+    }
+}
+
+bool
+finished_okay(const char *context, int status, const char *error)
+{
+    // If we get an error, something failed before the program had been successfully started.
+    if (error != NULL) {
+        ERROR("%s failed on startup: %s", context, error);
+    }
+
+    // The key file generation process completed
+    else if (WIFEXITED(status)) {
+        if (WEXITSTATUS(status) != 0) {
+            ERROR("%s program exited with status %d", context, status);
+            // And that means we don't have DNS Push--sorry!
+        } else {
+            return true;
+        }
+    } else if (WIFSIGNALED(status)) {
+        ERROR("%s program exited on signal %d", context, WTERMSIG(status));
+        // And that means we don't have DNS Push--sorry!
+    } else if (WIFSTOPPED(status)) {
+        ERROR("%s program stopped on signal %d", context, WSTOPSIG(status));
+        // And that means we don't have DNS Push--sorry!
+    } else {
+        ERROR("%s program exit status unknown: %d", context, status);
+        // And that means we don't have DNS Push--sorry!
+    }
+    return false;
+}
+
+// Called after the cert has been generated.
+void
+certfile_finished_callback(subproc_t *subproc, int status, const char *error)
+{
+    // If we were able to generate a cert, we can start DNS Push service and start advertising it.
+    if (finished_okay("Certificate signing", status, error)) {
+        int i = num_listeners;
+
+        dnssd_push_setup();
+
+        for (; i < num_listeners; i++) {
+            INFO("Started " PUB_S_SRP, listener[i]->name);
+        }
+    }
+}
+
+// Called after the key has been generated.
+void
+keyfile_finished_callback(subproc_t *subproc, int status, const char *error)
+{
+    if (finished_okay("Keyfile generation", status, error)) {
+            INFO("Keyfile generation completed.");
+
+            // XXX dates need to not be constant!!!
+            keyprogram_start(CERTWRITE_PROGRAM, certfile_finished_callback,
+                             "selfsign=1", NULL, "issuer_key", tls_key_filename, "issuer_name=CN", my_name,
+                             "not_before=20190226000000", NULL, "not_after=20211231235959", NULL, "is_ca=1", NULL,
+                             "max_pathlen=0", NULL, "output_file", tls_cert_filename, NULL);
+    }
+
+}
+
 int
 main(int argc, char **argv)
 {
     int i;
-    int16_t port;
-    comm_t *tcp4_listener;
-    comm_t *udp4_listener;
+    bool tls_fail = false;
 
-    port = htons(53);
+    udp_port = tcp_port = 53;
+    tls_port = 853;
 
-    // Read the configuration from the command line.
+    // Parse command line arguments
     for (i = 1; i < argc; i++) {
-        return usage(argv[0]);
+        if (!strcmp(argv[i], "--tls-fail")) {
+            tls_fail = true;
+        } else {
+            return usage(argv[0]);
+        }
     }
 
-    if (!ioloop_init()) {
+    // Read the config file
+    if (!config_parse(NULL, "/etc/dnssd-proxy.cf", dp_verbs, NUMCFVERBS)) {
         return 1;
     }
 
+    // Insist that we have at least one address we're listening on.
+    if (num_listen_addrs == 0 && num_publish_addrs == 0) {
+        ERROR("Please configure at least one my-ipv4-addr and/or one my-ipv6-addr.");
+        return 1;
+    }
+
+    ioloop_map_interface_addresses(&served_domains, ifaddr_callback);
+
     // Set up hardwired answers
     dnssd_hardwired_setup();
 
-    // XXX Support IPv6!
-    tcp4_listener = setup_listener_socket(AF_INET, IPPROTO_TCP, port, "IPv4 DNS Push Listener", dns_input, connected, 0);
-    if (tcp4_listener == NULL) {
+#ifndef EXCLUDE_TLS
+    if (!srp_tls_init()) {
+        return 1;
+    }
+
+    // The tls_fail flag allows us to run the proxy in such a way that TLS connections will fail.
+    // This is never what you want in production, but is useful for testing.
+    if (!tls_fail) {
+        if (access(tls_key_filename, R_OK) < 0) {
+            keyprogram_start(GENKEY_PROGRAM, keyfile_finished_callback,
+                             "type=rsa", NULL, "rsa_keysize=4096", NULL, "filename", tls_key_filename, NULL);
+        } else if (access(tls_cert_filename, R_OK) < 0) {
+            keyfile_finished_callback(NULL, 0, NULL);
+        } else if (srp_tls_server_init(NULL, tls_cert_filename, tls_key_filename)) {
+            // If we've been able to set up TLS, then we can do DNS push.
+            dnssd_push_setup();
+        }
+    }
+#endif
+
+    if (!ioloop_init()) {
+        return 1;
+    }
+
+    for (i = 0; i < num_listen_addrs; i++) {
+        listener[num_listeners] = ioloop_setup_listener(AF_UNSPEC, false, false, udp_port, listen_addrs[i], NULL,
+                                                        "DNS UDP Listener", dns_input, NULL, NULL, NULL, NULL, NULL);
+        if (listener[num_listeners] == NULL) {
+            ERROR("UDP listener %s: fail.", listen_addrs[i]);
+            return 1;
+        }
+        num_listeners++;
+    }
+
+    listener[num_listeners] = ioloop_setup_listener(AF_INET, true, false, tcp_port, NULL, NULL,
+                                                    "IPv4 TCP DNS Listener", dns_input, NULL, NULL, NULL, NULL, NULL);
+    if (listener[num_listeners] == NULL) {
         ERROR("TCPv4 listener: fail.");
         return 1;
+    } else {
+        num_listeners++;
     }
-    
-    udp4_listener = setup_listener_socket(AF_INET, IPPROTO_UDP, port, "IPv4 DNS UDP Listener", dns_input, 0, 0);
-    if (udp4_listener == NULL) {
-        ERROR("UDP4 listener: fail.");
+
+    listener[num_listeners] = ioloop_setup_listener(AF_INET6, true, false, tcp_port, NULL, NULL,
+                                                    "IPv6 TCP DNS Listener", dns_input, NULL, NULL, NULL, NULL, NULL);
+    if (listener[num_listeners] == NULL) {
+        ERROR("TCPv6 listener: fail.");
         return 1;
+    } else {
+        num_listeners++;
     }
-    
-    do {
-        int something = 0;
-        something = ioloop_events(0);
-        INFO("dispatched %d events.", something);
-    } while (1);
+
+    // If we haven't been given any addresses to listen on, listen on an IPv4 address and an IPv6 address.
+    if (num_listen_addrs == 0) {
+        listener[num_listeners] = ioloop_setup_listener(AF_INET, IPPROTO_UDP, false, udp_port, NULL, NULL,
+                                                        "IPv4 DNS UDP Listener", dns_input, NULL, NULL, NULL, NULL,
+                                                        NULL);
+        if (listener[num_listeners] == NULL) {
+            ERROR("UDP4 listener: fail.");
+            return 1;
+        }
+        num_listeners++;
+
+        listener[num_listeners] = ioloop_setup_listener(AF_INET6, IPPROTO_UDP, false, udp_port, NULL, NULL,
+                                                        "IPv6 DNS UDP Listener", dns_input, NULL, NULL, NULL, NULL,
+                                                        NULL);
+       if (listener[num_listeners] == NULL) {
+            ERROR("UDP6 listener: fail.");
+            return 1;
+        }
+        num_listeners++;
+    }
+
+    for (i = 0; i < num_listeners; i++) {
+        INFO("Started " PUB_S_SRP, listener[i]->name);
+    }
+
+    ioloop();
 }
 
 // Local Variables:
diff --git a/ServiceRegistration/dnssd-relay.c b/ServiceRegistration/dnssd-relay.c
new file mode 100644 (file)
index 0000000..b47f346
--- /dev/null
@@ -0,0 +1,532 @@
+/* dnssd-relay.c
+ *
+ * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This is a Discovery Proxy module for the SRP gateway.
+ *
+ * The motivation here is that it makes sense to co-locate the SRP relay and the Discovery Proxy because
+ * these functions are likely to co-exist on the same node, listening on the same port.  For homenet-style
+ * name resolution, we need a DNS proxy that implements DNSSD Discovery Proxy for local queries, but
+ * forwards other queries to an ISP resolver.  The SRP gateway is already expecting to do this.
+ * This module implements the functions required to allow the SRP gateway to also do Discovery Relay.
+ *
+ * The Discovery Proxy relies on Apple's DNS-SD library and the mDNSResponder DNSSD server, which is included
+ * in Apple's open source mDNSResponder package, available here:
+ *
+ *            https://opensource.apple.com/tarballs/mDNSResponder/
+ */
+
+#define __APPLE_USE_RFC_3542
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <ifaddrs.h>
+#include <net/if.h>
+
+#include "dns_sd.h"
+#include "srp.h"
+#include "dns-msg.h"
+#include "srp-crypto.h"
+#define DNSMessageHeader dns_wire_t
+#include "dso.h"
+#include "ioloop.h"
+#include "srp-tls.h"
+#include "config-parse.h"
+
+// Enumerate the list of interfaces, map them to interface indexes, give each one a name
+// Have a tree of subdomains for matching
+
+// Configuration file settings
+uint16_t udp_port;
+uint16_t tcp_port;
+uint16_t tls_port;
+#define MAX_ADDRS 10
+char *listen_addrs[MAX_ADDRS];
+int num_listen_addrs = 0;
+char *publish_addrs[MAX_ADDRS];
+int num_publish_addrs = 0;
+char *tls_cacert_filename = NULL;
+char *tls_cert_filename = "/etc/dnssd-relay/server.crt";
+char *tls_key_filename = "/etc/dnssd-relay/server.key";
+
+// Code
+
+int64_t dso_transport_idle(void *context, int64_t now, int64_t next_event)
+{
+    return next_event;
+}
+
+void
+dp_simple_response(comm_t *comm, int rcode)
+{
+    if (comm->send_response) {
+        struct iovec iov;
+        dns_wire_t response;
+        memset(&response, 0, DNS_HEADER_SIZE);
+
+        // We take the ID and the opcode from the incoming message, because if the
+        // header has been mangled, we (a) wouldn't have gotten here and (b) don't
+        // have any better choice anyway.
+        response.id = comm->message->wire.id;
+        dns_qr_set(&response, dns_qr_response);
+        dns_opcode_set(&response, dns_opcode_get(&comm->message->wire));
+        dns_rcode_set(&response, rcode);
+        iov.iov_base = &response;
+        iov.iov_len = DNS_HEADER_SIZE; // No RRs
+        comm->send_response(comm, comm->message, &iov, 1);
+    }
+}
+
+bool
+dso_send_formerr(dso_state_t *dso, const dns_wire_t *header)
+{
+    comm_t *transport = dso->transport;
+    (void)header;
+    dp_simple_response(transport, dns_rcode_formerr);
+    return true;
+}
+
+static void dso_message(comm_t *comm, const dns_wire_t *header, dso_state_t *dso)
+{
+    switch(dso->primary.opcode) {
+    case kDSOType_DNSPushSubscribe:
+        dns_push_subscription_change("DNS Push Subscribe", comm, header, dso);
+        break;
+    case kDSOType_DNSPushUnsubscribe:
+        dns_push_subscription_change("DNS Push Unsubscribe", comm, header, dso);
+        break;
+
+    case kDSOType_DNSPushReconfirm:
+        dns_push_reconfirm(comm, header, dso);
+        break;
+
+    case kDSOType_DNSPushUpdate:
+        INFO("dso_message: bogus push update message %d", dso->primary.opcode);
+        dso_drop(dso);
+        break;
+
+    default:
+        INFO("dso_message: unexpected primary TLV %d", dso->primary.opcode);
+        dp_simple_response(comm, dns_rcode_dsotypeni);
+        break;
+    }
+    // XXX free the message if we didn't consume it.
+}
+
+static void dns_push_callback(void *context, const void *event_context,
+                              dso_state_t *dso, dso_event_type_t eventType)
+{
+    const dns_wire_t *message;
+    switch(eventType)
+    {
+    case kDSOEventType_DNSMessage:
+        // We shouldn't get here because we already handled any DNS messages
+        message = event_context;
+        INFO("dns_push_callback: DNS Message (opcode=%d) received from " PRI_S_SRP, dns_opcode_get(message),
+             dso->remote_name);
+        break;
+    case kDSOEventType_DNSResponse:
+        // We shouldn't get here because we already handled any DNS messages
+        message = event_context;
+        INFO("dns_push_callback: DNS Response (opcode=%d) received from " PRI_S_SRP, dns_opcode_get(message),
+             dso->remote_name);
+        break;
+    case kDSOEventType_DSOMessage:
+        INFO("dns_push_callback: DSO Message (Primary TLV=%d) received from " PRI_S_SRP,
+             dso->primary.opcode, dso->remote_name);
+        message = event_context;
+        dso_message((comm_t *)context, message, dso);
+        break;
+    case kDSOEventType_DSOResponse:
+        INFO("dns_push_callback: DSO Response (Primary TLV=%d) received from " PRI_S_SRP,
+             dso->primary.opcode, dso->remote_name);
+        break;
+
+    case kDSOEventType_Finalize:
+        INFO("dns_push_callback: Finalize");
+        break;
+
+    case kDSOEventType_Connected:
+        INFO("dns_push_callback: Connected to " PRI_S_SRP, dso->remote_name);
+        break;
+
+    case kDSOEventType_ConnectFailed:
+        INFO("dns_push_callback: Connection to " PRI_S_SRP " failed", dso->remote_name);
+        break;
+
+    case kDSOEventType_Disconnected:
+        INFO("dns_push_callback: Connection to " PRI_S_SRP " disconnected", dso->remote_name);
+        break;
+    case kDSOEventType_ShouldReconnect:
+        INFO("dns_push_callback: Connection to " PRI_S_SRP " should reconnect (not for a server)", dso->remote_name);
+        break;
+    case kDSOEventType_Inactive:
+        INFO("dns_push_callback: Inactivity timer went off, closing connection.");
+        // XXX
+        break;
+    case kDSOEventType_Keepalive:
+        INFO("dns_push_callback: should send a keepalive now.");
+        break;
+    case kDSOEventType_KeepaliveRcvd:
+        INFO("dns_push_callback: keepalive received.");
+        break;
+    case kDSOEventType_RetryDelay:
+        INFO("dns_push_callback: keepalive received.");
+        break;
+    }
+}
+
+void
+dp_dns_query(comm_t *comm, dns_rr_t *question)
+{
+    int rcode;
+    dnssd_query_t *query = dp_query_generate(comm, question, false, &rcode);
+    const char *failnote = NULL;
+    if (!query) {
+        dp_simple_response(comm, rcode);
+        return;
+    }
+
+    // For regular DNS queries, copy the ID, etc.
+    query->response->id = comm->message->wire.id;
+    query->response->bitfield = comm->message->wire.bitfield;
+    dns_rcode_set(query->response, dns_rcode_noerror);
+
+    // For DNS queries, we need to return the question.
+    query->response->qdcount = htons(1);
+    if (query->iface != NULL) {
+        TOWIRE_CHECK("name", &query->towire, dns_name_to_wire(NULL, &query->towire, query->name));
+        TOWIRE_CHECK("enclosing_domain", &query->towire,
+                     dns_full_name_to_wire(&query->enclosing_domain_pointer,
+                                           &query->towire, query->iface->domain));
+    } else {
+        TOWIRE_CHECK("full name", &query->towire, dns_full_name_to_wire(NULL, &query->towire, query->name));
+    }
+    TOWIRE_CHECK("TYPE", &query->towire, dns_u16_to_wire(&query->towire, question->type));    // TYPE
+    TOWIRE_CHECK("CLASS", &query->towire, dns_u16_to_wire(&query->towire, question->qclass));  // CLASS
+    if (failnote != NULL) {
+        ERROR("dp_dns_query: failure encoding question: " PUB_S_SRP, failnote);
+        goto fail;
+    }
+
+    // We should check for OPT RR, but for now assume it's there.
+    query->is_edns0 = true;
+
+    if (!dp_query_start(comm, query, &rcode, dns_query_callback)) {
+    fail:
+        dp_simple_response(comm, rcode);
+        free(query->name);
+        free(query);
+        return;
+    }
+
+    // XXX make sure that finalize frees this.
+    if (comm->message) {
+        query->question = comm->message;
+        comm->message = NULL;
+    }
+}
+
+void dso_transport_finalize(comm_t *comm)
+{
+    dso_state_t *dso = comm->dso;
+    INFO("dso_transport_finalize: " PRI_S_SRP, dso->remote_name);
+    if (comm) {
+        ioloop_close(&comm->io);
+    }
+    free(dso);
+    comm->dso = NULL;
+}
+
+void dns_evaluate(comm_t *comm)
+{
+    dns_rr_t question;
+    unsigned offset = 0;
+
+    // Drop incoming responses--we're a server, so we only accept queries.
+    if (dns_qr_get(&comm->message->wire) == dns_qr_response) {
+        return;
+    }
+
+    // If this is a DSO message, see if we have a session yet.
+    switch(dns_opcode_get(&comm->message->wire)) {
+    case dns_opcode_dso:
+        if (!comm->tcp_stream) {
+            ERROR("DSO message received on non-tcp socket " PRI_S_SRP, comm->name);
+            dp_simple_response(comm, dns_rcode_notimp);
+            return;
+        }
+
+        if (!comm->dso) {
+            comm->dso = dso_create(true, 0, comm->name, dns_push_callback, comm, comm);
+            if (!comm->dso) {
+                ERROR("Unable to create a dso context for " PRI_S_SRP, comm->name);
+                dp_simple_response(comm, dns_rcode_servfail);
+                ioloop_close(&comm->io);
+                return;
+            }
+            comm->dso->transport_finalize = dso_transport_finalize;
+        }
+        dso_message_received(comm->dso, (uint8_t *)&comm->message->wire, comm->message->length);
+        break;
+
+    case dns_opcode_query:
+        // In theory this is permitted but it can't really be implemented because there's no way
+        // to say "here's the answer for this, and here's why that failed.
+        if (ntohs(comm->message->wire.qdcount) != 1) {
+            dp_simple_response(comm, dns_rcode_formerr);
+            return;
+        }
+        if (!dns_rr_parse(&question, comm->message->wire.data, comm->message->length, &offset, 0)) {
+            dp_simple_response(comm, dns_rcode_formerr);
+            return;
+        }
+        dp_dns_query(comm, &question);
+        dns_rrdata_free(&question);
+        break;
+
+        // No support for other opcodes yet.
+    default:
+        dp_simple_response(comm, dns_rcode_notimp);
+        break;
+    }
+}
+
+void dns_input(comm_t *comm)
+{
+    dns_evaluate(comm);
+    if (comm->message != NULL) {
+        message_free(comm->message);
+        comm->message = NULL;
+    }
+}
+
+static int
+usage(const char *progname)
+{
+    ERROR("usage: " PUB_S_SRP, progname);
+    ERROR("ex: dnssd-proxy");
+    return 1;
+}
+
+// Called whenever we get a connection.
+void
+connected(comm_t *comm)
+{
+    INFO("connection from " PRI_S_SRP, comm->name);
+    return;
+}
+
+static bool config_string_handler(char **ret, const char *filename, const char *string, int lineno, bool tdot,
+                                  bool ldot)
+{
+    char *s;
+    int add_trailing_dot = 0;
+    int add_leading_dot = ldot ? 1 : 0;
+    int len = strlen(string);
+
+    // Space for NUL and leading dot.
+    if (tdot && len > 0 && string[len - 1] != '.') {
+        add_trailing_dot = 1;
+    }
+    s = malloc(strlen(string) + add_leading_dot + add_trailing_dot + 1);
+    if (s == NULL) {
+        ERROR("Unable to allocate domain name " PRI_S_SRP, string);
+        return false;
+    }
+    *ret = s;
+    if (ldot) {
+        *s++ = '.';
+    }
+    strcpy(s, string);
+    if (add_trailing_dot) {
+        s[len] = '.';
+        s[len + 1] = 0;
+    }
+    return true;
+}
+
+// Config file parsing...
+static bool interface_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
+{
+    interface_t *interface = calloc(1, sizeof *interface);
+    if (interface == NULL) {
+        ERROR("Unable to allocate interface " PUB_S_SRP, hunks[1]);
+        return false;
+    }
+
+    interface->name = strdup(hunks[1]);
+    if (interface->name == NULL) {
+        ERROR("Unable to allocate interface name " PUB_S_SRP, hunks[1]);
+        free(interface);
+        return false;
+    }
+
+    if (!strcmp(hunks[0], "nopush")) {
+        interface->no_push = true;
+    }
+
+    if (new_served_domain(interface, hunks[2]) == NULL) {
+        free(interface->name);
+        free(interface);
+        return false;
+    }
+    return true;
+}
+
+static bool port_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
+{
+    char *ep = NULL;
+    long port = strtol(hunks[1], &ep, 10);
+    if (port < 0 || port > 65535 || *ep != 0) {
+        ERROR("Invalid port number: " PUB_S_SRP, hunks[1]);
+        return false;
+    }
+    if (!strcmp(hunks[0], "udp-port")) {
+        udp_port = port;
+    } else if (!strcmp(hunks[0], "tcp-port")) {
+        tcp_port = port;
+    } else if (!strcmp(hunks[0], "tls-port")) {
+        tls_port = port;
+    }
+    return true;
+}
+
+static bool listen_addr_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
+{
+    if (num_listen_addrs == MAX_ADDRS) {
+        ERROR("Only %d IPv4 listen addresses can be configured.", MAX_ADDRS);
+        return false;
+    }
+    return config_string_handler(&listen_addrs[num_listen_addrs++], filename, hunks[1], lineno, false, false);
+}
+
+static bool tls_key_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
+{
+    return config_string_handler(&tls_key_filename, filename, hunks[1], lineno, false, false);
+}
+
+static bool tls_cert_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
+{
+    return config_string_handler(&tls_cert_filename, filename, hunks[1], lineno, false, false);
+}
+
+static bool tls_cacert_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
+{
+    return config_string_handler(&tls_cacert_filename, filename, hunks[1], lineno, false, false);
+}
+
+config_file_verb_t dp_verbs[] = {
+    { "interface",    3, 3, interface_handler },    // interface <name> <domain>
+    { "nopush",       3, 3, interface_handler },    // nopush <name> <domain>
+    { "udp-port",     2, 2, port_handler },         // udp-port <number>
+    { "tcp-port",     2, 2, port_handler },         // tcp-port <number>
+    { "tls-port",     2, 2, port_handler },         // tls-port <number>
+    { "tls-key",      2, 2, tls_key_handler },      // tls-key <filename>
+    { "tls-cert",     2, 2, tls_cert_handler },     // tls-cert <filename>
+    { "tls-cacert",   2, 2, tls_cacert_handler },   // tls-cacert <filename>
+    { "listen-addr",  2, 2, listen_addr_handler },  // listen-addr <IP address>
+};
+#define NUMCFVERBS ((sizeof dp_verbs) / sizeof (config_file_verb_t))
+
+int
+main(int argc, char **argv)
+{
+    int i;
+    comm_t *listener[4 + MAX_ADDRS];
+    int num_listeners = 0;
+
+    udp_port = tcp_port = 53;
+    tls_port = 853;
+
+    // Parse command line arguments
+    for (i = 1; i < argc; i++) {
+         return usage(argv[0]);
+    }
+
+    // Read the config file
+    if (!config_parse(NULL, "/etc/dnssd-relay.cf", dp_verbs, NUMCFVERBS)) {
+        return 1;
+    }
+
+    map_interfaces();
+
+    if (!srp_tls_init()) {
+        return 1;
+    }
+
+    if (!ioloop_init()) {
+        return 1;
+    }
+
+    for (i = 0; i < num_listen_addrs; i++) {
+        listener[num_listeners] = setup_listener_socket(AF_UNSPEC, IPPROTO_TCP, true,
+                                                        tls_port, listen_addrs[i], "DNS TLS Listener", dns_input,
+                                                        connected, 0);
+        if (listener[num_listeners] == NULL) {
+            ERROR("TLS4 listener: fail.");
+            return 1;
+        }
+        num_listeners++;
+       }
+
+    // If we haven't been given any addresses to listen on, listen on an IPv4 address and an IPv6 address.
+    if (num_listen_addrs == 0) {
+        listener[num_listeners] = setup_listener_socket(AF_INET, IPPROTO_TCP, true, tls_port, NULL,
+                                                        "IPv4 DNS TLS Listener", dns_input, 0, 0);
+        if (listener[num_listeners] == NULL) {
+            ERROR("UDP4 listener: fail.");
+            return 1;
+        }
+        num_listeners++;
+
+        listener[num_listeners] = setup_listener_socket(AF_INET6, IPPROTO_TCP, true, tls_port, NULL,
+                                                        "IPv6 DNS TLS Listener", dns_input, 0, 0);
+        if (listener[num_listeners] == NULL) {
+            ERROR("UDP6 listener: fail.");
+            return 1;
+        }
+        num_listeners++;
+    }
+
+    for (i = 0; i < num_listeners; i++) {
+        INFO("Started " PRI_S_SRP, listener[i]->name);
+    }
+
+    do {
+        int something = 0;
+        something = ioloop_events(0);
+        INFO("dispatched %d events.", something);
+    } while (1);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
index 01525362dd1ae2b4a1c05017a0e4419b81feef08..ba9e9dfcead989c9c792d9c4d63d62ab927327ab 100644 (file)
@@ -1,6 +1,6 @@
 /* fromwire.c
  *
- * Copyright (c) 2018 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2018, 2019 Apple Computer, Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
 #include <stdio.h>
 #include <unistd.h>
 #include <string.h>
-#include <sys/errno.h>
+#include <errno.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <stdlib.h>
 bool
 dns_opt_parse(dns_edns0_t *NONNULL *NULLABLE ret, dns_rr_t *rr)
 {
+    dns_edns0_t *edns0, **p_edns0 = ret;
+    unsigned offset = 0;
+    dns_rdata_unparsed_t opt;
+
+    // This would be a weird coding error.
+    if (rr->type != dns_rrtype_opt) {
+        return false;
+    }
+    opt = rr->data.unparsed;
+
+    // RDATA is a series of TLVs
+    while (offset < opt.len) {
+        uint16_t tlv_type, tlv_len;
+
+        // Parse the TLV type and length.
+        if (!dns_u16_parse(opt.data, opt.len, &offset, &tlv_type) ||
+            !dns_u16_parse(opt.data, opt.len, &offset, &tlv_len))
+        {
+            return false;
+        }
+
+        // Range check the contents.
+        if (offset + tlv_len > opt.len) {
+            return false;
+        }
+
+        edns0 = calloc(1, tlv_len + sizeof(*edns0));
+        if (edns0 == NULL) {
+            return false;
+        }
+        // Stash the record.
+        edns0->length = tlv_len;
+        edns0->type = tlv_type;
+        memcpy(edns0->data, &opt.data[offset], tlv_len);
+        *p_edns0 = edns0;
+        p_edns0 = &edns0->next;
+        offset += tlv_len;
+    }
     return true;
 }
 
@@ -54,7 +92,7 @@ dns_label_parse(const uint8_t *buf, unsigned mlen, unsigned *NONNULL offp)
         return NULL;
     }
 
-    rv = calloc(llen + 1 - DNS_MAX_LABEL_SIZE + sizeof *rv, 1);
+    rv = calloc(1, (sizeof(*rv) - DNS_MAX_LABEL_SIZE) + llen + 1);
     if (rv == NULL) {
         DEBUG("memory allocation for %u byte label (%.*s) failed.\n",
               *offp + llen + 1, *offp + llen + 1, &buf[*offp + 1]);
@@ -68,9 +106,9 @@ dns_label_parse(const uint8_t *buf, unsigned mlen, unsigned *NONNULL offp)
     return rv;
 }
 
-bool
-dns_name_parse(dns_label_t *NONNULL *NULLABLE ret, const uint8_t *buf, unsigned len,
-               unsigned *NONNULL offp, unsigned base)
+static bool
+dns_name_parse_in(dns_label_t *NONNULL *NULLABLE ret, const uint8_t *buf, unsigned len,
+                  unsigned *NONNULL offp, unsigned base)
 {
     dns_label_t *rv;
 
@@ -87,17 +125,17 @@ dns_name_parse(dns_label_t *NONNULL *NULLABLE ret, const uint8_t *buf, unsigned
         }
         pointer = (((unsigned)buf[*offp] & 0x3f) << 8) | (unsigned)buf[*offp + 1];
         *offp += 2;
-        if (pointer >= base) {
-            // Don't allow a pointer forward, or to a pointer we've already visited.
-            DEBUG("compression pointer points forward: %u >= %u.\n", pointer, base);
-            return false;
-        }
         if (pointer < DNS_HEADER_SIZE) {
             // Don't allow pointers into the header.
             DEBUG("compression pointer points into header: %u.\n", pointer);
             return false;
         }
         pointer -= DNS_HEADER_SIZE;
+        if (pointer >= base) {
+            // Don't allow a pointer forward, or to a pointer we've already visited.
+            DEBUG("compression pointer points forward: %u >= %u.\n", pointer, base);
+            return false;
+        }
         if (buf[pointer] & 0xC0) {
             // If this is a pointer to a pointer, it's not valid.
             DEBUG("compression pointer points into pointer: %u %02x%02x.\n", pointer,
@@ -110,7 +148,7 @@ dns_name_parse(dns_label_t *NONNULL *NULLABLE ret, const uint8_t *buf, unsigned
                   pointer, buf[pointer]);
             return false;
         }
-        return dns_name_parse(ret, buf, len, &pointer, pointer);
+        return dns_name_parse_in(ret, buf, len, &pointer, pointer);
     }
     // We don't support binary labels, which are historical, and at this time there are no other valid
     // DNS label types.
@@ -118,7 +156,7 @@ dns_name_parse(dns_label_t *NONNULL *NULLABLE ret, const uint8_t *buf, unsigned
         DEBUG("invalid label type: %x\n", buf[*offp]);
         return false;
     }
-    
+
     rv = dns_label_parse(buf, len, offp);
     if (rv == NULL) {
         return false;
@@ -129,7 +167,24 @@ dns_name_parse(dns_label_t *NONNULL *NULLABLE ret, const uint8_t *buf, unsigned
     if (rv->len == 0) {
         return true;
     }
-    return dns_name_parse(&rv->next, buf, len, offp, base);
+    return dns_name_parse_in(&rv->next, buf, len, offp, base);
+}
+
+bool
+dns_name_parse(dns_label_t *NONNULL *NULLABLE ret, const uint8_t *buf,
+               unsigned len, unsigned *NONNULL offp, unsigned base)
+{
+    dns_label_t *rv = NULL, *next;
+
+    if (!dns_name_parse_in(&rv, buf, len, offp, base)) {
+        for (; rv != NULL; rv = next) {
+            next = rv->next;
+            free(rv);
+        }
+        return false;
+    }
+    *ret = rv;
+    return true;
 }
 
 bool
@@ -179,122 +234,136 @@ dns_u32_parse(const uint8_t *buf, unsigned len, unsigned *NONNULL offp, uint32_t
 }
 
 static void
-dns_name_dump(FILE *outfile, dns_label_t *name)
+dns_rrdata_dump(dns_rr_t *rr)
 {
+    int i;
+    char nbuf[INET6_ADDRSTRLEN];
     char buf[DNS_MAX_NAME_SIZE_ESCAPED + 1];
-    
-    dns_name_print(name, buf, sizeof buf);
-    fputs(buf, outfile);
-}
+    char outbuf[2048];
+    char *obp;
+    ssize_t output_len, avail = 2048;
+
+#define ADVANCE(result, start, remaining) \
+    output_len = strlen(start);           \
+    result = start + output_len;          \
+    avail = (remaining) - output_len
+#define DEPCHAR(ch)     \
+    do {                     \
+        if (avail > 1) {     \
+            *obp++ = (ch);   \
+            *obp = 0;        \
+            --avail;         \
+        }                    \
+    } while (0)
 
-static void
-dns_rrdata_dump(FILE *outfile, dns_rr_t *rr)
-{
-    int i;
-    char nbuf[80];
-    dns_txt_element_t *txt;
 
     switch(rr->type) {
     case dns_rrtype_key:
-        fprintf(outfile, "KEY <AC %d> <Z %d> <XT %d> <ZZ %d> <NAMTYPE %d> <ZZZZ %d> <ORY %d> %d %d ",
-                ((rr->data.key.flags & 0xC000) >> 14 & 3), ((rr->data.key.flags & 0x2000) >> 13) & 1,
-                ((rr->data.key.flags & 0x1000) >> 12) & 1, ((rr->data.key.flags & 0xC00) >> 10) & 3,
-                ((rr->data.key.flags & 0x300) >> 8) & 3, ((rr->data.key.flags & 0xF0) >> 4) & 15, rr->data.key.flags & 15,
-                rr->data.key.protocol, rr->data.key.algorithm);
+        snprintf(outbuf, sizeof(outbuf),
+                 "KEY <AC %d> <Z %d> <XT %d> <ZZ %d> <NAMTYPE %d> <ZZZZ %d> <ORY %d> %d %d ",
+                 ((rr->data.key.flags & 0xC000) >> 14 & 3), ((rr->data.key.flags & 0x2000) >> 13) & 1,
+                 ((rr->data.key.flags & 0x1000) >> 12) & 1, ((rr->data.key.flags & 0xC00) >> 10) & 3,
+                 ((rr->data.key.flags & 0x300) >> 8) & 3, ((rr->data.key.flags & 0xF0) >> 4) & 15,
+                 rr->data.key.flags & 15, rr->data.key.protocol, rr->data.key.algorithm);
+        ADVANCE(obp, outbuf, sizeof outbuf);
+
         for (i = 0; i < rr->data.key.len; i++) {
             if (i == 0) {
-                fprintf(outfile, "%d [%02x", rr->data.key.len, rr->data.key.key[i]);
+                snprintf(obp, avail, "%d [%02x", rr->data.key.len, rr->data.key.key[i]);
+                ADVANCE(obp, obp, avail);
             } else {
-                fprintf(outfile, " %02x", rr->data.key.key[i]);
+                snprintf(obp, avail, " %02x", rr->data.key.key[i]);
+                ADVANCE(obp, obp, avail);
             }
         }
-        fputc(']', outfile);
+        DEPCHAR(']');
         break;
-        
+
     case dns_rrtype_sig:
-        fprintf(outfile, "SIG %d %d %d %lu %lu %lu %d ",
-                rr->data.sig.type, rr->data.sig.algorithm, rr->data.sig.label,
-                (unsigned long)rr->data.sig.rrttl, (unsigned long)rr->data.sig.expiry,
-                (unsigned long)rr->data.sig.inception, rr->data.sig.key_tag);
-        dns_name_dump(outfile, rr->data.sig.signer);
+        dns_name_print(rr->data.sig.signer, buf, sizeof(buf));
+        snprintf(outbuf, sizeof(outbuf), "SIG %d %d %d %lu %lu %lu %d %s",
+                 rr->data.sig.type, rr->data.sig.algorithm, rr->data.sig.label,
+                 (unsigned long)rr->data.sig.rrttl, (unsigned long)rr->data.sig.expiry,
+                 (unsigned long)rr->data.sig.inception, rr->data.sig.key_tag, buf);
+        ADVANCE(obp, outbuf, sizeof outbuf);
         for (i = 0; i < rr->data.sig.len; i++) {
             if (i == 0) {
-                fprintf(outfile, "%d [%02x", rr->data.sig.len, rr->data.sig.signature[i]);
+                snprintf(obp, avail, "%d [%02x", rr->data.sig.len, rr->data.sig.signature[i]);
+                ADVANCE(obp, obp, avail);
             } else {
-                fprintf(outfile, " %02x", rr->data.sig.signature[i]);
+                snprintf(obp, avail, " %02x", rr->data.sig.signature[i]);
+                ADVANCE(obp, obp, avail);
             }
         }
-        fputc(']', outfile);
+        DEPCHAR(']');
         break;
-        
+
     case dns_rrtype_srv:
-        fprintf(outfile, "SRV %d %d %d ", rr->data.srv.priority, rr->data.srv.weight, rr->data.srv.port);
-        dns_name_dump(outfile, rr->data.ptr.name);
+        dns_name_print(rr->data.srv.name, buf, sizeof(buf));
+        snprintf(outbuf, sizeof(outbuf), "SRV %d %d %d %s", rr->data.srv.priority, rr->data.srv.weight,
+                 rr->data.srv.port, buf);
+        ADVANCE(obp, outbuf, sizeof(outbuf));
         break;
 
     case dns_rrtype_ptr:
-        fputs("PTR ", outfile);
-        dns_name_dump(outfile, rr->data.ptr.name);
+        dns_name_print(rr->data.ptr.name, buf, sizeof(buf));
+        snprintf(outbuf, sizeof(outbuf), "PTR %s", buf);
+        ADVANCE(obp, outbuf, sizeof(outbuf));
         break;
 
     case dns_rrtype_cname:
-        fputs("CNAME ", outfile);
-        dns_name_dump(outfile, rr->data.ptr.name);
+        dns_name_print(rr->data.cname.name, buf, sizeof(buf));
+        snprintf(outbuf, sizeof(outbuf), "CNAME %s", buf);
+        ADVANCE(obp, outbuf, sizeof(outbuf));
         break;
 
     case dns_rrtype_a:
-        fputs("A", outfile);
-        for (i = 0; i < rr->data.a.num; i++) {
-            inet_ntop(AF_INET, &rr->data.a.addrs[i], nbuf, sizeof nbuf);
-            putc(' ', outfile);
-            fputs(nbuf, outfile);
-        }
+        inet_ntop(AF_INET, &rr->data.a, nbuf, sizeof(nbuf));
+        snprintf(outbuf, sizeof(outbuf), "A %s", nbuf);
+        ADVANCE(obp, outbuf, sizeof(outbuf));
         break;
-        
+
     case dns_rrtype_aaaa:
-        fputs("AAAA", outfile);
-        for (i = 0; i < rr->data.aaaa.num; i++) {
-            inet_ntop(AF_INET6, &rr->data.aaaa.addrs[i], nbuf, sizeof nbuf);
-            putc(' ', outfile);
-            fputs(nbuf, outfile);
-        }
+        inet_ntop(AF_INET6, &rr->data.aaaa, nbuf, sizeof(nbuf));
+        snprintf(outbuf, sizeof(outbuf), "AAAA %s", nbuf);
+        ADVANCE(obp, outbuf, sizeof(outbuf));
         break;
 
     case dns_rrtype_txt:
-        fputs("TXT", outfile);
-        for (txt = rr->data.txt; txt; txt = txt->next) {
-            putc(' ', outfile);
-            putc('"', outfile);
-            for (i = 0; i < txt->len; i++) {
-                if (isascii(txt->data[i]) && isprint(txt->data[i])) {
-                    putc(txt->data[i], outfile);
-                } else {
-                    fprintf(outfile, "<%x>", txt->data[i]);
-                }
+        strcpy(outbuf, "TXT ");
+        ADVANCE(obp, outbuf, sizeof(outbuf));
+        for (i = 0; i < rr->data.txt.len; i++) {
+            if (isascii(rr->data.txt.data[i]) && isprint(rr->data.txt.data[i])) {
+                DEPCHAR(rr->data.txt.data[i]);
+            } else {
+                snprintf(obp, avail, "<%x>", rr->data.txt.data[i]);
+                ADVANCE(obp, obp, avail);
             }
-            putc('"', outfile);
         }
+        DEPCHAR('"');
         break;
 
     default:
-        fprintf(outfile, "<rrtype %d>:", rr->type);
+        snprintf(outbuf, sizeof(outbuf), "<rrtype %d>:", rr->type);
+        ADVANCE(obp, outbuf, sizeof(outbuf));
         if (rr->data.unparsed.len == 0) {
-            fputs(" <none>", outfile);
+            snprintf(obp, avail, " <none>");
+            ADVANCE(obp, obp, avail);
         } else {
             for (i = 0; i < rr->data.unparsed.len; i++) {
-                fprintf(outfile, " %02x", rr->data.unparsed.data[i]);
+                snprintf(obp, avail, " %02x", rr->data.unparsed.data[i]);
+                ADVANCE(obp, obp, avail);
             }
         }
         break;
     }
+    DEBUG(PUB_S_SRP, outbuf);
 }
 
 bool
-dns_rdata_parse_data(dns_rr_t *NONNULL rr, const uint8_t *buf, unsigned *NONNULL offp, unsigned target, unsigned rdlen, unsigned rrstart)
+dns_rdata_parse_data(dns_rr_t *NONNULL rr, const uint8_t *buf, unsigned *NONNULL offp, unsigned target, unsigned rdlen,
+                     unsigned rrstart)
 {
-    uint16_t addrlen;
-    dns_txt_element_t *txt, **ptxt;
-
     switch(rr->type) {
     case dns_rrtype_key:
         if (!dns_u16_parse(buf, target, offp, &rr->data.key.flags) ||
@@ -333,7 +402,7 @@ dns_rdata_parse_data(dns_rr_t *NONNULL rr, const uint8_t *buf, unsigned *NONNULL
         memcpy(rr->data.sig.signature, &buf[*offp], rr->data.sig.len);
         *offp += rr->data.sig.len;
         break;
-        
+
     case dns_rrtype_srv:
         if (!dns_u16_parse(buf, target, offp, &rr->data.srv.priority) ||
             !dns_u16_parse(buf, target, offp, &rr->data.srv.weight) ||
@@ -343,6 +412,7 @@ dns_rdata_parse_data(dns_rr_t *NONNULL rr, const uint8_t *buf, unsigned *NONNULL
         // This fallthrough assumes that the first element in the srv, ptr and cname structs is
         // a pointer to a domain name.
 
+    case dns_rrtype_ns:
     case dns_rrtype_ptr:
     case dns_rrtype_cname:
         if (!dns_name_parse(&rr->data.ptr.name, buf, target, offp, *offp)) {
@@ -350,52 +420,33 @@ dns_rdata_parse_data(dns_rr_t *NONNULL rr, const uint8_t *buf, unsigned *NONNULL
         }
         break;
 
-        // We assume below that the a and aaaa structures in the data union are exact aliases of
-        // each another.
     case dns_rrtype_a:
-        addrlen = 4;
-        goto addr_parse;
-        
-    case dns_rrtype_aaaa:
-        addrlen = 16;
-    addr_parse:
-        if (rdlen & (addrlen - 1)) {
-            DEBUG("dns_rdata_parse: %s rdlen not an even multiple of %u: %u",
-                  addrlen == 4 ? "A" : "AAAA", addrlen, rdlen);
+        if (rdlen != 4) {
+            DEBUG("dns_rdata_parse: A rdlen is not 4: %u", rdlen);
             return false;
         }
-        rr->data.a.addrs = malloc(rdlen);
-        if (rr->data.a.addrs == NULL) {
+        memcpy(&rr->data.a, &buf[*offp], rdlen);
+        *offp = target;
+        break;
+
+    case dns_rrtype_aaaa:
+        if (rdlen != 16) {
+            DEBUG("dns_rdata_parse: AAAA rdlen is not 16: %u", rdlen);
             return false;
         }
-        rr->data.a.num = rdlen /  addrlen;
-        memcpy(rr->data.a.addrs, &buf[*offp], rdlen);
+        memcpy(&rr->data.aaaa, &buf[*offp], rdlen);
         *offp = target;
         break;
-        
+
     case dns_rrtype_txt:
-        ptxt = &rr->data.txt;
-        while (*offp < target) {
-            unsigned tlen = buf[*offp];
-            if (*offp + tlen + 1 > target) {
-                DEBUG("dns_rdata_parse: TXT RR length is larger than available space: %u %u",
-                      *offp + tlen + 1, target);
-                *ptxt = NULL;
-                return false;
-            }
-            txt = malloc(tlen + 1 + sizeof *txt);
-            if (txt == NULL) {
-                DEBUG("dns_rdata_parse: no memory for TXT RR");
-                return false;
-            }
-            txt->len = tlen;
-            ++*offp;
-            memcpy(txt->data, &buf[*offp], tlen);
-            *offp += tlen;
-            txt->data[tlen] = 0;
-            *ptxt = txt;
-            ptxt = &txt->next;
+        rr->data.txt.len = target - *offp;
+        rr->data.txt.data = malloc(rr->data.txt.len);
+        if (rr->data.txt.data == NULL) {
+            DEBUG("dns_rdata_parse: no memory for TXT RR");
+            return false;
         }
+        memcpy(rr->data.txt.data, &buf[*offp], rr->data.txt.len);
+        *offp = target;
         break;
 
     default:
@@ -423,7 +474,7 @@ dns_rdata_parse(dns_rr_t *NONNULL rr,
 {
     uint16_t rdlen;
     unsigned target;
-    
+
     if (!dns_u16_parse(buf, len, offp, &rdlen)) {
         return false;
     }
@@ -439,11 +490,12 @@ dns_rr_parse(dns_rr_t *NONNULL rr,
              const uint8_t *buf, unsigned len, unsigned *NONNULL offp, bool rrdata_expected)
 {
     int rrstart = *offp; // Needed to mark the start of the SIG RR for SIG(0).
-    memset(rr, 0, sizeof *rr);
+
+    memset(rr, 0, sizeof(*rr));
     if (!dns_name_parse(&rr->name, buf, len, offp, *offp)) {
         return false;
     }
-    
+
     if (!dns_u16_parse(buf, len, offp, &rr->type)) {
         return false;
     }
@@ -451,7 +503,7 @@ dns_rr_parse(dns_rr_t *NONNULL rr,
     if (!dns_u16_parse(buf, len, offp, &rr->qclass)) {
         return false;
     }
-    
+
     if (rrdata_expected) {
         if (!dns_u32_parse(buf, len, offp, &rr->ttl)) {
             return false;
@@ -460,59 +512,54 @@ dns_rr_parse(dns_rr_t *NONNULL rr,
             return false;
         }
     }
-        
-    printf("rrtype: %u  qclass: %u  name: ", rr->type, rr->qclass);
-    dns_name_dump(stdout, rr->name);
+
+    DNS_NAME_GEN_SRP(rr->name, name_buf);
+    DEBUG("rrtype: %u  qclass: %u  name: " PRI_DNS_NAME_SRP PUB_S_SRP,
+          rr->type, rr->qclass, DNS_NAME_PARAM_SRP(rr->name, name_buf), rrdata_expected ? "  rrdata:" : "");
     if (rrdata_expected) {
-        printf("  rrdata: ");
-        dns_rrdata_dump(stdout, rr);
+        dns_rrdata_dump(rr);
     }
-    printf("\n");
     return true;
 }
 
-void dns_name_free(dns_label_t *name)
-{
-    dns_label_t *next;
-    if (name == NULL) {
-        return;
-    }
-    next = name->next;
-    free(name);
-    return dns_name_free(next);
-}    
-
 void
 dns_rrdata_free(dns_rr_t *rr)
 {
     switch(rr->type) {
+    case dns_rrtype_a:
+    case dns_rrtype_aaaa:
+        break;
+
     case dns_rrtype_key:
         free(rr->data.key.key);
         break;
-        
+
     case dns_rrtype_sig:
         dns_name_free(rr->data.sig.signer);
         free(rr->data.sig.signature);
         break;
-        
+
     case dns_rrtype_srv:
     case dns_rrtype_ptr:
     case dns_rrtype_cname:
         dns_name_free(rr->data.ptr.name);
+#ifndef __clang_analyzer__
         rr->data.ptr.name = NULL;
-        break;
+#endif
+            break;
 
-    case dns_rrtype_a:
-    case dns_rrtype_aaaa:
-        free(rr->data.a.addrs);
-        rr->data.a.addrs = NULL;
-        break;
-        
     case dns_rrtype_txt:
+        free(rr->data.txt.data);
+#ifndef __clang_analyzer__
+        rr->data.txt.data = NULL;
+#endif
+        break;
+
     default:
-        free(rr->data.unparsed.data);
+        if (rr->data.unparsed.len > 0 && rr->data.unparsed.data != NULL) {
+            free(rr->data.unparsed.data);
+        }
         rr->data.unparsed.data = NULL;
-        break;
     }
 }
 
@@ -520,17 +567,17 @@ void
 dns_message_free(dns_message_t *message)
 {
     int i;
+    dns_edns0_t *edns0, *next;
 
 #define FREE(count, sets)                           \
-    for (i = 0; i < message->count; i++) {          \
-        dns_rr_t *set = &message->sets[i];          \
-        if (set->name) {                            \
-            dns_name_free(set->name);               \
-            set->name = NULL;                       \
-        }                                           \
-        dns_rrdata_free(set);                       \
-    }                                               \
     if (message->sets) {                            \
+        for (i = 0; i < message->count; i++) {      \
+            dns_rr_t *set = &message->sets[i];      \
+            if (set->name) {                        \
+                dns_name_free(set->name);           \
+            }                                       \
+            dns_rrdata_free(set);                   \
+        }                                           \
         free(message->sets);                        \
     }
     FREE(qdcount, questions);
@@ -538,28 +585,36 @@ dns_message_free(dns_message_t *message)
     FREE(nscount, authority);
     FREE(arcount, additional);
 #undef FREE
+    for (edns0 = message->edns0; edns0 != NULL; edns0 = next) {
+        next = edns0->next;
+        free(edns0);
+    }
+    free(message);
 }
 
 bool
 dns_wire_parse(dns_message_t *NONNULL *NULLABLE ret, dns_wire_t *message, unsigned len)
 {
     unsigned offset = 0;
-    dns_message_t *rv = calloc(sizeof *rv, 1);
+    unsigned data_len = len - DNS_HEADER_SIZE;
+    dns_message_t *rv = calloc(1, sizeof(*rv));
     int i;
-    
+
     if (rv == NULL) {
         return false;
     }
-    
+
 #define PARSE(count, sets, name, rrdata_expected)                                   \
     rv->count = ntohs(message->count);                                              \
     if (rv->count > 50) {                                                           \
+        rv->count = 0;                                                              \
         dns_message_free(rv);                                                       \
         return false;                                                               \
     }                                                                               \
+    DEBUG("Section %s, %d records", name, rv->count);                               \
                                                                                     \
-    if (rv->qdcount != 0) {                                                         \
-        rv->sets = calloc(sizeof *rv->sets, rv->count);                             \
+    if (rv->count != 0) {                                                           \
+        rv->sets = calloc(rv->count, sizeof(*rv->sets));                            \
         if (rv->sets == NULL) {                                                     \
             dns_message_free(rv);                                                   \
             return false;                                                           \
@@ -567,9 +622,9 @@ dns_wire_parse(dns_message_t *NONNULL *NULLABLE ret, dns_wire_t *message, unsign
     }                                                                               \
                                                                                     \
     for (i = 0; i < rv->count; i++) {                                               \
-        if (!dns_rr_parse(&rv->sets[i], message->data, len, &offset, rrdata_expected)) {       \
+        if (!dns_rr_parse(&rv->sets[i], message->data, data_len, &offset, rrdata_expected)) {    \
             dns_message_free(rv);                                                   \
-            fprintf(stderr, name " %d RR parse failed.\n", i);                      \
+            ERROR(name " %d RR parse failed.\n", i);                                \
             return false;                                                           \
         }                                                                           \
     }
@@ -578,8 +633,8 @@ dns_wire_parse(dns_message_t *NONNULL *NULLABLE ret, dns_wire_t *message, unsign
     PARSE(nscount,  authority, "authority", true);
     PARSE(arcount, additional, "additional", true);
 #undef PARSE
-    
-    for (i = 0; i < rv->ancount; i++) {
+
+    for (i = 0; i < rv->arcount; i++) {
         // Parse EDNS(0)
         if (rv->additional[i].type == dns_rrtype_opt) {
             if (!dns_opt_parse(&rv->edns0, &rv->additional[i])) {
@@ -592,154 +647,6 @@ dns_wire_parse(dns_message_t *NONNULL *NULLABLE ret, dns_wire_t *message, unsign
     return true;
 }
 
-const char *NONNULL
-dns_name_print(dns_name_t *NONNULL name, char *buf, int bufmax)
-{
-    dns_label_t *lp;
-    int ix = 0;
-    int i;
-
-    // Copy the labels in one at a time, putting a dot between each one; if there isn't room
-    // in the buffer (shouldn't be the case), copy as much as will fit, leaving room for a NUL
-    // termination.
-    for (lp = name; lp; lp = lp->next) {
-        if (ix != 0) {
-            if (ix + 2 >= bufmax) {
-                break;
-            }
-            buf[ix++] = '.';
-        }
-        for (i = 0; i < lp->len; i++) {
-            if (isascii(lp->data[i]) && isprint(lp->data[i])) {
-                if (ix + 2 >= bufmax) {
-                    break;
-                }
-                buf[ix++] = lp->data[i];
-            } else {
-                if (ix + 5 >= bufmax) {
-                    break;
-                }
-                buf[ix++] = '\\';
-                buf[ix++] = '0' + (lp->data[i] >> 6);
-                buf[ix++] = '0' + (lp->data[i] >> 3) & 3;
-                buf[ix++] = '0' + lp->data[i] & 3;
-            }
-        }
-        if (i != lp->len) {
-            break;
-        }
-    }
-    buf[ix++] = 0;
-    return buf;
-}
-
-bool
-labeleq(const char *label1, const char *label2, size_t len)
-{
-    int i;
-    for (i = 0; i < len; i++) {
-        if (isascii(label1[i]) && isascii(label2[i])) {
-            if (tolower(label1[i]) != tolower(label2[i])) {
-                return false;
-            }
-        }
-        else {
-            if (label1[i] != label2[i]) {
-                return false;
-            }
-        }
-    }
-    return true;
-}
-
-bool
-dns_names_equal(dns_label_t *NONNULL name1, dns_label_t *NONNULL name2)
-{
-    if (name1->len != name2->len) {
-        return false;
-    }
-    if (name1->len != 0 && !labeleq(name1->data, name2->data, name1->len) != 0) {
-        return false;
-    }
-    if (name1->next != NULL && name2->next != NULL) {
-        return dns_names_equal(name1->next, name2->next);
-    }
-    if (name1->next == NULL && name2->next == NULL) {
-        return true;
-    }
-    return false;
-}
-
-// Note that "foo.arpa" is not the same as "foo.arpa."
-bool
-dns_names_equal_text(dns_label_t *NONNULL name1, const char *NONNULL name2)
-{
-    const char *ndot;
-    ndot = strchr(name2, '.');
-    if (ndot == NULL) {
-        ndot = name2 + strlen(name2);
-    }
-    if (name1->len != ndot - name2) {
-        return false;
-    }
-    if (name1->len != 0 && !labeleq(name1->data, name2, name1->len) != 0) {
-        return false;
-    }
-    if (name1->next != NULL && *ndot == '.') {
-        return dns_names_equal_text(name1->next, ndot + 1);
-    }
-    if (name1->next == NULL && *ndot == 0) {
-        return true;
-    }
-    return false;
-}
-
-// Find the length of a name in uncompressed wire format.
-// This is in fromwire because we use it for validating signatures, and don't need it for
-// sending.
-static size_t
-dns_name_wire_length_in(dns_label_t *NONNULL name, size_t ret)
-{
-    // Root label.
-    if (name == NULL)
-        return ret;
-    return dns_name_wire_length_in(name->next, ret + name->len + 1);
-}
-
-size_t
-dns_name_wire_length(dns_label_t *NONNULL name)
-{
-    return dns_name_wire_length_in(name, 0);
-}
-
-// Copy a name we've parsed from a message out in canonical wire format so that we can
-// use it to verify a signature.   As above, not actually needed for copying to a message
-// we're going to send, since in that case we want to try to compress.
-static size_t
-dns_name_to_wire_canonical_in(uint8_t *NONNULL buf, size_t max, size_t ret, dns_label_t *NONNULL name)
-{
-    INFO("dns_name_to_wire_canonical_in: buf %p max %zd ret %zd  name = %p '%.*s'",
-         buf, max, ret, name, name ? name->len : 0, name ? name->data : "");
-    if (name == NULL) {
-        return ret;
-    }
-    if (max < name->len + 1) {
-        return 0;
-    }
-    *buf = name->len;
-    memcpy(buf + 1, name->data, name->len);
-    return dns_name_to_wire_canonical_in(buf + name->len + 1,
-                                         max - name->len - 1, ret + name->len + 1, name->next);
-}
-
-size_t
-dns_name_to_wire_canonical(uint8_t *NONNULL buf, size_t max, dns_label_t *NONNULL name)
-{
-    return dns_name_to_wire_canonical_in(buf, max, 0, name);
-}
-    
-
-
 // Local Variables:
 // mode: C
 // tab-width: 4
diff --git a/ServiceRegistration/hmac-macos.c b/ServiceRegistration/hmac-macos.c
new file mode 100644 (file)
index 0000000..949261d
--- /dev/null
@@ -0,0 +1,99 @@
+/* hash.c
+ *
+ * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * DNS SIG(0) signature generation for DNSSD SRP using Security Framework.
+ *
+ * Functions required for loading, saving, and generating public/private keypairs, extracting the public key
+ * into KEY RR data, and computing hashatures.
+ */
+
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "srp.h"
+#include "dns-msg.h"
+#define SRP_CRYPTO_MACOS_INTERNAL
+#include "srp-crypto.h"
+
+// Function to generate a signature given some data and a private key
+void
+srp_hmac_iov(hmac_key_t *key, uint8_t *output, size_t max, struct iovec *iov, int count)
+{
+    // int digest_size = 0;
+    // int i, line;
+    (void)count;(void)iov;(void)output; (void)key; (void)max;
+#define KABLOOIE line = __LINE__ - 1; goto kablooie
+#if 0
+    switch(key->algorithm) {
+    case  SRP_HMAC_TYPE_SHA256:
+        // digest_size = mbedtls_md_get_size(md_type);
+        // break;
+    default:
+        ERROR("srp_hmac_iov: unsupported HMAC hash algorithm: %d", key->algorithm);
+        return;
+    }
+    if (max < digest_size) {
+        ERROR("srp_hmac_iov: not enough space in output buffer (%lu) for hash (%d).",
+              (unsigned long)max, digest_size);
+        return;
+    }
+#endif
+    // if ((status = mbedtls_md_hmac_starts(&ctx, key->secret, key->length)) != 0) {
+    //     KABLOOIE;
+    // }
+       // for (i = 0; i < count; i++) {
+    //     if ((status = mbedtls_md_hmac_update(&ctx, iov[i].iov_base, iov[i].iov_len)) != 0) {
+    //         KABLOOIE;
+    //     }
+       // }
+       // if ((status = mbedtls_md_hmac_finish(&ctx, output)) != 0) {
+    //     KABLOOIE;
+       // }
+}
+
+int
+srp_base64_parse(char *src, size_t *len_ret, uint8_t *buf, size_t buflen)
+{
+    (void)src; (void)len_ret; (void)buf; (void)buflen;
+#if 0
+    size_t slen = strlen(src);
+    int ret = mbedtls_base64_decode(buf, buflen, len_ret, (const unsigned char *)src, slen);
+    if (ret == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) {
+        return ENOBUFS;
+    } else if (ret == MBEDTLS_ERR_BASE64_INVALID_CHARACTER) {
+        return EILSEQ;
+    } else if (ret < 0) {
+        return EINVAL;
+    }
+    return 0;
+#else
+    return EINVAL;
+#endif
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/hmac-mbedtls.c b/ServiceRegistration/hmac-mbedtls.c
new file mode 100644 (file)
index 0000000..6f35d43
--- /dev/null
@@ -0,0 +1,109 @@
+/* hash.c
+ *
+ * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * DNS SIG(0) hashature generation for DNSSD SRP using mbedtls.
+ *
+ * Functions required for loading, saving, and generating public/private keypairs, extracting the public key
+ * into KEY RR data, and computing hashatures.
+ */
+
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "srp.h"
+#include "dns-msg.h"
+#define SRP_CRYPTO_MBEDTLS_INTERNAL
+#include "srp-crypto.h"
+
+// Function to generate a signature given some data and a private key
+void
+srp_hmac_iov(hmac_key_t *key, uint8_t *output, size_t max, struct iovec *iov, int count)
+{
+    int status;
+    char errbuf[64];
+    mbedtls_md_context_t ctx;
+    const mbedtls_md_info_t *md_type;
+    int digest_size;
+       int i, line;
+#define KABLOOIE line = __LINE__ - 1; goto kablooie
+
+    switch(key->algorithm) {
+    case  SRP_HMAC_TYPE_SHA256:
+        md_type = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
+        if (md_type == NULL) {
+            ERROR("srp_hmac_iov: HMAC_SHA256 support missing");
+            return;
+        }
+        digest_size = mbedtls_md_get_size(md_type);
+        break;
+    default:
+        ERROR("srp_hmac_iov: unsupported HMAC hash algorithm: %d", key->algorithm);
+        return;
+    }
+    if (max < digest_size) {
+        ERROR("srp_hmac_iov: not enough space in output buffer (%lu) for hash (%d).",
+              (unsigned long)max, digest_size);
+        return;
+    }
+
+    if ((status = mbedtls_md_setup(&ctx, md_type, 1)) != 0) {
+        KABLOOIE;
+    kablooie:
+        mbedtls_strerror(status, errbuf, sizeof errbuf);
+        ERROR("srp_hmac_iov failed at hmac-mbedtls.c line %d: " PUB_S_SRP, line, errbuf);
+    }
+
+    if ((status = mbedtls_md_hmac_starts(&ctx, key->secret, key->length)) != 0) {
+        KABLOOIE;
+    }
+       for (i = 0; i < count; i++) {
+        if ((status = mbedtls_md_hmac_update(&ctx, iov[i].iov_base, iov[i].iov_len)) != 0) {
+            KABLOOIE;
+        }
+       }
+       if ((status = mbedtls_md_hmac_finish(&ctx, output)) != 0) {
+        KABLOOIE;
+       }
+}
+
+int
+srp_base64_parse(char *src, size_t *len_ret, uint8_t *buf, size_t buflen)
+{
+    size_t slen = strlen(src);
+    int ret = mbedtls_base64_decode(buf, buflen, len_ret, (const unsigned char *)src, slen);
+    if (ret == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) {
+        return ENOBUFS;
+    } else if (ret == MBEDTLS_ERR_BASE64_INVALID_CHARACTER) {
+        return EILSEQ;
+    } else if (ret < 0) {
+        return EINVAL;
+    }
+    return 0;
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/hmac-openssl.c b/ServiceRegistration/hmac-openssl.c
new file mode 100644 (file)
index 0000000..76176f6
--- /dev/null
@@ -0,0 +1,147 @@
+/* hmac-openssl.c
+ *
+ * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Hashed message authentication code functions using OpenSSL.
+ */
+
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "srp.h"
+#include "dns-msg.h"
+#define SRP_CRYPTO_OPENSSL_INTERNAL
+#include "srp-crypto.h"
+
+// Function to generate a signature given some data and a private key
+void
+srp_hmac_iov(hmac_key_t *key, uint8_t *output, size_t max, struct iovec *iov, int count)
+{
+    int status;
+    char errbuf[64];
+    uint8_t kipad[SRP_SHA256_BLOCK_SIZE], kopad[SRP_SHA256_BLOCK_SIZE], intermediate[SRP_SHA256_HASH_SIZE];
+    EVP_MD_CTX inner, outer;
+    const EVP_MD md;
+    int i;
+
+    if (key->algorithm != SRP_HMAC_TYPE_SHA256) {
+        ERROR("srp_hmac_iov: unsupported HMAC hash algorithm: %d", key->algorithm);
+        return;
+    }
+    if (max < SRP_SHA256_HASH_SIZE) {
+        ERROR("srp_hmac_iov: not enough space in output buffer (%lu) for hash (%d).",
+              (unsigned long)max, SRP_SHA256_HASH_SIZE);
+        return;
+    }
+
+    md = EVP_sha256();
+    EVP_MD_CTX_INIT(&inner);
+
+    // If the key is longer than the block size, hash it and use the digest as the key.
+    if (key->length > SRP_SHA256_BLOCK_SIZE) {
+        if ((status = EVP_DigestInit(&inner, md)) != 0) {
+            ERROR("srp_hmac_iov failed to initialize key digest");
+            return;
+        }
+        // Compute H(K)
+        if ((status = EVP_DigestUpdate(&inner, key, key->length)) != 0) {
+            ERROR("srp_hmac_iov failed to hash key");
+            return;
+        }
+        if ((status = EVP_DigestFinal(&inner, intermediate, SRP_SHA256_HASH_SIZE)) != 0) {
+            ERROR("srp_hmac_iov failed to digest key");
+            return;
+        }
+        EVP_MD_CTX_INIT(&inner);
+    }
+
+    // Compute key ^ kipad and key ^ kopad
+    for (i = 0; i < SRP_SHA256_BLOCK_SIZE; i++) {
+        uint8_t byte = i >= key->length ? 0 : key->secret[i];
+        kipad[i] = byte ^ 0x36;
+        kopad[i] = byte ^ 0x5c;
+    }
+
+    if ((status = EVP_DigestInit(&inner, md)) != 0) {
+        ERROR("srp_hmac_iov failed to initialize inner digest");
+        return;
+    }
+    // Compute H(K xor ipad, text)
+    if ((status = EVP_DigestUpdate(&inner, kipad, SRP_SHA256_BLOCK_SIZE)) != 0) {
+        ERROR("srp_hmac_iov failed to hash ipad to inner digest");
+        return;
+    }
+    for (i = 0; i < count; i++) {
+        if ((status = EVP_DigestUpdate(&inner, iov[i].iov_base, iov[i].iov_len)) != 0) {
+            ERROR("srp_hmac_iov failed to hash chunk %d to inner digest", i);
+            return;
+        }
+    }
+    if ((status = EVP_DigestFinal(&inner, intermediate, SRP_SHA256_HASH_SIZE)) != 0) {
+        ERROR("srp_hmac_iov failed to hash ipad to inner digest");
+        return;
+    }
+
+    // Compute H(K xor opad, H(K xor ipad, text))
+    EVP_MD_CTX_INIT(&outer);
+    if ((status = EVP_DigestInit(&outer, md)) != 0) {
+        ERROR("srp_hmac_iov failed to initialize outer digest");
+        return;
+    }
+    if ((status = EVP_DigestUpdate(&outer, kopad, SRP_SHA256_BLOCK_SIZE)) != 0) {
+        ERROR("srp_hmac_iov failed to hash outer pad");
+        return;
+        goto kablooie;
+    }
+    if ((status = EVP_DigestUpdate(&outer, intermediate, SRP_SHA256_HASH_SIZE)) != 0) {
+        ERROR("srp_hmac_iov failed to hash outer digest");
+        return;
+        goto kablooie;
+    }
+    if ((status = EVP_DigestFinal(&outer, output, max)) != 0) {
+        ERROR("srp_hmac_iov failed to hash outer outer pad with inner digest");
+        return;
+    }
+    // Bob's your uncle...
+}
+
+int
+srp_base64_parse(char *src, size_t *len_ret, uint8_t *buf, size_t buflen)
+{
+    size_t slen = strlen(src);
+    int ret = mbedtls_base64_decode(buf, buflen, len_ret, (const unsigned char *)src, slen);
+    if (ret == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) {
+        return ENOBUFS;
+    } else if (ret == MBEDTLS_ERR_BASE64_INVALID_CHARACTER) {
+        return EILSEQ;
+    } else if (ret < 0) {
+        return EINVAL;
+    }
+    return 0;
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
index 3218e7b0e9ba6bcbb81c877d247250b8580f40d6..9fccb18816f75b0e8cea2dc2fd655f48684a1b22 100644 (file)
@@ -1,6 +1,6 @@
-/* dispatch.c
+/* ioloop.c
  *
- * Copyright (c) 2018 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2018-2019 Apple Computer, Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  */
 
 #define __APPLE_USE_RFC_3542
+#define _GNU_SOURCE
 
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <sys/uio.h>
-#include <sys/errno.h>
+#include <errno.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#ifdef USE_KQUEUE
 #include <sys/event.h>
+#endif
+#include <sys/wait.h>
 #include <fcntl.h>
 #include <sys/time.h>
+#include <signal.h>
+#include <net/if.h>
+#include <ifaddrs.h>
+
+#include "dns_sd.h"
 
 #include "srp.h"
 #include "dns-msg.h"
 #include "srp-crypto.h"
 #include "ioloop.h"
-#include "dnssd-proxy.h"
+#ifndef EXCLUDE_TLS
+#include "srp-tls.h"
+#endif
 
-#define USE_SELECT
-#pragma mark Globals
 io_t *ios;
+wakeup_t *wakeups;
+subproc_t *subprocesses;
 int64_t ioloop_now;
 
 #ifdef USE_KQUEUE
@@ -52,14 +63,20 @@ getipaddr(addr_t *addr, const char *p)
 {
     if (inet_pton(AF_INET, p, &addr->sin.sin_addr)) {
         addr->sa.sa_family = AF_INET;
+#ifndef NOT_HAVE_SA_LEN
+        addr->sa.sa_len = sizeof addr->sin;
+#endif
         return sizeof addr->sin;
     }  else if (inet_pton(AF_INET6, p, &addr->sin6.sin6_addr)) {
         addr->sa.sa_family = AF_INET6;
+#ifndef NOT_HAVE_SA_LEN
+        addr->sa.sa_len = sizeof addr->sin6;
+#endif
         return sizeof addr->sin6;
     } else {
         return 0;
     }
-}                
+}
 
 int64_t
 ioloop_timenow()
@@ -108,11 +125,25 @@ ioloop_close(io_t *io)
     io->sock = -1;
 }
 
+static void
+add_io(io_t *io)
+{
+    io_t **iop;
+
+    // Add the new reader to the end of the list if it's not on the list.
+    for (iop = &ios; *iop != NULL && *iop != io; iop = &((*iop)->next))
+        ;
+    if (*iop == NULL) {
+        *iop = io;
+        io->next = NULL;
+    }
+}
+
 void
-add_reader(io_t *io, io_callback_t callback, io_callback_t finalize)
+ioloop_add_reader(io_t *io, io_callback_t callback, io_callback_t finalize)
 {
-    io->next = ios;
-    ios = io;
+    add_io(io);
+
     io->read_callback = callback;
     io->finalize = finalize;
 #ifdef USE_SELECT
@@ -132,6 +163,96 @@ add_reader(io_t *io, io_callback_t callback, io_callback_t finalize)
 #endif // USE_EPOLL
 }
 
+void
+ioloop_add_writer(io_t *io, io_callback_t callback, io_callback_t finalize)
+{
+    add_io(io);
+
+    io->write_callback = callback;
+#ifdef USE_SELECT
+    io->want_write = true;
+#endif
+#ifdef USE_EPOLL
+#endif
+#ifdef USE_KQUEUE
+    struct kevent ev;
+    int rv;
+    EV_SET(&ev, io->sock, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, io);
+    rv = kevent(kq, &ev, 1, NULL, 0, NULL);
+    if (rv < 0) {
+        ERROR("kevent add: %s", strerror(errno));
+        return;
+    }
+#endif // USE_EPOLL
+}
+
+void
+drop_writer(io_t *io)
+{
+#ifdef USE_SELECT
+    io->want_write = false;
+#endif
+#ifdef USE_EPOLL
+#endif
+#ifdef USE_KQUEUE
+    struct kevent ev;
+    int rv;
+    EV_SET(&ev, io->sock, EVFILT_WRITE, EV_ADD | EV_DISABLE, 0, 0, io);
+    rv = kevent(kq, &ev, 1, NULL, 0, NULL);
+    if (rv < 0) {
+        ERROR("kevent add: %s", strerror(errno));
+        return;
+    }
+#endif // USE_EPOLL
+}
+
+static void
+add_remove_wakeup(wakeup_t *io, bool remove)
+{
+    wakeup_t **p_wakeups;
+
+    // Add the new reader to the end of the list if it's not on the list.
+    for (p_wakeups = &wakeups; *p_wakeups != NULL && *p_wakeups != io; p_wakeups = &((*p_wakeups)->next))
+        ;
+    if (remove) {
+        if (*p_wakeups != NULL) {
+            *p_wakeups = io->next;
+            io->next = NULL;
+        }
+    } else {
+        if (*p_wakeups == NULL) {
+            *p_wakeups = io;
+            io->next = NULL;
+        }
+    }
+}
+
+void
+ioloop_add_wake_event(wakeup_t *wakeup, void *context, wakeup_callback_t callback, int milliseconds)
+{
+    add_remove_wakeup(wakeup, false);
+    wakeup->wakeup_time = ioloop_timenow() + milliseconds;
+    wakeup->wakeup = callback;
+    wakeup->context = context;
+}
+
+void
+ioloop_cancel_wake_event(wakeup_t *wakeup)
+{
+    add_remove_wakeup(wakeup, true);
+    wakeup->wakeup_time = 0;
+}
+
+static void
+subproc_free(subproc_t *subproc)
+{
+    int i;
+    for (i = 0; i < subproc->argc; i++) {
+        free(subproc->argv[i]);
+    }
+    free(subproc);
+}
+
 bool
 ioloop_init(void)
 {
@@ -150,12 +271,16 @@ int
 ioloop_events(int64_t timeout_when)
 {
     io_t *io, **iop;
+    wakeup_t *wakeup, **p_wakeup;
     int nev = 0, rv;
     int64_t now = ioloop_timenow();
     int64_t next_event = timeout_when;
     int64_t timeout = 0;
 
-    INFO("%qd.%03qd seconds have passed on entry to ioloop_events", (now - ioloop_now) / 1000, (now - ioloop_now) % 1000);
+    if (ioloop_now != 0) {
+        INFO("%lld.%03lld seconds have passed on entry to ioloop_events",
+             (long long)((now - ioloop_now) / 1000), (long long)((now - ioloop_now) % 1000));
+    }
     ioloop_now = now;
 
     // A timeout of zero means don't time out.
@@ -173,21 +298,37 @@ ioloop_events(int64_t timeout_when)
     FD_ZERO(&reads);
     FD_ZERO(&writes);
     FD_ZERO(&errors);
-    
 #endif
-    iop = &ios;
-    while (*iop) {
-        io = *iop;
-        if (io->sock != -1 && io->wakeup_time != 0) {
-            if (io->wakeup_time <= ioloop_now) {
-                io->wakeup_time = 0;
-                io->wakeup(io);
+#ifdef USE_KQUEUE
+    struct timespec ts;
+#endif
+    p_wakeup = &wakeups;
+    while (*p_wakeup) {
+        wakeup = *p_wakeup;
+        if (wakeup->wakeup_time != 0) {
+            // We loop here in case the wakeup callback sets another wakeup--if it does, we check
+            // again.
+            while (wakeup->wakeup_time <= ioloop_now) {
+                wakeup->wakeup_time = 0;
+                wakeup->wakeup(wakeup->context);
                 ++nev;
-            } else if (io->wakeup_time < next_event) {
-                next_event = io->wakeup_time;
+                if (wakeup->wakeup_time == 0) {
+                    // Take the wakeup off the list.
+                    *p_wakeup = wakeup->next;
+                    wakeup->next = NULL;
+                    break;
+                }
+            }
+            if (wakeup->wakeup_time < next_event) {
+                next_event = wakeup->wakeup_time;
             }
         }
+    }
 
+    iop = &ios;
+    while (*iop) {
+        io = *iop;
+        // If the I/O is dead, finalize or free it.
         if (io->sock == -1) {
             *iop = io->next;
             if (io->finalize) {
@@ -198,25 +339,49 @@ ioloop_events(int64_t timeout_when)
             continue;
         }
 
-        // INFO("now: %qd  io %d wakeup_time %qd  next_event %qd", ioloop_now, io->sock, io->wakeup_time, next_event);
+        iop = &io->next;
+    }
 
-        // If we were given a timeout in the future, or told to wait indefinitely, wait until the next event.
-        if (timeout_when == 0 || timeout_when > ioloop_now) {
-            timeout = next_event - ioloop_now;
-            // Don't choose a time so far in the future that it might overflow some math in the kernel.
-            if (timeout > IOLOOP_DAY * 100) {
-                timeout = IOLOOP_DAY * 100;
-            }
+    // INFO("now: %ld  io %d wakeup_time %ld  next_event %ld", ioloop_now, io->sock, io->wakeup_time, next_event);
+
+    // If we were given a timeout in the future, or told to wait indefinitely, wait until the next event.
+    if (timeout_when == 0 || timeout_when > ioloop_now) {
+        timeout = next_event - ioloop_now;
+        // Don't choose a time so far in the future that it might overflow some math in the kernel.
+        if (timeout > IOLOOP_DAY * 100) {
+            timeout = IOLOOP_DAY * 100;
+        }
 #ifdef USE_SELECT
-            tv.tv_sec = timeout / 1000;
-            tv.tv_usec = (timeout % 1000) * 1000;
+        tv.tv_sec = timeout / 1000;
+        tv.tv_usec = (timeout % 1000) * 1000;
 #endif
 #ifdef USE_KQUEUE
-            ts.tv_sec = timeout / 1000;
-            ts.tv_nsec = (timeout % 1000) * 1000 * 1000;
+        ts.tv_sec = timeout / 1000;
+        ts.tv_nsec = (timeout % 1000) * 1000 * 1000;
 #endif
+    }
+
+    while (subprocesses != NULL) {
+        int status;
+        pid_t pid;
+        pid = waitpid(-1, &status, WNOHANG);
+        if (pid <= 0) {
+            break;
+        }
+        subproc_t **sp, *subproc;
+        for (sp = &subprocesses; (*sp) != NULL; sp = &(*sp)->next) {
+            subproc = *sp;
+            if (subproc->pid == pid) {
+                if (!WIFSTOPPED(status)) {
+                    *sp = subproc->next;
+                }
+                subproc->callback(subproc, status, NULL);
+                if (!WIFSTOPPED(status)) {
+                    subproc_free(subproc);
+                    break;
+                }
+            }
         }
-        iop = &io->next;
     }
 
 #ifdef USE_SELECT
@@ -236,14 +401,15 @@ ioloop_events(int64_t timeout_when)
 #endif
 
 #ifdef USE_SELECT
-    INFO("waiting %ld %d seconds", tv.tv_sec, tv.tv_usec);
-    rv = select(nfds, &reads, &writes, &writes, &tv);
+    INFO("waiting %lld %lld seconds", (long long)tv.tv_sec, (long long)tv.tv_usec);
+    rv = select(nfds, &reads, &writes, &errors, &tv);
     if (rv < 0) {
         ERROR("select: %s", strerror(errno));
         exit(1);
     }
     now = ioloop_timenow();
-    INFO("%qd.%03qd seconds passed waiting, got %d events", (now - ioloop_now) / 1000, (now - ioloop_now) % 1000, rv);
+    INFO("%lld.%03lld seconds passed waiting, got %d events", (long long)((now - ioloop_now) / 1000),
+         (long long)((now - ioloop_now) % 1000), rv);
     ioloop_now = now;
     for (io = ios; io; io = io->next) {
         if (io->sock != -1) {
@@ -259,22 +425,26 @@ ioloop_events(int64_t timeout_when)
 #ifdef USE_KQUEUE
 #define KEV_MAX 20
     struct kevent evs[KEV_MAX];
-    int i, rv;
-    struct timespec ts;
+    int i;
 
-    INFO("waiting %qd/%qd seconds", ts.tv_sec, ts.tv_nsec);
+    INFO("waiting %lld/%lld seconds", (long long)ts.tv_sec, (long long)ts.tv_nsec);
     do {
         rv = kevent(kq, NULL, 0, evs, KEV_MAX, &ts);
         now = ioloop_timenow();
-        INFO("%qd.%03qd seconds passed waiting, got %d events", (now - ioloop_now) / 1000, (now - ioloop_now) % 1000, rv);
+        INFO("%lld.%03lld seconds passed waiting, got %d events", (long long)((now - ioloop_now) / 1000),
+             (long long)((now - ioloop_now) % 1000), rv);
         ioloop_now = now;
         ts.tv_sec = 0;
         ts.tv_nsec = 0;
         if (rv < 0) {
-            ERROR("kevent poll: %s", strerror(errno));
-            exit(1);
+            if (errno == EINTR) {
+                rv = 0;
+            } else {
+                ERROR("kevent poll: %s", strerror(errno));
+                exit(1);
+            }
         }
-        for (i = 0; i < nev; i++) {
+        for (i = 0; i < rv; i++) {
             io = evs[i].udata;
             if (evs[i].filter == EVFILT_WRITE) {
                 io->write_callback(io);
@@ -309,7 +479,7 @@ udp_read_callback(io_t *io)
     msg.msg_namelen = sizeof src;
     msg.msg_control = cmsgbuf;
     msg.msg_controllen = sizeof cmsgbuf;
-    
+
     rv = recvmsg(connection->io.sock, &msg, 0);
     if (rv < 0) {
         ERROR("udp_read_callback: %s", strerror(errno));
@@ -323,20 +493,35 @@ udp_read_callback(io_t *io)
     memcpy(&message->src, &src, sizeof src);
     message->length = rv;
     memcpy(&message->wire, msgbuf, rv);
-    
+
     // For UDP, we use the interface index as part of the validation strategy, so go get
     // the interface index.
     for (cmh = CMSG_FIRSTHDR(&msg); cmh; cmh = CMSG_NXTHDR(&msg, cmh)) {
         if (cmh->cmsg_level == IPPROTO_IPV6 && cmh->cmsg_type == IPV6_PKTINFO) {
-            struct in6_pktinfo pktinfo;    
+            struct in6_pktinfo pktinfo;
 
             memcpy(&pktinfo, CMSG_DATA(cmh), sizeof pktinfo);
             message->ifindex = pktinfo.ipi6_ifindex;
-        } else if (cmh->cmsg_level == IPPROTO_IP && cmh->cmsg_type == IP_PKTINFO) { 
+
+            /* Get the destination address, for use when replying. */
+            message->local.sin6.sin6_family = AF_INET6;
+            message->local.sin6.sin6_port = 0;
+            message->local.sin6.sin6_addr = pktinfo.ipi6_addr;
+#ifndef NOT_HAVE_SA_LEN
+            message->local.sin6.sin6_len = sizeof message->local;
+#endif
+        } else if (cmh->cmsg_level == IPPROTO_IP && cmh->cmsg_type == IP_PKTINFO) {
             struct in_pktinfo pktinfo;
-          
+
             memcpy(&pktinfo, CMSG_DATA(cmh), sizeof pktinfo);
             message->ifindex = pktinfo.ipi_ifindex;
+
+            message->local.sin.sin_family = AF_INET;
+            message->local.sin.sin_port = 0;
+            message->local.sin.sin_addr = pktinfo.ipi_addr;
+#ifndef NOT_HAVE_SA_LEN
+            message->local.sin.sin_len = sizeof message->local;
+#endif
         }
     }
     connection->message = message;
@@ -346,67 +531,88 @@ udp_read_callback(io_t *io)
 static void
 tcp_read_callback(io_t *context)
 {
+    uint8_t *read_ptr;
+    size_t read_len;
     comm_t *connection = (comm_t *)context;
-    int rv;
+    ssize_t rv;
     if (connection->message_length_len < 2) {
-        rv = read(connection->io.sock, &connection->message_length_bytes[connection->message_length_len],
-                  2 - connection->message_length_len);
+        read_ptr = connection->message_length_bytes;
+        read_len = 2 - connection->message_length_len;
+    } else {
+        read_ptr = &connection->buf[connection->message_cur];
+        read_len = connection->message_length - connection->message_cur;
+    }
+
+    if (connection->tls_context != NULL) {
+#ifndef EXCLUDE_TLS
+        rv = srp_tls_read(connection, read_ptr, read_len);
+        if (rv == 0) {
+            // This isn't an EOF: that's returned as an error status.   This just means that
+            // whatever data was available to be read was consumed by the TLS protocol without
+            // producing anything to read at the app layer.
+            return;
+        } else if (rv < 0) {
+            ERROR("TLS return that we can't handle.");
+            close(connection->io.sock);
+            connection->io.sock = -1;
+            srp_tls_context_free(connection);
+            return;
+        }
+#else
+        ERROR("tls context with TLS excluded in tcp_read_callback.");
+        return;
+#endif
+    } else {
+        rv = read(connection->io.sock, read_ptr, read_len);
+
         if (rv < 0) {
-        read_error:
             ERROR("tcp_read_callback: %s", strerror(errno));
             close(connection->io.sock);
             connection->io.sock = -1;
             // connection->io.finalize() will be called from the io loop.
             return;
         }
+
         // If we read zero here, the remote endpoint has closed or shutdown the connection.  Either case is
         // effectively the same--if we are sensitive to read events, that means that we are done processing
         // the previous message.
         if (rv == 0) {
-        eof:
             ERROR("tcp_read_callback: remote end (%s) closed connection on %d", connection->name, connection->io.sock);
             close(connection->io.sock);
             connection->io.sock = -1;
             // connection->io.finalize() will be called from the io loop.
             return;
         }
+    }
+    if (connection->message_length_len < 2) {
         connection->message_length_len += rv;
         if (connection->message_length_len == 2) {
             connection->message_length = (((uint16_t)connection->message_length_bytes[0] << 8) |
                                           ((uint16_t)connection->message_length_bytes[1]));
-        }
-        return;
-    }
 
-    // If we only just got the length, we need to allocate a message
-    if (connection->message == NULL) {
-        connection->message = message_allocate(connection->message_length);
-        if (!connection->message) {
-            ERROR("udp_read_callback: out of memory");
-            return;
+            if (connection->message == NULL) {
+                connection->message = message_allocate(connection->message_length);
+                if (!connection->message) {
+                    ERROR("udp_read_callback: out of memory");
+                    return;
+                }
+                connection->buf = (uint8_t *)&connection->message->wire;
+                connection->message->length = connection->message_length;
+                memset(&connection->message->src, 0, sizeof connection->message->src);
+            }
+        }
+    } else {
+        connection->message_cur += rv;
+        if (connection->message_cur == connection->message_length) {
+            connection->message_cur = 0;
+            connection->datagram_callback(connection);
+            // Caller is expected to consume the message, we are immediately ready for the next read.
+            connection->message_length = connection->message_length_len = 0;
         }
-        connection->buf = (uint8_t *)&connection->message->wire;
-        connection->message->length = connection->message_length;
-        memset(&connection->message->src, 0, sizeof connection->message->src);
-    }
-
-    rv = read(connection->io.sock, &connection->buf[connection->message_cur],
-              connection->message_length - connection->message_cur);
-    if (rv < 0) {
-        goto read_error;
-    }
-    if (rv == 0) {
-        goto eof;
-    }
-
-    connection->message_cur += rv;
-    if (connection->message_cur == connection->message_length) {
-        connection->datagram_callback(connection);
-        // Caller is expected to consume the message, we are immediately ready for the next read.
-        connection->message_length = connection->message_length_len = 0;
     }
 }
 
+
 static void
 tcp_send_response(comm_t *comm, message_t *responding_to, struct iovec *iov, int iov_len)
 {
@@ -435,15 +641,25 @@ tcp_send_response(comm_t *comm, message_t *responding_to, struct iovec *iov, int
     lenbuf[1] = payload_length & 0xff;
     payload_length += 2;
 
-    memset(&mh, 0, sizeof mh);
-    mh.msg_iov = &iovec[0];
-    mh.msg_iovlen = iov_len + 1;
-    mh.msg_name = 0;
-
 #ifndef MSG_NOSIGNAL
 #define MSG_NOSIGNAL 0
 #endif
-    status = sendmsg(comm->io.sock, &mh, MSG_NOSIGNAL);
+    if (comm->tls_context != NULL) {
+#ifndef EXCLUDE_TLS
+        status = srp_tls_write(comm, iovec, iov_len + 1);
+#else
+        ERROR("TLS context not null with TLS excluded.");
+        status = -1;
+        errno = ENOTSUP;
+#endif
+    } else {
+        memset(&mh, 0, sizeof mh);
+        mh.msg_iov = &iovec[0];
+        mh.msg_iovlen = iov_len + 1;
+        mh.msg_name = 0;
+
+        status = sendmsg(comm->io.sock, &mh, MSG_NOSIGNAL);
+    }
     if (status < 0 || status != payload_length) {
         if (status < 0) {
             ERROR("tcp_send_response: write failed: %s", strerror(errno));
@@ -456,34 +672,106 @@ tcp_send_response(comm_t *comm, message_t *responding_to, struct iovec *iov, int
 }
 
 static void
-udp_send_response(comm_t *comm, message_t *responding_to, struct iovec *iov, int iov_len)
+udp_send_message(comm_t *comm, addr_t *source, addr_t *dest, int ifindex, struct iovec *iov, int iov_len)
 {
     struct msghdr mh;
+    uint8_t cmsg_buf[128];
+    struct cmsghdr *cmsg;
+    int status;
+
     memset(&mh, 0, sizeof mh);
     mh.msg_iov = iov;
     mh.msg_iovlen = iov_len;
-    mh.msg_name = &responding_to->src;
-    if (responding_to->src.sa.sa_family == AF_INET) {
-        mh.msg_namelen = sizeof (struct sockaddr_in);
-    } else if (responding_to->src.sa.sa_family == AF_INET6) {
-        mh.msg_namelen = sizeof (struct sockaddr_in6);
+    mh.msg_name = dest;
+    mh.msg_control = cmsg_buf;
+    if (source == NULL && ifindex == 0) {
+        mh.msg_controllen = 0;
     } else {
-        ERROR("send_udp_response: unknown family %d", responding_to->src.sa.sa_family);
-        abort();
+        mh.msg_controllen = sizeof cmsg_buf;
+        cmsg = CMSG_FIRSTHDR(&mh);
+
+        if (source->sa.sa_family == AF_INET) {
+            struct in_pktinfo *inp;
+            mh.msg_namelen = sizeof (struct sockaddr_in);
+            mh.msg_controllen = CMSG_SPACE(sizeof *inp);
+            cmsg->cmsg_level = IPPROTO_IP;
+            cmsg->cmsg_type = IP_PKTINFO;
+            cmsg->cmsg_len = CMSG_LEN(sizeof *inp);
+            inp = (struct in_pktinfo *)CMSG_DATA(cmsg);
+            memset(inp, 0, sizeof *inp);
+            inp->ipi_ifindex = ifindex;
+            if (source) {
+                inp->ipi_spec_dst = source->sin.sin_addr;
+                inp->ipi_addr = source->sin.sin_addr;
+            }
+        } else if (source->sa.sa_family == AF_INET6) {
+            struct in6_pktinfo *inp;
+            mh.msg_namelen = sizeof (struct sockaddr_in6);
+            mh.msg_controllen = CMSG_SPACE(sizeof *inp);
+            cmsg->cmsg_level = IPPROTO_IPV6;
+            cmsg->cmsg_type = IPV6_PKTINFO;
+            cmsg->cmsg_len = CMSG_LEN(sizeof *inp);
+            inp = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+            memset(inp, 0, sizeof *inp);
+            inp->ipi6_ifindex = ifindex;
+            if (source) {
+                inp->ipi6_addr = source->sin6.sin6_addr;
+            }
+        } else {
+            ERROR("udp_send_response: unknown family %d", source->sa.sa_family);
+            abort();
+        }
+    }
+    status = sendmsg(comm->io.sock, &mh, 0);
+    if (status < 0) {
+        ERROR("udp_send_message: %s", strerror(errno));
+    }
+}
+
+static void
+udp_send_response(comm_t *comm, message_t *responding_to, struct iovec *iov, int iov_len)
+{
+    udp_send_message(comm, &responding_to->local, &responding_to->src, responding_to->ifindex, iov, iov_len);
+}
+
+static void
+udp_send_multicast(comm_t *comm, int ifindex, struct iovec *iov, int iov_len)
+{
+    udp_send_message(comm, NULL, &comm->multicast, ifindex, iov, iov_len);
+}
+
+static void
+udp_send_connected_response(comm_t *comm, message_t *responding_to, struct iovec *iov, int iov_len)
+{
+    int status = writev(comm->io.sock, iov, iov_len);
+    (void)responding_to;
+    if (status < 0) {
+        ERROR("udp_send_connected: %s", strerror(errno));
     }
-    sendmsg(comm->io.sock, &mh, 0);
 }
 
 // When a communication is closed, scan the io event list to see if any other ios are referencing this one.
 void
-comm_finalize(io_t *io_in) {
-    io_t *io;
+comm_finalize(io_t *io_in)
+{
+    comm_t *comm = (comm_t *)io_in;
+    comm_free(comm);
+}
 
-    for (io = ios; io; io = io->next) {
-        if (io->cancel_on_close == io_in && io->cancel != NULL) {
-            io->cancel(io);
-        }
+bool
+comm_valid(comm_t *comm)
+{
+    if (comm->io.sock != -1) {
+        return true;
     }
+    return false;
+}
+
+void
+comm_close(comm_t *comm)
+{
+    close(comm->io.sock);
+    comm->io.sock = -1;
 }
 
 static void
@@ -509,20 +797,33 @@ listen_callback(io_t *context)
                                   : (void *)&addr.sin6.sin6_addr), addrbuf, sizeof addrbuf);
     addrlen = strlen(addrbuf);
     snprintf(&addrbuf[addrlen], (sizeof addrbuf) - addrlen, "%%%d",
-             (addr.sa.sa_family == AF_INET ? addr.sin.sin_port : addr.sin6.sin6_port));
+             ntohs((addr.sa.sa_family == AF_INET ? addr.sin.sin_port : addr.sin6.sin6_port)));
     comm = calloc(1, sizeof *comm);
     comm->name = strdup(addrbuf);
     comm->io.sock = rv;
+    comm->io.container = comm;
     comm->address = addr;
     comm->datagram_callback = listener->datagram_callback;
     comm->send_response = tcp_send_response;
     comm->tcp_stream = true;
 
+    if (listener->tls_context == (tls_context_t *)-1) {
+#ifndef EXCLUDE_TLS
+        if (!srp_tls_listen_callback(comm)) {
+            ERROR("TLS  setup failed.");
+            close(comm->io.sock);
+            free(comm);
+            return;
+        }
+#else
+        ERROR("TLS context not null in listen_callback when TLS excluded.");
+        return;
+#endif
+    }
     if (listener->connected) {
         listener->connected(comm);
     }
-    add_reader(&comm->io, tcp_read_callback, NULL);
-    comm->io.finalize = comm_finalize;
+    ioloop_add_reader(&comm->io, tcp_read_callback, listener->connection_finalize);
 
 #ifdef SO_NOSIGPIPE
     int one = 1;
@@ -534,49 +835,147 @@ listen_callback(io_t *context)
 }
 
 comm_t *
-setup_listener_socket(int family, int protocol, uint16_t port, const char *name,
-                      comm_callback_t datagram_callback,
-                      comm_callback_t connected, void *context)
+ioloop_setup_listener(int family, bool stream, bool tls, uint16_t port, const char *ip_address, const char *multicast,
+                      const char *name, comm_callback_t datagram_callback,
+                      comm_callback_t connected, comm_callback_t disconnected,
+                      io_callback_t finalize, io_callback_t connection_finalize, void *context)
 {
     comm_t *listener;
     socklen_t sl;
     int rv;
-    int flag = 1;
-    
+    int false_flag = 0;
+    int true_flag = 1;
+
     listener = calloc(1, sizeof *listener);
     if (listener == NULL) {
         return NULL;
     }
+    listener->io.container = listener;
     listener->name = strdup(name);
     if (!listener->name) {
         free(listener);
         return NULL;
     }
-    listener->io.sock = socket(family, protocol == IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM, protocol);
+    listener->io.sock = socket(family, stream ? SOCK_STREAM : SOCK_DGRAM, stream ? IPPROTO_TCP : IPPROTO_UDP);
     if (listener->io.sock < 0) {
         ERROR("Can't get socket: %s", strerror(errno));
-        comm_free(listener);
-        return NULL;
+        goto out;
     }
-    rv = setsockopt(listener->io.sock, SOL_SOCKET, SO_REUSEPORT, &flag, sizeof flag);
+    rv = setsockopt(listener->io.sock, SOL_SOCKET, SO_REUSEADDR, &true_flag, sizeof true_flag);
+    if (rv < 0) {
+        ERROR("SO_REUSEADDR failed: %s", strerror(errno));
+        goto out;
+    }
+
+    rv = setsockopt(listener->io.sock, SOL_SOCKET, SO_REUSEPORT, &true_flag, sizeof true_flag);
     if (rv < 0) {
         ERROR("SO_REUSEPORT failed: %s", strerror(errno));
-        comm_free(listener);
-        return NULL;
+        goto out;
+    }
+
+    if (ip_address != NULL) {
+        sl = getipaddr(&listener->address, ip_address);
+        if (sl == 0) {
+            goto out;
+        }
+        if (family == AF_UNSPEC) {
+            family = listener->address.sa.sa_family;
+        } else if (listener->address.sa.sa_family != family) {
+            ERROR("%s is not a %s address.", ip_address, family == AF_INET ? "IPv4" : "IPv6");
+            goto out;
+        }
+    }
+
+    if (multicast != 0) {
+        if (stream) {
+            ERROR("Unable to do non-datagram multicast.");
+            goto out;
+        }
+        sl = getipaddr(&listener->multicast, multicast);
+        if (sl == 0) {
+            goto out;
+        }
+        if (listener->multicast.sa.sa_family != family) {
+            ERROR("multicast address %s from different family than listen address %s.", multicast, ip_address);
+            goto out;
+        }
+        listener->is_multicast = true;
+
+        if (family == AF_INET) {
+            struct ip_mreq im;
+            int ttl = 255;
+            im.imr_multiaddr = listener->multicast.sin.sin_addr;
+            im.imr_interface.s_addr = 0;
+            rv = setsockopt(listener->io.sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &im, sizeof im);
+            if (rv < 0) {
+                ERROR("Unable to join %s multicast group: %s", multicast, strerror(errno));
+                goto out;
+            }
+            rv = setsockopt(listener->io.sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof ttl);
+            if (rv < 0) {
+                ERROR("Unable to set IP multicast TTL to 255 for %s: %s", multicast, strerror(errno));
+                goto out;
+            }
+            rv = setsockopt(listener->io.sock, IPPROTO_IP, IP_TTL, &ttl, sizeof ttl);
+            if (rv < 0) {
+                ERROR("Unable to set IP TTL to 255 for %s: %s", multicast, strerror(errno));
+                goto out;
+            }
+            rv = setsockopt(listener->io.sock, IPPROTO_IP, IP_MULTICAST_LOOP, &false_flag, sizeof false_flag);
+            if (rv < 0) {
+                ERROR("Unable to set IP Multcast loopback to false for %s: %s", multicast, strerror(errno));
+                goto out;
+            }
+        } else {
+            struct ipv6_mreq im;
+            int hops = 255;
+            im.ipv6mr_multiaddr = listener->multicast.sin6.sin6_addr;
+            im.ipv6mr_interface = 0;
+            rv = setsockopt(listener->io.sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, &im, sizeof im);
+            if (rv < 0) {
+                ERROR("Unable to join %s multicast group: %s", multicast, strerror(errno));
+                goto out;
+            }
+            rv = setsockopt(listener->io.sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, sizeof hops);
+            if (rv < 0) {
+                ERROR("Unable to set IPv6 multicast hops to 255 for %s: %s", multicast, strerror(errno));
+                goto out;
+            }
+            rv = setsockopt(listener->io.sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hops, sizeof hops);
+            if (rv < 0) {
+                ERROR("Unable to set IPv6 hops to 255 for %s: %s", multicast, strerror(errno));
+                goto out;
+            }
+            rv = setsockopt(listener->io.sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &false_flag, sizeof false_flag);
+            if (rv < 0) {
+                ERROR("Unable to set IPv6 Multcast loopback to false for %s: %s", multicast, strerror(errno));
+                goto out;
+            }
+        }
     }
 
     if (family == AF_INET) {
         sl = sizeof listener->address.sin;
-        listener->address.sin.sin_port = port ? port : htons(53);
+        listener->address.sin.sin_port = port ? htons(port) : htons(53);
     } else {
         sl = sizeof listener->address.sin6;
-        listener->address.sin6.sin6_port = port ? port : htons(53);
+        listener->address.sin6.sin6_port = port ? htons(port) : htons(53);
+        // Don't use a dual-stack socket.
+        rv = setsockopt(listener->io.sock, IPPROTO_IPV6, IPV6_V6ONLY, &true_flag, sizeof true_flag);
+        if (rv < 0) {
+            ERROR("Unable to set IPv6-only flag on %s socket for %s",
+                  tls ? "TLS" : (stream ? "TCP" : "UDP"), ip_address == NULL ? "<0>" : ip_address);
+            goto out;
+        }
     }
+
     listener->address.sa.sa_family = family;
+#ifndef NOT_HAVE_SA_LEN
     listener->address.sa.sa_len = sl;
+#endif
     if (bind(listener->io.sock, &listener->address.sa, sl) < 0) {
-        ERROR("Can't bind to 0#53/%s%s: %s",
-                protocol == IPPROTO_UDP ? "udp" : "tcp", family == AF_INET ? "v4" : "v6",
+        ERROR("Can't bind to %s#%d/%s%s: %s", ip_address == NULL ? "<0>" : ip_address, port,
+                stream ? "tcp" : "udp", family == AF_INET ? "v4" : "v6",
                 strerror(errno));
     out:
         close(listener->io.sock);
@@ -584,30 +983,383 @@ setup_listener_socket(int family, int protocol, uint16_t port, const char *name,
         return NULL;
     }
 
-    if (protocol == IPPROTO_TCP) {
+    if (tls) {
+#ifndef EXCLUDE_TLS
+        if (!stream) {
+            ERROR("Asked to do TLS over UDP, which we don't do yet.");
+            goto out;
+        }
+        listener->tls_context = (tls_context_t *)-1;
+#else
+        ERROR("TLS requested when TLS is excluded.");
+        goto out;
+#endif
+    }
+
+    if (stream) {
         if (listen(listener->io.sock, 5 /* xxx */) < 0) {
-            ERROR("Can't listen on 0#53/%s%s: %s.",
-                    protocol == IPPROTO_UDP ? "udp" : "tcp", family == AF_INET ? "v4" : "v6",
+            ERROR("Can't listen on %s#%d/%s%s: %s", ip_address == NULL ? "<0>" : ip_address, ntohs(port),
+                    tls ? "tls" : "tcp", family == AF_INET ? "v4" : "v6",
                     strerror(errno));
             goto out;
-        }                
-        add_reader(&listener->io, listen_callback, NULL);
+        }
+        listener->connection_finalize = connection_finalize;
+        ioloop_add_reader(&listener->io, listen_callback, finalize);
     } else {
         rv = setsockopt(listener->io.sock, family == AF_INET ? IPPROTO_IP : IPPROTO_IPV6,
-                        family == AF_INET ? IP_PKTINFO : IPV6_RECVPKTINFO, &flag, sizeof flag);
+                        family == AF_INET ? IP_PKTINFO : IPV6_RECVPKTINFO, &true_flag, sizeof true_flag);
         if (rv < 0) {
             ERROR("Can't set %s: %s.", family == AF_INET ? "IP_PKTINFO" : "IPV6_RECVPKTINFO",
                     strerror(errno));
             goto out;
         }
-        add_reader(&listener->io, udp_read_callback, NULL);
+        ioloop_add_reader(&listener->io, udp_read_callback, finalize);
         listener->send_response = udp_send_response;
+        listener->send_message = udp_send_message;
+        if (listener->is_multicast) {
+            listener->send_multicast = udp_send_multicast;
+        }
     }
     listener->datagram_callback = datagram_callback;
     listener->connected = connected;
     return listener;
 }
 
+static void
+connect_callback(io_t *context)
+{
+    int result;
+    socklen_t len = sizeof result;
+    comm_t *connection = (comm_t *)context;
+
+    // If connect failed, indicate that it failed.
+    if (getsockopt(context->sock, SOL_SOCKET, SO_ERROR, &result, &len) < 0) {
+        ERROR("connect_callback: unable to get connect error: socket %d: Error %d (%s)",
+              context->sock, result, strerror(result));
+        connection->disconnected(connection, result);
+        comm_close(connection);
+        return;
+    }
+
+    // If this is a TLS connection, set up TLS.
+    if (connection->tls_context == (tls_context_t *)-1) {
+#ifndef EXCLUDE_TLS
+        srp_tls_connect_callback(connection);
+#else
+        ERROR("connect_callback: tls_context triggered with TLS excluded.");
+        connection->disconnected(connection, 0);
+        comm_close(connection);
+        return;
+#endif
+    }
+
+    connection->send_response = tcp_send_response;
+    connection->connected(connection);
+    drop_writer(&connection->io);
+    ioloop_add_reader(&connection->io, tcp_read_callback, connection->io.finalize);
+}
+
+// Currently we don't do DNS lookups, despite the host identifier being an IP address.
+comm_t *
+ioloop_connect(addr_t *NONNULL remote_address, bool tls, bool stream,
+               comm_callback_t datagram_callback, comm_callback_t connected,
+               disconnect_callback_t disconnected, io_callback_t finalize, void *context)
+{
+    comm_t *connection;
+    socklen_t sl;
+    char buf[INET6_ADDRSTRLEN + 7];
+    char *s;
+
+    if (!stream && (connected != NULL || disconnected != NULL)) {
+        ERROR("connected and disconnected callbacks not valid for datagram connections");
+        return NULL;
+    }
+    if (stream && (connected == NULL || disconnected == NULL)) {
+        ERROR("connected and disconnected callbacks are required for stream connections");
+        return NULL;
+    }
+    connection = calloc(1, sizeof *connection);
+    if (connection == NULL) {
+        ERROR("No memory for connection structure.");
+        return NULL;
+    }
+    connection->io.container = connection;
+    if (inet_ntop(remote_address->sa.sa_family, (remote_address->sa.sa_family == AF_INET
+                                                 ? (void *)&remote_address->sin.sin_addr
+                                                 : (void *)&remote_address->sin6.sin6_addr), buf,
+                  INET6_ADDRSTRLEN) == NULL) {
+        ERROR("inet_ntop failed to convert remote address: %s", strerror(errno));
+        free(connection);
+        return NULL;
+    }
+    s = buf + strlen(buf);
+    sprintf(s, "%%%hu", ntohs(remote_address->sa.sa_family == AF_INET
+                              ? remote_address->sin.sin_port
+                              : remote_address->sin6.sin6_port));
+    connection->name = strdup(buf);
+    if (!connection->name) {
+        free(connection);
+        return NULL;
+    }
+    connection->io.sock = socket(remote_address->sa.sa_family,
+                                 stream ? SOCK_STREAM : SOCK_DGRAM, stream ? IPPROTO_TCP : IPPROTO_UDP);
+    if (connection->io.sock < 0) {
+        ERROR("Can't get socket: %s", strerror(errno));
+        comm_free(connection);
+        return NULL;
+    }
+    connection->address = *remote_address;
+    if (fcntl(connection->io.sock, F_SETFL, O_NONBLOCK) < 0) {
+        ERROR("connect_to_host: %s: Can't set O_NONBLOCK: %s", connection->name, strerror(errno));
+        comm_free(connection);
+        return NULL;
+    }
+#ifdef NOT_HAVE_SA_LEN
+    sl = (remote_address->sa.sa_family == AF_INET
+          ? sizeof remote_address->sin
+          : sizeof remote_address->sin6);
+#else
+    sl = remote_address->sa.sa_len;
+#endif
+    // Connect to the host
+    if (connect(connection->io.sock, &connection->address.sa, sl) < 0) {
+        if (errno != EINPROGRESS && errno != EAGAIN) {
+            ERROR("Can't connect to %s: %s", connection->name, strerror(errno));
+            comm_free(connection);
+            return NULL;
+        }
+    }
+    // At this point if we are doing TCP, we do not yet have a connection, but the connection should be in
+    // progress, and we should get a write select event when the connection succeeds or fails.
+    // UDP is connectionless, so the connect() call just sets the default destination for send() on
+    // the socket.
+
+    if (tls) {
+#ifndef TLS_EXCLUDED
+        connection->tls_context = (tls_context_t *)-1;
+#else
+        ERROR("connect_to_host: tls requested when excluded.");
+        comm_free(connection);
+        return NULL;
+#endif
+    }
+
+    connection->connected = connected;
+    connection->disconnected = disconnected;
+    connection->datagram_callback = datagram_callback;
+    connection->context = context;
+    if (!stream) {
+        connection->send_response = udp_send_connected_response;
+        ioloop_add_reader(&connection->io, udp_read_callback, finalize);
+    } else {
+        ioloop_add_writer(&connection->io, connect_callback, finalize);
+    }
+
+    return connection;
+}
+
+typedef struct interface_addr interface_addr_t;
+struct interface_addr {
+    interface_addr_t *next;
+    char *name;
+    addr_t addr;
+    addr_t mask;
+    int index;
+};
+interface_addr_t *interface_addresses;
+
+bool
+ioloop_map_interface_addresses(void *context, interface_callback_t callback)
+{
+    struct ifaddrs *ifaddrs, *ifp;
+    interface_addr_t *kept_ifaddrs = NULL, **ki_end = &kept_ifaddrs;
+    interface_addr_t *new_ifaddrs = NULL, **ni_end = &new_ifaddrs;
+    interface_addr_t **ip, *nif;
+    char *ifname = NULL;
+    int ifindex = 0;
+
+    if (getifaddrs(&ifaddrs) < 0) {
+        ERROR("getifaddrs failed: %s", strerror(errno));
+        return false;
+    }
+
+    for (ifp = ifaddrs; ifp; ifp = ifp->ifa_next) {
+        // Is this an interface address we can use?
+        if (ifp->ifa_addr != NULL && ifp->ifa_netmask != NULL &&
+            (ifp->ifa_addr->sa_family == AF_INET ||
+             ifp->ifa_addr->sa_family == AF_INET6) &&
+            (ifp->ifa_flags & IFF_UP) &&
+            !(ifp->ifa_flags & IFF_POINTOPOINT))
+        {
+            bool keep = false;
+            for (ip = &interface_addresses; *ip != NULL; ) {
+                interface_addr_t *ia = *ip;
+                // Same interface and address?
+                if (!strcmp(ia->name, ifp->ifa_name) &&
+                    ifp->ifa_addr->sa_family == ia->addr.sa.sa_family &&
+                    ((ifp->ifa_addr->sa_family == AF_INET &&
+                      ((struct sockaddr_in *)ifp->ifa_addr)->sin_addr.s_addr == ia->addr.sin.sin_addr.s_addr) ||
+                     (ifp->ifa_addr->sa_family == AF_INET6 &&
+                      !memcmp(&((struct sockaddr_in6 *)ifp->ifa_addr)->sin6_addr,
+                              &ia->addr.sin6.sin6_addr, sizeof ia->addr.sin6.sin6_addr))) &&
+                    ((ifp->ifa_netmask->sa_family == AF_INET &&
+                      ((struct sockaddr_in *)ifp->ifa_netmask)->sin_addr.s_addr == ia->mask.sin.sin_addr.s_addr) ||
+                     (ifp->ifa_netmask->sa_family == AF_INET6 &&
+                      !memcmp(&((struct sockaddr_in6 *)ifp->ifa_netmask)->sin6_addr,
+                              &ia->mask.sin6.sin6_addr, sizeof ia->mask.sin6.sin6_addr))))
+                {
+                    *ki_end = ia;
+                    ki_end = &ia->next;
+                    keep = true;
+                    break;
+                } else {
+                    ip = &ia->next;
+                }
+            }
+            // If keep is false, this is a new interface.
+            if (!keep) {
+                nif = calloc(1, strlen(ifp->ifa_name) + 1 + sizeof *nif);
+                // We don't have a way to fix nif being null; what this means is that we don't detect a new
+                // interface address.
+                if (nif != NULL) {
+                    nif->name = (char *)(nif + 1);
+                    strcpy(nif->name, ifp->ifa_name);
+                    if (ifp->ifa_addr->sa_family == AF_INET) {
+                        nif->addr.sin = *((struct sockaddr_in *)ifp->ifa_addr);
+                        nif->mask.sin = *((struct sockaddr_in *)ifp->ifa_netmask);
+                    } else {
+                        nif->addr.sin6 = *((struct sockaddr_in6 *)ifp->ifa_addr);
+                        nif->mask.sin6 = *((struct sockaddr_in6 *)ifp->ifa_netmask);
+                    }
+                    *ni_end = nif;
+                    ni_end = &nif->next;
+                }
+            }
+        }
+    }
+
+    // Report and free deleted interface addresses...
+    for (nif = interface_addresses; nif; ) {
+        interface_addr_t *next = nif->next;
+        callback(context, nif->name, &nif->addr, &nif->mask, nif->index, interface_address_deleted);
+        free(nif);
+        nif = next;
+    }
+
+    // Report added interface addresses...
+    for (nif = new_ifaddrs; nif; nif = nif->next) {
+        // Get interface index using standard API if AF_LINK didn't work.
+        if (nif->index == 0) {
+            if (ifindex != 0 && ifname != NULL && !strcmp(ifname, nif->name)) {
+                nif->index = ifindex;
+            } else {
+                ifname = nif->name;
+                ifindex = if_nametoindex(nif->name);
+                nif->index = ifindex;
+                INFO("Got interface index for " PUB_S_SRP " the hard way: %d", nif->name, nif->index);
+            }
+        }
+        callback(context, nif->name, &nif->addr, &nif->mask, nif->index, interface_address_added);
+    }
+
+    // Restore kept interface addresses and append new addresses to the list.
+    interface_addresses = kept_ifaddrs;
+    for (ip = &new_ifaddrs; *ip; ip = &(*ip)->next)
+        ;
+    *ip = new_ifaddrs;
+    return true;
+}
+
+// Invoke the specified executable with the specified arguments.   Call callback when it exits.
+// All failures are reported through the callback.
+subproc_t *
+ioloop_subproc(const char *exepath, char *NULLABLE *argv, int argc, subproc_callback_t callback)
+{
+    subproc_t *subproc = calloc(1, sizeof *subproc);
+    int i;
+    pid_t pid;
+
+    if (subproc == NULL) {
+        callback(NULL, 0, "out of memory");
+        return NULL;
+    }
+    if (argc > MAX_SUBPROC_ARGS) {
+        callback(NULL, 0, "too many subproc args");
+        subproc_free(subproc);
+        return NULL;
+    }
+
+    subproc->argv[0] = strdup(exepath);
+    if (subproc->argv[0] == NULL) {
+        subproc_free(subproc);
+        return NULL;
+    }
+    subproc->argc++;
+    for (i = 0; i < argc; i++) {
+        subproc->argv[i + 1] = strdup(argv[i]);
+        if (subproc->argv[i + 1] == NULL) {
+            subproc_free(subproc);
+            return NULL;
+        }
+        subproc->argc++;
+    }
+    pid = vfork();
+    if (pid == 0) {
+        execv(exepath, subproc->argv);
+        _exit(errno);
+        // NOTREACHED
+    }
+    if (pid == -1) {
+        callback(subproc, 0, strerror(errno));
+        subproc_free(subproc);
+        return NULL;
+    }
+
+    subproc->callback = callback;
+    subproc->pid = pid;
+    subproc->next = subprocesses;
+    subprocesses = subproc;
+    return subproc;
+}
+
+static void
+dnssd_txn_callback(io_t *io)
+{
+    dnssd_txn_t *txn = (dnssd_txn_t *)io;
+    int status = DNSServiceProcessResult(txn->sdref);
+    if (status != kDNSServiceErr_NoError) {
+        if (txn->close_callback != NULL) {
+            txn->close_callback(txn->context, status);
+        }
+    }
+}
+
+void
+dnssd_txn_finalize(io_t *io)
+{
+    dnssd_txn_t *txn = (dnssd_txn_t *)io;
+
+    if (txn->finalize_callback) {
+        txn->finalize_callback(txn->context);
+    }
+}
+
+dnssd_txn_t *
+ioloop_dnssd_txn_add(DNSServiceRef ref,
+                     dnssd_txn_finalize_callback_t finalize_callback, dnssd_txn_close_callback_t close_callback)
+{
+    dnssd_txn_t *txn = calloc(1, sizeof(*txn));
+    if (txn != NULL) {
+        RETAIN(txn);
+        io->sdref = sdref;
+        txn->io.sock = DNSServiceRefSockFD(txn->sdref);
+        txn->finalize_callback = finalize_callback;
+        txn->close_callback = close_callback;
+        ioloop_add_reader(&txn->io, dnssd_txn_callback, dnssd_txn_finalize);
+    }
+    return txn;
+}
+
 // Local Variables:
 // mode: C
 // tab-width: 4
index 9d73d5f9690fe3975243a843c2f3a3cc25c972d6..448773d7e7112db714127d2436e9ccb37ce61cb4 100644 (file)
@@ -1,4 +1,4 @@
-/* ioloop.c
+/* ioloop.h
  *
  * Copyright (c) 2018-2019 Apple Computer, Inc. All rights reserved.
  *
 #ifndef __IOLOOP_H
 #define __IOLOOP_H
 
+#ifdef IOLOOP_MACOS
+#include <nw/private.h>
+#include <Network/Network.h>
+#include <xpc/xpc.h>
+#include <xpc/private.h>
+#endif
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <net/if_dl.h>
+
 #ifndef __DSO_H
 typedef struct dso_state dso_state_t;
 #endif
@@ -29,57 +40,165 @@ union addr {
     struct sockaddr sa;
     struct sockaddr_in sin;
     struct sockaddr_in6 sin6;
+    struct {
+        char len;
+        char family;
+        int index;
+        uint8_t addr[8];
+    } ether_addr;
 };
 
-typedef struct message message_t;
+#define IOLOOP_NTOP(addr, buf) \
+    (((addr)->sa.sa_family == AF_INET || (addr)->sa.sa_family == AF_INET6) \
+     ? (inet_ntop((addr)->sa.sa_family, ((addr)->sa.sa_family == AF_INET \
+                                        ? (void *)&(addr)->sin.sin_addr \
+                                        : (void *)&(addr)->sin6.sin6_addr), buf, sizeof buf) != NULL) \
+    : snprintf(buf, sizeof buf, "Address type %d", (addr)->sa.sa_family))
+
 struct message {
+    int ref_count;
+#ifndef IOLOOP_MACOS
     addr_t src;
+    addr_t local;
+#endif
     int ifindex;
-    size_t length;
+    uint16_t length;
     dns_wire_t wire;
 };
 
+
 typedef struct dso_transport comm_t;
 typedef struct io io_t;
-typedef void (*io_callback_t)(io_t *NONNULL io);
+typedef struct subproc subproc_t;
+typedef struct wakeup wakeup_t;
+typedef struct dnssd_txn dnssd_txn_t;
+
+typedef void (*dnssd_txn_finalize_callback_t)(void *NONNULL context);
+typedef void (*wakeup_callback_t)(void *NONNULL context);
+typedef void (*finalize_callback_t)(void *NONNULL context);
+typedef void (*cancel_callback_t)(void *NONNULL context);
+typedef void (*ready_callback_t)(void *NONNULL context, uint16_t port);
+typedef void (*io_callback_t)(io_t *NONNULL io, void *NONNULL context);
 typedef void (*comm_callback_t)(comm_t *NONNULL comm);
-typedef void (*send_response_t)(comm_t *NONNULL comm, message_t *NONNULL responding_to, struct iovec *NONNULL iov, int count);
+typedef void (*datagram_callback_t)(comm_t *NONNULL comm, message_t *NONNULL message, void *NULLABLE context);
+typedef void (*connect_callback_t)(comm_t *NONNULL connection, void *NULLABLE context);
+typedef void (*disconnect_callback_t)(comm_t *NONNULL comm, int error);
+enum interface_address_change { interface_address_added, interface_address_deleted, interface_address_unchanged };
+typedef void (*interface_callback_t)(void *NULLABLE context, const char *NONNULL name,
+                                     const addr_t *NONNULL address, const addr_t *NONNULL netmask,
+                                     uint32_t flags, enum interface_address_change event_type);
+typedef void (*subproc_callback_t)(void *NULLABLE context, int status, const char *NULLABLE error);
+#ifdef IOLOOP_MACOS
+typedef bool (*ioloop_xpc_callback_t)(xpc_connection_t NULLABLE conn, xpc_object_t NULLABLE request);
+#endif
 
-#define IOLOOP_SECOND  1000LL
-#define IOLOOP_MINUTE  60 * IOLOOP_SECOND
-#define IOLOOP_HOUR            60 * IOLOOP_MINUTE
-#define IOLOOP_DAY             24 * IOLOOP_HOUR
+typedef struct tls_context tls_context_t;
+
+#define IOLOOP_SECOND   1000LL
+#define IOLOOP_MINUTE   60 * IOLOOP_SECOND
+#define IOLOOP_HOUR     60 * IOLOOP_MINUTE
+#define IOLOOP_DAY      24 * IOLOOP_HOUR
 
 struct io {
+    int ref_count;
     io_t *NULLABLE next;
     io_callback_t NULLABLE read_callback;
     io_callback_t NULLABLE write_callback;
-    io_callback_t NULLABLE finalize;
-    io_callback_t NULLABLE wakeup;
-    io_callback_t NULLABLE cancel;
-    int sock;
-    int64_t wakeup_time;
+    finalize_callback_t NULLABLE finalize;
+    void *NULLABLE context;
     io_t *NULLABLE cancel_on_close;
+#ifdef IOLOOP_MACOS
+    dispatch_source_t NULLABLE read_source;
+    dispatch_source_t NULLABLE write_source;
+#else
     bool want_read : 1;
     bool want_write : 1;
+#endif
+    int fd;
+};
+
+struct wakeup {
+    int ref_count;
+    wakeup_t *NULLABLE next;
+    void *NULLABLE context;
+    wakeup_callback_t NULLABLE wakeup;
+    finalize_callback_t NULLABLE finalize;
+#ifdef IOLOOP_MACOS
+    dispatch_source_t NULLABLE dispatch_source;
+#else
+    int64_t wakeup_time;
+#endif
 };
 
 struct dso_transport {
+    int ref_count;
+#ifdef IOLOOP_MACOS
+    nw_connection_t NULLABLE connection;
+    nw_listener_t NULLABLE listener;
+    nw_parameters_t NULLABLE parameters;
+    int writes_pending;
+    bool read_pending; // Only ever one.
+    bool server;       // Indicates that this connection was created by a listener
+    bool connection_ready;
+    wakeup_t *NULLABLE idle_timer;
+    // nw_connection objects aren't necessarily ready to write to immediately. But when we create an outgoing connection, we
+    // typically want to write to it immediately. So we have a one-datum queue in case this happens; if the connection takes
+    // so long to get ready that another write happens, we drop the first write. This will work okay for UDP connections, where
+    // the retransmit logic is in the application. For future, we may want to rearchitect the flow so that the write is always
+    // done in a callback.
+    dispatch_data_t NULLABLE pending_write;
+#else
     io_t io;
+#endif
+    uint16_t listen_port;
+    uint16_t *NULLABLE avoid_ports;
+    int num_avoid_ports;
+    bool avoiding;
     char *NONNULL name;
     void *NULLABLE context;
-    comm_callback_t NULLABLE datagram_callback;
+    datagram_callback_t NULLABLE datagram_callback;
     comm_callback_t NULLABLE close_callback;
-    send_response_t NULLABLE send_response;
-    comm_callback_t NULLABLE connected;
+    connect_callback_t NULLABLE connected;
+    disconnect_callback_t NULLABLE disconnected;
+    finalize_callback_t NULLABLE finalize;
+    cancel_callback_t NULLABLE cancel;
+    ready_callback_t NULLABLE ready;
     message_t *NULLABLE message;
     uint8_t *NULLABLE buf;
     dso_state_t *NULLABLE dso;
-    addr_t address;
+    tls_context_t *NULLABLE tls_context;
+    addr_t address, multicast;
     size_t message_length_len;
     size_t message_length, message_cur;
     uint8_t message_length_bytes[2];
     bool tcp_stream: 1;
+    bool is_multicast: 1;
+};
+
+#define MAX_SUBPROC_ARGS 20
+struct subproc {
+    int ref_count;
+#ifdef IOLOOP_MACOS
+    dispatch_source_t NULLABLE dispatch_source;
+#else
+    subproc_t *NULLABLE next;
+#endif
+    int pipe_fds[2];
+    io_t *NULLABLE output_fd;
+    void *NULLABLE context;
+    subproc_callback_t NONNULL callback;
+    finalize_callback_t NULLABLE finalize;
+    char *NULLABLE argv[MAX_SUBPROC_ARGS + 1];
+    int argc;
+    pid_t pid;
+};
+
+struct dnssd_txn {
+    int ref_count;
+    DNSServiceRef NULLABLE sdref;
+    void *NULLABLE context;
+    void *NULLABLE aux_pointer;
+    dnssd_txn_finalize_callback_t NULLABLE finalize_callback;
 };
 
 extern int64_t ioloop_now;
@@ -87,15 +206,90 @@ int getipaddr(addr_t *NONNULL addr, const char *NONNULL p);
 int64_t ioloop_timenow(void);
 message_t *NULLABLE message_allocate(size_t message_size);
 void message_free(message_t *NONNULL message);
-void comm_free(comm_t *NONNULL comm);
 void ioloop_close(io_t *NONNULL io);
-void add_reader(io_t *NONNULL io, io_callback_t NONNULL callback, io_callback_t NULLABLE finalize);
+void ioloop_add_reader(io_t *NONNULL io, io_callback_t NONNULL callback);
+wakeup_t *NULLABLE ioloop_wakeup_create(void);
+#define ioloop_wakeup_retain(wakeup) ioloop_wakeup_retain_(wakeup, __FILE__, __LINE__)
+void ioloop_wakeup_retain_(wakeup_t *NONNULL wakeup, const char *NONNULL file, int line);
+#define ioloop_wakeup_release(wakeup) ioloop_wakeup_release_(wakeup, __FILE__, __LINE__)
+void ioloop_wakeup_release_(wakeup_t *NONNULL wakeup, const char *NONNULL file, int line);
+bool ioloop_add_wake_event(wakeup_t *NONNULL wakeup, void *NULLABLE context,
+                           wakeup_callback_t NONNULL callback, finalize_callback_t NULLABLE finalize,
+                           int milliseconds);
+void ioloop_cancel_wake_event(wakeup_t *NONNULL wakeup);
+
 bool ioloop_init(void);
-int ioloop_events(int64_t timeout_when);
-comm_t *NULLABLE setup_listener_socket(int family, int protocol, uint16_t port, const char *NONNULL name,
-                                       comm_callback_t NONNULL datagram_callback,
-                                       comm_callback_t NULLABLE connected, void *NULLABLE context);
+int ioloop(void);
+
+#define ioloop_comm_retain(comm) ioloop_comm_retain_(comm, __FILE__, __LINE__)
+void ioloop_comm_retain_(comm_t *NONNULL comm, const char *NONNULL file, int line);
+#define ioloop_comm_release(wakeup) ioloop_comm_release_(wakeup, __FILE__, __LINE__)
+void ioloop_comm_release_(comm_t *NONNULL comm, const char *NONNULL file, int line);
+void ioloop_comm_cancel(comm_t *NONNULL comm);
+#define ioloop_listener_retain(comm) ioloop_listener_retain_(comm, __FILE__, __LINE__)
+void ioloop_listener_retain_(comm_t *NONNULL listener, const char *NONNULL file, int line);
+#define ioloop_listener_release(wakeup) ioloop_listener_release_(wakeup, __FILE__, __LINE__)
+void ioloop_listener_release_(comm_t *NONNULL listener, const char *NONNULL file, int line);
+void ioloop_listener_cancel(comm_t *NONNULL comm);
+comm_t *NULLABLE ioloop_listener_create(bool stream, bool tls, uint16_t *NULLABLE avoid_ports, int num_avoid_ports,
+                                        const addr_t *NULLABLE ip_address, const char *NULLABLE multicast,
+                                        const char *NONNULL name, datagram_callback_t NONNULL datagram_callback,
+                                        connect_callback_t NULLABLE connected, cancel_callback_t NULLABLE cancel,
+                                        ready_callback_t NULLABLE ready, finalize_callback_t NULLABLE finalize,
+                                        void *NULLABLE context);
+comm_t *NULLABLE ioloop_connection_create(addr_t *NONNULL remote_address, bool tls, bool stream,
+                                          datagram_callback_t NONNULL datagram_callback,
+                                          connect_callback_t NULLABLE connected,
+                                          disconnect_callback_t NULLABLE disconnected,
+                                          finalize_callback_t NULLABLE finalize,
+                                          void *NONNULL context);
+#define ioloop_message_retain(wakeup) ioloop_message_retain_(wakeup, __FILE__, __LINE__)
+void ioloop_message_retain_(message_t *NONNULL message, const char *NONNULL file, int line);
+#define ioloop_message_release(wakeup) ioloop_message_release_(wakeup, __FILE__, __LINE__)
+void ioloop_message_release_(message_t *NONNULL message, const char *NONNULL file, int line);
+bool ioloop_send_message(comm_t *NONNULL connection, message_t *NULLABLE responding_to,
+                         struct iovec *NONNULL iov, int iov_len);
+bool ioloop_map_interface_addresses(void *NULLABLE context, interface_callback_t NONNULL callback);
+ssize_t ioloop_recvmsg(int sock, uint8_t *NONNULL buffer, size_t buffer_length, int *NONNULL ifindex,
+                       int *NONNULL hoplimit, addr_t *NONNULL source, addr_t *NONNULL destination);
+#define ioloop_subproc_release(subproc) ioloop_subproc_release_(subproc, __FILE__, __LINE__)
+void ioloop_subproc_release_(subproc_t *NONNULL subproc, const char *NONNULL file, int line);
+#define ioloop_subproc_retain(subproc) ioloop_subproc_retain_(subproc, __FILE__, __LINE__)
+void ioloop_subproc_retain_(subproc_t *NONNULL subproc, const char *NONNULL file, int line);
+subproc_t *NULLABLE ioloop_subproc(const char *NONNULL exepath, char *NULLABLE *NONNULL argv, int argc,
+                                   subproc_callback_t NULLABLE callback, io_callback_t NULLABLE output_callback,
+                                   void *NULLABLE context);
+#define ioloop_dnssd_txn_add(ref, context, finalize) ioloop_dnssd_txn_add_(ref, context, finalize, __FILE__, __LINE__)
+dnssd_txn_t *NULLABLE
+ioloop_dnssd_txn_add_(DNSServiceRef NONNULL ref, void *NULLABLE context,
+                      dnssd_txn_finalize_callback_t NULLABLE callback, const char *NONNULL file, int line);
+void ioloop_dnssd_txn_cancel(dnssd_txn_t *NONNULL txn);
+#define ioloop_dnssd_txn_retain(txn) ioloop_dnssd_txn_retain_(txn, __FILE__, __LINE__)
+void ioloop_dnssd_txn_retain_(dnssd_txn_t *NONNULL txn, const char *NONNULL file, int line);
+#define ioloop_dnssd_txn_release(txn) ioloop_dnssd_txn_release_(txn, __FILE__, __LINE__)
+void ioloop_dnssd_txn_release_(dnssd_txn_t *NONNULL txn, const char *NONNULL file, int line);
+#endif
+void ioloop_dnssd_txn_set_aux_pointer(dnssd_txn_t *NONNULL txn, void *NULLABLE aux_pointer);
+void *NULLABLE ioloop_dnssd_txn_get_aux_pointer(dnssd_txn_t *NONNULL txn);
+void *NULLABLE ioloop_dnssd_txn_get_context(dnssd_txn_t *NONNULL txn);
+
+#define ioloop_file_descriptor_create(fd, context, finalize) \
+    ioloop_file_descriptor_create_(fd, context, finalize, __FILE__, __LINE__)
+io_t *NULLABLE ioloop_file_descriptor_create_(int fd, void *NULLABLE context, finalize_callback_t NULLABLE finalize,
+                                              const char *NONNULL file, int line);
+#define ioloop_file_descriptor_retain(file_descriptor) ioloop_file_descriptor_retain_(file_descriptor, __FILE__, \
+                                                            __LINE__)
+void ioloop_file_descriptor_retain_(io_t *NONNULL file_descriptor, const char *NONNULL file, int line);
+#define ioloop_file_descriptor_release(file_descriptor) ioloop_file_descriptor_release_(file_descriptor, __FILE__, \
+                                                            __LINE__)
+void ioloop_file_descriptor_release_(io_t *NONNULL file_descriptor, const char *NONNULL file, int line);
+
+bool ioloop_interface_monitor_start(void);
+
+#ifdef IOLOOP_MACOS
+xpc_connection_t NULLABLE ioloop_create_xpc_service(const char *NONNULL name, ioloop_xpc_callback_t NONNULL callback);
 #endif
+
 // Local Variables:
 // mode: C
 // tab-width: 4
index eb39d6e7503aa9e3c876c72cef8bfbbd2571e15a..5fbc5a95aeb19b28978672cc17a12139de4f62e3 100644 (file)
 #include "srp.h"
 #include "dns-msg.h"
 #include "srp-crypto.h"
+#include "srp-api.h"
 
 int
 main(int argc, char **argv)
 {
-    const char *keyfile_name = "srp-simple.key";
+    const char *key_name = "com.apple.srp-client.host-key";
     srp_key_t *key;
 
-    key = srp_load_keypair(keyfile_name);
+    key = srp_get_key(key_name);
     if (key == NULL) {
         if (key == NULL) {
-            printf("Unable to load key from %s.", keyfile_name);
+            printf("Unable to load key from %s.", key_name);
             exit(1);
         }
     }
diff --git a/ServiceRegistration/log_srp.m b/ServiceRegistration/log_srp.m
new file mode 100644 (file)
index 0000000..79a25ed
--- /dev/null
@@ -0,0 +1,155 @@
+//
+//  log_srp.m
+//  log_srp
+//
+
+#import <CoreUtils/CoreUtils.h>
+#import <Foundation/Foundation.h>
+#import <os/log_private.h>
+#import <arpa/inet.h>
+#import "DNSMessage.h"
+
+// Attribute string macro
+#define AStr(str) [[NSAttributedString alloc] initWithString:(str)]
+#define AStrWithFormat(format, ...) AStr(([[NSString alloc] initWithFormat:format, __VA_ARGS__]))
+
+typedef struct srp_os_log_formatter srp_os_log_formatter_t;
+struct srp_os_log_formatter {
+       const char * const type;
+       NS_RETURNS_RETAINED NSAttributedString *(*function)(id);
+};
+
+static NS_RETURNS_RETAINED NSAttributedString *
+srp_os_log_copy_formatted_string_ipv6_addr_segment(id value)
+{
+#define PREFIX_CLASS_MAX_LENGTH 6 // 6 is for <space>(ULA) or <space>(LUA) or <space>(GUA)
+       NSData *data;
+       const uint8_t * addr_data;
+       NSAttributedString * a_str;
+       char buf[INET6_ADDRSTRLEN + PREFIX_CLASS_MAX_LENGTH];
+       char *delimiter;
+       char *ptr;
+       char *ptr_limit;
+       NSString *ns_str;
+
+       buf[sizeof(buf) - 1] = '\0';
+
+       // The passing data should be NSData type.
+       require_action([(NSObject *)value isKindOfClass:[NSData class]], exit,
+               a_str = AStrWithFormat(@"<failed to decode - invalid data type: %@>", [(NSObject *)value description]));
+
+       data = (NSData *)value;
+       // The passing data must have valid length.
+#define IPv6_ADDR_SIZE 16
+       require_action(data.bytes != nil && data.length > 0 && data.length <= IPv6_ADDR_SIZE, exit,
+               a_str = AStrWithFormat(@"<failed to decode - NIL or invalid data length: %lu>", (unsigned long)data.length));
+
+       addr_data = data.bytes;
+       delimiter = "";
+       ptr = buf;
+       ptr_limit = buf + sizeof(buf);
+       for (size_t i = 0; i < data.length; i++) {
+               require_action((size_t)(ptr_limit - ptr) > strlen(delimiter) + 2, exit,
+                       a_str = AStrWithFormat(@"<failed to decode - buffer space not enough: i: %lu>", i));
+               ptr += snprintf(ptr, ptr_limit - ptr, "%s%02x", delimiter, addr_data[i]);
+               if ((i + 1) % 2 == 0) {
+                       delimiter = ":";
+               } else {
+                       delimiter = "";
+               }
+       }
+
+       ns_str = @(buf);
+       a_str = AStr(ns_str != nil ? ns_str : nil);
+exit:
+       return a_str;
+}
+
+static NS_RETURNS_RETAINED NSAttributedString *
+srp_os_log_copy_formatted_string_domain_name(id value)
+{
+       NSData *data;
+       NSAttributedString *a_str;
+       char buf[kDNSServiceMaxDomainName];
+       OSStatus ret;
+       NSString *ns_str;
+
+       // The passing data should be NSData type.
+       require_action([(NSObject *)value isKindOfClass:[NSData class]], exit,
+               a_str = AStrWithFormat(@"<failed to decode - invalid data type: %@>", [(NSObject *)value description]));
+
+       data = (NSData *)value;
+       // NULL pointer is allowed.
+       require_action_quiet(data.bytes != nil, exit, a_str = AStr(@"<null>"));
+
+       // The passing data must have valid length.
+       require_action(data.length > 0 && data.length <= kDomainNameLengthMax, exit,
+               a_str = AStrWithFormat(@"<failed to decode - NIL or invalid data length: %lu>", (unsigned long)data.length));
+
+       buf[kDNSServiceMaxDomainName - 1] = '\0';
+       ret = DomainNameToString((const uint8_t *)data.bytes, ((const uint8_t *) data.bytes) + data.length, buf, NULL);
+       require_action(ret == kNoErr, exit, a_str = AStr(@"Malformed Domain Name"));
+
+       ns_str = @(buf);
+       a_str = AStr(ns_str != nil ? ns_str : nil);
+exit:
+       return a_str;
+}
+
+static NS_RETURNS_RETAINED NSAttributedString *
+srp_os_log_copy_formatted_string_mac_addr(id value)
+{
+       NSData *data;
+       NSAttributedString *a_str;
+#define MaxMACAddrStrLen 18 // 17 plus '\0'
+       char buf[MaxMACAddrStrLen];
+       OSStatus ret;
+       NSString *ns_str;
+       const uint8_t *mac_addr = NULL;
+
+       buf[MaxMACAddrStrLen - 1] = '\0';
+
+       // The passing data should be NSData type.
+       require_action([(NSObject *)value isKindOfClass:[NSData class]], exit,
+               a_str = AStrWithFormat(@"<failed to decode - invalid data type: %@>", [(NSObject *)value description]));
+
+       data = (NSData *)value;
+#define MACAddressLen 6
+       require_action(data.bytes != nil && data.length == MACAddressLen, exit,
+               a_str = AStrWithFormat(@"<failed to decode - NIL or invalid data length: %lu>", (unsigned long)data.length));
+
+       mac_addr = (const uint8_t *)data.bytes;
+       ret = snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
+               mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
+
+       require_action(ret > 0, exit, a_str = AStr(@"<failed to decode - MAC address conversion failed>"));
+
+       ns_str = @(buf);
+       a_str = AStr(ns_str != nil ? ns_str : nil);
+exit:
+       return a_str;
+}
+
+NS_RETURNS_RETAINED
+NSAttributedString *
+OSLogCopyFormattedString(const char *type, id value, __unused os_log_type_info_t info)
+{
+       NSAttributedString *result_str = nil;
+
+       static const srp_os_log_formatter_t formatters[] = {
+               {.type = "in6_addr_segment", .function = srp_os_log_copy_formatted_string_ipv6_addr_segment},
+               {.type = "domain_name", .function = srp_os_log_copy_formatted_string_domain_name},
+               {.type = "mac_addr", .function = srp_os_log_copy_formatted_string_mac_addr},
+       };
+
+       for (size_t i = 0; i < countof(formatters); i++) {
+               if (strcmp(type, formatters[i].type) != 0) {
+                       continue;
+               }
+
+               result_str = formatters[i].function(value);
+               break;
+       }
+
+       return result_str;
+}
diff --git a/ServiceRegistration/macos-ioloop.c b/ServiceRegistration/macos-ioloop.c
new file mode 100644 (file)
index 0000000..27ca76d
--- /dev/null
@@ -0,0 +1,1496 @@
+/* macos-ioloop.c
+ *
+ * Copyright (c) 2018-2020 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Simple event dispatcher for DNS.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/uio.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <signal.h>
+#include <net/if.h>
+#include <ifaddrs.h>
+#include <dns_sd.h>
+
+#include <dispatch/dispatch.h>
+
+#include "srp.h"
+#include "dns-msg.h"
+#include "srp-crypto.h"
+#include "ioloop.h"
+#include "xpc_client_advertising_proxy.h"
+
+static bool connection_write_now(comm_t *NONNULL connection);
+
+dispatch_queue_t ioloop_main_queue;
+
+// Forward references
+static void tcp_start(comm_t *NONNULL connection);
+
+int64_t
+ioloop_timenow(void)
+{
+    int64_t now;
+    struct timeval tv;
+    gettimeofday(&tv, 0);
+    now = (int64_t)tv.tv_sec * 1000 + (int64_t)tv.tv_usec / 1000;
+    return now;
+}
+
+static void
+wakeup_event(void *context)
+{
+    wakeup_t *wakeup = context;
+
+    // All ioloop wakeups are one-shot.
+    ioloop_cancel_wake_event(wakeup);
+
+    // Call the callback, which mustn't be null.
+    wakeup->wakeup(wakeup->context);
+}
+
+static void
+wakeup_finalize(void *context)
+{
+    wakeup_t *wakeup = context;
+    if (wakeup->ref_count == 0) {
+        if (wakeup->dispatch_source != NULL) {
+            dispatch_release(wakeup->dispatch_source);
+            wakeup->dispatch_source = NULL;
+        }
+        if (wakeup->finalize != NULL) {
+            wakeup->finalize(wakeup->context);
+        }
+        free(wakeup);
+    }
+}
+
+void
+ioloop_wakeup_retain_(wakeup_t *wakeup, const char *file, int line)
+{
+    (void)file; (void)line;
+    RETAIN(wakeup);
+}
+
+void
+ioloop_wakeup_release_(wakeup_t *wakeup, const char *file, int line)
+{
+    (void)file; (void)line;
+    RELEASE(wakeup, wakeup_finalize);
+}
+
+wakeup_t *
+ioloop_wakeup_create(void)
+{
+    wakeup_t *ret = calloc(1, sizeof(*ret));
+    if (ret) {
+        RETAIN_HERE(ret);
+    }
+    return ret;
+}
+
+bool
+ioloop_add_wake_event(wakeup_t *wakeup, void *context, wakeup_callback_t callback, wakeup_callback_t finalize,
+                      int milliseconds)
+{
+    if (callback == NULL) {
+        ERROR("ioloop_add_wake_event called with null callback");
+        return false;
+    }
+    if (wakeup->dispatch_source != NULL) {
+        ioloop_cancel_wake_event(wakeup);
+    }
+    wakeup->wakeup = callback;
+    wakeup->context = context;
+    wakeup->finalize = finalize;
+
+    wakeup->dispatch_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, ioloop_main_queue);
+    if (wakeup->dispatch_source == NULL) {
+        ERROR("dispatch_source_create failed in ioloop_add_wake_event().");
+        return false;
+    }
+    dispatch_source_set_event_handler_f(wakeup->dispatch_source, wakeup_event);
+    dispatch_set_context(wakeup->dispatch_source, wakeup);
+
+    // libdispatch doesn't allow events that are scheduled to happen right now. But it is actually useful to be
+    // able to trigger an event to happen immediately, and this is the easiest way to do it from ioloop-we
+    // can't rely on just scheduling an asynchronous event on an event loop because that's specific to Mac.
+    if (milliseconds <= 0) {
+        ERROR("ioloop_add_wake_event: milliseconds = %d", milliseconds);
+        milliseconds = 10;
+    }
+    dispatch_source_set_timer(wakeup->dispatch_source,
+                              dispatch_time(DISPATCH_TIME_NOW, (uint64_t)milliseconds * NSEC_PER_SEC / 1000),
+                              (uint64_t)milliseconds * NSEC_PER_SEC / 1000, NSEC_PER_SEC / 100);
+    dispatch_resume(wakeup->dispatch_source);
+
+    return true;
+}
+
+void
+ioloop_cancel_wake_event(wakeup_t *wakeup)
+{
+    if (wakeup->dispatch_source != NULL) {
+        dispatch_source_cancel(wakeup->dispatch_source);
+        dispatch_release(wakeup->dispatch_source);
+        wakeup->dispatch_source = NULL;
+    }
+}
+
+bool
+ioloop_init(void)
+{
+    ioloop_main_queue = dispatch_get_main_queue();
+    dispatch_retain(ioloop_main_queue);
+    return true;
+}
+
+int
+ioloop(void)
+{
+    dispatch_main();
+    return 0;
+}
+
+#define connection_cancel(conn) connection_cancel_(conn, __FILE__, __LINE__)
+static void
+connection_cancel_(nw_connection_t connection, const char *file, int line)
+{
+    if (connection == NULL) {
+        INFO("connection_cancel: null connection at " PUB_S_SRP ":%d", file, line);
+    } else {
+        INFO("connection_cancel: " PUB_S_SRP ":%d", file, line);
+        nw_connection_cancel(connection);
+    }
+}
+
+static void
+comm_finalize(comm_t *comm)
+{
+    ERROR("comm_finalize");
+    if (comm->connection != NULL) {
+        nw_release(comm->connection);
+        comm->connection = NULL;
+    }
+    if (comm->listener != NULL) {
+        nw_release(comm->listener);
+        comm->listener = NULL;
+    }
+    if (comm->parameters) {
+        nw_release(comm->parameters);
+        comm->parameters = NULL;
+    }
+    if (comm->pending_write != NULL) {
+        dispatch_release(comm->pending_write);
+        comm->pending_write = NULL;
+    }
+    // If there is an nw_connection_t or nw_listener_t outstanding, then we will get an asynchronous callback
+    // later on.  So we can't actually free the data structure yet, but the good news is that comm_finalize() will
+    // be called again later when the last outstanding asynchronous cancel is done, and then all of the stuff
+    // that follows this will happen.
+#ifndef __clang_analyzer__
+    if (comm->ref_count > 0) {
+        return;
+    }
+#endif
+    if (comm->idle_timer != NULL) {
+        ioloop_cancel_wake_event(comm->idle_timer);
+        RELEASE_HERE(comm->idle_timer, wakeup_finalize);
+    }
+    if (comm->name != NULL) {
+        free(comm->name);
+    }
+    if (comm->finalize != NULL) {
+        comm->finalize(comm->context);
+    }
+    free(comm);
+}
+
+void
+ioloop_comm_retain_(comm_t *comm, const char *file, int line)
+{
+    (void)file; (void)line;
+    RETAIN(comm);
+}
+
+void
+ioloop_comm_release_(comm_t *comm, const char *file, int line)
+{
+    (void)file; (void)line;
+    RELEASE(comm, comm_finalize);
+}
+
+static message_t *
+message_create(size_t message_size)
+{
+    message_t *message;
+
+    // Never should have a message shorter than this.
+    if (message_size < DNS_HEADER_SIZE) {
+        return NULL;
+    }
+
+    message = (message_t *)malloc(message_size + (sizeof (message_t)) - (sizeof (dns_wire_t)));
+    if (message) {
+        memset(message, 0, (sizeof (message_t)) - (sizeof (dns_wire_t)));
+        RETAIN_HERE(message);
+    }
+    return message;
+}
+
+void
+ioloop_comm_cancel(comm_t *connection)
+{
+    if (connection->connection != NULL) {
+        connection_cancel(connection->connection);
+    }
+}
+
+static void
+message_finalize(message_t *message)
+{
+    free(message);
+}
+
+void
+ioloop_message_retain_(message_t *message, const char *file, int line)
+{
+    (void)file; (void)line;
+    RETAIN(message);
+}
+
+void
+ioloop_message_release_(message_t *message, const char *file, int line)
+{
+    (void)file; (void)line;
+    RELEASE(message, message_finalize);
+}
+
+bool
+ioloop_send_message(comm_t *connection, message_t *responding_to, struct iovec *iov, int iov_len)
+{
+    dispatch_data_t data = NULL, new_data, combined;
+    int i;
+    uint16_t len = 0;
+
+    // Not needed on OSX because UDP conversations are treated as "connections."
+    (void)responding_to;
+
+    if (connection->connection == NULL) {
+        return false;
+    }
+
+    // Create a dispatch_data_t object that contains the data in the iov.
+    for (i = 0; i < iov_len; i++) {
+        new_data = dispatch_data_create(iov->iov_base, iov->iov_len,
+                                        ioloop_main_queue, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
+        len += iov->iov_len;
+        if (data != NULL) {
+            if (new_data != NULL) {
+                // Subsequent times through
+                combined = dispatch_data_create_concat(data, new_data);
+                dispatch_release(data);
+                dispatch_release(new_data);
+                data = combined;
+            } else {
+                // Fail
+                dispatch_release(data);
+                data = NULL;
+            }
+        } else {
+            // First time through
+            data = new_data;
+        }
+        if (data == NULL) {
+            ERROR("ioloop_send_message: no memory.");
+            return false;
+        }
+    }
+
+    if (len == 0) {
+        if (data) {
+            dispatch_release(data);
+        }
+        return false;
+    }
+
+    // TCP requires a length as well as the payload.
+    if (connection->tcp_stream) {
+        len = htons(len);
+        new_data = dispatch_data_create(&len, sizeof (len), ioloop_main_queue, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
+        if (new_data == NULL) {
+            if (data != NULL) {
+                dispatch_release(data);
+            }
+            return false;
+        }
+        // Length is at beginning.
+        combined = dispatch_data_create_concat(new_data, data);
+        dispatch_release(data);
+        dispatch_release(new_data);
+        if (combined == NULL) {
+            return false;
+        }
+        data = combined;
+    }
+
+    if (connection->pending_write != NULL) {
+        ERROR("Dropping pending write on " PRI_S_SRP, connection->name ? connection->name : "<null>");
+    }
+    connection->pending_write = data;
+    if (connection->connection_ready) {
+        return connection_write_now(connection);
+    }
+    return true;
+}
+
+static bool
+connection_write_now(comm_t *connection)
+{
+    // Retain the connection once for each write that's pending, so that it's never finalized while
+    // there's a write in progress.
+    connection->writes_pending++;
+    RETAIN_HERE(connection);
+    nw_connection_send(connection->connection, connection->pending_write, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true,
+                       ^(nw_error_t  _Nullable error) {
+                           if (error != NULL) {
+                               ERROR("ioloop_send_message: write failed: " PUB_S_SRP,
+                                     strerror(nw_error_get_error_code(error)));
+                               connection_cancel(connection->connection);
+                           }
+                           if (connection->writes_pending > 0) {
+                               connection->writes_pending--;
+                               RELEASE_HERE(connection, comm_finalize);
+                           } else {
+                               ERROR("ioloop_send_message: write callback reached with no writes marked pending.");
+                           }
+                       });
+    // nw_connection_send should retain this, so let go of our reference to it.
+    dispatch_release(connection->pending_write);
+    connection->pending_write = NULL;
+    return true;
+}
+
+static bool
+datagram_read(comm_t *connection, size_t length, dispatch_data_t content, nw_error_t error)
+{
+    message_t *message = NULL;
+    bool ret = true, *retp = &ret;
+
+    if (error != NULL) {
+        ERROR("datagram_read: " PUB_S_SRP, strerror(nw_error_get_error_code(error)));
+        ret = false;
+        goto out;
+    }
+    if (length > UINT16_MAX) {
+        ERROR("datagram_read: oversized datagram length %zd", length);
+        ret = false;
+        goto out;
+    }
+    message = message_create(length);
+    if (message == NULL) {
+        ERROR("datagram_read: unable to allocate message.");
+        ret = false;
+        goto out;
+    }
+    message->length = (uint16_t)length;
+    dispatch_data_apply(content,
+                        ^bool (dispatch_data_t __unused region, size_t offset, const void *buffer, size_t size) {
+            if (message->length < offset + size) {
+                ERROR("datagram_read: data region %zd:%zd is out of range for message length %d",
+                      offset, size, message->length);
+                *retp = false;
+                return false;
+            }
+            memcpy(((uint8_t *)&message->wire) + offset, buffer, size);
+            return true;
+        });
+    if (ret == true) {
+        // Process the message.
+        connection->datagram_callback(connection, message, connection->context);
+    }
+
+    out:
+    if (message != NULL) {
+        ioloop_message_release(message);
+    }
+    if (!ret) {
+        connection_cancel(connection->connection);
+    }
+    return ret;
+}
+
+static void
+tcp_read(comm_t *connection, size_t length, dispatch_data_t content, nw_error_t error)
+{
+    if (error != NULL) {
+        connection_cancel(connection->connection);
+        return;
+    }
+    if (datagram_read(connection, length, content, error)) {
+        // Wait for the next frame
+        tcp_start(connection);
+    }
+}
+
+static void
+tcp_read_length(comm_t *connection, dispatch_data_t content, nw_error_t error)
+{
+    size_t length;
+    uint32_t bytes_to_read;
+    const uint8_t *lenbuf;
+    dispatch_data_t map;
+
+    if (error != NULL) {
+        ERROR("tcp_read_length: " PUB_S_SRP, strerror(nw_error_get_error_code(error)));
+    fail:
+        connection_cancel(connection->connection);
+        return;
+    }
+    if (connection->connection == NULL) {
+        return;
+    }
+    if (content == NULL) {
+        INFO("tcp_read_length: remote end closed connection.");
+        goto fail;
+    }
+
+    map = dispatch_data_create_map(content, (const void **)&lenbuf, &length);
+    if (map == NULL) {
+        ERROR("tcp_read_length: map create failed");
+        goto fail;
+    } else if (length != 2) {
+        ERROR("tcp_read_length: invalid length = %zu", length);
+        goto fail;
+    }
+    bytes_to_read = ((unsigned)(lenbuf[0]) << 8) | ((unsigned)lenbuf[1]);
+    nw_connection_receive(connection->connection, bytes_to_read, bytes_to_read,
+                              ^(dispatch_data_t new_content, nw_content_context_t __unused new_context,
+                                bool __unused is_complete, nw_error_t new_error) {
+                                  tcp_read(connection, bytes_to_read, new_content, new_error);
+                              });
+}
+
+static void __unused
+connection_idle_wakeup_callback(void *context)
+{
+    comm_t *connection = context;
+    ERROR("Connection " PRI_S_SRP " has gone idle", connection->name);
+    connection_cancel(connection->connection);
+}
+
+static void __unused
+connection_idle_wakeup_finalize(void *context)
+{
+    comm_t *connection = context;
+    connection->idle_timer = NULL;
+}
+
+static void
+tcp_start(comm_t *connection)
+{
+    if (connection->connection == NULL) {
+        return;
+    }
+    // We want to disconnect if the connection is idle for more than a short while.
+    if (connection->idle_timer == NULL) {
+        connection->idle_timer = ioloop_wakeup_create();
+        if (connection->idle_timer == NULL) {
+            // If we can't set up a timer, drop the connection now.
+            connection_cancel(connection->connection);
+            return;
+        }
+    }
+    ioloop_add_wake_event(connection->idle_timer, connection,
+                          connection_idle_wakeup_callback, connection_idle_wakeup_finalize,
+                          60 * 1000); // One minute
+    nw_connection_receive(connection->connection, 2, 2,
+                          ^(dispatch_data_t content, nw_content_context_t __unused context,
+                            bool is_complete, nw_error_t error) {
+                              // For TCP connections, is_complete means the other end closed the connection.
+                              if (is_complete || content == NULL) {
+                                  INFO("tcp_start: remote end closed connection.");
+                                  connection_cancel(connection->connection);
+                              } else {
+                                  tcp_read_length(connection, content, error);
+                              }
+                          });
+}
+
+static void
+udp_start(comm_t *connection)
+{
+    if (connection->connection == NULL) {
+        return;
+    }
+
+    // UDP is connectionless; the "connection" is just a placeholder that allows us to reply to the source.
+    // In principle, the five-tuple that is represented by the connection object should die as soon as the
+    // client is done retransmitting, since a later transaction should come from a different source port.
+    // Consequently, we set an idle timer: if we don't see any packets on this five-tuple after twenty seconds,
+    // it's unlikely that we will see any more, so it's time to collect the connection.  If another packet
+    // does come in after this, a new connection will be created. The only risk is that if the cancel comes
+    // after a packet has arrived and been consumed by the nw_connection, but before we've called nw_connection_read,
+    // it will be lost. This should never happen for an existing SRP client, since the longest retry interval
+    // by default is 15 seconds; as the retry intervals get longer, it becomes safer to collect the connection
+    // and allow it to be recreated.
+    if (connection->server) {
+        if (connection->idle_timer == NULL) {
+            connection->idle_timer = ioloop_wakeup_create();
+            if (connection->idle_timer == NULL) {
+                // If we can't set up a timer, drop the connection now.
+                connection_cancel(connection->connection);
+                return;
+            }
+        }
+        ioloop_add_wake_event(connection->idle_timer, connection,
+                              connection_idle_wakeup_callback, connection_idle_wakeup_finalize,
+                              20 * 1000); // 20 seconds (15 seconds is the SRP client retry interval)
+    }
+
+    connection->read_pending = true;    // When a read is pending, we have an extra refcount on the connection
+    RETAIN_HERE(connection);
+    nw_connection_receive_message(connection->connection,
+                          ^(dispatch_data_t content, nw_content_context_t __unused context,
+                            bool __unused is_complete, nw_error_t error) {
+                              bool proceed = true;
+                              if (content != NULL) {
+                                  proceed = datagram_read(connection, dispatch_data_get_size(content),
+                                                          content, error);
+                              }
+                              if (content == NULL || error != NULL) {
+                                  connection_cancel(connection->connection);
+                              }
+                              // Once we have a five-tuple connection, we can't easily get rid of it, so keep
+                              // reading.
+                              else if (proceed) {
+                                  udp_start(connection);
+                              }
+                              RELEASE_HERE(connection, comm_finalize);
+                          });
+}
+
+static void
+connection_state_changed(comm_t *connection, nw_connection_state_t state, nw_error_t error)
+{
+    (void)error;
+    if (state == nw_connection_state_ready) {
+        INFO("connection_state_changed: " PRI_S_SRP " state is ready; error = %p",
+             connection->name != NULL ? connection->name : "<no name>", error);
+        // Set up a reader.
+        if (connection->tcp_stream) {
+            tcp_start(connection);
+        } else {
+            udp_start(connection);
+        }
+        connection->connection_ready = true;
+        // If there's a write pending, send it now.
+        if (connection->pending_write) {
+            connection_write_now(connection);
+        }
+    } else if (state == nw_connection_state_failed) {
+        INFO("connection_state_changed: " PRI_S_SRP " state is failed; error = %p",
+             connection->name != NULL ? connection->name : "<no name>", error);
+        connection_cancel(connection->connection);
+    } else if (state == nw_connection_state_cancelled) {
+        INFO("connection_state_changed: " PRI_S_SRP " state is canceled; error = %p",
+             connection->name != NULL ? connection->name : "<no name>", error);
+        // This releases the final reference to the connection object, which was held by the nw_connection_t.
+        RELEASE_HERE(connection, comm_finalize);
+    } else {
+        INFO("connection_state_changed: " PRI_S_SRP " state is %d; error = %p",
+             connection->name != NULL ? connection->name : "<no name>", state, error);
+    }
+}
+
+static void
+connection_callback(comm_t *listener, nw_connection_t new_connection)
+{
+    comm_t *connection = calloc(1, sizeof *connection);
+    if (connection == NULL) {
+        ERROR("Unable to receive connection: no memory.");
+        // Assuming that since we haven't retained the connection, it will be released?
+        // XXX RefCount Check.
+        return;
+    }
+
+    connection->connection = new_connection;
+    nw_retain(connection->connection);
+
+    connection->name = nw_connection_copy_description(connection->connection);
+    if (connection->name != NULL) {
+        INFO("Received connection from " PRI_S_SRP, connection->name);
+    } else {
+        ERROR("Unable to get description of new connection.");
+    }
+    connection->datagram_callback = listener->datagram_callback;
+    connection->tcp_stream = listener->tcp_stream;
+    connection->server = true;
+    nw_connection_set_state_changed_handler(connection->connection,
+                                            ^(nw_connection_state_t state, nw_error_t error)
+                                            { connection_state_changed(connection, state, error); });
+    nw_connection_set_queue(connection->connection, ioloop_main_queue);
+    nw_connection_start(connection->connection);
+    // new_connection holds a reference to the connection until it is canceled.
+    RETAIN_HERE(connection);
+    if (listener->connected != NULL) {
+        listener->connected(connection, listener->context);
+    }
+}
+
+static void
+listener_finalize(comm_t *listener)
+{
+    if (listener->listener != NULL) {
+        nw_release(listener->listener);
+        listener->listener = NULL;
+    }
+    if (listener->name != NULL) {
+        free(listener->name);
+    }
+    if (listener->parameters) {
+        nw_release(listener->parameters);
+    }
+    if (listener->avoid_ports != NULL) {
+        free(listener->avoid_ports);
+    }
+    if (listener->finalize) {
+        listener->finalize(listener->context);
+    }
+    free(listener);
+}
+
+void
+ioloop_listener_retain_(comm_t *listener, const char *file, int line)
+{
+    RETAIN(listener);
+}
+
+void
+ioloop_listener_release_(comm_t *listener, const char *file, int line)
+{
+    RELEASE(listener, listener_finalize);
+}
+
+void
+ioloop_listener_cancel(comm_t *connection)
+{
+    if (connection->listener != NULL) {
+        nw_listener_cancel(connection->listener);
+        nw_release(connection->listener);
+        connection->listener = NULL;
+    }
+}
+
+static void
+ioloop_listener_state_changed_handler(comm_t *listener, nw_listener_state_t state, nw_error_t error)
+{
+    int i;
+    if (error != NULL) {
+        INFO("nw_listener_create:state changed: error");
+    } else {
+        if (state == nw_listener_state_waiting) {
+            INFO("nw_listener_create: waiting");
+            return;
+        } else if (state == nw_listener_state_failed) {
+            INFO("nw_listener_create: failed");
+            nw_listener_cancel(listener->listener);
+        } else if (state == nw_listener_state_ready) {
+            INFO("nw_listener_create: ready");
+            if (listener->avoiding) {
+                listener->listen_port = nw_listener_get_port(listener->listener);
+                if (listener->avoid_ports != NULL) {
+                    for (i = 0; i < listener->num_avoid_ports; i++) {
+                        if (listener->avoid_ports[i] == listener->listen_port) {
+                            INFO("ioloop_listener_state_changed_handler: Got port %d, which we are avoiding.",
+                                 listener->listen_port);
+                            listener->avoiding = true;
+                            listener->listen_port = 0;
+                            nw_listener_cancel(listener->listener);
+                            return;
+                        }
+                    }
+                }
+                INFO("ioloop_listener_state_changed_handler: Got port %d.", listener->listen_port);
+                listener->avoiding = false;
+                if (listener->ready) {
+                    listener->ready(listener->context, listener->listen_port);
+                }
+            }
+        } else if (state == nw_listener_state_cancelled) {
+            INFO("ioloop_listener_state_changed_handler: cancelled");
+            nw_release(listener->listener);
+            listener->listener = NULL;
+            if (listener->avoiding) {
+                listener->listener = nw_listener_create(listener->parameters);
+                if (listener->listener == NULL) {
+                    ERROR("ioloop_listener_state_changed_handler: Unable to recreate listener.");
+                    goto cancel;
+                } else {
+                    RETAIN_HERE(listener);
+                    nw_listener_set_state_changed_handler(listener->listener,
+                                                          ^(nw_listener_state_t ev_state, nw_error_t ev_error) {
+                            ioloop_listener_state_changed_handler(listener, ev_state, ev_error);
+                        });
+                }
+            } else {
+                ;
+            cancel:
+                if (listener->cancel) {
+                    listener->cancel(listener->context);
+                }
+                RELEASE_HERE(listener, listener_finalize);
+            }
+        }
+    }
+}
+
+comm_t *
+ioloop_listener_create(bool stream, bool tls, uint16_t *avoid_ports, int num_avoid_ports,
+                       const addr_t *ip_address, const char *multicast, const char *name,
+                       datagram_callback_t datagram_callback, connect_callback_t connected, cancel_callback_t cancel,
+                       ready_callback_t ready, finalize_callback_t finalize, void *context)
+{
+    comm_t *listener;
+    int family = (ip_address != NULL) ? ip_address->sa.sa_family : AF_UNSPEC;
+    uint16_t port;
+    char portbuf[10];
+    nw_endpoint_t endpoint;
+
+    if (ip_address == NULL) {
+        port = 0;
+    } else {
+        port = (family == AF_INET) ? ip_address->sin.sin_port : ip_address->sin6.sin6_port;
+    }
+
+    if (multicast != NULL) {
+        ERROR("ioloop_setup_listener: multicast not supported.");
+        return NULL;
+    }
+
+    if (datagram_callback == NULL) {
+        ERROR("ioloop_setup: no datagram callback provided.");
+        return NULL;
+    }
+
+    sprintf(portbuf, "%d", port);
+    listener = calloc(1, sizeof(*listener));
+    if (listener == NULL) {
+        if (ip_address == NULL) {
+            ERROR("No memory for listener on <NULL>#%d", port);
+        } else if (family == AF_INET) {
+            IPv4_ADDR_GEN_SRP(&ip_address->sin.sin_addr.s_addr, ipv4_addr_buf);
+            ERROR("No memory for listener on " PRI_IPv4_ADDR_SRP "#%d",
+                  IPv4_ADDR_PARAM_SRP(&ip_address->sin.sin_addr.s_addr, ipv4_addr_buf), port);
+        } else if (family == AF_INET6) {
+            SEGMENTED_IPv6_ADDR_GEN_SRP(ip_address->sin6.sin6_addr.s6_addr, ipv6_addr_buf);
+            ERROR("No memory for listener on " PRI_SEGMENTED_IPv6_ADDR_SRP "#%d",
+                  SEGMENTED_IPv6_ADDR_PARAM_SRP(ip_address->sin6.sin6_addr.s6_addr, ipv6_addr_buf), port);
+        } else {
+            ERROR("No memory for listener on <family address other than AF_INET or AF_INET6: %d>#%d", family, port);
+        }
+        return NULL;
+    }
+    if (avoid_ports != NULL) {
+        listener->avoid_ports = malloc(num_avoid_ports * sizeof(uint16_t));
+        if (listener->avoid_ports == NULL) {
+            if (ip_address == NULL) {
+                ERROR("No memory for listener avoid_ports on <NULL>#%d", port);
+            } else if (family == AF_INET) {
+                IPv4_ADDR_GEN_SRP(&ip_address->sin.sin_addr.s_addr, ipv4_addr_buf);
+                ERROR("No memory for listener avoid_ports on " PRI_IPv4_ADDR_SRP "#%d",
+                      IPv4_ADDR_PARAM_SRP(&ip_address->sin.sin_addr.s_addr, ipv4_addr_buf), port);
+            } else if (family == AF_INET6) {
+                SEGMENTED_IPv6_ADDR_GEN_SRP(ip_address->sin6.sin6_addr.s6_addr, ipv6_addr_buf);
+                ERROR("No memory for listener avoid_ports on " PRI_SEGMENTED_IPv6_ADDR_SRP "#%d",
+                      SEGMENTED_IPv6_ADDR_PARAM_SRP(ip_address->sin6.sin6_addr.s6_addr, ipv6_addr_buf), port);
+            } else {
+                ERROR("No memory for listener avoid_ports on <family address other than AF_INET or AF_INET6: %d>#%d",
+                      family, port);
+            }
+            free(listener);
+            return NULL;
+        }
+        listener->num_avoid_ports = num_avoid_ports;
+        listener->avoiding = true;
+    }
+    RETAIN_HERE(listener);
+    if (port == 0) {
+        endpoint = NULL;
+        // Even though we don't have any ports to avoid, we still want the "avoiding" behavior in this case, since that
+        // is what triggers a call to the ready handler, which passes the port number that we got to it.
+        listener->avoiding = true;
+    } else {
+        listener->listen_port = port;
+        char ip_address_str[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
+        if (ip_address == NULL) {
+            if (family == AF_INET) {
+                snprintf(ip_address_str, sizeof(ip_address_str), "0.0.0.0");
+            } else {
+                // AF_INET6 or AF_UNSPEC
+                snprintf(ip_address_str, sizeof(ip_address_str), "::");
+            }
+        } else {
+            inet_ntop(family, ip_address->sa.sa_data, ip_address_str, sizeof(ip_address_str));
+        }
+        endpoint = nw_endpoint_create_host(ip_address_str, portbuf);
+        if (endpoint == NULL) {
+            ERROR("No memory for listener endpoint.");
+            RELEASE_HERE(listener, listener_finalize);
+            return NULL;
+        }
+    }
+
+    if (stream) {
+        listener->parameters = nw_parameters_create_secure_tcp(tls ? NW_PARAMETERS_DEFAULT_CONFIGURATION
+                                                                    : NW_PARAMETERS_DISABLE_PROTOCOL,
+                                                               NW_PARAMETERS_DEFAULT_CONFIGURATION);
+    } else {
+        if (tls) {
+            ERROR("DTLS support not implemented.");
+            nw_release(endpoint);
+            RELEASE_HERE(listener, listener_finalize);
+            return NULL;
+        }
+        listener->parameters = nw_parameters_create_secure_udp(NW_PARAMETERS_DISABLE_PROTOCOL,
+                                                               NW_PARAMETERS_DEFAULT_CONFIGURATION);
+    }
+    if (listener->parameters == NULL) {
+        ERROR("No memory for listener parameters.");
+        nw_release(endpoint);
+        RELEASE_HERE(listener, listener_finalize);
+        return NULL;
+    }
+
+    if (endpoint != NULL) {
+        nw_parameters_set_local_endpoint(listener->parameters, endpoint);
+        nw_release(endpoint);
+    }
+
+    if (tls) {
+        nw_protocol_options_t tls_options = nw_tls_create_options();
+        if (tls_options == NULL) {
+            ERROR("No memory for tls protocol options.");
+            RELEASE_HERE(listener, listener_finalize);
+            return NULL;
+        }
+        // XXX set up the listener certificate(s).
+        // XXX how to configure this onto the parameters object?
+    }
+
+    // Set SO_REUSEADDR.
+    nw_parameters_set_reuse_local_address(listener->parameters, true);
+
+    // Create the nw_listener_t.
+    listener->listener = nw_listener_create(listener->parameters);
+    if (listener->listener == NULL) {
+        ERROR("no memory for nw_listener object");
+        RELEASE_HERE(listener, listener_finalize);
+        return NULL;
+    }
+    nw_listener_set_new_connection_handler(listener->listener,
+                                           ^(nw_connection_t connection) { connection_callback(listener, connection); }
+                                           );
+
+    RETAIN_HERE(listener); // for the nw_listener_t
+    nw_listener_set_state_changed_handler(listener->listener, ^(nw_listener_state_t state, nw_error_t error) {
+            ioloop_listener_state_changed_handler(listener, state, error);
+        });
+
+    listener->name = strdup(name);
+    listener->datagram_callback = datagram_callback;
+    listener->cancel = cancel;
+    listener->ready = ready;
+    listener->finalize = finalize;
+    listener->context = context;
+    listener->connected = connected;
+    listener->tcp_stream = stream;
+
+    nw_listener_set_queue(listener->listener, ioloop_main_queue);
+    nw_listener_start(listener->listener);
+    // Listener has one refcount
+    return listener;
+}
+
+comm_t *
+ioloop_connection_create(addr_t *NONNULL remote_address, bool tls, bool stream,
+                         datagram_callback_t datagram_callback, connect_callback_t connected,
+                         disconnect_callback_t disconnected, finalize_callback_t finalize, void *context)
+{
+    comm_t *connection;
+    char portbuf[10];
+    nw_parameters_t parameters;
+    nw_endpoint_t endpoint;
+    char addrbuf[INET6_ADDRSTRLEN];
+
+    inet_ntop(remote_address->sa.sa_family, (remote_address->sa.sa_family == AF_INET
+                                             ? (void *)&remote_address->sin.sin_addr
+                                             : (void *)&remote_address->sin6.sin6_addr), addrbuf, sizeof addrbuf);
+    sprintf(portbuf, "%d", (remote_address->sa.sa_family == AF_INET
+                            ? ntohs(remote_address->sin.sin_port)
+                            : ntohs(remote_address->sin6.sin6_port)));
+    connection = calloc(1, sizeof(*connection));
+    if (connection == NULL) {
+        ERROR("No memory for connection");
+        return NULL;
+    }
+    // If we don't release this because of an error, this is the caller's reference to the comm_t.
+    RETAIN_HERE(connection);
+    endpoint = nw_endpoint_create_host(addrbuf, portbuf);
+    if (endpoint == NULL) {
+        ERROR("No memory for connection endpoint.");
+        RELEASE_HERE(connection, comm_finalize);
+        return NULL;
+    }
+
+    if (stream) {
+        parameters = nw_parameters_create_secure_tcp(tls ? NW_PARAMETERS_DEFAULT_CONFIGURATION
+                                                            : NW_PARAMETERS_DISABLE_PROTOCOL,
+                                                     NW_PARAMETERS_DEFAULT_CONFIGURATION);
+    } else {
+        if (tls) {
+            ERROR("DTLS support not implemented.");
+            nw_release(endpoint);
+            RELEASE_HERE(connection, comm_finalize);
+            return NULL;
+        }
+        parameters = nw_parameters_create_secure_udp(NW_PARAMETERS_DISABLE_PROTOCOL,
+                                                     NW_PARAMETERS_DEFAULT_CONFIGURATION);
+    }
+    if (parameters == NULL) {
+        ERROR("No memory for connection parameters.");
+        nw_release(endpoint);
+        RELEASE_HERE(connection, comm_finalize);
+        return NULL;
+    }
+
+    if (tls) {
+#ifdef NOTYET
+        nw_protocol_options_t tls_options = nw_tls_create_options();
+        if (tls_options == NULL) {
+            ERROR("No memory for tls protocol options.");
+            RELEASE_HERE(connection, comm_finalize);
+            return NULL;
+        }
+        // XXX set up the connection certificate(s).
+        // XXX how to configure this onto the parameters object?
+#endif
+    }
+
+    connection->name = strdup(addrbuf);
+
+    // Create the nw_connection_t.
+    connection->connection = nw_connection_create(endpoint, parameters);
+    nw_release(endpoint);
+    nw_release(parameters);
+    if (connection->connection == NULL) {
+        ERROR("no memory for nw_connection object");
+        RELEASE_HERE(connection, comm_finalize);
+        return NULL;
+    }
+
+    connection->datagram_callback = datagram_callback;
+    connection->connected = connected;
+    connection->disconnected = disconnected;
+    connection->finalize = finalize;
+    connection->tcp_stream = stream;
+    connection->context = context;
+    nw_connection_set_state_changed_handler(connection->connection,
+                                            ^(nw_connection_state_t state, nw_error_t error)
+                                            { connection_state_changed(connection, state, error); });
+    nw_connection_set_queue(connection->connection, ioloop_main_queue);
+    // Until we get the canceled callback in connection_state_changed, the nw_connection_t holds a reference to this
+    // comm_t object.
+    RETAIN_HERE(connection);
+    nw_connection_start(connection->connection);
+    return connection;
+}
+
+static void
+subproc_finalize(subproc_t *subproc)
+{
+    int i;
+    for (i = 0; i < subproc->argc; i++) {
+        if (subproc->argv[i] != NULL) {
+            free(subproc->argv[i]);
+            subproc->argv[i] = NULL;
+        }
+    }
+    if (subproc->dispatch_source != NULL) {
+        dispatch_release(subproc->dispatch_source);
+    }
+    if (subproc->output_fd != NULL) {
+        ioloop_file_descriptor_release(subproc->output_fd);
+    }
+    if (subproc->finalize != NULL) {
+        subproc->finalize(subproc->context);
+    }
+    free(subproc);
+}
+
+static void subproc_cancel(void *context)
+{
+    subproc_t *subproc = context;
+    subproc->dispatch_source = NULL;
+    RELEASE_HERE(subproc, subproc_finalize);
+}
+
+static void
+subproc_event(void *context)
+{
+    subproc_t *subproc = context;
+    pid_t pid;
+    int status;
+
+    pid = waitpid(subproc->pid, &status, WNOHANG);
+    if (pid <= 0) {
+        return;
+    }
+    subproc->callback(subproc, status, NULL);
+    if (!WIFSTOPPED(status)) {
+        dispatch_source_cancel(subproc->dispatch_source);
+    }
+}
+
+static void subproc_output_finalize(void *context)
+{
+    subproc_t *subproc = context;
+    if (subproc->output_fd) {
+        subproc->output_fd = NULL;
+    }
+}
+
+void
+ioloop_subproc_release_(subproc_t *subproc, const char *file, int line)
+{
+    RELEASE(subproc, subproc_finalize);
+}
+
+// Invoke the specified executable with the specified arguments.   Call callback when it exits.
+// All failures are reported through the callback.
+subproc_t *
+ioloop_subproc(const char *exepath, char *NULLABLE *argv, int argc,
+               subproc_callback_t callback, io_callback_t output_callback, void *context)
+{
+    subproc_t *subproc;
+    int i, rv;
+    posix_spawn_file_actions_t actions;
+    posix_spawnattr_t attrs;
+
+    if (callback == NULL) {
+        ERROR("ioloop_add_wake_event called with null callback");
+        return NULL;
+    }
+
+    if (argc > MAX_SUBPROC_ARGS) {
+        callback(NULL, 0, "too many subproc args");
+        return NULL;
+    }
+
+    subproc = calloc(1, sizeof *subproc);
+    if (subproc == NULL) {
+        callback(NULL, 0, "out of memory");
+        return NULL;
+    }
+    RETAIN_HERE(subproc);
+    if (output_callback != NULL) {
+        rv = pipe(subproc->pipe_fds);
+        if (rv < 0) {
+            callback(NULL, 0, "unable to create pipe.");
+            RELEASE_HERE(subproc, subproc_finalize);
+            return NULL;
+        }
+        subproc->output_fd = ioloop_file_descriptor_create(subproc->pipe_fds[0], subproc, subproc_output_finalize);
+        if (subproc->output_fd == NULL) {
+            callback(NULL, 0, "out of memory.");
+            close(subproc->pipe_fds[0]);
+            close(subproc->pipe_fds[1]);
+            RELEASE_HERE(subproc, subproc_finalize);
+            return NULL;
+        }
+    }
+                                                           
+    subproc->argv[0] = strdup(exepath);
+    if (subproc->argv[0] == NULL) {
+        RELEASE_HERE(subproc, subproc_finalize);
+        callback(NULL, 0, "out of memory");
+        return NULL;
+    }
+    subproc->argc++;
+    for (i = 0; i < argc; i++) {
+        subproc->argv[i + 1] = strdup(argv[i]);
+        if (subproc->argv[i + 1] == NULL) {
+            RELEASE_HERE(subproc, subproc_finalize);
+            callback(NULL, 0, "out of memory");
+            return NULL;
+        }
+        subproc->argc++;
+    }
+
+    // Set up for posix_spawn
+    posix_spawn_file_actions_init(&actions);
+    if (output_callback != NULL) {
+        posix_spawn_file_actions_adddup2(&actions, subproc->pipe_fds[1], STDOUT_FILENO);
+        posix_spawn_file_actions_addclose(&actions, subproc->pipe_fds[0]);
+        posix_spawn_file_actions_addclose(&actions, subproc->pipe_fds[1]);
+    }
+    posix_spawnattr_init(&attrs);
+    extern char **environ;
+    rv = posix_spawn(&subproc->pid, exepath, &actions, &attrs, subproc->argv, environ);
+    posix_spawn_file_actions_destroy(&actions);
+    posix_spawnattr_destroy(&attrs);
+    if (rv < 0) {
+        ERROR("posix_spawn failed for " PUB_S_SRP ": " PUB_S_SRP, subproc->argv[0], strerror(errno));
+        callback(subproc, 0, strerror(errno));
+        RELEASE_HERE(subproc, subproc_finalize);
+        return NULL;
+    }
+    subproc->callback = callback;
+    subproc->context = context;
+
+    subproc->dispatch_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, subproc->pid, DISPATCH_PROC_EXIT,
+                                                      ioloop_main_queue);
+    if (subproc->dispatch_source == NULL) {
+        ERROR("dispatch_source_create failed in ioloop_add_wake_event().");
+        return false;
+    }
+    dispatch_retain(subproc->dispatch_source);
+    dispatch_source_set_event_handler_f(subproc->dispatch_source, subproc_event);
+    dispatch_source_set_cancel_handler_f(subproc->dispatch_source, subproc_cancel);
+    dispatch_set_context(subproc->dispatch_source, subproc);
+    dispatch_activate(subproc->dispatch_source);
+    RETAIN_HERE(subproc); // Dispatch has a reference
+
+    // Now that we have a viable subprocess, add the reader callback.
+    if (output_callback != NULL && subproc->output_fd != NULL) {
+        close(subproc->pipe_fds[1]);
+        ioloop_add_reader(subproc->output_fd, output_callback);
+    }
+    return subproc;
+}
+
+void
+ioloop_dnssd_txn_cancel(dnssd_txn_t *txn)
+{
+    if (txn->sdref != NULL) {
+        DNSServiceRefDeallocate(txn->sdref);
+        txn->sdref = NULL;
+    } else {
+        INFO("ioloop_dnssd_txn_cancel: dead transaction.");
+    }
+}
+
+static void
+dnssd_txn_finalize(dnssd_txn_t *txn)
+{
+    if (txn->sdref != NULL) {
+        ioloop_dnssd_txn_cancel(txn);
+    }
+    if (txn->finalize_callback) {
+        txn->finalize_callback(txn->context);
+    }
+    free(txn);
+}
+
+void
+ioloop_dnssd_txn_retain_(dnssd_txn_t *dnssd_txn, const char *file, int line)
+{
+    (void)file; (void)line;
+    RETAIN(dnssd_txn);
+}
+
+void
+ioloop_dnssd_txn_release_(dnssd_txn_t *dnssd_txn, const char *file, int line)
+{
+    (void)file; (void)line;
+    RELEASE(dnssd_txn, dnssd_txn_finalize);
+}
+
+dnssd_txn_t *
+ioloop_dnssd_txn_add_(DNSServiceRef ref, void *context, finalize_callback_t finalize_callback, const char *file,
+                      int line)
+{
+    dnssd_txn_t *txn = calloc(1, sizeof(*txn));
+    (void)file; (void)line;
+
+    if (txn != NULL) {
+        RETAIN(txn);
+        txn->sdref = ref;
+        txn->context = context;
+        txn->finalize_callback = finalize_callback;
+        DNSServiceSetDispatchQueue(ref, ioloop_main_queue);
+    }
+    return txn;
+}
+
+void
+ioloop_dnssd_txn_set_aux_pointer(dnssd_txn_t *NONNULL txn, void *aux_pointer)
+{
+    txn->aux_pointer = aux_pointer;
+}
+
+void *
+ioloop_dnssd_txn_get_aux_pointer(dnssd_txn_t *NONNULL txn)
+{
+    return txn->aux_pointer;
+}
+
+void *
+ioloop_dnssd_txn_get_context(dnssd_txn_t *NONNULL txn)
+{
+    return txn->context;
+}
+
+static bool
+ioloop_xpc_client_is_entitled(xpc_connection_t conn, const char *entitlement_name)
+{
+    bool entitled = false;
+    xpc_object_t entitled_obj = xpc_connection_copy_entitlement_value(conn, entitlement_name);
+
+    if (entitled_obj) {
+        if (xpc_get_type(entitled_obj) == XPC_TYPE_BOOL && xpc_bool_get_value(entitled_obj)) {
+            entitled = true;
+        }
+        xpc_release(entitled_obj);
+    } else {
+        ERROR("ioloop_xpc_client_is_entitled: Client Entitlement is NULL");
+    }
+
+    if (!entitled) {
+        ERROR("ioloop_xpc_client_is_entitled: Client is missing Entitlement!");
+    }
+
+    return entitled;
+}
+
+static void
+ioloop_xpc_accept(xpc_connection_t conn, const char *name, ioloop_xpc_callback_t callback)
+{
+    struct state {
+        xpc_connection_t conn;
+        ioloop_xpc_callback_t callback;
+    } *state;
+
+    if (conn == NULL) {
+        ERROR("ioloop_xpc_accept: listener has been canceled.");
+        return;
+    }
+
+    state = calloc(1, sizeof(*state));
+    if (state == NULL) {
+        ERROR("ioloop_xpc_accept: no memory for xpc connection state.");
+        return;
+    }
+
+    int pid = xpc_connection_get_pid(conn);
+    int uid = xpc_connection_get_euid(conn);
+
+    if (!ioloop_xpc_client_is_entitled(conn, name)) {
+        ERROR("ioloop_xpc_accept: connection from uid %d pid %d is missing entitlement " PUB_S_SRP ".", uid, pid, name);
+        xpc_connection_cancel(conn);
+        free(state);
+        return;
+    }
+
+    state->conn = conn;
+    xpc_retain(conn);
+    state->callback = callback;
+    xpc_connection_set_target_queue(conn, ioloop_main_queue);
+    xpc_connection_set_event_handler(conn, ^(xpc_object_t request) {
+            xpc_type_t type = xpc_get_type(request);
+
+            if (request == XPC_ERROR_CONNECTION_INVALID) {
+                INFO("ioloop_xpc_accept event handler: connection has been finalized.");
+                if (state->callback != NULL) {
+                    state->callback(state->conn, NULL);
+                }
+                // We are guaranteed that this is the last callback, so we can safely free state.
+                if (state->conn != NULL) {
+                    xpc_release(state->conn);
+                    state->conn = NULL;
+                }
+                free(state);
+            } else if (type == XPC_TYPE_DICTIONARY) {
+                // If the callback returns false, that means that we're done.
+                if (state->callback != NULL) {
+                    if (!state->callback(state->conn, request)) {
+                        INFO("ioloop_xpc_accept event handler: callback indicated done.");
+                        xpc_connection_cancel(state->conn);
+                        state->callback = NULL;
+                    } else {
+                        INFO("ioloop_xpc_accept event handler: continuing.");
+                    }
+                }
+            } else {
+                INFO("ioloop_xpc_accept event handler: client went away.");
+                // Passing a null request to the callback means the client went away.
+                xpc_connection_cancel(state->conn);
+                if (state->callback != NULL) {
+                    callback(state->conn, NULL);
+                }
+                state->callback = NULL;
+            }
+        });
+    xpc_connection_resume(conn);
+}    
+
+xpc_connection_t
+ioloop_create_xpc_service(const char *name, ioloop_xpc_callback_t callback)
+{
+    xpc_connection_t listener = xpc_connection_create_mach_service(name, ioloop_main_queue,
+                                                                   XPC_CONNECTION_MACH_SERVICE_LISTENER);
+    if (listener == NULL || xpc_get_type(listener) != XPC_TYPE_CONNECTION) {
+        ERROR("ioloop_create_xpc_service: " PUB_S_SRP ": unable to create listener %p", name, listener);
+        if (listener != NULL) {
+            xpc_release(listener);
+        }
+        return NULL;
+    }
+
+    xpc_connection_set_event_handler(listener, ^(xpc_object_t eventmsg) {
+            xpc_type_t type = xpc_get_type(eventmsg);
+
+            if (type == XPC_TYPE_CONNECTION) {
+                INFO("ioloop_create_xpc_service: New " PUB_S_SRP " Client %p", name, eventmsg);
+                ioloop_xpc_accept((xpc_connection_t)eventmsg, name, callback);
+            }
+            else if (type == XPC_TYPE_ERROR) // Ideally, we would never hit these cases
+            {
+                ERROR("ioloop_create_xpc_service: XPCError: " PUB_S_SRP,
+                      xpc_dictionary_get_string(eventmsg, XPC_ERROR_KEY_DESCRIPTION));
+                callback(NULL, NULL);
+            }
+            else
+            {
+                INFO("ioloop_create_xpc_service: Unknown EventMsg type");
+            }
+        });
+    xpc_connection_resume(listener);
+    return listener;
+}
+
+static void
+file_descriptor_finalize(void *context)
+{
+    io_t *file_descriptor = context;
+    if (file_descriptor->ref_count == 0) {
+        if (file_descriptor->finalize) {
+            file_descriptor->finalize(file_descriptor->context);
+        }
+        free(file_descriptor);
+    }
+}
+
+void
+ioloop_file_descriptor_retain_(io_t *file_descriptor, const char *file, int line)
+{
+    (void)file; (void)line;
+    RETAIN(file_descriptor);
+}
+
+void
+ioloop_file_descriptor_release_(io_t *file_descriptor, const char *file, int line)
+{
+    (void)file; (void)line;
+    RELEASE(file_descriptor, file_descriptor_finalize);
+}
+
+io_t *
+ioloop_file_descriptor_create_(int fd, void *context, finalize_callback_t finalize, const char *file, int line)
+{
+    io_t *ret;
+    ret = calloc(1, sizeof(*ret));
+    if (ret) {
+        ret->fd = fd;
+        ret->context = context;
+        ret->finalize = finalize;
+        RETAIN(ret);
+    }
+    return ret;
+}
+
+static void
+ioloop_read_cancel(void *context)
+{
+    io_t *io = context;
+
+    if (io->read_source != NULL) {
+        dispatch_release(io->read_source);
+        io->read_source = NULL;
+        // Release the reference count that dispatch was holding.
+        RELEASE_HERE(io, file_descriptor_finalize);
+    }
+}
+
+static void
+ioloop_read_event(void *context)
+{
+    io_t *io = context;
+
+    if (io->read_callback != NULL) {
+        io->read_callback(io, io->context);
+    }
+}
+
+void
+ioloop_close(io_t *io)
+{
+    if (io->read_source != NULL) {
+        dispatch_cancel(io->read_source);
+    }
+    if (io->write_source != NULL) {
+        dispatch_cancel(io->write_source);
+    }
+    io->fd = -1;
+}
+
+void
+ioloop_add_reader(io_t *NONNULL io, io_callback_t NONNULL callback)
+{
+    io->read_callback = callback;
+    if (io->read_source == NULL) {
+        io->read_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, io->fd, 0, ioloop_main_queue);
+    }
+    if (io->read_source == NULL) {
+        ERROR("dispatch_source_create: unable to create read dispatch source.");
+        return;
+    }
+    dispatch_source_set_event_handler_f(io->read_source, ioloop_read_event);
+    dispatch_source_set_cancel_handler_f(io->read_source, ioloop_read_cancel);
+    dispatch_set_context(io->read_source, io);
+    RETAIN_HERE(io); // Dispatch will hold a reference.
+    dispatch_resume(io->read_source);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/posix.c b/ServiceRegistration/posix.c
new file mode 100644 (file)
index 0000000..2c2bd2f
--- /dev/null
@@ -0,0 +1,359 @@
+/* posix.c
+ *
+ * Copyright (c) 2018-2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * utility functions common to all posix implementations (e.g., MacOS, Linux).
+ */
+
+#define _GNU_SOURCE
+
+#include <netinet/in.h>
+#include <net/if.h>
+#include <netinet/in_var.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#include <ifaddrs.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <arpa/inet.h>
+#include "dns_sd.h"
+#include "srp.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+
+typedef struct interface_addr interface_addr_t;
+struct interface_addr {
+    interface_addr_t *next;
+    char *name;
+    addr_t addr;
+    addr_t mask;
+    uint32_t flags;
+};
+interface_addr_t *interface_addresses;
+
+bool
+ioloop_map_interface_addresses(void *context, interface_callback_t callback)
+{
+    struct ifaddrs *ifaddrs, *ifp;
+    interface_addr_t *kept_ifaddrs = NULL, **ki_end = &kept_ifaddrs;
+    interface_addr_t *new_ifaddrs = NULL, **ni_end = &new_ifaddrs;
+    interface_addr_t **ip, *nif;
+
+    if (getifaddrs(&ifaddrs) < 0) {
+        ERROR("getifaddrs failed: " PUB_S_SRP, strerror(errno));
+        return false;
+    }
+
+    for (ifp = ifaddrs; ifp; ifp = ifp->ifa_next) {
+        bool remove = false;
+        bool keep = true;
+
+        // Check for temporary addresses, etc.
+        if (ifp->ifa_addr != NULL && ifp->ifa_addr->sa_family == AF_INET6) {
+            struct in6_ifreq ifreq;
+            int sock;
+            strlcpy(ifreq.ifr_name, ifp->ifa_name, sizeof(ifp->ifa_name));
+            if ((sock = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
+                ERROR("socket(AF_INET6, SOCK_DGRAM): " PUB_S_SRP, strerror(errno));
+                continue;
+            }
+            memcpy(&ifreq.ifr_addr, ifp->ifa_addr, sizeof ifreq.ifr_addr);
+            if (ioctl(sock, SIOCGIFAFLAG_IN6, &ifreq) < 0) {
+                ERROR("ioctl(SIOCGIFAFLAG_IN6): " PUB_S_SRP, strerror(errno));
+                close(sock);
+                continue;
+            }
+            uint32_t flags = ifreq.ifr_ifru.ifru_flags6;
+            if (flags & (IN6_IFF_ANYCAST | IN6_IFF_TENTATIVE | IN6_IFF_DETACHED | IN6_IFF_TEMPORARY)) {
+                keep = false;
+            }
+            if (flags & IN6_IFF_DEPRECATED) {
+                remove = true;
+            }
+            close(sock);
+        }
+
+#ifdef DEBUG_AF_LINK
+        if (ifp->ifa_addr != NULL && ifp->ifa_addr->sa_family != AF_INET && ifp->ifa_addr->sa_family != AF_INET6) {
+            struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifp->ifa_addr;
+            const uint8_t *addr = (uint8_t *)LLADDR(sdl);
+            INFO("%.*s index %d alen %d dlen %d SDL: %02x:%02x:%02x:%02x:%02x:%02x",
+                 sdl->sdl_nlen, sdl->sdl_data, sdl->sdl_index, sdl->sdl_alen, sdl->sdl_slen, 
+                 addr[0],  addr[1],  addr[2], addr[3],  addr[4],  addr[5]);
+        }
+#endif // DEBUG_AF_LINK
+
+        // Is this an interface address we can use?
+        if (keep && ifp->ifa_addr != NULL &&
+            (ifp->ifa_addr->sa_family == AF_LINK ||
+             ((ifp->ifa_addr->sa_family == AF_INET6 || ifp->ifa_addr->sa_family == AF_INET) && ifp->ifa_netmask != NULL)
+             ) &&
+            (ifp->ifa_flags & IFF_UP))
+        {
+            keep = false;
+            for (ip = &interface_addresses; *ip != NULL; ) {
+                interface_addr_t *ia = *ip;
+                // Same interface and address?
+                if (!remove && !strcmp(ia->name, ifp->ifa_name) &&
+                    ifp->ifa_addr->sa_family == ia->addr.sa.sa_family &&
+                    (ifp->ifa_addr->sa_family == AF_LINK ||
+                     (((ifp->ifa_addr->sa_family == AF_INET &&
+                        ((struct sockaddr_in *)ifp->ifa_addr)->sin_addr.s_addr == ia->addr.sin.sin_addr.s_addr) ||
+                       (ifp->ifa_addr->sa_family == AF_INET6 &&
+                        !memcmp(&((struct sockaddr_in6 *)ifp->ifa_addr)->sin6_addr,
+                                &ia->addr.sin6.sin6_addr, sizeof ia->addr.sin6.sin6_addr))) &&
+                      ((ifp->ifa_netmask->sa_family == AF_INET &&
+                        ((struct sockaddr_in *)ifp->ifa_netmask)->sin_addr.s_addr == ia->mask.sin.sin_addr.s_addr) ||
+                       (ifp->ifa_netmask->sa_family == AF_INET6 &&
+                        !memcmp(&((struct sockaddr_in6 *)ifp->ifa_netmask)->sin6_addr,
+                                &ia->mask.sin6.sin6_addr, sizeof ia->mask.sin6.sin6_addr))))))
+                {
+                    *ip = ia->next;
+                    *ki_end = ia;
+                    ki_end = &ia->next;
+                    ia->next = NULL;
+                    keep = true;
+                    break;
+                } else {
+                    ip = &ia->next;
+                }
+            }
+            // If keep is false, this is a new interface/address.
+            if (!keep) {
+                size_t len = strlen(ifp->ifa_name);
+                nif = calloc(1, len + 1 + sizeof *nif);
+                // We don't have a way to fix nif being null; what this means is that we don't detect a new
+                // interface address.
+                if (nif != NULL) {
+                    nif->name = (char *)(nif + 1);
+                    strlcpy(nif->name, ifp->ifa_name, len + 1);
+                    if (ifp->ifa_addr->sa_family == AF_INET) {
+                        nif->addr.sin = *((struct sockaddr_in *)ifp->ifa_addr);
+                        nif->mask.sin = *((struct sockaddr_in *)ifp->ifa_netmask);
+
+                        IPv4_ADDR_GEN_SRP(&nif->mask.sin.sin_addr.s_addr, __new_interface_ipv4_addr);
+                        INFO("ioloop_map_interface_addresses: new IPv4 interface address added - ifname: " PUB_S_SRP
+                             ", addr: " PRI_IPv4_ADDR_SRP, nif->name,
+                             IPv4_ADDR_PARAM_SRP(&nif->mask.sin.sin_addr.s_addr, __new_interface_ipv4_addr));
+                    } else if (ifp->ifa_addr->sa_family == AF_INET6) {
+                        nif->addr.sin6 = *((struct sockaddr_in6 *)ifp->ifa_addr);
+                        nif->mask.sin6 = *((struct sockaddr_in6 *)ifp->ifa_netmask);
+
+                        SEGMENTED_IPv6_ADDR_GEN_SRP(nif->addr.sin6.sin6_addr.s6_addr, __new_interface_ipv6_addr);
+                        INFO("ioloop_map_interface_addresses: new IPv6 interface address added - ifname: " PUB_S_SRP
+                             ", addr: " PRI_SEGMENTED_IPv6_ADDR_SRP, nif->name,
+                             SEGMENTED_IPv6_ADDR_PARAM_SRP(nif->addr.sin6.sin6_addr.s6_addr,
+                                                           __new_interface_ipv6_addr));
+                    } else {
+                        struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifp->ifa_addr;
+                        memset(&nif->mask, 0, sizeof(nif->mask));
+                        if (sdl->sdl_alen == 6) {
+                            nif->addr.ether_addr.len = 6;
+                            memcpy(nif->addr.ether_addr.addr, LLADDR(sdl), 6);
+                        } else {
+                            nif->addr.ether_addr.len = 0;
+                        }
+                        nif->addr.ether_addr.index = sdl->sdl_index;
+                        nif->addr.ether_addr.family = AF_LINK;
+                    }
+                    nif->flags = ifp->ifa_flags;
+                    *ni_end = nif;
+                    ni_end = &nif->next;
+                }
+            }
+        }
+    }
+
+    // Get rid of any link-layer addresses for which there is no other address on that interface
+    // This is clunky, but we can't assume that the AF_LINK address will come after some other
+    // address, so there's no more efficient way to do this that I can think of.
+    for (ip = &new_ifaddrs; *ip; ) {
+        if ((*ip)->addr.sa.sa_family == AF_LINK) {
+            bool drop = true;
+            for (nif = new_ifaddrs; nif; nif = nif->next) {
+                if (nif != *ip && !strcmp(nif->name, (*ip)->name)) {
+                    drop = false;
+                    break;
+                }
+            }
+            if (drop) {
+                nif = *ip;
+                *ip = nif->next;
+                free(nif);
+            } else {
+                ip = &(*ip)->next;
+            }
+        } else {
+            ip = &(*ip)->next;
+        }
+    }
+
+#ifdef TOO_MUCH_INFO
+    char infobuf[1000];
+    int i;
+    for (i = 0; i < 3; i++) {
+        char *infop = infobuf;
+        int len, lim = sizeof infobuf;
+        const char *title;
+        switch(i) {
+        case 0:
+            title = "deleted";
+            nif = interface_addresses;
+            break;
+        case 1:
+            title = "   kept";
+            nif = kept_ifaddrs;
+            break;
+        case 2:
+            title = "    new";
+            nif = new_ifaddrs;
+            break;
+        default:
+            abort();
+        }
+        for (; nif; nif = nif->next) {
+            snprintf(infop, lim, " %p (", nif);
+            len = (int)strlen(infop);
+            lim -= len;
+            infop += len;
+            inet_ntop(AF_INET6, &nif->addr.sin6.sin6_addr, infop, lim);
+            len = (int)strlen(infop);
+            lim -= len;
+            infop += len;
+            if (lim > 1) {
+                *infop++ = ')';
+                lim--;
+            }
+        }
+        *infop = 0;
+        INFO(PUB_S_SRP ":" PUB_S_SRP, title, infobuf);
+    }
+#endif
+
+    // Report and free deleted interface addresses...
+    for (nif = interface_addresses; nif; ) {
+        interface_addr_t *next = nif->next;
+        callback(context, nif->name, &nif->addr, &nif->mask, nif->flags, interface_address_deleted);
+        free(nif);
+        nif = next;
+    }
+
+    // Report added interface addresses...
+    for (nif = new_ifaddrs; nif; nif = nif->next) {
+        callback(context, nif->name, &nif->addr, &nif->mask, nif->flags, interface_address_added);
+    }
+
+    // Report unchanged interface addresses...
+    for (nif = kept_ifaddrs; nif; nif = nif->next) {
+        callback(context, nif->name, &nif->addr, &nif->mask, nif->flags, interface_address_unchanged);
+    }
+
+    // Restore kept interface addresses and append new addresses to the list.
+    interface_addresses = kept_ifaddrs;
+    for (ip = &interface_addresses; *ip; ip = &(*ip)->next)
+        ;
+    *ip = new_ifaddrs;
+    freeifaddrs(ifaddrs);
+    return true;
+}
+
+ssize_t
+ioloop_recvmsg(int sock, uint8_t *buffer, size_t buffer_length, int *ifindex, int *hop_limit, addr_t *source,
+               addr_t *destination)
+{
+    ssize_t rv;
+    struct msghdr msg;
+    struct iovec bufp;
+    char cmsgbuf[128];
+    struct cmsghdr *cmh;
+
+    bufp.iov_base = buffer;
+    bufp.iov_len = buffer_length;
+    msg.msg_iov = &bufp;
+    msg.msg_iovlen = 1;
+    msg.msg_name = source;
+    msg.msg_namelen = sizeof(*source);
+    msg.msg_control = cmsgbuf;
+    msg.msg_controllen = sizeof(cmsgbuf);
+
+    rv = recvmsg(sock, &msg, 0);
+    if (rv < 0) {
+        return rv;
+    }
+
+    // For UDP, we use the interface index as part of the validation strategy, so go get
+    // the interface index.
+    for (cmh = CMSG_FIRSTHDR(&msg); cmh; cmh = CMSG_NXTHDR(&msg, cmh)) {
+        if (cmh->cmsg_level == IPPROTO_IPV6 && cmh->cmsg_type == IPV6_PKTINFO &&
+            cmh->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo)))
+        {
+            struct in6_pktinfo pktinfo;
+
+            memcpy(&pktinfo, CMSG_DATA(cmh), sizeof pktinfo);
+            *ifindex = pktinfo.ipi6_ifindex;
+
+            /* Get the destination address, for use when replying. */
+            destination->sin6.sin6_family = AF_INET6;
+            destination->sin6.sin6_port = 0;
+            destination->sin6.sin6_addr = pktinfo.ipi6_addr;
+#ifndef NOT_HAVE_SA_LEN
+            destination->sin6.sin6_len = sizeof(destination->sin6);
+#endif
+        } else if (cmh->cmsg_level == IPPROTO_IP && cmh->cmsg_type == IP_PKTINFO &&
+                   cmh->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
+            struct in_pktinfo pktinfo;
+
+            memcpy(&pktinfo, CMSG_DATA(cmh), sizeof pktinfo);
+            *ifindex = pktinfo.ipi_ifindex;
+
+            destination->sin.sin_family = AF_INET;
+            destination->sin.sin_port = 0;
+            destination->sin.sin_addr = pktinfo.ipi_addr;
+#ifndef NOT_HAVE_SA_LEN
+            destination->sin.sin_len = sizeof(destination->sin);
+#endif
+        } else if (cmh->cmsg_level == IPPROTO_IPV6 && cmh->cmsg_type == IPV6_HOPLIMIT &&
+                   cmh->cmsg_len == CMSG_LEN(sizeof(int))) {
+            *hop_limit = *(int *)CMSG_DATA(cmh);
+        }
+    }
+    return rv;
+}
+
+#ifdef DEBUG_FD_LEAKS
+int
+get_num_fds(void)
+{
+    DIR *dirfd = opendir("/dev/fd");
+    int num = 0;
+    if (dirfd == NULL) {
+        return -1;
+    }
+    while (readdir(dirfd) != NULL) {
+        num++;
+    }
+    closedir(dirfd);
+    return num;
+}
+#endif // DEBUG_VERBOSE
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/ra-tester/ra-tester.c b/ServiceRegistration/ra-tester/ra-tester.c
new file mode 100644 (file)
index 0000000..22a5bfd
--- /dev/null
@@ -0,0 +1,113 @@
+/* ra-tester.c
+ *
+ * Copyright (c) 2020 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the SRP Advertising Proxy, which is an SRP Server
+ * that offers registered addresses using mDNS.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <dns_sd.h>
+#include <net/if.h>
+
+#include "srp.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-crypto.h"
+#include "ioloop.h"
+#include "dnssd-proxy.h"
+#include "srp-gw.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "config-parse.h"
+#include "route.h"
+
+static void
+usage(void)
+{
+    ERROR("ra-tester -t <thread interface name> --h <home interface name>");
+    exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+    int i;
+       extern char *thread_interface_name;
+    extern char *home_interface_name;
+       extern bool advertise_default_route_on_thread;
+
+    for (i = 1; i < argc; i++) {
+        if (!strcmp(argv[i], "-t")) {
+            if (i + 1 == argc) {
+                usage();
+            }
+            thread_interface_name = argv[i + 1];
+            i++;
+        } else if (!strcmp(argv[i], "-h")) {
+            if (i + 1 == argc) {
+                usage();
+            }
+            home_interface_name = argv[i + 1];
+            i++;
+        } else {
+            usage();
+        }
+    }
+
+    if (thread_interface_name == NULL) {
+        INFO("thread interface name required.");
+        usage();
+    }
+    if (home_interface_name == NULL) {
+        INFO("home interface name required.");
+        usage();
+    }
+    OPENLOG(log_stderr);
+
+    if (!ioloop_init()) {
+        return 1;
+    }
+
+    if (!start_icmp_listener()) {
+        return 1;
+    }
+
+    thread_network_startup();
+
+    do {
+        int something = 0;
+        ioloop();
+        INFO("dispatched %d events.", something);
+    } while (1);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/route.c b/ServiceRegistration/route.c
new file mode 100644 (file)
index 0000000..dda17f5
--- /dev/null
@@ -0,0 +1,5204 @@
+/* route.c
+ *
+ * Copyright (c) 2019-2020 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the implementation for Thread Border Router routing.
+ * The state of the Thread network is tracked, the state of the infrastructure
+ * network is tracked, and policy decisions are made as to what to advertise
+ * on both networks.
+ */
+
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet6/in6_var.h>
+#include <netinet/icmp6.h>
+#include <netinet6/nd6.h>
+#include <net/if_media.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <arpa/inet.h>
+#include <sys/sysctl.h>
+#include <stdlib.h>
+
+#ifdef IOLOOP_MACOS
+#include <xpc/xpc.h>
+
+#include <TargetConditionals.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+#include <SystemConfiguration/SCPrivate.h>
+#include <SystemConfiguration/SCNetworkConfigurationPrivate.h>
+#include <SystemConfiguration/SCNetworkSignature.h>
+#include <network_information.h>
+
+#include <CoreUtils/CoreUtils.h>
+#endif // IOLOOP_MACOS
+
+#ifndef OPEN_SOURCE
+// For now, we need backwards compatibility with old service type, only on the server.
+#  define THREAD_SERVICE_SEND_BOTH 1
+#endif
+
+#include "srp.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "route.h"
+#if TARGET_OS_TV
+#include "cti-services.h"
+#endif
+#include "srp-gw.h"
+#include "srp-proxy.h"
+
+typedef union {
+    uint16_t len;
+    struct rt_msghdr route;
+    struct if_msghdr interface;
+    struct if_msghdr2 if2;
+    struct ifa_msghdr address;
+    uint8_t bytes[512];
+} route_message_t;
+
+typedef struct icmp_listener {
+    io_t *io_state;
+    int sock;
+    uint32_t unsolicited_interval;
+} icmp_listener_t;
+
+typedef struct thread_prefix thread_prefix_t;
+struct thread_prefix {
+    int ref_count;
+    thread_prefix_t *next;
+    struct in6_addr prefix;
+    int prefix_len;
+    bool user, ncp, stable;
+    bool previous_user, previous_ncp, previous_stable;
+};
+struct thread_prefix *thread_prefixes, *published_thread_prefix, *adopted_thread_prefix;
+
+typedef struct thread_pref_id thread_pref_id_t;
+struct thread_pref_id {
+    int ref_count;
+    thread_pref_id_t *next;
+    uint8_t partition_id[4]; // Partition id on which this prefix is claimed
+    uint8_t prefix[5];       // 40-bit ULA prefix identifier (no need to keep the whole prefix)
+    bool user, ncp, stable;
+    bool previous_user, previous_ncp, previous_stable;
+};
+struct thread_pref_id *thread_pref_ids;
+
+typedef struct thread_service thread_service_t;
+struct thread_service {
+    int ref_count;
+    thread_service_t *next;
+    uint8_t address[16]; // IPv6 address on which service is offered
+    uint8_t port[2];     // Port (network byte order)
+    bool user, ncp, stable;
+    bool previous_user, previous_ncp, previous_stable;
+};
+struct thread_service *thread_services;
+
+struct network_link {
+    network_link_t *next;
+    int ref_count;
+    uint64_t last_seen;
+    uint8_t *NULLABLE signature;
+    long signature_length;
+    int32_t prefix_number;
+    interface_t *primary; // This is the interface on which this prefix is being advertised.
+};
+
+interface_t *interfaces;
+network_link_t *network_links; // Our list of network links
+CFMutableArrayRef network_link_array; // The same list as a CFArray, so that we can write it to preferences.
+icmp_listener_t icmp_listener;
+bool have_thread_prefix = false;
+struct in6_addr my_thread_prefix;
+struct in6_addr srp_listener_ip_address;
+char thread_address_string[INET6_ADDRSTRLEN];
+uint16_t srp_service_listen_port;
+uint8_t thread_partition_id[4];
+srp_proxy_listener_state_t *srp_listener;
+struct in6_addr ula_prefix;
+int num_thread_interfaces; // Should be zero or one.
+int ula_serial = 1;
+bool advertise_default_route_on_thread;
+subproc_t *thread_interface_enumerator_process;
+subproc_t *thread_prefix_adder_process;
+subproc_t *link_route_adder_process;
+subproc_t *thread_rti_setter_process;
+subproc_t *thread_forwarding_setter_process;
+subproc_t *thread_proxy_service_adder_process;
+subproc_t *tcpdump_logger_process;
+char *thread_interface_name;
+char *home_interface_name;
+bool thread_proxy_service_setup_done;
+bool interface_state_stable = false;
+bool have_non_thread_interface = false;
+
+#ifndef RA_TESTER
+cti_network_state_t current_thread_state = kCTI_NCPState_Uninitialized;
+cti_network_node_type_t current_thread_role = kCTI_NetworkNodeType_Unknown;
+cti_connection_t thread_role_context;
+cti_connection_t thread_state_context;
+cti_connection_t thread_service_context;
+cti_connection_t thread_prefix_context;
+cti_connection_t thread_partition_id_context;
+#endif
+
+nw_path_evaluator_t path_evaluator;
+
+
+#define CONFIGURE_STATIC_INTERFACE_ADDRESSES 1
+#define USE_IPCONFIGURATION_SERVICE 1
+
+static void refresh_interface_list(void);
+static void router_advertisement_send(interface_t *NONNULL interface);
+static void icmp_send(uint8_t *NONNULL message, size_t length,
+                      interface_t *NONNULL interface, const struct in6_addr *NONNULL destination);
+static void interface_beacon_schedule(interface_t *NONNULL interface, unsigned when);
+static void interface_prefix_configure(struct in6_addr prefix, interface_t *NONNULL interface);
+static void interface_prefix_evaluate(interface_t *interface);
+static void start_router_solicit(interface_t *interface);
+#ifndef RA_TESTER
+static void routing_policy_evaluate_all_interfaces(bool assume_changed);
+#endif
+static void routing_policy_evaluate(interface_t *interface, bool assume_changed);
+static void post_solicit_policy_evaluate(void *context);
+
+#ifndef RA_TESTER
+static void partition_state_reset(void);
+static void partition_unpublish_prefix(thread_prefix_t *NONNULL prefix);
+static void partition_unpublish_adopted_prefix(bool wait);
+static void partition_publish_my_prefix(void);
+static void partition_adopt_prefix(thread_prefix_t *NONNULL prefix);
+static bool partition_prefix_is_present(struct in6_addr *prefix_addr, int length);
+static bool partition_pref_id_is_present(struct in6_addr *NONNULL prefix_addr);
+static thread_prefix_t *NULLABLE partition_find_lowest_valid_prefix(void);
+static thread_pref_id_t *NULLABLE partition_find_lowest_valid_pref_id(void);
+static void partition_pref_id_timeout(void *__unused NULLABLE context);
+static void partition_post_election_wakeup(void *__unused NULLABLE context);
+static void partition_post_partition_timeout(void *__unused NULLABLE context);
+static void partition_discontinue_srp_service(void);
+static void partition_utun0_address_changed(const struct in6_addr *NONNULL addr, enum interface_address_change change);
+static bool partition_wait_for_prefix_settling(wakeup_callback_t NONNULL callback, uint64_t now);
+static void partition_got_tunnel_name(void);
+static void partition_prefix_set_changed(void);
+static void partition_pref_id_set_changed(void);
+static void partition_id_changed(void);
+static void partition_remove_service_done(void *__unused NULLABLE context, cti_status_t status);
+static void partition_stop_advertising_service(void);
+static void partition_proxy_listener_ready(void *__unused NULLABLE context, uint16_t port);
+static void partition_maybe_advertise_service(void);
+static void partition_service_set_changed(void);
+static void partition_maybe_enable_services(void);
+static void partition_disable_service(void);
+static void partition_schedule_service_add_wakeup(void);
+
+static uint64_t partition_last_prefix_set_change;
+static uint64_t partition_last_pref_id_set_change;
+static uint64_t partition_last_partition_id_change;
+static uint64_t partition_last_role_change;
+static uint64_t partition_last_state_change;
+static uint64_t partition_settle_start;
+static uint64_t partition_service_last_add_time;
+static bool partition_id_is_known;
+static bool partition_have_prefix_list;
+static bool partition_have_pref_id_list;
+static bool partition_tunnel_name_is_known;
+static bool partition_can_advertise_service;
+static bool partition_service_blocked;
+static bool partition_can_provide_routing;
+static bool partition_may_offer_service = false;
+static bool partition_settle_satisfied = true;
+static wakeup_t *partition_settle_wakeup;
+static wakeup_t *partition_post_partition_wakeup;
+static wakeup_t *partition_pref_id_wait_wakeup;
+static wakeup_t *partition_service_add_pending_wakeup;
+#endif
+
+static void
+interface_finalize(void *context)
+{
+    interface_t *interface = context;
+    if (interface->name != NULL) {
+        free(interface->name);
+    }
+    if (interface->beacon_wakeup != NULL) {
+        ioloop_wakeup_release(interface->beacon_wakeup);
+    }
+    if (interface->post_solicit_wakeup != NULL) {
+        ioloop_wakeup_release(interface->post_solicit_wakeup);
+    }
+    if (interface->router_solicit_wakeup != NULL) {
+        ioloop_wakeup_release(interface->router_solicit_wakeup);
+    }
+    if (interface->deconfigure_wakeup != NULL) {
+        ioloop_wakeup_release(interface->deconfigure_wakeup);
+    }
+    free(interface);
+}
+
+void
+interface_retain_(interface_t *interface, const char *file, int line)
+{
+    (void)file; (void)line;
+    RETAIN(interface);
+}
+
+void
+interface_release_(interface_t *interface, const char *file, int line)
+{
+    (void)file; (void)line;
+    RELEASE(interface, interface_finalize);
+}
+
+interface_t *
+interface_create_(const char *name, int ifindex, const char *file, int line)
+{
+    interface_t *ret;
+
+    if (name == NULL) {
+        ERROR("interface_create: missing name");
+        return NULL;
+    }
+
+    ret = calloc(1, sizeof(*ret));
+    if (ret) {
+        RETAIN(ret);
+        ret->name = strdup(name);
+        if (ret->name == NULL) {
+            ERROR("interface_create: no memory for name");
+            RELEASE(ret, interface_finalize);
+            return NULL;
+        }
+        ret->deconfigure_wakeup = ioloop_wakeup_create();
+        if (ret->deconfigure_wakeup == NULL) {
+            ERROR("No memory for interface deconfigure wakeup on " PUB_S_SRP ".", ret->name);
+            RELEASE(ret, interface_finalize);
+            return NULL;
+        }
+
+        ret->index = ifindex;
+        ret->inactive = true;
+        // Interfaces are ineligible for routing until explicitly identified as eligible.
+        ret->ineligible = true;
+    }
+    return ret;
+}
+
+#ifndef RA_TESTER
+static void
+thread_prefix_finalize(thread_prefix_t *prefix)
+{
+    free(prefix);
+}
+
+#define thread_prefix_create(prefix, prefix_length) thread_prefix_create_(prefix, prefix_length, __FILE__, __LINE__)
+static thread_prefix_t *
+thread_prefix_create_(struct in6_addr *address, int prefix_length, const char *file, int line)
+{
+    thread_prefix_t *prefix;
+
+    prefix = calloc(1, (sizeof *prefix));
+    if (prefix != NULL) {
+        memcpy(&prefix->prefix, address, 16);
+        prefix->prefix_len = prefix_length;
+        RETAIN(prefix);
+    }
+    return prefix;
+}
+
+static void
+thread_service_finalize(thread_service_t *service)
+{
+    free(service);
+}
+
+#define thread_service_create(address, port) thread_service_create_(address, port, __FILE__, __LINE__)
+static thread_service_t *
+thread_service_create_(uint8_t *address, uint8_t *port, const char *file, int line)
+{
+    thread_service_t *service;
+
+    service = calloc(1, sizeof(*service));
+    if (service != NULL) {
+        memcpy(&service->address, address, 16);
+        memcpy(&service->port, port, 2);
+        RETAIN(service);
+    }
+    return service;
+}
+
+static void
+thread_pref_id_finalize(thread_pref_id_t *pref_id)
+{
+    free(pref_id);
+}
+
+#define thread_pref_id_create(partition_id, prefix) thread_pref_id_create_(partition_id, prefix, __FILE__, __LINE__)
+static thread_pref_id_t *
+thread_pref_id_create_(uint8_t *partition_id, uint8_t *prefix, const char *file, int line)
+{
+    thread_pref_id_t *pref_id;
+
+    pref_id = calloc(1, sizeof(*pref_id));
+    if (pref_id != NULL) {
+        memcpy(&pref_id->partition_id, partition_id, 4);
+        memcpy(&pref_id->prefix, prefix, 5);
+        RETAIN(pref_id);
+    }
+    return pref_id;
+}
+#endif // RA_TESTER
+
+static void
+icmp_message_free(icmp_message_t *message)
+{
+    if (message->options != NULL) {
+        free(message->options);
+    }
+    free(message);
+}
+
+static void
+icmp_message_dump(icmp_message_t *message,
+                  const struct in6_addr * const source_address, const struct in6_addr * const destination_address)
+{
+    link_layer_address_t *lladdr;
+    prefix_information_t *prefix_info;
+    route_information_t *route_info;
+    int i;
+    char retransmission_timer_buf[11]; // Maximum size of a uint32_t printed as decimal.
+    char *retransmission_timer = "infinite";
+
+    if (message->retransmission_timer != ND6_INFINITE_LIFETIME) {
+        snprintf(retransmission_timer_buf, sizeof(retransmission_timer_buf), "%" PRIu32, message->retransmission_timer);
+        retransmission_timer = retransmission_timer_buf;
+    }
+
+    SEGMENTED_IPv6_ADDR_GEN_SRP(source_address->s6_addr, src_addr_buf);
+    SEGMENTED_IPv6_ADDR_GEN_SRP(destination_address->s6_addr, dst_addr_buf);
+    if (message->type == icmp_type_router_advertisement) {
+        INFO("router advertisement from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP
+             " hop_limit %d on " PUB_S_SRP ": checksum = %x "
+             "cur_hop_limit = %d flags = %x router_lifetime = %d reachable_time = %" PRIu32
+             " retransmission_timer = " PUB_S_SRP,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(source_address->s6_addr, src_addr_buf),
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(destination_address->s6_addr, dst_addr_buf),
+             message->hop_limit, message->interface->name, message->checksum, message->cur_hop_limit, message->flags,
+             message->router_lifetime, message->reachable_time, retransmission_timer);
+    } else if (message->type == icmp_type_router_solicitation) {
+        INFO("router solicitation from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP
+             " hop_limit %d on " PUB_S_SRP ": code = %d checksum = %x",
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(source_address->s6_addr, src_addr_buf),
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(destination_address->s6_addr, dst_addr_buf),
+             message->hop_limit, message->interface->name,
+             message->code, message->checksum);
+    } else {
+        INFO("icmp message from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP " hop_limit %d on "
+             PUB_S_SRP ": type = %d code = %d checksum = %x",
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(source_address->s6_addr, src_addr_buf),
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(destination_address->s6_addr, dst_addr_buf),
+             message->hop_limit, message->interface->name, message->type,
+             message->code, message->checksum);
+    }
+
+    for (i = 0; i < message->num_options; i++) {
+        icmp_option_t *option = &message->options[i];
+        switch(option->type) {
+        case icmp_option_source_link_layer_address:
+            lladdr = &option->option.link_layer_address;
+            INFO("  source link layer address " PRI_MAC_ADDR_SRP, MAC_ADDR_PARAM_SRP(lladdr->address));
+            break;
+        case icmp_option_target_link_layer_address:
+            lladdr = &option->option.link_layer_address;
+            INFO("  destination link layer address " PRI_MAC_ADDR_SRP, MAC_ADDR_PARAM_SRP(lladdr->address));
+            break;
+        case icmp_option_prefix_information:
+            prefix_info = &option->option.prefix_information;
+            SEGMENTED_IPv6_ADDR_GEN_SRP(prefix_info->prefix.s6_addr, prefix_buf);
+            INFO("  prefix info: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d %x %" PRIu32 " %" PRIu32,
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix_info->prefix.s6_addr, prefix_buf), prefix_info->length,
+                 prefix_info->flags, prefix_info->valid_lifetime, prefix_info->preferred_lifetime);
+            break;
+        case icmp_option_route_information:
+            route_info = &option->option.route_information;
+                SEGMENTED_IPv6_ADDR_GEN_SRP(route_info->prefix.s6_addr, router_prefix_buf);
+            INFO("  route info: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d %x %d",
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(route_info->prefix.s6_addr, router_prefix_buf), route_info->length,
+                 route_info->flags, route_info->route_lifetime);
+            break;
+        default:
+            INFO("  option type %d", option->type);
+            break;
+        }
+    }
+}
+
+static bool
+icmp_message_parse_options(icmp_message_t *message, uint8_t *icmp_buf, unsigned length, unsigned *offset)
+{
+    uint8_t option_type, option_length_8;
+    unsigned option_length;
+    unsigned scan_offset = *offset;
+    icmp_option_t *option;
+    uint32_t reserved32;
+    prefix_information_t *prefix_information;
+    route_information_t *route_information;
+    int prefix_bytes;
+
+    // Count the options and validate the lengths
+    while (scan_offset < length) {
+        if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_type)) {
+            return false;
+        }
+        if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_length_8)) {
+            return false;
+        }
+        if (scan_offset + option_length_8 * 8 - 2 > length) {
+            ERROR("icmp_option_parse: option type %d length %d is longer than remaining available space %u",
+                  option_type, option_length_8 * 8, length - scan_offset + 2);
+            return false;
+        }
+        scan_offset += option_length_8 * 8 - 2;
+        message->num_options++;
+    }
+    message->options = calloc(message->num_options, sizeof(*message->options));
+    if (message->options == NULL) {
+        ERROR("No memory for icmp options.");
+        return false;
+    }
+    option = message->options;
+    while (*offset < length) {
+        scan_offset = *offset;
+        if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_type)) {
+            return false;
+        }
+        if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_length_8)) {
+            return false;
+        }
+        // We already validated the length in the previous pass.
+        option->type = option_type;
+        option_length = option_length_8 * 8;
+
+        switch(option_type) {
+        case icmp_option_source_link_layer_address:
+        case icmp_option_target_link_layer_address:
+            // At this juncture we are assuming that everything we care about looks like an
+            // ethernet interface.  So for this case, length should be 8.
+            if (option_length != 8) {
+                INFO("Ignoring unexpectedly long link layer address: %d", option_length);
+                // Don't store the option.
+                message->num_options--;
+                *offset += option_length;
+                continue;
+            }
+            option->option.link_layer_address.length = 6;
+            memcpy(option->option.link_layer_address.address, &icmp_buf[scan_offset], 6);
+            break;
+        case icmp_option_prefix_information:
+            prefix_information = &option->option.prefix_information;
+            // Only a length of 32 is valid.  This is an invalid ICMP packet, not just misunderunderstood
+            if (option_length != 32) {
+                return false;
+            }
+            // prefix length 8
+            if (!dns_u8_parse(icmp_buf, length, &scan_offset, &prefix_information->length)) {
+                return false;
+            }
+            // flags 8a
+            if (!dns_u8_parse(icmp_buf, length, &scan_offset, &prefix_information->flags)) {
+                return false;
+            }
+            // valid lifetime 32
+            if (!dns_u32_parse(icmp_buf, length, &scan_offset,
+                               &prefix_information->valid_lifetime)) {
+                return false;
+            }
+            // preferred lifetime 32
+            if (!dns_u32_parse(icmp_buf, length, &scan_offset,
+                               &prefix_information->preferred_lifetime)) {
+                return false;
+            }
+            // reserved2 32
+            if (!dns_u32_parse(icmp_buf, length, &scan_offset, &reserved32)) {
+                return false;
+            }
+            // prefix 128
+            memcpy(&prefix_information->prefix, &icmp_buf[scan_offset], 16);
+            break;
+        case icmp_option_route_information:
+            route_information = &option->option.route_information;
+
+            // route length 8
+            if (!dns_u8_parse(icmp_buf, length, &scan_offset, &route_information->length)) {
+                return false;
+            }
+            switch(option_length) {
+            case 8:
+                prefix_bytes = 0;
+                break;
+            case 16:
+                prefix_bytes = 8;
+                break;
+            case 24:
+                prefix_bytes = 16;
+                break;
+            default:
+                ERROR("invalid route information option length %d for route length %d",
+                      option_length, route_information->length);
+                return false;
+            }
+            // flags 8
+            if (!dns_u8_parse(icmp_buf, length, &scan_offset, &route_information->flags)) {
+                return false;
+            }
+            // route lifetime 32
+            if (!dns_u32_parse(icmp_buf, length, &scan_offset, &route_information->route_lifetime)) {
+                return false;
+            }
+            // route (64, 96 or 128)
+            if (prefix_bytes > 0) {
+                memcpy(&route_information->prefix, &icmp_buf[scan_offset], prefix_bytes);
+            }
+            memset(&((uint8_t *)&route_information->prefix)[prefix_bytes], 0, 16 - prefix_bytes);
+            break;
+        default:
+        case icmp_option_mtu:
+        case icmp_option_redirected_header:
+            // don't care
+            break;
+        }
+        *offset += option_length;
+        option++;
+    }
+    return true;
+}
+
+static void
+set_router_mode(interface_t *interface, int mode)
+{
+    struct in6_ifreq router_interface;
+    int sock, ret;
+
+    sock = socket(PF_INET6, SOCK_DGRAM, 0);
+    if (sock < 0) {
+        ERROR("socket(PF_INET6, SOCK_DGRAM, 0) failed " PUB_S_SRP ": " PUB_S_SRP, interface->name, strerror(errno));
+        return;
+    }
+
+    memset(&router_interface, 0, sizeof (router_interface));
+    strlcpy(router_interface.ifr_name, interface->name, sizeof(interface->name));
+    router_interface.ifr_ifru.ifru_intval = mode;
+    // Fix this
+#ifndef SIOCSETROUTERMODE_IN6
+#define SIOCSETROUTERMODE_IN6   _IOWR('i', 136, struct in6_ifreq)
+#endif /* SIOCSETROUTERMODE_IN6 */
+    ret = ioctl(sock, SIOCSETROUTERMODE_IN6, &router_interface);
+    if (ret < 0) {
+        ERROR("Unable to enable router mode on " PUB_S_SRP ": " PUB_S_SRP, interface->name, strerror(errno));
+    } else {
+        INFO("enabled router mode for " PUB_S_SRP ": " PUB_S_SRP ".", interface->name,
+             (mode == IPV6_ROUTER_MODE_DISABLED
+                ? "disabled"
+                : (mode == IPV6_ROUTER_MODE_EXCLUSIVE ? "exclusive" : "hybrid")));
+    }
+    close(sock);
+}
+
+
+static void
+interface_wakeup_finalize(void *context)
+{
+    interface_t *interface = context;
+    interface->beacon_wakeup = NULL;
+}
+
+static void
+interface_deconfigure_finalize(void *context)
+{
+    interface_t *interface = context;
+    interface->deconfigure_wakeup = NULL;
+}
+
+static void
+interface_prefix_deconfigure(void *context)
+{
+    interface_t *interface = context;
+    INFO("interface_prefix_deconfigure - ifname: " PUB_S_SRP ", prefix: "
+         ", preferred time: %" PRIu32 ", valid time: %" PRIu32, interface->name, interface->preferred_lifetime,
+         interface->valid_lifetime);
+
+    // If our on-link prefix is still deprecated (preferred_lifetime == 0 means that the prefix is in deprecated state),
+    // deconfigure it from the interface.
+    if (interface->preferred_lifetime == 0 && interface->ip_configuration_service != NULL) {
+        CFRelease(interface->ip_configuration_service);
+        interface->ip_configuration_service = NULL;
+        interface->valid_lifetime = 0;
+        interface->on_link_prefix_configured = false;
+        interface->advertise_ipv6_prefix = false;
+        SEGMENTED_IPv6_ADDR_GEN_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf);
+        INFO("interface_prefix_deconfigure: deconfigure the prefix immediately - prefix: " PRI_SEGMENTED_IPv6_ADDR_SRP,
+            SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf));
+    }
+    interface->deprecate_deadline = 0;
+}
+
+static void
+interface_beacon(void *context)
+{
+    interface_t *interface = context;
+    uint64_t now = ioloop_timenow();
+
+    INFO("interface_beacon:" PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP,
+         interface->deprecate_deadline > now ? " ddl>now" : "",
+#ifdef RA_TESTER
+         "",
+#else
+         partition_can_provide_routing ? " canpr" : " !canpr",
+#endif
+         interface->advertise_ipv6_prefix ? " pio" : " !pio",
+         interface->sent_first_beacon ? "" : " first beacon");
+
+    if (interface->deprecate_deadline > now) {
+        // The remaining valid lifetime is the time left until the deadline.
+        interface->valid_lifetime = (uint32_t)((interface->deprecate_deadline - now) / 1000);
+        if (interface->valid_lifetime < icmp_listener.unsolicited_interval) {
+            SEGMENTED_IPv6_ADDR_GEN_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf);
+            INFO("interface_beacon: prefix valid life time is less than the unsolicited interval, stop advertising it "
+                 "and prepare to deconfigure the prefix - ifname: " PUB_S_SRP "prefix: " PRI_SEGMENTED_IPv6_ADDR_SRP
+                 ", preferred time: %" PRIu32 ", valid time: %" PRIu32 ", unsolicited interval: %" PRIu32,
+                 interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf),
+                 interface->preferred_lifetime, interface->valid_lifetime, icmp_listener.unsolicited_interval);
+            interface->advertise_ipv6_prefix = false;
+            ioloop_add_wake_event(interface->deconfigure_wakeup,
+                                  interface, interface_prefix_deconfigure,
+                                  interface_deconfigure_finalize, interface->valid_lifetime * 1000);
+        }
+    }
+
+#ifndef RA_TESTER
+    // If we have been beaconing, and router mode has been disabled, and we don't have
+    // an on-link prefix to advertise, discontinue beaconing.
+    if (partition_can_provide_routing || interface->advertise_ipv6_prefix) {
+#endif
+
+    // Send an RA.
+        router_advertisement_send(interface);
+        interface->sent_first_beacon = true;
+        interface->last_beacon = ioloop_timenow();;
+#ifndef RA_TESTER
+    }
+#endif
+    if (interface->num_beacons_sent < 3) {
+        // Schedule a beacon for between 8 and 16 seconds in the future (<MAX_INITIAL_RTR_ADVERT_INTERVAL)
+        interface_beacon_schedule(interface, 8000 + srp_random16() % 8000);
+    } else {
+        interface_beacon_schedule(interface, icmp_listener.unsolicited_interval);
+    }
+    interface->num_beacons_sent++;
+}
+
+static void
+interface_beacon_schedule(interface_t *interface, unsigned when)
+{
+    uint64_t now = ioloop_timenow();
+    unsigned interval;
+
+    // If we haven't sent our first beacon, now's a good time to configure router mode on the interface.
+    if (!interface->sent_first_beacon) {
+        int    mode;
+
+#ifdef RA_TESTER
+        mode = (strcmp(interface->name, thread_interface_name) == 0) ? IPV6_ROUTER_MODE_EXCLUSIVE
+                                                                        : IPV6_ROUTER_MODE_HYBRID;
+#else
+        mode = IPV6_ROUTER_MODE_HYBRID;
+#endif
+        set_router_mode(interface, mode);
+    }
+
+    // Make sure we haven't send an RA too recently.
+    if (when < MIN_DELAY_BETWEEN_RAS && now - interface->last_beacon < MIN_DELAY_BETWEEN_RAS) {
+        when = MIN_DELAY_BETWEEN_RAS;
+    }
+    // Add up to a second of jitter.
+    when += srp_random16() % 1024;
+    interface->next_beacon = now + when;
+    if (interface->beacon_wakeup == NULL) {
+        interface->beacon_wakeup = ioloop_wakeup_create();
+        if (interface->beacon_wakeup == NULL) {
+            ERROR("Unable to allocate beacon wakeup for " PUB_S_SRP, interface->name);
+            return;
+        }
+    } else {
+        // We can reschedule a beacon for sooner if we get a router solicit; in this case, we
+        // need to cancel the existing beacon wakeup, and if there is none scheduled, this will
+        // be a no-op.
+        ioloop_cancel_wake_event(interface->beacon_wakeup);
+    }
+    if (interface->next_beacon - now > UINT_MAX) {
+        interval = UINT_MAX;
+    } else {
+        interval = (unsigned)(interface->next_beacon - now);
+    }
+    INFO("Scheduling " PUB_S_SRP "beacon on " PUB_S_SRP " for %u milliseconds in the future",
+         interface->sent_first_beacon ? "first " : "", interface->name, interval);
+    ioloop_add_wake_event(interface->beacon_wakeup, interface, interface_beacon, interface_wakeup_finalize, interval);
+}
+
+static void
+router_discovery_start(interface_t *interface)
+{
+    INFO("Starting router discovery on " PUB_S_SRP, interface->name);
+
+    // Immediately when an interface shows up, start doing router solicits.
+    start_router_solicit(interface);
+
+    if (interface->post_solicit_wakeup == NULL) {
+        interface->post_solicit_wakeup = ioloop_wakeup_create();
+        if (interface->post_solicit_wakeup == NULL) {
+            ERROR("No memory for post-solicit RA wakeup on " PUB_S_SRP ".", interface->name);
+        }
+    } else {
+        ioloop_cancel_wake_event(interface->post_solicit_wakeup);
+    }
+
+    // In 20 seconds, check the results of router discovery and update policy as needed.
+    if (interface->post_solicit_wakeup) {
+        ioloop_add_wake_event(interface->post_solicit_wakeup, interface, post_solicit_policy_evaluate,
+                              NULL, 20 * 1000);
+    }
+    interface->router_discovery_in_progress = true;
+}
+
+static void
+flush_stale_routers(interface_t *interface, uint64_t now)
+{
+    icmp_message_t *router, **p_router;
+
+    // Flush stale routers.
+    for (p_router = &interface->routers; *p_router != NULL; ) {
+        router = *p_router;
+        if (now - router->received_time > MAX_ROUTER_RECEIVED_TIME_GAP_BEFORE_STALE)  {
+            *p_router = router->next;
+            SEGMENTED_IPv6_ADDR_GEN_SRP(router->source.s6_addr, __router_src_addr_buf);
+            INFO("flush_stale_routers: flushing stale router - ifname: " PUB_S_SRP
+                 ", router src: " PRI_SEGMENTED_IPv6_ADDR_SRP, interface->name,
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, __router_src_addr_buf));
+            icmp_message_free(router);
+        } else {
+            p_router = &(*p_router)->next;
+        }
+    }
+}
+
+static void
+router_discovery_stop(interface_t *interface, uint64_t now)
+{
+    if (!interface->router_discovery_complete) {
+        INFO("router_discovery_stop: stopping router discovery on " PUB_S_SRP, interface->name);
+    }
+    if (interface->router_solicit_wakeup != NULL) {
+        ioloop_cancel_wake_event(interface->router_solicit_wakeup);
+    }
+    if (interface->post_solicit_wakeup != NULL) {
+        ioloop_cancel_wake_event(interface->post_solicit_wakeup);
+    }
+    if (interface->vicarious_discovery_complete != NULL) {
+        ioloop_cancel_wake_event(interface->vicarious_discovery_complete);
+        INFO("router_discovery_stop: stopping vicarious router discovery on " PUB_S_SRP, interface->name);
+    }
+    interface->router_discovery_complete = true;
+    interface->router_discovery_in_progress = false;
+    interface->vicarious_router_discovery_in_progress = false;
+    flush_stale_routers(interface, now);
+
+    // See if we need a new prefix on the interface.
+    interface_prefix_evaluate(interface);
+}
+
+static void
+adjust_router_received_time(interface_t *const interface, const uint64_t now, const int64_t time_adjusted)
+{
+    icmp_message_t *router;
+
+    for (router = interface->routers; router != NULL; router = router->next) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(router->source.s6_addr, __router_src_addr_buf);
+        // Only adjust the received time once.
+        if (router->received_time_already_adjusted) {
+            DEBUG("adjust_router_received_time: received time already adjusted - remaining time: %llu, "
+                  "router src: " PRI_SEGMENTED_IPv6_ADDR_SRP, (now - router->received_time) / MSEC_PER_SEC,
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, __router_src_addr_buf));
+            continue;
+        }
+        require_action_quiet(
+            (time_adjusted > 0 && (UINT64_MAX - now) > (uint64_t)time_adjusted) ||
+            (time_adjusted < 0 && now > ((uint64_t)-time_adjusted)), exit,
+            ERROR("adjust_router_received_time: invalid adjusted values is causing overflow - "
+                  "now: %" PRIu64 ", time_adjusted: %" PRId64, now, time_adjusted));
+        router->received_time = now + time_adjusted;
+        router->received_time_already_adjusted = true; // Only adjust the icmp message received time once.
+        DEBUG("adjust_router_received_time: router received time is adjusted - router src: " PRI_SEGMENTED_IPv6_ADDR_SRP
+              ", adjusted value: %" PRId64,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, __router_src_addr_buf), time_adjusted);
+    }
+
+exit:
+    return;
+}
+
+static void
+make_all_routers_nearly_stale(interface_t *interface, uint64_t now)
+{
+    // Make every router go stale in 19.999 seconds.   This means that if we don't get a response
+    // to our solicit in 20 seconds, then when the timeout callback is called, there will be no
+    // routers on the interface that aren't stale, which will trigger router discovery.
+    adjust_router_received_time(interface, now, 19999 - 600 * MSEC_PER_SEC);
+}
+
+static void
+vicarious_discovery_callback(void *context)
+{
+    interface_t *interface = context;
+    INFO("Vicarious router discovery finished on " PUB_S_SRP ".", interface->name);
+    interface->vicarious_router_discovery_in_progress = false;
+    // At this point, policy evaluate will show all the routes that were present before vicarious
+    // discovery as stale, so policy_evaluate will start router discovery if we didn't get any
+    // RAs containing on-link prefixes.
+    routing_policy_evaluate(interface, false);
+}
+
+#ifndef RA_TESTER
+static void
+routing_policy_evaluate_all_interfaces(bool assume_changed)
+{
+    interface_t *interface;
+
+    for (interface = interfaces; interface; interface = interface->next) {
+        routing_policy_evaluate(interface, assume_changed);
+    }
+}
+#endif
+
+static void
+routing_policy_evaluate(interface_t *interface, bool assume_changed)
+{
+    icmp_message_t *router;
+    bool new_prefix = false;    // new prefix means that srp-mdns-proxy received a new prefix from the wire, which it
+                                // did not know before.
+    bool on_link_prefix_present = false;
+    bool something_changed = assume_changed;
+    uint64_t now = ioloop_timenow();
+    bool stale_routers_exist = false;
+
+    // No action on interfaces that aren't eligible for routing or that isn't currently active.
+    if (interface->ineligible || interface->inactive) {
+        INFO("not evaluating policy on " PUB_S_SRP " because it's " PUB_S_SRP, interface->name,
+             interface->ineligible ? "ineligible" : "inactive");
+        return;
+    }
+
+    // See if we have a prefix from some other router
+    for (router = interface->routers; router; router = router->next) {
+        icmp_option_t *option = router->options;
+        int i;
+        if (now - router->received_time > MAX_ROUTER_RECEIVED_TIME_GAP_BEFORE_STALE)  {
+            stale_routers_exist = true;
+            SEGMENTED_IPv6_ADDR_GEN_SRP(router->source.s6_addr, router_src_addr_buf);
+            INFO("Router " PRI_SEGMENTED_IPv6_ADDR_SRP " is stale by %" PRIu64 " milliseconds",
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, router_src_addr_buf),
+                 now - router->received_time);
+        } else {
+            for (i = 0; i < router->num_options; i++) {
+                if (option->type == icmp_option_prefix_information) {
+                    prefix_information_t *prefix = &option->option.prefix_information;
+                    if ((prefix->flags & ND_OPT_PI_FLAG_ONLINK) &&
+                        ((prefix->flags & ND_OPT_PI_FLAG_AUTO) || (router->flags & ND_RA_FLAG_MANAGED)) &&
+                        prefix->preferred_lifetime > 0)
+                    {
+                        // If this is a new icmp_message received now and contains PIO.
+                        if (router->new_router) {
+                            new_prefix = true;
+                            router->new_router = false; // clear the bit since srp-mdns-proxy already processed it.
+                        }
+
+                        // Right now all we need is to see if there is an on-link prefix.
+                        on_link_prefix_present = true;
+                        SEGMENTED_IPv6_ADDR_GEN_SRP(router->source.s6_addr, __router_src_add_buf);
+                        SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, __pio_prefix_buf);
+                        DEBUG("routing_policy_evaluate: router has PIO - ifname: " PUB_S_SRP ", router src: " PRI_SEGMENTED_IPv6_ADDR_SRP
+                             ", prefix: " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                             interface->name,
+                             SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, __router_src_add_buf),
+                             SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, __pio_prefix_buf));
+                    }
+                }
+                option++;
+            }
+        }
+    }
+
+    INFO("policy on " PUB_S_SRP ": " PUB_S_SRP "stale " /* stale_routers_exist ? */
+         PUB_S_SRP "disco " /* interface->router_discovery_complete ? */
+         PUB_S_SRP "present " /* on_link_prefix_present ? */
+         PUB_S_SRP "advert " /* interface->advertise_ipv6_prefix ? */
+         PUB_S_SRP "conf " /* interface->on_link_prefix_configured ? */
+         PUB_S_SRP "new_prefix " /* new_prefix ? */
+         "preferred = %" PRIu32 " valid = %" PRIu32 " deadline = %llu",
+         interface->name, stale_routers_exist ? "" : "!", interface->router_discovery_complete ? "" : "!",
+         on_link_prefix_present ? "" : "!", interface->advertise_ipv6_prefix ? "" : "!",
+         interface->on_link_prefix_configured ? "" : "!", new_prefix ? "" : "!",
+         interface->preferred_lifetime, interface->valid_lifetime, interface->deprecate_deadline);
+
+    // If there are stale routers, start doing router discovery again to see if we can get them to respond.
+    // Also, if we have not yet done router discovery, do it now.
+    if ((!interface->router_discovery_complete || stale_routers_exist) && !on_link_prefix_present) {
+        if (!interface->router_discovery_in_progress) {
+            // Start router discovery.
+            router_discovery_start(interface);
+        } else {
+            INFO("routing_policy_evaluate: router discovery in progress");
+        }
+    }
+    // If we are advertising a prefix and there's another on-link prefix, deprecate the one we are
+    // advertising.
+    else if (interface->advertise_ipv6_prefix && on_link_prefix_present) {
+        // If we have been advertising a preferred prefix, deprecate it.
+        SEGMENTED_IPv6_ADDR_GEN_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf);
+        if (interface->preferred_lifetime != 0) {
+            INFO("routing_policy_evaluate: deprecating interface prefix in 30 minutes - prefix: " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf));
+            interface->preferred_lifetime = 0;
+            interface->deprecate_deadline = now + 1800 * 1000;
+            something_changed = true;
+        } else {
+            INFO("routing_policy_evaluate: prefix deprecating in progress - prefix: " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf));
+        }
+    }
+    // If there is no on-link prefix and we aren't advertising, or have deprecated, start advertising
+    // again (or for the first time).
+    else if (!on_link_prefix_present && interface->router_discovery_complete &&
+             interface->link != NULL && interface->link->primary == interface &&
+             (!interface->advertise_ipv6_prefix || interface->deprecate_deadline != 0 ||
+              interface->preferred_lifetime == 0)) {
+
+        SEGMENTED_IPv6_ADDR_GEN_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf);
+        INFO("routing_policy_evaluate: advertising prefix again - ifname: " PUB_S_SRP ", prefix: " PRI_SEGMENTED_IPv6_ADDR_SRP, interface->name,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf));
+
+        // If we were deprecating, stop.
+        ioloop_cancel_wake_event(interface->deconfigure_wakeup);
+        interface->deprecate_deadline = 0;
+
+        // Start advertising immediately, 30 minutes.
+        interface->preferred_lifetime = interface->valid_lifetime = 1800;
+
+        // If the on-link prefix isn't configured on the interface, do that.
+        if (!interface->on_link_prefix_configured) {
+#ifndef RA_TESTER
+            if (!interface->is_thread) {
+#endif
+                interface_prefix_configure(interface->ipv6_prefix, interface);
+#ifndef RA_TESTER
+            } else {
+                INFO("Not setting up " PUB_S_SRP " because it is the thread interface", interface->name);
+            }
+#endif
+        } else {
+            // Configuring the on-link prefix takes a while, so we want to re-evaluate after it's finished.
+            interface->advertise_ipv6_prefix = true;
+            something_changed = true;
+        }
+    }
+
+    // If we've been looking to see if there's an on-link prefix, and we got one from the new router advertisement,
+    // stop looking for new one.
+    if (new_prefix) {
+        router_discovery_stop(interface, now);
+    }
+
+    // If anything changed, do an immediate beacon; otherwise wait until the next one.
+    // Also when something changed, set the number of transmissions back to zero so that
+    // we send a few initial beacons quickly for reliability.
+    if (something_changed) {
+        INFO("change on " PUB_S_SRP ": " PUB_S_SRP "disco " PUB_S_SRP "present " PUB_S_SRP "advert " PUB_S_SRP
+             "conf preferred = %" PRIu32 " valid = %" PRIu32 " deadline = %llu",
+             interface->name, interface->router_discovery_complete ? "" : "!", on_link_prefix_present ? "" : "!",
+             interface->advertise_ipv6_prefix ? "" : "!", interface->on_link_prefix_configured ? "" : "!",
+             interface->preferred_lifetime,
+             interface->valid_lifetime, interface->deprecate_deadline);
+        interface->num_beacons_sent = 0;
+        interface_beacon_schedule(interface, 0);
+    }
+}
+
+static void
+start_vicarious_router_discovery_if_appropriate(interface_t *const interface)
+{
+    if (!interface->advertise_ipv6_prefix &&
+        !interface->vicarious_router_discovery_in_progress && !interface->router_discovery_in_progress)
+    {
+        if (interface->vicarious_discovery_complete == NULL) {
+            interface->vicarious_discovery_complete = ioloop_wakeup_create();
+        } else {
+            ioloop_cancel_wake_event(interface->vicarious_discovery_complete);
+        }
+        if (interface->vicarious_discovery_complete != NULL) {
+            ioloop_add_wake_event(interface->vicarious_discovery_complete,
+                                  interface, vicarious_discovery_callback, NULL, 20 * 1000);
+            interface->vicarious_router_discovery_in_progress = true;
+        }
+        // In order for vicarious router discovery to be useful, we need all of the routers
+        // that were present when the first solicit was received to be stale when we give up
+        // on vicarious discovery.  If we got any router advertisements, these will not be
+        // stale, and that means vicarious discovery succeeded.
+        make_all_routers_nearly_stale(interface, ioloop_timenow());
+        INFO("start_vicarious_router_discovery_if_appropriate: Starting vicarious router discovery on " PUB_S_SRP,
+             interface->name);
+    }
+}
+
+static void
+router_solicit(icmp_message_t *message)
+{
+    interface_t *iface, *interface;
+
+    // Further validate the message
+    if (message->hop_limit != 255 || message->code != 0) {
+        ERROR("Invalid router solicitation, hop limit = %d, code = %d", message->hop_limit, message->code);
+    }
+    if (IN6_IS_ADDR_UNSPECIFIED(&message->source)) {
+        icmp_option_t *option = message->options;
+        int i;
+        for (i = 0; i < message->num_options; i++) {
+            if (option->type == icmp_option_source_link_layer_address) {
+                ERROR("source link layer address in router solicitation from unspecified IP address");
+                return;
+            }
+            option++;
+        }
+    } else {
+        // Make sure it's not from this host
+        for (iface = interfaces; iface; iface = iface->next) {
+            if (iface->have_link_layer_address && !memcmp(&message->source,
+                                                          &iface->link_local, sizeof(message->source))) {
+                INFO("dropping router solicitation sent from this host.");
+                return;
+            }
+        }
+    }
+    interface = message->interface;
+
+    // Schedule an immediate send, which will be delayed by up to a second.
+    if (!interface->ineligible && !interface->inactive) {
+        interface_beacon_schedule(interface, 0);
+    }
+
+    // When we receive a router solicit, it means that a host is looking for a router.   We should
+    // expect to hear replies if they are multicast.   If we hear no replies, it could mean there is
+    // no on-link prefix.   In this case, we restart our own router discovery process.  There is no
+    // need to do this if we are the one advertising a prefix.
+    start_vicarious_router_discovery_if_appropriate(interface);
+}
+
+static void
+router_advertisement(icmp_message_t *message)
+{
+    interface_t *iface;
+    icmp_message_t *router, **rp;
+    if (message->hop_limit != 255 || message->code != 0 || !IN6_IS_ADDR_LINKLOCAL(&message->source)) {
+        ERROR("Invalid router advertisement, hop limit = %d, code = %d", message->hop_limit, message->code);
+        icmp_message_free(message);
+        return;
+    }
+    for (iface = interfaces; iface != NULL; iface = iface->next) {
+        if (iface->have_link_layer_address && !memcmp(&message->source,
+                                                      &iface->link_local, sizeof(message->source))) {
+            INFO("dropping router advertisement sent from this host.");
+            icmp_message_free(message);
+            return;
+        }
+    }
+
+    // See if we've had other advertisements from this router.
+    for (rp = &message->interface->routers; *rp != NULL; rp = &(*rp)->next) {
+        router = *rp;
+        if (!memcmp(&router->source, &message->source, sizeof(message->source))) {
+            message->next = router->next;
+            *rp = message;
+            icmp_message_free(router);
+            break;
+        }
+    }
+    // If not, save it.
+    if (*rp == NULL) {
+        *rp = message;
+    }
+
+    // Something may have changed, so do a policy recalculation for this interface
+    routing_policy_evaluate(message->interface, false);
+}
+
+static void
+icmp_callback(io_t *NONNULL io, void *__unused context)
+{
+    ssize_t rv;
+    uint8_t icmp_buf[1500];
+    unsigned offset = 0, length = 0;
+    uint32_t reserved32;
+    int ifindex;
+    addr_t src, dest;
+    interface_t *interface;
+    int hop_limit;
+
+    rv = ioloop_recvmsg(io->fd, &icmp_buf[0], sizeof(icmp_buf), &ifindex, &hop_limit, &src, &dest);
+    if (rv < 0) {
+        ERROR("icmp_callback: can't read ICMP message: " PUB_S_SRP, strerror(errno));
+        return;
+    }
+
+    icmp_message_t *message = calloc(1, sizeof(*message));
+    if (message == NULL) {
+        ERROR("Unable to allocate icmp_message_t for parsing");
+        return;
+    }
+
+    message->source = src.sin6.sin6_addr;
+    message->destination = dest.sin6.sin6_addr;
+    message->hop_limit = hop_limit;
+    for (interface = interfaces; interface; interface = interface->next) {
+        if (interface->index == ifindex) {
+            message->interface = interface;
+            break;
+        }
+    }
+    message->received_time = ioloop_timenow();
+    message->received_time_already_adjusted = false;
+    message->new_router = true;
+
+    if (message->interface == NULL) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(message->source.s6_addr, src_buf);
+        SEGMENTED_IPv6_ADDR_GEN_SRP(message->destination.s6_addr, dst_buf);
+        INFO("ICMP message type %d from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP
+             " on interface index %d, which isn't listed.",
+             icmp_buf[0], SEGMENTED_IPv6_ADDR_PARAM_SRP(message->source.s6_addr, src_buf),
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(message->destination.s6_addr, dst_buf), ifindex);
+        icmp_message_free(message);
+        return;
+    }
+
+    length = (unsigned)rv;
+    if (length < sizeof (struct icmp6_hdr)) {
+        ERROR("Short ICMP message: length %zd is shorter than ICMP header length %zd", rv, sizeof(struct icmp6_hdr));
+        icmp_message_free(message);
+        return;
+    }
+
+    // The increasingly innaccurately named dns parse functions will work fine for this.
+    if (!dns_u8_parse(icmp_buf, length, &offset, &message->type)) {
+        goto out;
+    }
+    if (!dns_u8_parse(icmp_buf, length, &offset, &message->code)) {
+        goto out;
+    }
+    // XXX check the checksum
+    if (!dns_u16_parse(icmp_buf, length, &offset, &message->checksum)) {
+        goto out;
+    }
+    switch(message->type) {
+    case icmp_type_router_advertisement:
+        if (!dns_u8_parse(icmp_buf, length, &offset, &message->cur_hop_limit)) {
+            goto out;
+        }
+        if (!dns_u8_parse(icmp_buf, length, &offset, &message->flags)) {
+            goto out;
+        }
+        if (!dns_u16_parse(icmp_buf, length, &offset, &message->router_lifetime)) {
+            goto out;
+        }
+        if (!dns_u32_parse(icmp_buf, length, &offset, &message->reachable_time)) {
+            goto out;
+        }
+        if (!dns_u32_parse(icmp_buf, length, &offset, &message->retransmission_timer)) {
+            goto out;
+        }
+
+        if (!icmp_message_parse_options(message, icmp_buf, length, &offset)) {
+            goto out;
+        }
+        icmp_message_dump(message, &message->source, &message->destination);
+        router_advertisement(message);
+        // router_advertisement() is ressponsible for freeing the messaage if it doesn't need it.
+        return;
+        break;
+
+    case icmp_type_router_solicitation:
+        if (!dns_u32_parse(icmp_buf, length, &offset, &reserved32)) {
+            goto out;
+        }
+        if (!icmp_message_parse_options(message, icmp_buf, length, &offset)) {
+            goto out;
+        }
+        icmp_message_dump(message, &message->source, &message->destination);
+        router_solicit(message);
+        break;
+    case icmp_type_neighbor_advertisement:
+    case icmp_type_neighbor_solicitation:
+    case icmp_type_echo_request:
+    case icmp_type_echo_reply:
+    case icmp_type_redirect:
+        break;
+    }
+
+out:
+    icmp_message_free(message);
+    return;
+}
+
+#ifdef MONITOR_ROUTING_SOCKET
+static void
+route_message(io_t *__unused rt, route_message_t *message)
+{
+    addr_t *addr;
+
+    switch(message->route.rtm_type) {
+        // When an interface goes up, or when an address is added, we get one of these.
+    case RTM_NEWADDR:
+        INFO("Message length %d, version %d, type RTM_NEWADDR, index %d",
+             message->len, message->route.rtm_version, message->address.ifam_index);
+        // ifa_msghdr followed by zero or more addresses
+        // Addresses start on 32-bit boundaries and are sockaddrs with sa_len indicating the size.
+        addr = (addr_t *)((&message->address) + 1);
+        break;
+        // When an interface goes down, we may get one of these.  Also when an address is deleted for some reason.
+    case RTM_DELADDR:
+        INFO("Message length %d, version %d, type RTM_DELADDR, index %d",
+             message->len, message->route.rtm_version, message->address.ifam_index);
+        // ifa_msghdr followed by zero or more addresses
+        addr = (addr_t *)((&message->address) + 1);
+        break;
+        // When an interface goes up or down, we get one of these.
+    case RTM_IFINFO:
+        INFO("Message length %d, version %d, type RTM_IFINFO, index %d",
+             message->len, message->route.rtm_version, message->interface.ifm_index);
+        // if_msghdr followed by zero or more addresses
+        addr = (addr_t *)((&message->interface) + 1);
+        break;
+    case RTM_IFINFO2:
+        INFO("Message length %d, version %d, type RTM_IFINFO2, index %d",
+             message->len, message->route.rtm_version, message->if2.ifm_index);
+        addr = (addr_t *)((&message->if2) + 1);
+        break;
+    case RTM_ADD:
+        INFO("Message length %d, version %d, type RTM_ADD, index %d",
+             message->len, message->route.rtm_version, message->if2.ifm_index);
+        addr = (addr_t *)((&message->if2) + 1);
+        break;
+    case RTM_DELETE:
+        INFO("Message length %d, version %d, type RTM_DELETE, index %d",
+             message->len, message->route.rtm_version, message->if2.ifm_index);
+        addr = (addr_t *)((&message->if2) + 1);
+        break;
+    case RTM_CHANGE:
+        INFO("Message length %d, version %d, type RTM_CHANGE, index %d",
+             message->len, message->route.rtm_version, message->if2.ifm_index);
+        addr = (addr_t *)((&message->if2) + 1);
+        break;
+    default:
+        INFO("Message length %d, version %d, type %d", message->len, message->route.rtm_version,
+             message->route.rtm_type);
+        break;
+    }
+    return;
+}
+
+static void
+route_callback(io_t *NONNULL io, void *__unused context)
+{
+    ssize_t rv;
+    route_message_t message;
+
+    rv = read(io->fd, &message, sizeof message);
+    if (rv < 0) {
+        ERROR("route_callback: read returned " PUB_S_SRP, strerror(errno));
+        ioloop_close(io);
+        return;
+    } else if (rv == 0) {
+        ERROR("route_callback: read returned 0");
+        ioloop_close(io);
+        return;
+    } else {
+        // Process the message.
+        route_message(io, &message);
+        return;
+    }
+}
+
+static void
+route_entry(struct rt_msghdr2 *route)
+{
+    (void)route;
+}
+
+static void
+route_fetch(void)
+{
+    size_t table_size;
+#define NUM_SYSCTL_ARGS 6
+    int sysctl_args[NUM_SYSCTL_ARGS];
+    char *table, *next_route, *end;
+    struct rt_msghdr2 *route;
+    int rv;
+
+    sysctl_args[0] = CTL_NET;
+    sysctl_args[1] = PF_ROUTE;
+    sysctl_args[2] = 0;
+    sysctl_args[3] = 0;
+    sysctl_args[4] = NET_RT_DUMP2;
+    sysctl_args[5] = 0;
+
+    rv = sysctl(sysctl_args, NUM_SYSCTL_ARGS, NULL, &table_size, NULL, 0);
+    if (rv < 0) {
+        ERROR("route_fetch: sysctl failed getting routing table dump estimate: " PUB_S_SRP, strerror(errno));
+        return;
+    }
+
+    table = malloc(table_size);
+    if (table == NULL) {
+        ERROR("No memory for routing table of size %zu", table_size);
+        return;
+    }
+
+    rv = sysctl(sysctl_args, NUM_SYSCTL_ARGS, table, &table_size, NULL, 0);
+    if (rv < 0) {
+        ERROR("route_fetch: sysctl failed getting routing table dump: " PUB_S_SRP, strerror(errno));
+        return;
+    }
+
+    end = table + table_size;
+    for (next_route = table; next_route < end; next_route = next_route + route->rtm_msglen) {
+        route = (struct rt_msghdr2 *)next_route;
+        if (route->rtm_msglen + next_route > end) {
+            INFO("Bogus routing table--last route goes past end of buffer.");
+            break;
+        }
+        route_entry(route);
+    }
+}
+
+bool
+start_route_listener(void)
+{
+    int sock = socket(PF_ROUTE, SOCK_RAW, AF_INET);
+    if (sock < 0) {
+        ERROR("Unable to listen for link status change events: " PUB_S_SRP, strerror(errno));
+        return false;
+    }
+
+    io_t *io = ioloop_file_descriptor_create(sock, NULL, NULL);
+    if (io == NULL) {
+        ERROR("No memory for route I/O structure.");
+        close(sock);
+        return false;
+    }
+
+#ifdef RO_MSGFILTER
+    static uint8_t subscriptions[] = { RTM_NEWADDR, RTM_DELADDR, RTM_IFINFO, RTM_IFINFO2 };
+    if (setsockopt(routefd, PF_ROUTE, RO_MSGFILTER, &subscriptions, (socklen_t)sizeof(subscriptions)) < 0) {
+        ERROR("Unable to set routing socket subscriptions.");
+    }
+#endif
+
+    ioloop_add_reader(io, route_callback);
+
+    route_fetch();
+    return true;
+}
+#endif // MONITOR_ROUTING_SOCKET
+
+#if defined(USE_IPCONFIGURATION_SERVICE)
+static void
+dict_add_string_as_array(CFMutableDictionaryRef dict, CFStringRef prop_name, const char * str)
+{
+    CFArrayRef        array;
+    CFStringRef        prop_val;
+
+    if (str == NULL) {
+        return;
+    }
+    prop_val = CFStringCreateWithCString(NULL, str, kCFStringEncodingUTF8);
+    array = CFArrayCreate(NULL, (const void **)&prop_val, 1, &kCFTypeArrayCallBacks);
+    CFRelease(prop_val);
+    CFDictionarySetValue(dict, prop_name, array);
+    CFRelease(array);
+    return;
+}
+
+static void
+dict_add_int_as_array(CFMutableDictionaryRef dict, CFStringRef prop_name,
+              int int_val)
+{
+    CFArrayRef        array;
+    CFNumberRef        num;
+
+    num = CFNumberCreate(NULL, kCFNumberIntType, &int_val);
+    array = CFArrayCreate(NULL, (const void **)&num, 1, &kCFTypeArrayCallBacks);
+    CFRelease(num);
+    CFDictionarySetValue(dict, prop_name, array);
+    CFRelease(array);
+    return;
+}
+
+static CFDictionaryRef
+ipconfig_options_dict_create(CFDictionaryRef config_dict)
+{
+    return CFDictionaryCreate(NULL, (const void **)&kIPConfigurationServiceOptionIPv6Entity,
+                              (const void **)&config_dict, 1, &kCFTypeDictionaryKeyCallBacks,
+                              &kCFTypeDictionaryValueCallBacks);
+}
+
+static void
+ipconfig_service_changed(interface_t * interface)
+{
+    CFDictionaryRef     service_info;
+
+    if (interface->ip_configuration_service == NULL) {
+        INFO("ipconfig_service_changed: ip_configuration_service is NULL");
+        return;
+    }
+    service_info = IPConfigurationServiceCopyInformation(interface->ip_configuration_service);
+    if (service_info == NULL) {
+        INFO("ipconfig_service_changed: IPConfigurationService on " PUB_S_SRP " is incomplete", interface->name);
+    }
+    else {
+        INFO("ipconfig_service_changed: IPConfigurationService on " PUB_S_SRP " is ready", interface->name);
+        CFRelease(service_info);
+
+        // Now that the prefix is configured on the interface, we can start advertising it.
+        interface->on_link_prefix_configured = true;
+        routing_policy_evaluate(interface, true);
+    }
+    return;
+
+}
+
+
+static void
+ipconfig_service_callback(SCDynamicStoreRef __unused session, CFArrayRef __unused changes,
+                          void * info)
+{
+    interface_t *      interface = (interface_t *)info;
+
+    ipconfig_service_changed(interface);
+    return;
+}
+
+static void
+monitor_ipconfig_service(interface_t * interface)
+{
+    SCDynamicStoreContext    context = {
+                       .version = 0,
+                       .info = NULL,
+                       .retain = NULL,
+                       .release = NULL,
+                       .copyDescription = NULL
+    };
+    CFArrayRef            keys;
+    SCDynamicStoreRef    store;
+    CFStringRef            store_key;
+
+    if (interface->ip_configuration_store != NULL) {
+        INFO("Releasing old SCDynamicStore object for " PUB_S_SRP, interface->name);
+        SCDynamicStoreSetDispatchQueue(interface->ip_configuration_store, NULL);
+        CFRelease(interface->ip_configuration_store);
+        interface->ip_configuration_store = NULL;
+    }
+
+#define OUR_IDENTIFIER    CFSTR("ThreadBorderRouter")
+    context.info = interface;
+    store = SCDynamicStoreCreate(NULL, OUR_IDENTIFIER,
+                                 ipconfig_service_callback, &context);
+    store_key = IPConfigurationServiceGetNotificationKey(interface->ip_configuration_service);
+    keys = CFArrayCreate(NULL, (const void * *)&store_key,
+                         1,
+                         &kCFTypeArrayCallBacks);
+    SCDynamicStoreSetNotificationKeys(store, keys, NULL);
+    CFRelease(keys);
+
+    /* avoid race with being notified */
+    ipconfig_service_changed(interface);
+    SCDynamicStoreSetDispatchQueue(store, dispatch_get_main_queue());
+    interface->ip_configuration_store = (void *)store;
+}
+
+static Boolean
+start_ipconfig_service(interface_t *interface, const char *ip6addr_str)
+{
+       CFMutableDictionaryRef config_dict;
+    CFStringRef interface_name;
+    CFDictionaryRef options;
+
+    if (interface->ip_configuration_service != NULL) {
+        INFO("start_ipconfig_service: releasing old IPConfigurationService object for " PUB_S_SRP, interface->name);
+        CFRelease(interface->ip_configuration_service);
+        interface->ip_configuration_service = NULL;
+    }
+
+    // Create an IPv6 entity dictionary with ConfigMethod, Addresses, and PrefixLength properties
+    config_dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+    CFDictionarySetValue(config_dict, kSCPropNetIPv6ConfigMethod, kSCValNetIPv6ConfigMethodManual);
+#define PREFIX_LENGTH    64
+    dict_add_string_as_array(config_dict, kSCPropNetIPv6Addresses, ip6addr_str);
+    dict_add_int_as_array(config_dict, kSCPropNetIPv6PrefixLength, PREFIX_LENGTH);
+    options = ipconfig_options_dict_create(config_dict);
+    CFRelease(config_dict);
+    interface_name = CFStringCreateWithCString(NULL, interface->name, kCFStringEncodingUTF8);
+    interface->ip_configuration_service = IPConfigurationServiceCreate(interface_name, options);
+    CFRelease(interface_name);
+    CFRelease(options);
+    if (interface->ip_configuration_service == NULL) {
+        ERROR("start_ipconfig_service: IPConfigurationServiceCreate on " PUB_S_SRP " failed", interface->name);
+    }
+    else {
+        monitor_ipconfig_service(interface);
+        struct in6_addr ip6addr;
+        int ret = inet_pton(AF_INET6, ip6addr_str, ip6addr.s6_addr);
+        if (ret == 1) {
+            SEGMENTED_IPv6_ADDR_GEN_SRP(ip6addr.s6_addr, ip6addr_buf);
+            INFO("start_ipconfig_service: IPConfigurationServiceCreate on " PRI_S_SRP "/" PRI_SEGMENTED_IPv6_ADDR_SRP
+                  " succeeded", interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(ip6addr.s6_addr, ip6addr_buf));
+        }
+       }
+    return (interface->ip_configuration_service != NULL);
+}
+
+#elif defined(CONFIGURE_STATIC_INTERFACE_ADDRESSES_WITH_IPCONFIG)
+static void
+link_route_done(void *context, int status, const char *error)
+{
+    interface_t *interface = context;
+
+    if (error != NULL) {
+        ERROR("link_route_done on " PUB_S_SRP ": " PUB_S_SRP, interface->name, error);
+    } else {
+        INFO("link_route_done on " PUB_S_SRP ": %d.", interface->name, status);
+    }
+    ioloop_subproc_release(link_route_adder_process);
+    // Now that the on-link prefix is configured, time for a policy re-evaluation.
+    interface->on_link_prefix_configured = true;
+    routing_policy_evaluate(interface, true);
+}
+#endif
+
+static void
+interface_prefix_configure(struct in6_addr prefix, interface_t *interface)
+{
+    int sock;
+
+    sock = socket(PF_INET6, SOCK_DGRAM, 0);
+    if (sock < 0) {
+        ERROR("interface_prefix_configure: socket(PF_INET6, SOCK_DGRAM, 0) failed " PUB_S_SRP ": " PUB_S_SRP,
+              interface->name, strerror(errno));
+        return;
+    }
+#ifdef CONFIGURE_STATIC_INTERFACE_ADDRESSES
+    struct in6_addr interface_address = prefix;
+    char addrbuf[INET6_ADDRSTRLEN];
+    interface_address.s6_addr[15] = 1;
+    inet_ntop(AF_INET6, &interface_address, addrbuf, INET6_ADDRSTRLEN);
+#if defined (USE_IPCONFIGURATION_SERVICE)
+    if (!start_ipconfig_service(interface, addrbuf)) {
+        close(sock);
+        return;
+    }
+#elif defined(CONFIGURE_STATIC_INTERFACE_ADDRESSES_WITH_IPCONFIG)
+    char *args[] = { "set", interface->name, "MANUAL-V6", addrbuf, "64" };
+
+    INFO("interface_prefix_configure: /sbin/ipconfig " PUB_S_SRP " " PUB_S_SRP " " PUB_S_SRP " " PUB_S_SRP " "
+         PUB_S_SRP, args[0], args[1], args[2], args[3], args[4]);
+    link_route_adder_process = ioloop_subproc("/usr/sbin/ipconfig", args, 5, link_route_done, interface, NULL);
+    if (link_route_adder_process == NULL) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(interface_address.s6_addr, if_addr_buf);
+        ERROR("interface_prefix_configure: unable to set interface address for " PUB_S_SRP " to "
+              PRI_SEGMENTED_IPv6_ADDR_SRP ".", interface->name,
+              SEGMENTED_IPv6_ADDR_PARAM_SRP(interface_address.s6_addr, if_addr_buf));
+    }
+#else
+    struct in6_aliasreq alias_request;
+    memset(&alias_request, 0, sizeof(alias_request));
+    strlcpy(alias_request.ifra_name, interface->name, IFNAMSIZ);
+    alias_request.ifra_addr.sin6_family = AF_INET6;
+    alias_request.ifra_addr.sin6_len = sizeof(alias_request.ifra_addr);
+    memcpy(&alias_request.ifra_addr.sin6_addr, &interface_address, sizeof(alias_request.ifra_addr.sin6_addr));
+    alias_request.ifra_prefixmask.sin6_len = sizeof(alias_request.ifra_addr);
+    alias_request.ifra_prefixmask.sin6_family = AF_INET6;
+    memset(&alias_request.ifra_prefixmask.sin6_addr, 0xff, 8); // /64.
+    alias_request.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; // seconds, I hope?
+    alias_request.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; // seconds, I hope?
+    ret = ioctl(sock, SIOCAIFADDR_IN6, &alias_request);
+    if (ret < 0) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(interface_address.s6_addr, if_addr_buf);
+        ERROR("interface_prefix_configure: can't configure static address " PRI_SEGMENTED_IPv6_ADDR_SRP " on " PUB_S_SRP
+              ": " PUB_S_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(interface_address.s6_addr, if_addr_buf), interface->name,
+              strerror(errno));
+    } else {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(interface_address.s6_addr, if_addr_buf);
+        INFO("interface_prefix_configure: added address " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PUB_S_SRP,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(interface_address.s6_addr, if_addr_buf), interface->name);
+    }
+#endif // CONFIGURE_STATIC_INTERFACE_ADDRESSES_WITH_IPCONFIG
+#else
+    (void)prefix;
+#endif // CONFIGURE_STATIC_INTERFACE_ADDRESSES
+}
+
+#ifdef USE_SYSCTL_COMMMAND_TO_ENABLE_FORWARDING
+static void
+thread_forwarding_done(void *__unused context, int status, const char *error)
+{
+    if (error != NULL) {
+        ERROR("thread_forwarding_done: " PUB_S_SRP, error);
+    } else {
+        INFO("thread_forwarding_done: %d.", status);
+    }
+    ioloop_subproc_release(thread_forwarding_setter_process);
+}
+
+static void
+set_thread_forwarding(void)
+{
+    char *args[] = { "-w", "net.inet6.ip6.forwarding=1" };
+
+    INFO("/usr/sbin/sysctl " PUB_S_SRP " " PUB_S_SRP, args[0], args[1]);
+    thread_forwarding_setter_process = ioloop_subproc("/usr/sbin/sysctl", args, 2, thread_forwarding_done,
+                                                      NULL, NULL);
+    if (thread_forwarding_setter_process == NULL) {
+        ERROR("Unable to set thread forwarding enabled.");
+    }
+}
+
+#else
+
+static void
+set_thread_forwarding(void)
+{
+    int wun = 1;
+    int ret = sysctlbyname("net.inet6.ip6.forwarding", NULL, 0, &wun, sizeof(wun));
+    if (ret < 0) {
+        ERROR("set_thread_forwarding: " PUB_S_SRP, strerror(errno));
+    } else {
+        INFO("Enabled IPv6 forwarding.");
+    }
+}
+#endif // USE_SYSCTL_COMMMAND_TO_ENABLE_FORWARDING
+
+#ifdef NEED_THREAD_RTI_SETTER
+static void
+thread_rti_done(void *__unused context, int status, const char *error)
+{
+    if (error != NULL) {
+        ERROR("thread_rti_done: " PUB_S_SRP, error);
+    } else {
+        INFO("thread_rti_done: %d.", status);
+    }
+    ioloop_subproc_release(thread_rti_setter_process);
+}
+
+static void
+set_thread_rti(void)
+{
+    char *args[] = { "-w", "net.inet6.icmp6.nd6_process_rti=1" };
+    thread_rti_setter_process = ioloop_subproc("/usr/sbin/sysctl", args, 2, thread_rti_done,
+                                               NULL, NULL);
+    if (thread_rti_setter_process == NULL) {
+        ERROR("Unable to set thread rti enabled.");
+    }
+}
+#endif
+
+#if TARGET_OS_TV && !defined(RA_TESTER)
+#ifdef ADD_PREFIX_WITH_WPANCTL
+static void
+thread_prefix_done(void *__unused context, int status, const char *error)
+{
+    if (error != NULL) {
+        ERROR("thread_prefix_done: " PUB_S_SRP, error);
+    } else {
+        interface_t *interface;
+
+        INFO("thread_prefix_done: %d.", status);
+        for (interface = interfaces; interface; interface = interface->next) {
+            if (!interface->inactive) {
+                interface_beacon_schedule(interface, 0);
+            }
+        }
+    }
+    ioloop_subproc_release(thread_prefix_adder_process);
+}
+#endif
+
+static void
+cti_add_prefix_callback(void *__unused context, cti_status_t status)
+{
+    interface_t *interface;
+    INFO("cti_add_prefix_callback: %d", status);
+    for (interface = interfaces; interface; interface = interface->next) {
+        if (!interface->inactive) {
+            interface_beacon_schedule(interface, 0);
+        }
+    }
+}
+
+static thread_prefix_t *
+get_advertised_thread_prefix(void)
+{
+    if (published_thread_prefix != NULL) {
+        return published_thread_prefix;
+    } else {
+        return adopted_thread_prefix;
+    }
+    return NULL;
+}
+
+static void
+set_thread_prefix(void)
+{
+    char addrbuf[INET6_ADDRSTRLEN];
+    thread_prefix_t *advertised_thread_prefix = get_advertised_thread_prefix();
+    if (advertised_thread_prefix == NULL) {
+        ERROR("set_thread_prefix: no advertised thread prefix.");
+        return;
+    }
+    SEGMENTED_IPv6_ADDR_GEN_SRP(advertised_thread_prefix->prefix.s6_addr, thread_prefix_buf);
+    inet_ntop(AF_INET6, &advertised_thread_prefix->prefix, addrbuf, sizeof addrbuf);
+#ifdef ADD_PREFIX_WITH_WPANCTL
+    char *args[] = { "add-prefix", "--stable", "--preferred", "--slaac", "--default-route", "--on-mesh", addrbuf };
+    INFO("/usr/local/bin/wpanctl " PUB_S_SRP " " PUB_S_SRP " " PUB_S_SRP " " PUB_S_SRP " " PUB_S_SRP " " PUB_S_SRP " "
+         PRI_SEGMENTED_IPv6_ADDR_SRP, args[0], args[1], args[2], args[3], args[4], args[5],
+         SEGMENTED_IPv6_ADDR_PARAM_SRP(advertised_thread_prefix->prefix.s6_addr, thread_prefix_buf));
+    thread_prefix_adder_process = ioloop_subproc("/usr/local/bin/wpanctl", args, 7, thread_prefix_done,
+                                                 NULL, NULL);
+    if (thread_prefix_adder_process == NULL) {
+        ERROR("Unable to add thread interface prefix.");
+    }
+#else
+    INFO("add_prefix(true, true, true, true, " PRI_SEGMENTED_IPv6_ADDR_SRP ")",
+         SEGMENTED_IPv6_ADDR_PARAM_SRP(advertised_thread_prefix->prefix.s6_addr, thread_prefix_buf));
+    int status = cti_add_prefix(NULL, cti_add_prefix_callback, dispatch_get_main_queue(),
+                                &advertised_thread_prefix->prefix, advertised_thread_prefix->prefix_len,
+                                true, true, true, true);
+    if (status) {
+        ERROR("Unable to add thread interface prefix.");
+    }
+#endif
+}
+#endif // TARGET_OS_TV && !RA_TESTER
+
+static void
+router_advertisement_send(interface_t *interface)
+{
+    uint8_t *message;
+    dns_towire_state_t towire;
+
+    // Thread blocks RAs so no point sending them.
+    if (interface->inactive
+#ifndef RA_TESTER
+        || interface->is_thread
+#endif
+        ) {
+        return;
+    }
+
+#define MAX_ICMP_MESSAGE 1280
+    message = malloc(MAX_ICMP_MESSAGE);
+    if (message == NULL) {
+        ERROR("Unable to construct ICMP Router Advertisement: no memory");
+        return;
+    }
+
+    // Construct the ICMP header and options for each interface.
+    memset(&towire, 0, sizeof towire);
+    towire.p = message;
+    towire.lim = message + MAX_ICMP_MESSAGE;
+
+    // Construct the ICMP header.
+    // We use the DNS message construction functions because it's easy; probably should just make
+    // the towire functions more generic.
+    dns_u8_to_wire(&towire, ND_ROUTER_ADVERT);  // icmp6_type
+    dns_u8_to_wire(&towire, 0);                 // icmp6_code
+    dns_u16_to_wire(&towire, 0);                // The kernel computes the checksum (we don't technically have it).
+    dns_u8_to_wire(&towire, 0);                 // Hop limit, we don't set.
+    dns_u8_to_wire(&towire, 0);                 // Flags.  We don't offer DHCP, so We set neither the M nor the O bit.
+    // We are not a home agent, so no H bit.  Lifetime is 0, so Prf is 0.
+#ifdef ROUTER_LIFETIME_HACK
+    dns_u16_to_wire(&towire, 1800);             // Router lifetime, hacked.  This shouldn't ever be enabled.
+#else
+#ifdef RA_TESTER
+    // Advertise a default route on the simulated thread network
+    if (!strcmp(interface->name, thread_interface_name)) {
+        dns_u16_to_wire(&towire, 1800);         // Router lifetime for default route
+    } else {
+#endif
+        dns_u16_to_wire(&towire, 0);            // Router lifetime for non-default default route(s).
+#ifdef RA_TESTER
+    }
+#endif // RA_TESTER
+#endif // ROUTER_LIFETIME_HACK
+    dns_u32_to_wire(&towire, 0);                // Reachable time for NUD, we have no opinion on this.
+    dns_u32_to_wire(&towire, 0);                // Retransmission timer, again we have no opinion.
+
+    // Send Source link-layer address option
+    if (interface->have_link_layer_address) {
+        dns_u8_to_wire(&towire, ND_OPT_SOURCE_LINKADDR);
+        dns_u8_to_wire(&towire, 1); // length / 8
+        dns_rdata_raw_data_to_wire(&towire, &interface->link_layer, sizeof(interface->link_layer));
+        INFO("Advertising source lladdr " PRI_MAC_ADDR_SRP " on " PUB_S_SRP, MAC_ADDR_PARAM_SRP(interface->link_layer),
+             interface->name);
+    }
+
+#ifndef RA_TESTER
+    // Send MTU of 1280 for Thread?
+    if (interface->is_thread) {
+        dns_u8_to_wire(&towire, ND_OPT_MTU);
+        dns_u8_to_wire(&towire, 1); // length / 8
+        dns_u32_to_wire(&towire, 1280);
+        INFO("Advertising MTU of 1280 on " PUB_S_SRP, interface->name);
+    }
+#endif
+
+    // Send Prefix Information option if there's no IPv6 on the link.
+    if (interface->advertise_ipv6_prefix) {
+        dns_u8_to_wire(&towire, ND_OPT_PREFIX_INFORMATION);
+        dns_u8_to_wire(&towire, 4); // length / 8
+        dns_u8_to_wire(&towire, 64); // On-link prefix is always 64 bits
+        dns_u8_to_wire(&towire, ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO); // On link, autoconfig
+        dns_u32_to_wire(&towire, interface->valid_lifetime);
+        dns_u32_to_wire(&towire, interface->preferred_lifetime);
+        dns_u32_to_wire(&towire, 0); // Reserved
+        dns_rdata_raw_data_to_wire(&towire, &interface->ipv6_prefix, sizeof interface->ipv6_prefix);
+        SEGMENTED_IPv6_ADDR_GEN_SRP(interface->ipv6_prefix.s6_addr, ipv6_prefix_buf);
+        INFO("Advertising on-link prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " on " PUB_S_SRP,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, ipv6_prefix_buf), interface->name);
+    }
+
+#ifndef ND_OPT_ROUTE_INFORMATION
+#define ND_OPT_ROUTE_INFORMATION 24
+#endif
+    // In principle we can either send routes to links that are reachable by this router,
+    // or just advertise a router to the entire ULA /48.   In theory it doesn't matter
+    // which we do; if we support HNCP at some point we probably need to be specific, but
+    // for now being general is fine because we have no way to share a ULA.
+    // Unfortunately, some RIO implementations do not work with specific routes, so for now
+    // We are doing it the easy way and just advertising the /48.
+#define SEND_INTERFACE_SPECIFIC_RIOS 1
+#ifdef SEND_INTERFACE_SPECIFIC_RIOS
+
+    // If neither ROUTE_BETWEEN_NON_THREAD_LINKS nor RA_TESTER are defined, then we never want to
+    // send an RIO other than for the thread network prefix.
+#if defined (ROUTE_BETWEEN_NON_THREAD_LINKS) || defined(RA_TESTER)
+    interface_t *ifroute;
+    // Send Route Information option for other interfaces.
+    for (ifroute = interfaces; ifroute; ifroute = ifroute->next) {
+        if (ifroute->inactive) {
+            continue;
+        }
+        if (
+#ifndef RA_TESTER
+            partition_can_provide_routing &&
+#endif
+            ifroute->advertise_ipv6_prefix &&
+#ifdef SEND_ON_LINK_ROUTE
+            // In theory we don't want to send RIO for the on-link prefix, but there's this bug, see.
+            true &&
+#else
+            ifroute != interface &&
+#endif
+#ifdef RA_TESTER
+            // For the RA tester, we don't need to send an RIO to the thread network because we're the
+            // default router for that network.
+            strcmp(interface->name, thread_interface_name)
+#else
+            true
+#endif
+            )
+        {
+            dns_u8_to_wire(&towire, ND_OPT_ROUTE_INFORMATION);
+            dns_u8_to_wire(&towire, 2); // length / 8
+            dns_u8_to_wire(&towire, 64); // Interface prefixes are always 64 bits
+            dns_u8_to_wire(&towire, 0); // There's no reason at present to prefer one Thread BR over another
+            dns_u32_to_wire(&towire, 1800); // Route lifetime 1800 seconds (30 minutes)
+            dns_rdata_raw_data_to_wire(&towire, &ifroute->ipv6_prefix, 8); // /64 requires 8 bytes.
+            SEGMENTED_IPv6_ADDR_GEN_SRP(ifroute->ipv6_prefix.s6_addr, ipv6_prefix_buf);
+            INFO("Sending route to " PRI_SEGMENTED_IPv6_ADDR_SRP "%%" PUB_S_SRP " on " PUB_S_SRP,
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(ifroute->ipv6_prefix.s6_addr, ipv6_prefix_buf),
+                 ifroute->name, interface->name);
+        }
+    }
+#endif // ROUTE_BETWEEN_NON_THREAD_LINKS || RA_TESTER
+
+#ifndef RA_TESTER
+    // Send route information option for thread prefix
+    thread_prefix_t *advertised_thread_prefix = get_advertised_thread_prefix();
+    if (advertised_thread_prefix != NULL) {
+        dns_u8_to_wire(&towire, ND_OPT_ROUTE_INFORMATION);
+        dns_u8_to_wire(&towire, 2); // length / 8
+        dns_u8_to_wire(&towire, 64); // Interface prefixes are always 64 bits
+        dns_u8_to_wire(&towire, 0); // There's no reason at present to prefer one Thread BR over another
+        dns_u32_to_wire(&towire, 1800); // Route lifetime 1800 seconds (30 minutes)
+        dns_rdata_raw_data_to_wire(&towire, &advertised_thread_prefix->prefix, 8); // /64 requires 8 bytes.
+        SEGMENTED_IPv6_ADDR_GEN_SRP(advertised_thread_prefix->prefix.s6_addr, thread_prefix_buf);
+        INFO("Sending route to " PRI_SEGMENTED_IPv6_ADDR_SRP "%%" PUB_S_SRP " on " PUB_S_SRP,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(advertised_thread_prefix->prefix.s6_addr, thread_prefix_buf),
+             thread_interface_name, interface->name);
+    }
+#endif
+#else
+#ifndef SKIP_SLASH_48
+    dns_u8_to_wire(&towire, ND_OPT_ROUTE_INFORMATION);
+    dns_u8_to_wire(&towire, 3); // length / 8
+    dns_u8_to_wire(&towire, 48); // ULA prefixes are always 48 bits
+    dns_u8_to_wire(&towire, 0); // There's no reason at present to prefer one Thread BR over another
+    dns_u32_to_wire(&towire, 1800); // Route lifetime 1800 seconds (30 minutes)
+    dns_rdata_raw_data_to_wire(&towire, &ula_prefix, 16); // /48 requires 16 bytes
+#endif // SKIP_SLASH_48
+#endif // SEND_INTERFACE_SPECIFIC_RIOS
+
+    if (towire.error) {
+        ERROR("No space in ICMP output buffer for " PUB_S_SRP " at route.c:%d", interface->name, towire.line);
+        towire.error = 0;
+    } else {
+        icmp_send(message, towire.p - message, interface, &in6addr_linklocal_allnodes);
+    }
+    free(message);
+}
+
+static void
+router_solicit_send(interface_t *interface)
+{
+    uint8_t *message;
+    dns_towire_state_t towire;
+
+    // Thread blocks RSs so no point sending them.
+    if (interface->inactive
+#ifndef RA_TESTER
+        || interface->is_thread
+#endif
+        ) {
+        return;
+    }
+
+#define MAX_ICMP_MESSAGE 1280
+    message = malloc(MAX_ICMP_MESSAGE);
+    if (message == NULL) {
+        ERROR("Unable to construct ICMP Router Advertisement: no memory");
+        return;
+    }
+
+    // Construct the ICMP header and options for each interface.
+    memset(&towire, 0, sizeof towire);
+    towire.p = message;
+    towire.lim = message + MAX_ICMP_MESSAGE;
+
+    // Construct the ICMP header.
+    // We use the DNS message construction functions because it's easy; probably should just make
+    // the towire functions more generic.
+    dns_u8_to_wire(&towire, ND_ROUTER_SOLICIT);  // icmp6_type
+    dns_u8_to_wire(&towire, 0);                  // icmp6_code
+    dns_u16_to_wire(&towire, 0);                 // The kernel computes the checksum (we don't technically have it).
+    dns_u32_to_wire(&towire, 0);                 // Reserved32
+
+    // Send Source link-layer address option
+    if (interface->have_link_layer_address) {
+        dns_u8_to_wire(&towire, ND_OPT_SOURCE_LINKADDR);
+        dns_u8_to_wire(&towire, 1); // length / 8
+        dns_rdata_raw_data_to_wire(&towire, &interface->link_layer, sizeof(interface->link_layer));
+    }
+
+    if (towire.error) {
+        ERROR("No space in ICMP output buffer for " PUB_S_SRP " at route.c:%d", interface->name, towire.line);
+    } else {
+        icmp_send(message, towire.p - message, interface, &in6addr_linklocal_allrouters);
+    }
+    free(message);
+}
+
+static void
+icmp_send(uint8_t *message, size_t length, interface_t *interface, const struct in6_addr *destination)
+{
+    struct iovec iov;
+    socklen_t cmsg_length = CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof (int));
+    uint8_t *cmsg_buffer;
+    struct msghdr msg_header;
+    struct cmsghdr *cmsg_pointer;
+    struct in6_pktinfo *packet_info;
+    int hop_limit = 255;
+    ssize_t rv;
+    struct sockaddr_in6 dest;
+
+    // Make space for the control message buffer.
+    cmsg_buffer = malloc(cmsg_length);
+    if (cmsg_buffer == NULL) {
+        ERROR("Unable to construct ICMP Router Advertisement: no memory");
+        return;
+    }
+
+    // Send the message
+    memset(&dest, 0, sizeof(dest));
+    dest.sin6_family = AF_INET6;
+    dest.sin6_scope_id = interface->index;
+    dest.sin6_len = sizeof(dest);
+    msg_header.msg_namelen = sizeof(dest);
+    dest.sin6_addr = *destination;
+
+    msg_header.msg_name = &dest;
+    iov.iov_base = message;
+    iov.iov_len = length;
+    msg_header.msg_iov = &iov;
+    msg_header.msg_iovlen = 1;
+    msg_header.msg_control = cmsg_buffer;
+    msg_header.msg_controllen = cmsg_length;
+
+    // Specify the interface
+    cmsg_pointer = CMSG_FIRSTHDR(&msg_header);
+    cmsg_pointer->cmsg_level = IPPROTO_IPV6;
+    cmsg_pointer->cmsg_type = IPV6_PKTINFO;
+    cmsg_pointer->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+    packet_info = (struct in6_pktinfo *)CMSG_DATA(cmsg_pointer);
+    memset(packet_info, 0, sizeof(*packet_info));
+    packet_info->ipi6_ifindex = interface->index;
+
+    // Router advertisements and solicitations have a hop limit of 255
+    cmsg_pointer = CMSG_NXTHDR(&msg_header, cmsg_pointer);
+    cmsg_pointer->cmsg_level = IPPROTO_IPV6;
+    cmsg_pointer->cmsg_type = IPV6_HOPLIMIT;
+    cmsg_pointer->cmsg_len = CMSG_LEN(sizeof(int));
+    memcpy(CMSG_DATA(cmsg_pointer), &hop_limit, sizeof(hop_limit));
+
+    // Send it
+    rv = sendmsg(icmp_listener.io_state->fd, &msg_header, 0);
+    if (rv < 0) {
+        uint8_t *in6_addr_bytes = ((struct sockaddr_in6 *)(msg_header.msg_name))->sin6_addr.s6_addr;
+        SEGMENTED_IPv6_ADDR_GEN_SRP(in6_addr_bytes, in6_addr_buf);
+        ERROR("icmp_send: sending " PUB_S_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP " on interface " PUB_S_SRP
+              " index %d: " PUB_S_SRP, message[0] == ND_ROUTER_SOLICIT ? "solicit" : "advertise",
+              SEGMENTED_IPv6_ADDR_PARAM_SRP(in6_addr_bytes, in6_addr_buf),
+              interface->name, interface->index, strerror(errno));
+    } else if ((size_t)rv != iov.iov_len) {
+        ERROR("icmp_send: short send to interface " PUB_S_SRP ": %zd < %zd", interface->name, rv, iov.iov_len);
+    }
+    free(cmsg_buffer);
+}
+
+static void
+post_solicit_policy_evaluate(void *context)
+{
+    interface_t *interface = context;
+    INFO("Done waiting for router discovery to finish on " PUB_S_SRP, interface->name);
+    interface->router_discovery_complete = true;
+    interface->router_discovery_in_progress = false;
+    flush_stale_routers(interface, ioloop_timenow());
+
+    // See if we need a new prefix on the interface.
+    interface_prefix_evaluate(interface);
+
+    routing_policy_evaluate(interface, true);
+}
+
+static void
+dump_network_signature(char *buffer, size_t buffer_size, const uint8_t *signature, long length)
+{
+    char *hexp = buffer;
+    int i;
+    size_t left = buffer_size;
+    size_t len;
+
+    if (length == 0) {
+        strlcpy(buffer, "<NULL>", buffer_size);
+        return;
+    }
+    for (i = 0; i < length; i++) {
+        snprintf(hexp, left, "%02x", signature[i]);
+        len = strlen(hexp);
+        hexp += len;
+        left -= len;
+    }
+}
+
+static void
+network_link_finalize(network_link_t *link)
+{
+    if (link->signature != NULL) {
+        free(link->signature);
+    }
+    free(link);
+}
+
+#define network_link_create(signature, length) network_link_create_(signature, length, __FILE__, __LINE__);
+static network_link_t *
+network_link_create_(const uint8_t *signature, int length, const char *file, int line)
+{
+    network_link_t *link = calloc(1, sizeof(*link));
+    if (link != NULL) {
+        if (signature != NULL) {
+            if (length) {
+                link->signature = malloc(length);
+                if (link->signature == NULL) {
+                    INFO("network_link_create: no memory for signature.");
+                    free(link);
+                    return NULL;
+                }
+                memcpy(link->signature, signature, length);
+                link->signature_length = length;
+            }
+        }
+        RETAIN(link);
+    }
+    return link;
+}
+
+static CFDictionaryRef
+network_link_dictionary_copy(network_link_t *link)
+{
+    CFDictionaryRef dictionary = NULL;
+    OSStatus err;
+    err = CFPropertyListCreateFormatted(kCFAllocatorDefault, &dictionary,
+                                        "{"
+                                            "last-seen=%lli"
+                                            "signature=%D"
+                                            "prefix-number=%i"
+                                        "}",
+                                        link->last_seen,
+                                        link->signature, (int)link->signature_length,
+                                        link->prefix_number);
+        if (err != 0) {
+            dictionary = NULL;
+            ERROR("CFPropertyListCreateFormatted failed: %d", err);
+        }
+    return dictionary;
+}
+
+typedef struct network_link_parse_state network_link_parse_state_t;
+struct network_link_parse_state {
+    network_link_t *NONNULL link;
+    bool fail : 1;
+    bool last_seen : 1;
+    bool signature : 1;
+    bool prefix_number : 1;
+};
+
+static void
+network_link_parse(const void *key, const void *value, void *context)
+{
+    network_link_parse_state_t *parse_state = context;
+    int64_t last_seen;
+
+    if (parse_state->fail) {
+        return;
+    }
+    if (CFGetTypeID(key) != CFStringGetTypeID()) {
+        ERROR("network_link_parse: dictionary key not a string.");
+        parse_state->fail = true;
+        return;
+    }
+
+    if (CFStringCompare(key, CFSTR("last-seen"), 0) == kCFCompareEqualTo) {
+        // We store the last-seen time as a uint64 encoded as a string, because there is no uint64 CFNumber type.
+        // We store the prefix-number time as a CFNumber because it's a uint16_t.
+        if (CFGetTypeID(value) != CFNumberGetTypeID() ||
+            !CFNumberGetValue(value, kCFNumberSInt64Type, &last_seen))
+        {
+            ERROR("network_link_parse: last-seen is not a valid CFNumber");
+            parse_state->fail = true;
+        } else {
+            // For some reason CFNumber doesn't support uint64_t, but we are assuming that since we are copying this
+            // unchanged, there will be no error introduced by this case.
+            // CFProperyListCreateFormatted supports uint64_t, probably by doing the same thing.
+            parse_state->link->last_seen = (uint64_t)last_seen;
+            parse_state->last_seen = true;
+        }
+    } else if (CFStringCompare(key, CFSTR("signature"), 0) == kCFCompareEqualTo) {
+        const uint8_t *data_buffer;
+        long data_length;
+
+        // We store the signature as CFData.
+        if (CFGetTypeID(value) != CFDataGetTypeID()) {
+            ERROR("network_link_parse: Unable to get CFData for signature because it's not CFData.");
+            parse_state->fail = true;
+        } else {
+            data_buffer = CFDataGetBytePtr(value);
+            data_length = CFDataGetLength(value);
+            if (data_length < 1) {
+                parse_state->link->signature_length = 0;
+                parse_state->link->signature = NULL;
+                parse_state->signature = true;
+            } else {
+                parse_state->link->signature_length = data_length;
+                parse_state->link->signature = malloc(data_length);
+                if (parse_state->link->signature == NULL) {
+                    ERROR("network_link_parse: No memory for signature.");
+                    parse_state->fail = true;
+                } else {
+                    memcpy(parse_state->link->signature, data_buffer, data_length);
+                    parse_state->signature = true;
+                }
+            }
+        }
+    } else if (CFStringCompare(key, CFSTR("prefix-number"), 0) == kCFCompareEqualTo) {
+        // We store the prefix-number time as a CFNumber because it's a uint16_t.
+        if (CFGetTypeID(value) != CFNumberGetTypeID() ||
+            !CFNumberGetValue(value, kCFNumberSInt32Type, &parse_state->link->prefix_number))
+        {
+            ERROR("network_link_parse: prefix-number is not a valid CFNumber");
+            parse_state->fail = true;
+        } else if (parse_state->link->prefix_number < 0 || parse_state->link->prefix_number > UINT16_MAX) {
+            ERROR("network_link_parse: Invalid prefix-number: %" PRIu32, parse_state->link->prefix_number);
+            parse_state->fail = true;
+        } else {
+            parse_state->prefix_number = true;
+        }
+    } else {
+        char key_buffer[64];
+        if (!CFStringGetCString(key, key_buffer, sizeof(key_buffer), kCFStringEncodingUTF8)) {
+            INFO("Unexpected network link element dictionary key, but can't decode key");
+        } else {
+            INFO("Unexpected network link element dictionary key " PUB_S_SRP, key_buffer);
+        }
+        parse_state->fail = true;
+    }
+}
+
+static void
+network_link_apply(const void *value, void *context)
+{
+    bool *success = context;
+    CFDictionaryRef values = value;
+    network_link_parse_state_t parse_state;
+    char hexbuf[60];
+
+    if (*success == false) {
+        return;
+    }
+
+    memset(&parse_state, 0, sizeof parse_state);
+    parse_state.link = network_link_create(NULL, 0);
+    if (parse_state.link == NULL) {
+        ERROR("network_link_apply: no memory for link");
+        *success = false;
+        return;
+    }
+
+    // Parse the dictionary into the structure.
+    CFDictionaryApplyFunction(values, network_link_parse, &parse_state);
+
+    // Should have gotten three fields: last_seen, signature, prefix_number
+    if (!parse_state.last_seen) {
+        ERROR("network_link_apply: expecting last-seen");
+        parse_state.fail = true;
+    }
+    if (!parse_state.signature) {
+        ERROR("network_link_apply: expecting signature");
+        parse_state.fail = true;
+    }
+    if (!parse_state.prefix_number) {
+        ERROR("network_link_apply: expecting prefix-number");
+        parse_state.fail = true;
+    }
+    if (parse_state.fail) {
+        *success = false;
+        RELEASE_HERE(parse_state.link, network_link_finalize);
+        return;
+    }
+
+    dump_network_signature(hexbuf, sizeof hexbuf, parse_state.link->signature, parse_state.link->signature_length);
+
+    // If the link signature hasn't been seen in over a week, there is no need to remember it.  If no new links are
+    // seen, an old signature could persist for much longer than a week, but this is okay--the goal here is to prevent
+    // the link array from growing without bound, and whenver a link signature is added, the array is rewritte, at
+    // which point the old link signatures will be erased.
+    if (ioloop_timenow() - parse_state.link->last_seen > 1000 * 3600 * 24 * 7) {
+        INFO("network_link_apply: discarding link signature " PRI_S_SRP
+             ", prefix number %d, which is more than a week old", hexbuf, parse_state.link->prefix_number);
+        RELEASE_HERE(parse_state.link, network_link_finalize);
+        return;
+    }
+
+    parse_state.link->next = network_links;
+    network_links = parse_state.link;
+    INFO("network_link_apply: parsed link signature " PRI_S_SRP ", prefix number %d", hexbuf,
+         network_links->prefix_number);
+    // This is a temporary fix to clean up bogus link prefixes that may exist in preferences.
+    if (network_links->prefix_number == 0) {
+        network_links->prefix_number = ula_serial++;
+    } else {
+        if (network_links->prefix_number >= ula_serial) {
+            ula_serial = network_links->prefix_number + 1;
+        }
+    }
+}
+
+static void
+network_link_record(network_link_t *link)
+{
+    char hexbuf[60];
+    CFDictionaryRef link_dictionary;
+    if (network_link_array == NULL) {
+        ERROR("network_link_record: no network_link_array, can't record new link.");
+        return;
+    }
+    link_dictionary = network_link_dictionary_copy(link);
+    if (link_dictionary == NULL) {
+        ERROR("network_link_record: can't convert link into dictionary");
+        return;
+    }
+    CFArrayAppendValue(network_link_array, link_dictionary);
+
+    CFPreferencesSetValue(CFSTR("network-links"), network_link_array,
+                          CFSTR("com.apple.srp-mdns-proxy.preferences"),
+                          kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
+    if (!CFPreferencesSynchronize(CFSTR("com.apple.srp-mdns-proxy.preferences"),
+                                  kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)) {
+        ERROR("network_link_record: CFPreferencesSynchronize: Unable to store network link array.");
+    }
+    CFRelease(link_dictionary);
+    dump_network_signature(hexbuf, sizeof hexbuf, link->signature, link->signature_length);
+    INFO("network_link_record: recording link signature " PRI_S_SRP ", prefix number %d", hexbuf, link->prefix_number);
+}
+
+void
+ula_generate(void)
+{
+    char ula_prefix_buffer[INET6_ADDRSTRLEN];
+    struct in6_addr old_ula_prefix;
+    bool prefix_changed;
+
+    // Already have a prefix?
+    if (ula_prefix.s6_addr[0] == 0xfd) {
+        old_ula_prefix = ula_prefix;
+        prefix_changed = true;
+    } else {
+        prefix_changed = false;
+    }
+
+    memset(&ula_prefix, 0, sizeof(ula_prefix));
+    ula_prefix.s6_addr[0] = 0xfd;
+    arc4random_buf(&ula_prefix.s6_addr[1], 5); // 40 bits of randomness
+
+    inet_ntop(AF_INET6, &ula_prefix, ula_prefix_buffer, sizeof ula_prefix_buffer);
+    CFStringRef ula_string = CFStringCreateWithCString(NULL, ula_prefix_buffer, kCFStringEncodingUTF8);
+    if (ula_string == NULL) {
+        ERROR("ula_generate: unable to create a ULA prefix string to store in preferences.");
+    } else {
+        CFPreferencesSetValue(CFSTR("ula-prefix"), ula_string,
+                              CFSTR("com.apple.srp-mdns-proxy.preferences"),
+                              kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
+        if (!CFPreferencesSynchronize(CFSTR("com.apple.srp-mdns-proxy.preferences"),
+                                      kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)) {
+            ERROR("CFPreferencesSynchronize: Unable to store ULA prefix.");
+        }
+        CFRelease(ula_string);
+    }
+    if (prefix_changed) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(old_ula_prefix.s6_addr, old_prefix_buf);
+        SEGMENTED_IPv6_ADDR_GEN_SRP(ula_prefix.s6_addr, new_prefix_buf);
+        INFO("ula-generate: prefix changed from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(old_ula_prefix.s6_addr, old_prefix_buf),
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(ula_prefix.s6_addr, new_prefix_buf));
+    } else {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(ula_prefix.s6_addr, new_prefix_buf);
+        INFO("ula-generate: generated ULA prefix " PRI_SEGMENTED_IPv6_ADDR_SRP,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(ula_prefix.s6_addr, new_prefix_buf));
+    }
+
+    // Set up the thread prefix.
+    my_thread_prefix = ula_prefix;
+    have_thread_prefix = true;
+}
+
+static void
+ula_setup(void)
+{
+    char ula_prefix_buffer[INET6_ADDRSTRLEN];
+    bool have_stored_ula_prefix = false;
+
+    // Set up the ULA in case we need it.
+    CFPropertyListRef plist = CFPreferencesCopyValue(CFSTR("ula-prefix"),
+                                                     CFSTR("com.apple.srp-mdns-proxy.preferences"),
+                                                     kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
+    if (plist != NULL) {
+        if (CFGetTypeID(plist) == CFStringGetTypeID()) {
+            if (CFStringGetCString((CFStringRef)plist, ula_prefix_buffer, sizeof(ula_prefix_buffer),
+                                   kCFStringEncodingUTF8)) {
+                if (inet_pton(AF_INET6, ula_prefix_buffer, &ula_prefix)) {
+                    SEGMENTED_IPv6_ADDR_GEN_SRP(ula_prefix.s6_addr, ula_prefix_buf);
+                    INFO("ula_setup: re-using stored prefix " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                         SEGMENTED_IPv6_ADDR_PARAM_SRP(ula_prefix.s6_addr, ula_prefix_buf));
+                    have_stored_ula_prefix = true;
+                }
+            }
+        }
+        CFRelease(plist);
+
+        // Get the list of known network links (identified by network signature)
+        plist = CFPreferencesCopyValue(CFSTR("network-links"),
+                                       CFSTR("com.apple.srp-mdns-proxy.preferences"),
+                                       kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
+
+        if (plist != NULL) {
+            if (CFGetTypeID(plist) == CFArrayGetTypeID()) {
+                bool success = true;
+                CFArrayApplyFunction(plist, CFRangeMake(0,CFArrayGetCount(plist)), network_link_apply, &success);
+                if (success) {
+                    network_link_array = CFArrayCreateMutableCopy(NULL, 0, plist);
+                    if (network_link_array == NULL) {
+                        ERROR("ula_setup: no memory for network link array!");
+                    }
+                }
+            }
+            CFRelease(plist);
+        }
+    }
+
+    // If we didn't get any links, make an empty array.
+    if (network_link_array == NULL) {
+        network_link_array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+        if (network_link_array == NULL) {
+            ERROR("ula_setup: unable to make network_link_array.");
+        } else {
+            INFO("ula_setup: created empty network_link_array.");
+        }
+    }
+
+    // If we didn't already successfully fetch a stored prefix, try to store one.
+    if (!have_stored_ula_prefix) {
+        ula_generate();
+    } else {
+        // Set up the thread prefix.
+        my_thread_prefix = ula_prefix;
+        have_thread_prefix = true;
+    }
+}
+
+static void
+get_network_signature(interface_t *interface)
+{
+    nwi_state_t network_state;
+    nwi_ifstate_t interface_state;
+    int length = 0;
+    const uint8_t *signature = NULL;
+    network_link_t *link = NULL;
+    char hexbuf[60];
+
+    network_state = nwi_state_copy();
+    if (network_state != NULL) {
+        interface_state = nwi_state_get_ifstate(network_state, interface->name);
+        if (interface_state != NULL) {
+            signature = nwi_ifstate_get_signature(interface_state, AF_INET, &length);
+            if (signature != NULL) {
+                dump_network_signature(hexbuf, sizeof(hexbuf), signature, length);
+                INFO("get_network_signature: interface " PUB_S_SRP " has ipv4 signature " PRI_S_SRP,
+                     interface->name, hexbuf);
+            } else {
+                signature = nwi_ifstate_get_signature(interface_state, AF_INET6, &length);
+                if (signature != NULL) {
+                    dump_network_signature(hexbuf, sizeof(hexbuf), signature, length);
+                    INFO("get_network_signature: interface " PUB_S_SRP " has ipv6 signature " PRI_S_SRP,
+                         interface->name, hexbuf);
+                } else {
+                    INFO("get_network_signature: no signature on " PUB_S_SRP, interface->name);
+                }
+            }
+        }
+        if (signature == NULL) {
+            length = 0;
+        }
+        for (link = network_links; link != NULL; link = link->next) {
+            if (link->signature_length == length && (length == 0 || !memcmp(link->signature, signature, length))) {
+                break;
+            }
+        }
+        if (link == NULL) {
+            link = network_link_create(signature, length);
+        }
+        nwi_state_release(network_state);
+    } else {
+        ERROR("get_network_signature: nwi_state_copy() failed on " PUB_S_SRP, interface->name);
+    }
+
+    // If we didn't get a network signature, we're going to treat that as a signature. The previous call to
+    // network_link_create() can have the same effect.
+    if (link == NULL) {
+        link = network_link_create(NULL, 0);
+    }
+
+    if (link != NULL && link->prefix_number == 0) {
+        // Assign a prefix number to the link.
+        link->prefix_number = ula_serial++;
+        link->last_seen = ioloop_timenow();
+
+        // Save this link in memory.
+        link->next = network_links;
+        network_links = link;
+
+        // Save this link signature in the preferences.
+        network_link_record(link);
+    }
+    if (interface->link != link) {
+#if defined(USE_IPCONFIGURATION_SERVICE)
+        if (interface->on_link_prefix_configured) {
+            interface_prefix_deconfigure(interface);
+        }
+#endif
+        interface->link = link;
+    }
+}
+
+bool
+start_icmp_listener(void)
+{
+    int sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+    int true_flag = 1;
+#ifdef CONFIGURE_STATIC_INTERFACE_ADDRESSES
+    int false_flag = 0;
+#endif
+    struct icmp6_filter filter;
+    ssize_t rv;
+
+    if (sock < 0) {
+        ERROR("Unable to listen for icmp messages: " PUB_S_SRP, strerror(errno));
+        close(sock);
+        return false;
+    }
+
+    // Only accept router advertisements and router solicits.
+    ICMP6_FILTER_SETBLOCKALL(&filter);
+    ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
+    ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
+    rv = setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter));
+    if (rv < 0) {
+        ERROR("Can't set IPV6_RECVHOPLIMIT: " PUB_S_SRP ".", strerror(errno));
+        close(sock);
+        return false;
+    }
+
+    // We want a source address and interface index
+    rv = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &true_flag, sizeof(true_flag));
+    if (rv < 0) {
+        ERROR("Can't set IPV6_RECVPKTINFO: " PUB_S_SRP ".", strerror(errno));
+        close(sock);
+        return false;
+    }
+
+    // We need to be able to reject RAs arriving from off-link.
+    rv = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &true_flag, sizeof(true_flag));
+    if (rv < 0) {
+        ERROR("Can't set IPV6_RECVHOPLIMIT: " PUB_S_SRP ".", strerror(errno));
+        close(sock);
+        return false;
+    }
+
+#ifdef CONFIGURE_STATIC_INTERFACE_ADDRESSES
+    // Prevent our router advertisements from updating our routing table.
+    rv = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &false_flag, sizeof(false_flag));
+    if (rv < 0) {
+        ERROR("Can't set IPV6_RECVHOPLIMIT: " PUB_S_SRP ".", strerror(errno));
+        close(sock);
+        return false;
+    }
+#endif
+
+    icmp_listener.io_state = ioloop_file_descriptor_create(sock, NULL, NULL);
+    if (icmp_listener.io_state == NULL) {
+        ERROR("No memory for ICMP I/O structure.");
+        close(sock);
+        return false;
+    }
+
+    // Beacon out a router advertisement every three minutes.
+    icmp_listener.unsolicited_interval = 180 * 1000;
+    ioloop_add_reader(icmp_listener.io_state, icmp_callback);
+
+    // At this point we need to have a ULA prefix.
+    ula_setup();
+
+    return true;
+}
+
+static void
+interface_router_solicit_finalize(void *context)
+{
+    interface_t *interface = context;
+    interface->router_solicit_wakeup = NULL;
+}
+
+static void
+router_solicit_callback(void *context)
+{
+    interface_t *interface = context;
+    if (interface->is_thread) {
+        INFO("discontinuing router solicitations on thread interface " PUB_S_SRP, interface->name);
+        return;
+    }
+    if (interface->num_solicits_sent >= 3) {
+        INFO("Done sending router solicitations on " PUB_S_SRP ".", interface->name);
+        return;
+    }
+    INFO("sending router solicitation on " PUB_S_SRP , interface->name);
+    router_solicit_send(interface);
+
+    interface->num_solicits_sent++;
+    ioloop_add_wake_event(interface->router_solicit_wakeup,
+                          interface, router_solicit_callback, interface_router_solicit_finalize,
+                          RTR_SOLICITATION_INTERVAL * 1000 + srp_random16() % 1024);
+}
+
+static void
+start_router_solicit(interface_t *interface)
+{
+    if (interface->router_solicit_wakeup == NULL) {
+        interface->router_solicit_wakeup = ioloop_wakeup_create();
+        if (interface->router_solicit_wakeup == 0) {
+            ERROR("No memory for router solicit wakeup on " PUB_S_SRP ".", interface->name);
+            return;
+        }
+    } else {
+        ioloop_cancel_wake_event(interface->router_solicit_wakeup);
+    }
+    interface->num_solicits_sent = 0;
+    ioloop_add_wake_event(interface->router_solicit_wakeup, interface, router_solicit_callback,
+                          interface_router_solicit_finalize, 128 + srp_random16() % 896);
+}
+
+static void
+icmp_interface_subscribe(interface_t *interface, bool added)
+{
+    struct ipv6_mreq req;
+    int rv;
+
+    if (icmp_listener.io_state == NULL) {
+        ERROR("Interface subscribe without ICMP listener.");
+        return;
+    }
+
+    memset(&req, 0, sizeof req);
+    if (interface->index == -1) {
+        ERROR("icmp_interface_subscribe called before interface index fetch for " PUB_S_SRP, interface->name);
+        return;
+    }
+
+    req.ipv6mr_multiaddr = in6addr_linklocal_allrouters;
+    req.ipv6mr_interface = interface->index;
+    rv = setsockopt(icmp_listener.io_state->fd, IPPROTO_IPV6, added ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, &req,
+                    sizeof req);
+    if (rv < 0) {
+        ERROR("Unable to " PUB_S_SRP " all-routers multicast group on " PUB_S_SRP ": " PUB_S_SRP,
+              added ? "join" : "leave", interface->name, strerror(errno));
+        return;
+    } else {
+        INFO("icmp_interface_subscribe: " PUB_S_SRP "subscribed on interface " PUB_S_SRP, added ? "" : "un",
+             interface->name);
+    }
+}
+
+static interface_t *
+find_interface(const char *name, int ifindex)
+{
+    interface_t **p_interface, *interface = NULL;
+
+    for (p_interface = &interfaces; *p_interface; p_interface = &(*p_interface)->next) {
+        interface = *p_interface;
+        if (!strcmp(name, interface->name)) {
+            if (ifindex != -1 && interface->index != ifindex) {
+                INFO("interface name " PUB_S_SRP " index changed from %d to %d", name, interface->index, ifindex);
+                interface->index = ifindex;
+            }
+            break;
+        }
+    }
+
+    // If it's a new interface, make a structure.
+    // We could do a callback, but don't have a use case
+    if (*p_interface == NULL) {
+        interface = interface_create(name, ifindex);
+        *p_interface = interface;
+    }
+    return interface;
+}
+
+NW_EXPORT_PROJECT NW_RETURNS_RETAINED nw_path_evaluator_t
+nw_path_create_evaluator_for_listener(nw_parameters_t parameters,
+                                      int *out_error);
+
+static void
+interface_shutdown(interface_t *interface)
+{
+    icmp_message_t *router, *next;
+    INFO("Interface " PUB_S_SRP " went away.", interface->name);
+    if (interface->beacon_wakeup != NULL) {
+        ioloop_cancel_wake_event(interface->beacon_wakeup);
+    }
+    if (interface->post_solicit_wakeup != NULL) {
+        ioloop_cancel_wake_event(interface->post_solicit_wakeup);
+    }
+    if (interface->router_solicit_wakeup != NULL) {
+        ioloop_cancel_wake_event(interface->router_solicit_wakeup);
+    }
+    if (interface->deconfigure_wakeup != NULL) {
+        ioloop_cancel_wake_event(interface->deconfigure_wakeup);
+    }
+    if (interface->vicarious_discovery_complete != NULL) {
+        ioloop_cancel_wake_event(interface->vicarious_discovery_complete);
+    }
+    for (router = interface->routers; router; router = next) {
+        next = router->next;
+        icmp_message_free(router);
+    }
+    interface->routers = NULL;
+    if (interface->ip_configuration_service != NULL) {
+        CFRelease(interface->ip_configuration_service);
+        interface->ip_configuration_service = NULL;
+    }
+    interface->last_beacon = interface->next_beacon = 0;
+    interface->deprecate_deadline = 0;
+    interface->preferred_lifetime = interface->valid_lifetime = 0;
+    interface->num_solicits_sent = 0;
+    interface->inactive = true;
+    interface->ineligible = true;
+    interface->advertise_ipv6_prefix = false;
+    interface->have_link_layer_address = false;
+    interface->on_link_prefix_configured = false;
+    interface->sent_first_beacon = false;
+    interface->num_beacons_sent = 0;
+    interface->router_discovery_complete = false;
+    interface->router_discovery_in_progress = false;
+    interface->vicarious_router_discovery_in_progress = false;
+    interface->link = NULL;
+}
+
+static void
+interface_prefix_evaluate(interface_t *interface)
+{
+    char hexbuf[60];
+
+    // We are assuming here that the network signature can't change without us seeing a state transition.
+    // Cases where this assumption could be violated include unplugging a WiFi base station configured as
+    // a bridge from one ethernet network and plugging it into a different one.  We could trigger a
+    // re-evaluation when an IPv4 address on an interface changes, and also when there had been a prefix
+    // advertised and no longer is.
+    get_network_signature(interface);
+
+    // This should only happen if we're really low on memory.
+    if (interface->link == NULL) {
+        INFO("interface_prefix_evaluate: newly active interface " PUB_S_SRP " has no link.", interface->name);
+        return;
+    } else {
+        if (interface->link->primary != NULL &&
+            (interface->link->primary->inactive || interface->link->primary->ineligible))
+        {
+            INFO("Removing primary interface " PUB_S_SRP " from link because it's inactive or ineligible.",
+                 interface->link->primary->name);
+            interface->link->primary = NULL;
+        }
+
+        if (interface->link->primary == NULL) {
+            // Make this interface primary for the link.
+            interface->link->primary = interface;
+
+            // Set up the interface prefix using the prefix number for the link.
+            interface->ipv6_prefix = ula_prefix;
+            interface->ipv6_prefix.s6_addr[6] = interface->link->prefix_number >> 8;
+            interface->ipv6_prefix.s6_addr[7] = interface->link->prefix_number & 255;
+
+            dump_network_signature(hexbuf, sizeof(hexbuf), interface->link->signature,
+                                   interface->link->signature_length);
+            SEGMENTED_IPv6_ADDR_GEN_SRP(interface->ipv6_prefix.s6_addr, ipv6_prefix_buf);
+            INFO("Interface " PUB_S_SRP " is now primary for network " PRI_S_SRP " with prefix "
+                 PRI_SEGMENTED_IPv6_ADDR_SRP, interface->name, hexbuf,
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, ipv6_prefix_buf));
+        } else {
+            if (interface->link->primary != interface) {
+                INFO("interface_prefix_evaluate: not setting up " PUB_S_SRP " because interface " PUB_S_SRP
+                     " is primary for the link.", interface->name, interface->link->primary->name);
+            }
+        }
+    }
+}
+
+static void
+interface_active_state_evaluate(interface_t *interface, bool active_known, bool active)
+{
+    INFO("interface_active_state_evaluate: evaluating interface active status - ifname: " PUB_S_SRP
+         ", active_known: " PUB_S_SRP ", active: " PUB_S_SRP ", inactive: " PUB_S_SRP,
+         interface->name, active_known ? "true" : "false", active ? "true" : "false",
+         interface->inactive ? "true" : "false");
+
+    if (active_known && !active) {
+        if (!interface->inactive) {
+            // If we are the primary interface for the link to which we were connected, see if there's
+            // another interface on the link and in any case make this interface not primary for that
+             // link.
+            if (interface->link != NULL && interface->link->primary == interface) {
+                interface_t *scan;
+                interface->link->primary = NULL;
+                for (scan = interfaces; scan; scan = scan->next) {
+                    if (scan != interface && scan->link == interface->link && !scan->inactive && !scan->ineligible) {
+                        // Set up the thread-local prefix
+                        interface_prefix_evaluate(scan);
+
+                        // We need to reevaluate routing policy on the new primary interface now, because
+                        // there may be no new event there to trigger one.
+                        routing_policy_evaluate(scan, true);
+                        break;
+                    }
+                }
+            }
+
+            // Clean the slate.
+            icmp_interface_subscribe(interface, false);
+            interface_shutdown(interface);
+
+            // Zero IPv4 addresses.
+            interface->num_ipv4_addresses = 0;
+
+            INFO("interface_active_state_evaluate: interface went down - ifname: " PUB_S_SRP, interface->name);
+        }
+    } else if (active_known) {
+        if (interface->inactive) {
+            INFO("interface_active_state_evaluate: interface " PUB_S_SRP " showed up.", interface->name);
+#ifdef RA_TESTER
+            if (!strcmp(interface->name, thread_interface_name) || !strcmp(interface->name, home_interface_name)) {
+#endif
+                // Zero IPv4 addresses.
+                interface->num_ipv4_addresses = 0;
+
+                icmp_interface_subscribe(interface, true);
+                interface->inactive = false;
+
+                interface_prefix_evaluate(interface);
+#ifndef RA_TESTER
+                if (partition_can_provide_routing) {
+#endif
+                    router_discovery_start(interface);
+
+                    // If we already have a thread prefix, trigger beaconing now.
+                    if (published_thread_prefix != NULL || adopted_thread_prefix != NULL) {
+                        interface_beacon_schedule(interface, 0);
+                    } else {
+                        INFO("No prefix on thread network, so not scheduling beacon.");
+                    }
+#ifndef RA_TESTER
+                } else {
+                    INFO("Can't provide routing, so not scheduling beacon.");
+                }
+#endif
+#ifdef RA_TESTER
+            } else {
+                INFO("interface_active_state_evaluate: skipping interface " PUB_S_SRP
+                     " because it's not home or thread.", interface->name);
+            }
+#endif
+        }
+    }
+}
+
+static void
+nw_interface_state_changed(nw_interface_t iface, int sock, const char *name, bool ineligible)
+{
+    int ifindex = nw_interface_get_index(iface);
+    bool active = true;
+    bool active_known = false;
+    interface_t *interface;
+    struct ifmediareq media_request;
+
+    interface = find_interface(name, ifindex);
+    if (interface == NULL) {
+        return;
+    }
+
+    if (ineligible) {
+        return;
+    }
+    if (interface->ineligible) {
+        INFO("nw_interface_state_changed: interface " PUB_S_SRP " is eligible to be used for routing.", name);
+    }
+    interface->ineligible = false;
+
+    INFO("nw_interface_state_changed: interface " PUB_S_SRP " index %d: " PUB_S_SRP ", " PUB_S_SRP ")",
+         name, ifindex, (active_known ? (active ? "active" : "inactive") : "unknown"),
+         ineligible ? "ineligible" : "eligible");
+
+    if (sock > 0) {
+        memset(&media_request, 0, sizeof(media_request));
+        strlcpy(media_request.ifm_name, name, sizeof(media_request.ifm_name));
+        if (ioctl(sock, SIOCGIFXMEDIA, (caddr_t)&media_request) >= 0) {
+            if (media_request.ifm_status & IFM_ACTIVE) {
+                active = true;
+                active_known = true;
+            } else {
+                active = false;
+                active_known = true;
+            }
+        }
+    } else {
+        active = false;
+        active_known = false;
+    }
+
+    if (interface->index == -1) {
+        interface->index = ifindex;
+    }
+    interface_active_state_evaluate(interface, active_known, active);
+}
+
+static void
+ifaddr_callback(void *__unused context, const char *name, const addr_t *address, const addr_t *mask,
+                unsigned flags, enum interface_address_change change)
+{
+    char addrbuf[INET6_ADDRSTRLEN];
+    const uint8_t *addrbytes, *maskbytes, *prefp;
+    int preflen, i;
+    interface_t *interface;
+    bool is_thread_interface = false;
+
+    interface = find_interface(name, -1);
+    if (interface == NULL) {
+        ERROR("ifaddr_callback: find_interface returned NULL for " PUB_S_SRP, name);
+        return;
+    }
+
+    if (thread_interface_name != NULL && !strcmp(name, thread_interface_name)) {
+        is_thread_interface = true;
+    }
+
+    if (address->sa.sa_family == AF_INET) {
+        addrbytes = (uint8_t *)&address->sin.sin_addr;
+        maskbytes = (uint8_t *)&mask->sin.sin_addr;
+        prefp = maskbytes + 3;
+        preflen = 32;
+        if (change == interface_address_added) {
+            // Just got an IPv4 address?
+            if (!interface->num_ipv4_addresses) {
+                interface_prefix_evaluate(interface);
+            }
+            interface->num_ipv4_addresses++;
+        } else if (change == interface_address_deleted) {
+            interface->num_ipv4_addresses--;
+            // Just lost our last IPv4 address?
+            if (!interface->num_ipv4_addresses) {
+                interface_prefix_evaluate(interface);
+            }
+        }
+    } else if (address->sa.sa_family == AF_INET6) {
+        addrbytes = (uint8_t *)&address->sin6.sin6_addr;
+        maskbytes = (uint8_t *)&mask->sin6.sin6_addr;
+        prefp = maskbytes + 15;
+        preflen = 128;
+    } else if (address->sa.sa_family == AF_LINK) {
+        snprintf(addrbuf, sizeof addrbuf, "%02x:%02x:%02x:%02x:%02x:%02x",
+                 address->ether_addr.addr[0], address->ether_addr.addr[1],
+                 address->ether_addr.addr[2], address->ether_addr.addr[3],
+                 address->ether_addr.addr[4], address->ether_addr.addr[5]);
+        prefp = (uint8_t *)&addrbuf[0]; maskbytes = prefp + 1; // Skip prefix length calculation
+        preflen = 0;
+        addrbytes = NULL;
+    } else {
+        INFO("ifaddr_callback: Unknown address type %d", address->sa.sa_family);
+        return;
+    }
+
+    if (change != interface_address_unchanged) {
+        if (address->sa.sa_family == AF_LINK) {
+            if (!interface->ineligible) {
+                INFO("ifaddr_callback: interface " PUB_S_SRP PUB_S_SRP " " PUB_S_SRP " " PRI_MAC_ADDR_SRP " flags %x",
+                     name, is_thread_interface ? " (thread)" : "",
+                     change == interface_address_added ? "added" : "removed",
+                     MAC_ADDR_PARAM_SRP(address->ether_addr.addr), flags);
+            }
+        } else {
+            for (; prefp >= maskbytes; prefp--) {
+                if (*prefp) {
+                    break;
+                }
+                preflen -= 8;
+            }
+            for (i = 0; i < 8; i++) {
+                if (*prefp & (1<<i)) {
+                    break;
+                }
+                --preflen;
+            }
+            inet_ntop(address->sa.sa_family, addrbytes, addrbuf, sizeof addrbuf);
+            if (!interface->ineligible) {
+                if (address->sa.sa_family == AF_INET) {
+                    IPv4_ADDR_GEN_SRP(addrbytes, addr_buf);
+                    INFO("ifaddr_callback: interface " PUB_S_SRP PUB_S_SRP " " PUB_S_SRP " " PRI_IPv4_ADDR_SRP
+                         "/%d flags %x", name, is_thread_interface ? " (thread)" : "",
+                         change == interface_address_added ? "added" : "removed",
+                         IPv4_ADDR_PARAM_SRP(addrbytes, addr_buf), preflen, flags);
+                } else if (address->sa.sa_family == AF_INET6) {
+                    SEGMENTED_IPv6_ADDR_GEN_SRP(addrbytes, addr_buf);
+                    INFO("ifaddr_callback: interface " PUB_S_SRP PUB_S_SRP " " PUB_S_SRP " " PRI_SEGMENTED_IPv6_ADDR_SRP
+                         "/%d flags %x", name, is_thread_interface ? " (thread)" : "",
+                         change == interface_address_added ? "added" : "removed",
+                         SEGMENTED_IPv6_ADDR_PARAM_SRP(addrbytes, addr_buf), preflen, flags);
+                } else {
+                    INFO("ifaddr_callback - invalid sa_family: %d", address->sa.sa_family);
+                }
+
+                // When new IP address is removed, it is possible that the existing router information, such as
+                // PIO and RIO is no longer valid since srp-mdns-proxy is losing its IP address. In order to let it to
+                // flush the stale router information as soon as possible, we mark all the router as stale immediately,
+                // by setting the router received time to a value which is 601s ago (router will be stale if the router
+                // information is received for more than 600s). And then do router discovery for 20s, so we can ensure
+                // that all the stale router information will be updated during the discovery, or flushed away. If all
+                // routers are flushed, then srp-mdns-proxy will advertise its own prefix and configure the new IPv6
+                // address.
+                if ((address->sa.sa_family == AF_INET || address->sa.sa_family == AF_INET6) &&
+                    change == interface_address_deleted) {
+                    INFO("ifaddr_callback: making all routers stale and start router discovery due to removed address");
+                    adjust_router_received_time(interface, ioloop_timenow(),
+                                                -(MAX_ROUTER_RECEIVED_TIME_GAP_BEFORE_STALE + MSEC_PER_SEC));
+                    routing_policy_evaluate(interface, false);
+                }
+            }
+        }
+    }
+
+    // Not a broadcast interface
+    if (flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) {
+        // Not the thread interface
+        if (!is_thread_interface) {
+            return;
+        }
+    }
+
+    // 169.254.*
+    if (address->sa.sa_family == AF_INET && IN_LINKLOCAL(address->sin.sin_addr.s_addr)) {
+        return;
+    }
+
+    if (interface->index == -1) {
+        interface->index = address->ether_addr.index;
+    }
+    interface->is_thread = is_thread_interface;
+
+#if TARGET_OS_TV && !defined(RA_TESTER)
+    if (is_thread_interface && address->sa.sa_family == AF_INET6) {
+        partition_utun0_address_changed(&address->sin6.sin6_addr, change);
+    }
+#endif
+
+    if (address->sa.sa_family == AF_INET) {
+    } else if (address->sa.sa_family == AF_INET6) {
+        if (IN6_IS_ADDR_LINKLOCAL(&address->sin6.sin6_addr)) {
+            interface->link_local = address->sin6.sin6_addr;
+        }
+    } else if (address->sa.sa_family == AF_LINK) {
+        if (address->ether_addr.len == 6) {
+            memcpy(interface->link_layer, address->ether_addr.addr, 6);
+            interface->have_link_layer_address = true;
+        }
+    }
+}
+
+static void
+refresh_interface_list(void)
+{
+    ioloop_map_interface_addresses(NULL, ifaddr_callback);
+}
+
+static void
+nw_path_event(nw_path_t path)
+{
+    int sock = socket(PF_INET6, SOCK_DGRAM, 0);
+    INFO("nw_path_event");
+    nw_path_enumerate_interfaces(path, ^bool (nw_interface_t NONNULL iface) {
+            const char *name = nw_interface_get_name(iface);
+            CFStringRef sc_name = CFStringCreateWithCStringNoCopy(NULL, name, kCFStringEncodingUTF8, kCFAllocatorNull);
+            if (sc_name != NULL) {
+                SCNetworkInterfaceRef _SCNetworkInterfaceCreateWithBSDName(CFAllocatorRef allocator,
+                                                                           CFStringRef bsdName, UInt32 flags);
+                SCNetworkInterfaceRef sc_interface = _SCNetworkInterfaceCreateWithBSDName(NULL, sc_name, 0);
+                CFRelease(sc_name);
+                if (sc_interface == NULL) {
+                    ERROR("SCNetworkInterfaceCreateWithBSDName failed");
+                    nw_interface_state_changed(iface, sock, name, true);
+                    goto out;
+                } else {
+                    CFStringRef sc_type = SCNetworkInterfaceGetInterfaceType(sc_interface);
+                    if (sc_type == NULL) {
+                        ERROR("Unable to get interface type on " PUB_S_SRP, name);
+                        CFRelease(sc_interface);
+                        goto out;
+                    }
+                    CFStringRef _SCNetworkInterfaceGetIOPath(SCNetworkInterfaceRef interface);
+                    CFStringRef io_path = _SCNetworkInterfaceGetIOPath(sc_interface);
+                    bool is_usb = false;
+                    if (io_path == NULL) {
+                        if (strncmp(name, "utun", 4)) {
+                            ERROR("Unable to get interface I/O path.");
+                        }
+                    } else {
+#ifdef DEBUG_VERBOSE
+                        char pathname[1024];
+                        CFStringGetCString(io_path, pathname, sizeof(pathname), kCFStringEncodingUTF8);
+                        INFO("Interface " PUB_S_SRP " I/O Path: " PRI_S_SRP, name, pathname);
+#endif
+                        CFRange match = CFStringFind(io_path, CFSTR("AppleUSBDeviceNCMData"), 0);
+                        if (match.location != kCFNotFound) {
+                            is_usb = true;
+                        }
+                    }
+                    if (CFEqual(sc_type, kSCNetworkInterfaceTypeIEEE80211) ||
+                        (CFEqual(sc_type, kSCNetworkInterfaceTypeEthernet) && !is_usb))
+                    {
+                        nw_interface_state_changed(iface, sock, name, false);
+                    } else {
+                        nw_interface_state_changed(iface, sock, name, true);
+                    }
+                    CFRelease(sc_interface);
+                out:
+                    ;
+                }
+            } else {
+                nw_interface_state_changed(iface, sock, name, true);
+            }
+            return true;
+        });
+    close(sock);
+
+#if TARGET_OS_TV && !defined(RA_TESTER)
+    // If we do not have an active interface, we can't be advertising SRP service.
+    interface_t *interface;
+    bool active = false;
+    for (interface = interfaces; interface; interface = interface->next) {
+        if (!interface->ineligible && !interface->inactive) {
+            active = true;
+        }
+    }
+    if (active && !have_non_thread_interface) {
+        INFO("nw_path_event: we have an active interface");
+        have_non_thread_interface = true;
+        partition_can_advertise_service = true;
+    } else if (!active && have_non_thread_interface) {
+        INFO("nw_path_event: we no longer have an active interface");
+        have_non_thread_interface = false;
+        // Stop advertising the service, if we are doing so.
+        partition_discontinue_srp_service();
+    }
+#endif // TARGET_OS_TV && !defined(RA_TESTER)
+
+    refresh_interface_list();
+}
+
+#if TARGET_OS_TV && !defined(RA_TESTER)
+#ifdef GET_TUNNEL_NAME_WITH_WPANCTL
+static void
+thread_interface_output(io_t *io, void *__unused context)
+{
+    char inbuf[512];
+    ssize_t rv;
+    char *s, *t;
+
+    // We are assuming that wpanctl will never do partial-line writes.
+    rv = read(io->fd, inbuf, sizeof(inbuf) - 1);
+    if (rv < 0) {
+        ERROR("thread_interface_output: " PUB_S_SRP, strerror(errno));
+    }
+    INFO("read %" PRIs64 " bytes from wpanctl output", rv);
+    if (rv <= 0) {
+        INFO("Done with thread interface output.");
+        ioloop_close(io);
+    } else {
+        if (inbuf[rv - 1] == '\n') {
+            inbuf[rv - 1] = 0;
+            s = strchr(inbuf, '>');
+            if (s == NULL) {
+            bad:
+                ERROR("Bad wpanctl output: " PUB_S_SRP, inbuf);
+                return;
+            }
+            s = strchr(s, '(');
+            if (s == NULL) {
+                goto bad;
+            }
+            // We don't expect the end of string here.
+            if (*++s == '\0') {
+                goto bad;
+            }
+            t = strchr(s, ')');
+            if (s == t || t == NULL) {
+                goto bad;
+            }
+            *t = '\0';
+            if (num_thread_interfaces != 0) {
+                INFO("Already have a thread interface.");
+            } else {
+                num_thread_interfaces = 1;
+                thread_interface_name = strdup(s);
+                if (thread_interface_name == NULL) {
+                    ERROR("No memory to save thread interface name " PUB_S_SRP, s);
+                    return;
+                }
+                INFO("Thread interface at " PUB_S_SRP, thread_interface_name);
+                partition_got_tunnel_name();
+            }
+        } else {
+            goto bad;
+        }
+    }
+}
+
+static void
+thread_interface_done(void *__unused context, int status, const char *error)
+{
+    if (error != NULL) {
+        ERROR("thread_interface_done: " PUB_S_SRP, error);
+    } else {
+        INFO("thread_interface_done: %d.", status);
+    }
+    ioloop_subproc_release(thread_interface_enumerator_process);
+}
+#endif // GET_TUNNEL_NAME_WITH_WPANCTL
+
+static void
+cti_get_tunnel_name_callback(void *__unused context, const char *name, cti_status_t status)
+{
+    if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) {
+        INFO("cti_get_tunnel_name_callback: disconnected");
+        adv_xpc_disconnect();
+        return;
+    }
+
+    INFO("cti_get_tunnel_name_callback: " PUB_S_SRP " %d", name != NULL ? name : "<null>", status);
+    if (status != kCTIStatus_NoError) {
+        return;
+    }
+    num_thread_interfaces = 1;
+    if (thread_interface_name != NULL) {
+        free(thread_interface_name);
+    }
+    thread_interface_name = strdup(name);
+    if (thread_interface_name == NULL) {
+        ERROR("No memory to save thread interface name " PUB_S_SRP, name);
+        return;
+    }
+    INFO("Thread interface at " PUB_S_SRP, thread_interface_name);
+    partition_got_tunnel_name();
+}
+
+static void
+cti_get_role_callback(void *__unused context, cti_network_node_type_t role, cti_status_t status)
+{
+    bool am_thread_router = false;
+
+    if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) {
+        INFO("cti_get_role_callback: disconnected");
+        adv_xpc_disconnect();
+        return;
+    }
+
+    partition_last_role_change = ioloop_timenow();
+
+    if (status == kCTIStatus_NoError) {
+        if (role == kCTI_NetworkNodeType_Router || role == kCTI_NetworkNodeType_Leader) {
+            am_thread_router = true;
+        }
+
+        INFO("role is: " PUB_S_SRP " (%d)\n ", am_thread_router ? "router" : "not router", role);
+    } else {
+        ERROR("cti_get_role_callback: nonzero status %d", status);
+    }
+
+    // Our thread role doesn't actually matter, but it's useful to report it in the logs.
+}
+
+static void
+cti_get_state_callback(void *__unused context, cti_network_state_t state, cti_status_t status)
+{
+    bool associated = false;
+
+    if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) {
+        INFO("cti_get_state_callback: disconnected");
+        adv_xpc_disconnect();
+        return;
+    }
+
+    partition_last_state_change = ioloop_timenow();
+
+    if (status == kCTIStatus_NoError) {
+        if ((state == kCTI_NCPState_Associated)     || (state == kCTI_NCPState_Isolated) ||
+            (state == kCTI_NCPState_NetWake_Asleep) || (state == kCTI_NCPState_NetWake_Waking))
+        {
+            associated = true;
+        }
+
+        INFO("state is: " PUB_S_SRP " (%d)\n ", associated ? "associated" : "not associated", state);
+    } else {
+        ERROR("cti_get_state_callback: nonzero status %d", status);
+    }
+
+    if (current_thread_state != state) {
+        if (associated) {
+            current_thread_state = state;
+            partition_maybe_enable_services(); // but probably not
+        } else {
+            current_thread_state = state;
+            partition_disable_service();
+        }
+    }
+}
+
+static void
+cti_get_partition_id_callback(void *__unused context, int32_t partition_id, cti_status_t status)
+{
+    if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) {
+        INFO("cti_get_partition_id_callback: disconnected");
+        adv_xpc_disconnect();
+        return;
+    }
+
+    if (status == kCTIStatus_NoError) {
+        INFO("Partition ID changed to %" PRIu32, partition_id);
+        thread_partition_id[0] = (uint8_t)((partition_id >> 24) & 255);
+        thread_partition_id[1] = (uint8_t)((partition_id >> 16) & 255);
+        thread_partition_id[2] = (uint8_t)((partition_id >> 8) & 255);
+        thread_partition_id[3] = (uint8_t)(partition_id & 255);
+
+        partition_id_changed();
+    } else {
+        ERROR("cti_get_state_callback: nonzero status %d", status);
+    }
+}
+
+static void
+thread_service_note(thread_service_t *service, const char *event_description)
+{
+    uint16_t port;
+
+    port = (service->port[0] << 8) | service->port[1];
+    SEGMENTED_IPv6_ADDR_GEN_SRP(service->address, service_add_buf);
+    INFO("SRP service " PRI_SEGMENTED_IPv6_ADDR_SRP "%%%d " PUB_S_SRP,
+         SEGMENTED_IPv6_ADDR_PARAM_SRP(service->address, service_add_buf),
+         port, event_description);
+}
+
+static void
+thread_pref_id_note(thread_pref_id_t *pref_id, const char *event_description)
+{
+    struct in6_addr addr;
+
+    addr.s6_addr[0] = 0xfd;
+    memcpy(&addr.s6_addr[1], pref_id->prefix, 5);
+    memset(&addr.s6_addr[6], 0, 10);
+    SEGMENTED_IPv6_ADDR_GEN_SRP(addr.s6_addr, addr_buf);
+    INFO("pref:id " PRI_SEGMENTED_IPv6_ADDR_SRP ":%02x%02x%02x%02x " PUB_S_SRP,
+         SEGMENTED_IPv6_ADDR_PARAM_SRP(addr.s6_addr, addr_buf),
+         pref_id->partition_id[0], pref_id->partition_id[1], pref_id->partition_id[2], pref_id->partition_id[3],
+         event_description);
+}
+
+typedef struct state_debug_accumulator {
+    char change[20]; // " +stable +user +ncp"
+    char *p_change;
+    size_t left;
+    bool changed;
+} accumulator_t;
+
+static void
+accumulator_init(accumulator_t *accumulator)
+{
+    memset(accumulator, 0, sizeof(*accumulator));
+    accumulator->p_change = accumulator->change;
+    accumulator->left = sizeof(accumulator->change);
+}
+
+static void
+accumulate(accumulator_t *accumulator, bool previous, bool cur, const char *name)
+{
+    size_t len;
+    if (previous != cur) {
+        snprintf(accumulator->p_change, accumulator->left, "%s%s%s",
+                 accumulator->p_change == accumulator->change ? "" : " ", cur ? "+" : "-", name);
+        len = strlen(accumulator->p_change);
+        accumulator->p_change += len;
+        accumulator->left -= len;
+        accumulator->changed = true;
+    }
+}
+
+static void
+cti_service_list_callback(void *__unused context, cti_service_vec_t *services, cti_status_t status)
+{
+    size_t i;
+    thread_service_t **pservice = &thread_services, *service = NULL;
+    thread_pref_id_t **ppref_id = &thread_pref_ids, *pref_id = NULL;
+
+    if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) {
+        INFO("cti_get_service_list_callback: disconnected");
+        adv_xpc_disconnect();
+        return;
+    }
+
+    if (status != kCTIStatus_NoError) {
+        ERROR("cti_get_service_list_callback: %d", status);
+    } else {
+        // Delete any SRP services that are not in the list provided by Thread.
+        while (*pservice != NULL) {
+            service = *pservice;
+            for (i = 0; i < services->num; i++) {
+                cti_service_t *cti_service = services->services[i];
+                // Is this a valid SRP service?
+                if (IS_SRP_SERVICE(cti_service)) {
+                    // Is this service still present?
+                    if (!memcmp(&service->address, cti_service->server, 16) &&
+                        !memcmp(&service->port, &cti_service->server[16], 2)) {
+                        break;
+                    }
+                }
+            }
+            if (i == services->num) {
+                thread_service_note(service, "went away");
+                *pservice = service->next;
+                RELEASE_HERE(service, thread_service_finalize);
+            } else {
+                // We'll re-initialize these flags from the service list when we check for duplicates.
+                service->previous_user = service->user;
+                service->user = false;
+                service->previous_stable = service->stable;
+                service->stable = false;
+                service->previous_ncp = service->ncp;
+                service->ncp = false;
+                pservice = &service->next;
+            }
+        }
+        // On exit, pservice is pointing to the end-of-list pointer.
+
+        // Delete any pref_id services that are not in the list provided by Thread.
+        while (*ppref_id != NULL) {
+            pref_id = *ppref_id;
+            for (i = 0; i < services->num; i++) {
+                cti_service_t *cti_service = services->services[i];
+                // Is this an SRP service?
+                if (IS_PREF_ID_SERVICE(cti_service)) {
+                    // Is this service still present?
+                    if (!memcmp(&pref_id->partition_id, cti_service->server, 4) &&
+                        !memcmp(pref_id->prefix, &cti_service->server[4], 5))
+                    {
+                        break;
+                    }
+                }
+            }
+            if (i == services->num) {
+                thread_pref_id_note(pref_id, "went away");
+                *ppref_id = pref_id->next;
+                RELEASE_HERE(pref_id, thread_pref_id_finalize);
+            } else {
+                // We'll re-initialize these flags from the service list when we check for duplicates.
+                pref_id->previous_user = pref_id->user;
+                pref_id->user = false;
+                pref_id->previous_stable = pref_id->stable;
+                pref_id->stable = false;
+                pref_id->previous_ncp = pref_id->ncp;
+                pref_id->ncp = false;
+                ppref_id = &pref_id->next;
+            }
+        }
+        // On exit, pservice is pointing to the end-of-list pointer.
+
+        // Add any services that are not present.
+        for (i = 0; i < services->num; i++) {
+            cti_service_t *cti_service = services->services[i];
+            if (IS_SRP_SERVICE(cti_service)) {
+                for (service = thread_services; service != NULL; service = service->next) {
+                    if (!memcmp(&service->address, cti_service->server, 16) &&
+                        !memcmp(&service->port, &cti_service->server[16], 2)) {
+                        break;
+                    }
+                }
+                if (service == NULL) {
+                    service = thread_service_create(cti_service->server, &cti_service->server[16]);
+                    if (service == NULL) {
+                        ERROR("cti_service_list_callback: no memory for service.");
+                    } else {
+                        thread_service_note(service, "showed up");
+                        *pservice = service;
+                        pservice = &service->next;
+                    }
+                }
+                // Also, since we're combing the list, update ncp, user and stable flags.   Note that a service can
+                // appear more than once in the thread service list.
+                if (service != NULL) {
+                    if (cti_service->flags & kCTIFlag_NCP) {
+                        service->ncp = true;
+                    } else {
+                        service->user = true;
+                    }
+                    if (cti_service->flags & kCTIFlag_Stable) {
+                        service->stable = true;
+                    }
+                }
+            } else if (IS_PREF_ID_SERVICE(cti_service)) {
+                for (pref_id = thread_pref_ids; pref_id != NULL; pref_id = pref_id->next) {
+                    if (!memcmp(&pref_id->partition_id, cti_service->server, 4) &&
+                        !memcmp(pref_id->prefix, &cti_service->server[4], 5))
+                    {
+                        break;
+                    }
+                }
+                if (pref_id == NULL) {
+                    pref_id = thread_pref_id_create(cti_service->server, &cti_service->server[4]);
+                    if (pref_id == NULL) {
+                        ERROR("cti_service_list_callback: no memory for pref_id.");
+                    } else {
+                        thread_pref_id_note(pref_id, "showed up");
+                        *ppref_id = pref_id;
+                        ppref_id = &pref_id->next;
+                    }
+                }
+                // Also, since we're combing the list, update ncp, user and stable flags.   Note that a pref_id can
+                // appear more than once in the thread pref_id list.
+                if (pref_id != NULL) {
+                    if (!pref_id->ncp && (cti_service->flags & kCTIFlag_NCP)) {
+                        pref_id->ncp = true;
+                    } else if (!pref_id->user && !(cti_service->flags & kCTIFlag_NCP)) {
+                        pref_id->user = true;
+                    }
+                    if (cti_service->flags & kCTIFlag_Stable) {
+                        pref_id->stable = true;
+                    }
+                }
+            }
+        }
+
+        accumulator_t accumulator;
+        for (service = thread_services; service != NULL; service = service->next) {
+            accumulator_init(&accumulator);
+            accumulate(&accumulator, service->previous_ncp, service->ncp, "ncp");
+            accumulate(&accumulator, service->previous_stable, service->ncp, "stable");
+            accumulate(&accumulator, service->previous_user, service->user, "user");
+            if (accumulator.changed) {
+                thread_service_note(service, accumulator.change);
+            }
+        }
+        for (pref_id = thread_pref_ids; pref_id != NULL; pref_id = pref_id->next) {
+            accumulator_init(&accumulator);
+            accumulate(&accumulator, pref_id->previous_ncp, pref_id->ncp, "ncp");
+            accumulate(&accumulator, pref_id->previous_stable, pref_id->ncp, "stable");
+            accumulate(&accumulator, pref_id->previous_user, pref_id->user, "user");
+            if (accumulator.changed) {
+                thread_pref_id_note(pref_id, accumulator.change);
+            }
+        }
+
+        // At this point the thread prefix list contains the same information as what we just received.
+        // Trigger a "prefix set changed" event.
+        partition_service_set_changed();
+    }
+}
+
+static void
+cti_prefix_list_callback(void *__unused context, cti_prefix_vec_t *prefixes, cti_status_t status)
+{
+    size_t i;
+    thread_prefix_t **ppref = &thread_prefixes, *prefix = NULL;
+
+    if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) {
+        INFO("cti_get_prefix_list_callback: disconnected");
+        adv_xpc_disconnect();
+        return;
+    }
+
+    if (status != kCTIStatus_NoError) {
+        ERROR("cti_get_prefix_list_callback: %d", status);
+    } else {
+        // Delete any prefixes that are not in the list provided by Thread.
+        while (*ppref != NULL) {
+            prefix = *ppref;
+            for (i = 0; i < prefixes->num; i++) {
+                cti_prefix_t *cti_prefix = prefixes->prefixes[i];
+                // Is this prefix still present?
+                if (!memcmp(&prefix->prefix, &cti_prefix->prefix, 8)) {
+                    break;
+                }
+            }
+            if (i == prefixes->num) {
+                *ppref = prefix->next;
+                SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
+                INFO("cti_prefix_list_callback: prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " went away",
+                         SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf));
+                RELEASE_HERE(prefix, thread_prefix_finalize);
+            } else {
+                // We'll re-initialize these flags from the prefix list when we check for duplicates.
+                prefix->user = false;
+                prefix->stable = false;
+                prefix->ncp = false;
+                ppref = &prefix->next;
+            }
+        }
+        // On exit, ppref is pointing to the end-of-list pointer.
+
+        // Add any prefixes that are not present.
+        for (i = 0; i < prefixes->num; i++) {
+            cti_prefix_t *cti_prefix = prefixes->prefixes[i];
+            for (prefix = thread_prefixes; prefix != NULL; prefix = prefix->next) {
+                if (!memcmp(&prefix->prefix, &cti_prefix->prefix, 16)) {
+                    break;
+                }
+            }
+            if (prefix == NULL) {
+                prefix = thread_prefix_create(&cti_prefix->prefix, cti_prefix->prefix_length);
+                if (prefix == NULL) {
+                    ERROR("cti_prefix_list_callback: no memory for prefix.");
+                } else {
+                    SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
+                    INFO("cti_prefix_list_callback: prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " showed up",
+                         SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf));
+                    *ppref = prefix;
+                    ppref = &prefix->next;
+                }
+            }
+            // Also, since we're combing the list, update ncp, user and stable flags.   Note that a prefix can
+            // appear more than once in the thread prefix list.
+            if (prefix != NULL) {
+                if (cti_prefix->flags & kCTIFlag_NCP) {
+                    prefix->ncp = true;
+                } else {
+                    prefix->user = true;
+                }
+                if (cti_prefix->flags & kCTIFlag_Stable) {
+                    prefix->stable = true;
+                }
+            }
+        }
+
+        // At this point the thread prefix list contains the same information as what we just received.
+        // Trigger a "prefix set changed" event.
+        partition_prefix_set_changed();
+    }
+}
+
+static void
+get_thread_interface_list(void)
+{
+#ifdef GET_TUNNEL_NAME_WITH_WPANCTL
+    char *args[] = { "list" };
+    INFO("/usr/local/bin/wpanctl list");
+    thread_interface_enumerator_process = ioloop_subproc("/usr/local/bin/wpanctl", args, 1,
+                                                         thread_interface_done,
+                                                         thread_interface_output, NULL);
+    if (thread_interface_enumerator_process == NULL) {
+        ERROR("Unable to enumerate thread interfaces.");
+    }
+#endif
+
+}
+#endif // TARGET_OS_TV && !RA_TESTER
+
+#ifdef TCPDUMP_LOGGER
+static void
+tcpdump_output(io_t *io, void *__unused context)
+{
+    static char inbuf[1024];
+    static int offset;
+    ssize_t rv;
+    char *s;
+
+    if (offset + 1 != sizeof(inbuf)) {
+        rv = read(io->fd, &inbuf[offset], sizeof(inbuf) - 1 - offset);
+        if (rv < 0) {
+            ERROR("tcpdump_output: " PUB_S_SRP, strerror(errno));
+            return;
+        }
+        if (rv <= 0) {
+            INFO("Done with thread interface output.");
+            ioloop_close(io);
+            return;
+        }
+        offset += rv;
+    }
+    inbuf[offset] = 0;
+    if (offset + 1 == sizeof(inbuf)) {
+        s = &inbuf[offset];
+    } else {
+        s = strchr(inbuf, '\n');
+        if (s == NULL) {
+            return;
+        }
+        *s = 0;
+    }
+    INFO(PUB_S_SRP, inbuf);
+    if (s != &inbuf[offset]) {
+        memmove(inbuf, s, &inbuf[offset] - s);
+    }
+    offset = 0;
+}
+
+static void
+tcpdump_done(void *__unused context, int status, const char *error)
+{
+    if (error != NULL) {
+        ERROR("tcpdump_done: " PUB_S_SRP, error);
+    } else {
+        INFO("tcpdump_done: %d.", status);
+    }
+    ioloop_subproc_release(tcpdump_logger_process);
+}
+
+static void
+start_tcpdump_logger(void)
+{
+    char *args[] = { "-vv", "-s", "1500", "-n", "-e", "-i", "utun0", "-l", "udp", "port", "53" };
+    INFO("/usr/sbin/tcpdump -vv -s 1500 -n -e -i utun0 udp port 53");
+    tcpdump_logger_process = ioloop_subproc("/usr/sbin/tcpdump", args, 11, tcpdump_done,
+                                            tcpdump_output, NULL);
+    if (tcpdump_logger_process == NULL) {
+        ERROR("Unable to start tcpdump logger.");
+    }
+}
+#endif // TCPDUMP_LOGGER
+
+void
+thread_network_startup(void)
+{
+    INFO("thread_network_startup: Thread network started.");
+
+#ifdef MONTIOR_ROUTING_SOCKET
+    start_route_listener();
+#else
+    int error = 0;
+    nw_parameters_t params = nw_parameters_create();
+    if (path_evaluator != NULL) {
+        nw_path_evaluator_cancel(path_evaluator);
+        nw_release(path_evaluator);
+    }
+    path_evaluator = nw_path_create_evaluator_for_listener(params, &error);
+    nw_release(params);
+    if (path_evaluator == NULL || error != 0) {
+        ERROR("thread_network_startup: Unable to create network path evaluator.");
+        return;
+    }
+    nw_path_evaluator_set_update_handler(path_evaluator, dispatch_get_main_queue(), ^(nw_path_t path) {
+            nw_path_event(path);
+        });
+    nw_path_t initial = nw_path_evaluator_copy_path(path_evaluator);
+    nw_path_event(initial);
+    nw_release(initial);
+#endif // MONITOR_ROUTING_SOCKET
+#if TARGET_OS_TV && !defined(RA_TESTER)
+    get_thread_interface_list();
+#endif
+    set_thread_forwarding();
+#ifdef TCPDUMP_LOGGER
+    start_tcpdump_logger();
+#endif
+
+#ifndef RA_TESTER
+    cti_get_state(&thread_state_context, NULL, cti_get_state_callback, dispatch_get_main_queue());
+    cti_get_network_node_type(&thread_role_context, NULL, cti_get_role_callback, dispatch_get_main_queue());
+    cti_get_service_list(&thread_service_context, NULL, cti_service_list_callback, dispatch_get_main_queue());
+    cti_get_prefix_list(&thread_prefix_context, NULL, cti_prefix_list_callback, dispatch_get_main_queue());
+    cti_get_tunnel_name(NULL, cti_get_tunnel_name_callback, dispatch_get_main_queue());
+    cti_get_partition_id(&thread_partition_id_context, NULL, cti_get_partition_id_callback, dispatch_get_main_queue());
+#endif
+}
+
+void
+thread_network_shutdown(void)
+{
+    interface_t *interface;
+    network_link_t *link;
+#ifndef RA_TESTER
+    if (thread_state_context) {
+        INFO("thread_network_shutdown: discontinuing state events");
+        cti_events_discontinue(thread_state_context);
+    }
+    if (thread_role_context) {
+        INFO("thread_network_shutdown: discontinuing role events");
+        cti_events_discontinue(thread_role_context);
+    }
+    if (thread_service_context) {
+        INFO("thread_network_shutdown: discontinuing service events");
+        cti_events_discontinue(thread_service_context);
+    }
+    if (thread_prefix_context) {
+        INFO("thread_network_shutdown: discontinuing prefix events");
+        cti_events_discontinue(thread_prefix_context);
+    }
+    if (thread_partition_id_context) {
+        INFO("thread_network_shutdown: discontinuing partition ID events");
+        cti_events_discontinue(thread_partition_id_context);
+    }
+#endif
+    if (path_evaluator != NULL) {
+        nw_path_evaluator_cancel(path_evaluator);
+        nw_release(path_evaluator);
+        path_evaluator = NULL;
+    }
+    INFO("thread_network_shutdown: Thread network shutdown.");
+    // Stop all activity on interfaces.
+    for (interface = interfaces; interface; interface = interface->next) {
+        interface_shutdown(interface);
+    }
+    for (link = network_links; link; link = link->next) {
+        link->primary = NULL;
+    }
+
+#ifndef RA_TESTER
+    partition_state_reset();
+#endif
+}
+
+#ifndef RA_TESTER
+static void
+partition_state_reset(void)
+{
+    thread_prefix_t *prefix, *next_prefix = NULL;
+    thread_service_t *service, *next_service = NULL;
+    thread_pref_id_t *pref_id, *next_pref_id = NULL;
+
+    // Remove any saved state from the thread network.
+    for (prefix = thread_prefixes; prefix != NULL; prefix = next_prefix) {
+        next_prefix = prefix->next;
+        RELEASE_HERE(prefix, thread_prefix_finalize);
+    }
+    thread_prefixes = NULL;
+
+    if (published_thread_prefix != NULL) {
+        RELEASE_HERE(published_thread_prefix, thread_prefix_finalize);
+        published_thread_prefix = NULL;
+    }
+    if (adopted_thread_prefix != NULL) {
+        RELEASE_HERE(adopted_thread_prefix, thread_prefix_finalize);
+        adopted_thread_prefix = NULL;
+    }
+
+    for (service = thread_services; service != NULL; service = next_service) {
+        next_service = service->next;
+        RELEASE_HERE(service, thread_service_finalize);
+    }
+    thread_services = NULL;
+
+    for (pref_id = thread_pref_ids; pref_id != NULL; pref_id = next_pref_id) {
+        next_pref_id = pref_id->next;
+        RELEASE_HERE(pref_id, thread_pref_id_finalize);
+    }
+    thread_pref_ids = NULL;
+
+    current_thread_state = kCTI_NCPState_Uninitialized;
+    current_thread_role = kCTI_NetworkNodeType_Unknown;
+
+    partition_last_prefix_set_change = 0;
+    partition_last_pref_id_set_change = 0;
+    partition_last_partition_id_change = 0;
+    partition_last_role_change = 0;
+    partition_last_state_change = 0;
+    partition_settle_start = 0;
+    partition_service_last_add_time = 0;
+    partition_id_is_known = false;
+    partition_have_prefix_list = false;
+    partition_have_pref_id_list = false;
+    partition_tunnel_name_is_known = false;
+    partition_can_advertise_service = false;
+    partition_can_provide_routing = false;
+    partition_may_offer_service = false;
+    partition_settle_satisfied = true;
+
+    if (partition_settle_wakeup != NULL) {
+        ioloop_cancel_wake_event(partition_settle_wakeup);
+    }
+
+    if (partition_post_partition_wakeup != NULL) {
+        ioloop_cancel_wake_event(partition_post_partition_wakeup);
+    }
+
+    if (partition_pref_id_wait_wakeup != NULL) {
+        ioloop_cancel_wake_event(partition_pref_id_wait_wakeup);
+    }
+
+    if (partition_service_add_pending_wakeup != NULL) {
+        ioloop_cancel_wake_event(partition_service_add_pending_wakeup);
+    }
+}
+
+static int __unused
+prefcmp(uint8_t *a, uint8_t *b, int len)
+{
+    int i;
+    for (i = 0; i < len; i++) {
+        if (a[i] < b[i]) return -1;
+        if (a[i] > b[i]) return 1;
+    }
+    return 0;
+}
+
+static void
+partition_prefix_remove_callback(void *__unused context, cti_status_t status)
+{
+    if (status != kCTIStatus_NoError) {
+        ERROR("partition_unpublish_my_prefix: failed to unpublish my prefix: %d.", status);
+    } else {
+        INFO("partition_unpublish_my_prefix: done unpublishing my prefix.");
+    }
+}
+
+static void
+partition_stop_advertising_pref_id_done(void *__unused context, cti_status_t status)
+{
+    INFO("partition_stop_advertising_pref_id_done: %d", status);
+}
+
+void
+partition_stop_advertising_pref_id(void)
+{
+    // This should remove any copy of the service that this BR is advertising.
+    uint8_t service_info[] = { 0, 0, 0, 1 };
+    int status;
+
+    INFO("partition_stop_advertising_pref_id: %" PRIu64 "/%02x" , THREAD_ENTERPRISE_NUMBER, service_info[0]);
+    service_info[0] = THREAD_PREF_ID_OPTION & 255;
+    status = cti_remove_service(NULL, partition_stop_advertising_pref_id_done,
+                                    dispatch_get_main_queue(),
+                                    THREAD_ENTERPRISE_NUMBER, service_info, 1);
+    if (status != kCTIStatus_NoError) {
+        INFO("partition_stop_advertising_pref_id: status %d", status);
+    }
+}
+
+static void
+partition_advertise_pref_id_done(void *__unused context, cti_status_t status)
+{
+    INFO("partition_advertise_pref_id_done: %d", status);
+}
+
+static void
+partition_advertise_pref_id(uint8_t *prefix)
+{
+    // This should remove any copy of the service that this BR is advertising.
+    uint8_t service_info[] = { 0, 0, 0, 1 };
+    uint8_t pref_id[9];
+    memcpy(pref_id, thread_partition_id, 4);
+    memcpy(&pref_id[4], prefix, 5);
+    uint8_t full_prefix[6] = {0xfd, prefix[0], prefix[1], prefix[2], prefix[3], prefix[4]};
+
+    service_info[0] = THREAD_PREF_ID_OPTION & 255;
+    IPv6_PREFIX_GEN_SRP(full_prefix, sizeof(full_prefix), prefix_buf);
+    INFO("partition_advertise_pref_id: %" PRIu64 "/%02x/%02x%02x%02x%02x" PRI_IPv6_PREFIX_SRP,
+         THREAD_ENTERPRISE_NUMBER, service_info[0], pref_id[0], pref_id[1], pref_id[2], pref_id[3],
+         IPv6_PREFIX_PARAM_SRP(prefix_buf));
+    int status = cti_add_service(NULL, partition_advertise_pref_id_done, dispatch_get_main_queue(),
+                                 THREAD_ENTERPRISE_NUMBER, service_info, 1, pref_id, sizeof pref_id);
+    if (status != kCTIStatus_NoError) {
+        INFO("partition_advertise_pref_id: status %d", status);
+    }
+}
+
+static void
+partition_id_update(void)
+{
+    thread_prefix_t *advertised_prefix = get_advertised_thread_prefix();
+    if (advertised_prefix == NULL) {
+        INFO("partition_id_update: no advertised prefix, not advertising pref:id.");
+    } else if (advertised_prefix == adopted_thread_prefix) {
+        INFO("partition_id_update: not advertising pref:id for adopted prefix.");
+        partition_stop_advertising_pref_id();
+    } else {
+        partition_advertise_pref_id(((uint8_t *)&advertised_prefix->prefix) + 1);
+        INFO("partition_id_update: advertised pref:id for our prefix.");
+    }
+}
+
+static void
+partition_unpublish_prefix(thread_prefix_t *prefix)
+{
+    cti_status_t status = cti_remove_prefix(NULL, partition_prefix_remove_callback, dispatch_get_main_queue(),
+                                            &prefix->prefix, 64);
+    if (status != kCTIStatus_NoError) {
+        ERROR("partition_unpublish_prefix: prefix remove failed: %d.", status);
+    }
+    partition_stop_advertising_pref_id();
+}
+
+static void
+partition_refresh_and_re_evaluate(void)
+{
+    refresh_interface_list();
+    routing_policy_evaluate_all_interfaces(true);
+}
+
+typedef struct unadvertised_prefix_remove_state  unadvertised_prefix_remove_state_t;
+struct unadvertised_prefix_remove_state {
+    int num_unadvertised_prefixes;
+    int num_removals;
+    void (*continuation)(void);
+};
+
+static void
+partition_remove_all_prefixes_done(void *context, cti_status_t status)
+{
+    unadvertised_prefix_remove_state_t *state = context;
+    state->num_removals++;
+    if (state->num_removals == state->num_unadvertised_prefixes) {
+        INFO("partition_remove_all_prefixes_done:  DONE: status = %d num_removals = %d num_unadvertised = %d",
+             status, state->num_removals, state->num_unadvertised_prefixes);
+        void (*continuation)(void) = state->continuation;
+        free(state);
+        if (continuation != NULL) {
+            continuation();
+        } else {
+            INFO("partition_remove_all_prefixes_done: no continuation.");
+        }
+    } else {
+        INFO("partition_remove_all_prefixes_done: !DONE: status = %d num_removals = %d num_unadvertised = %d",
+             status, state->num_removals, state->num_unadvertised_prefixes);
+    }
+}
+
+static void
+partition_remove_all_unwanted_prefixes_inner(unadvertised_prefix_remove_state_t *state, thread_prefix_t *prefix)
+{
+    // Don't unpublish the adopted or published prefix.
+    if ((published_thread_prefix == NULL || memcmp(&published_thread_prefix->prefix, &prefix->prefix, 8)) &&
+        (adopted_thread_prefix == NULL || memcmp(&adopted_thread_prefix->prefix, &prefix->prefix, 8)))
+    {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
+        INFO("partition_remove_all_unwanted_prefixes: Removing prefix " PRI_SEGMENTED_IPv6_ADDR_SRP,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf));
+        cti_status_t status = cti_remove_prefix(state, partition_remove_all_prefixes_done,
+                                                dispatch_get_main_queue(), &prefix->prefix, 64);
+        if (status != kCTIStatus_NoError) {
+            ERROR("partition_remove_all_unwanted_prefixes: prefix remove failed: %d.", status);
+        }
+    }
+}
+
+static void
+partition_remove_all_unwanted_prefixes(void (*continuation)(void), thread_prefix_t *prefix_1, thread_prefix_t *prefix_2)
+{
+    unadvertised_prefix_remove_state_t *state = calloc(1, sizeof(*state));
+    if (state == NULL) {
+        INFO("partition_remove_all_unwanted_prefixes: no memory");
+        return;
+    }
+
+    // It's possible for us to get into a state where a prefix is published by this BR, but doesn't
+    // have a pref:id and isn't recognized as belonging to this BR.  This should never happen in practice,
+    // but if it does happen, the only thing that will eliminate it is a reboot.   In case this happens,
+    // we go through the list of prefixes that are marked ncp and unpublish them.
+    thread_prefix_t *prefix;
+
+    state->continuation = continuation;
+    for (prefix = thread_prefixes; prefix; prefix = prefix->next) {
+        if (!partition_pref_id_is_present(&prefix->prefix)) {
+            // It's possible for partition_remove_all_unwanted_prefixes to get called before we have a full list of
+            // recently-published prefixes. It is possible for either the published prefix or the adopted prefix to
+            // not be on the list of prefixes. The caller may however have wanted to change either of those pointers;
+            // in this case, it will pass in either or both of those pointers as prefix_1 and prefix_2; if we see those
+            // prefixes on the list, we don't need to unpublish them twice.
+            if (prefix_1 != NULL && !memcmp(&prefix->prefix, &prefix_1->prefix, 8)) {
+                prefix_1 = NULL;
+            }
+            if (prefix_2 != NULL && !memcmp(&prefix->prefix, &prefix_2->prefix, 8)) {
+                prefix_2 = NULL;
+            }
+            state->num_unadvertised_prefixes++;
+        }
+    }
+    if (prefix_1 != NULL) {
+        state->num_unadvertised_prefixes++;
+    }
+    if (prefix_2 != NULL) {
+        state->num_unadvertised_prefixes++;
+    }
+
+    // Now actually remove the prefixes.
+    for (prefix = thread_prefixes; prefix; prefix = prefix->next) {
+        if (!partition_pref_id_is_present(&prefix->prefix)) {
+            partition_remove_all_unwanted_prefixes_inner(state, prefix);
+        }
+    }
+    if (prefix_1 != NULL) {
+        partition_remove_all_unwanted_prefixes_inner(state, prefix_1);
+    }
+    if (prefix_2 != NULL) {
+        partition_remove_all_unwanted_prefixes_inner(state, prefix_2);
+    }
+
+    // If we didn't remove any prefixes, continue immediately.
+    if (state->num_unadvertised_prefixes == 0) {
+        if (state->continuation) {
+            state->continuation();
+        }
+        free(state);
+    } else if (!state->continuation) {
+        free(state);
+#ifdef __clang_analyzer__ // clang_analyzer is unable to follow the reference through the cti code.
+    } else {
+        free(state);
+#endif
+    }
+}
+
+static void
+partition_unpublish_adopted_prefix(bool wait)
+{
+    // Unpublish the adopted prefix
+    if (adopted_thread_prefix != NULL) {
+        partition_unpublish_prefix(adopted_thread_prefix);
+        INFO("partition_unpublish_adopted_prefix: started to unadopt prefix.");
+        RELEASE_HERE(adopted_thread_prefix, thread_prefix_finalize);
+        adopted_thread_prefix = NULL;
+    }
+
+    // Something changed, so do a routing policy update unless wait==true
+    if (!wait) {
+        partition_refresh_and_re_evaluate();
+    }
+}
+
+static void
+partition_publish_prefix_finish(void)
+{
+    INFO("partition_publish_prefix_finish: prefix unpublishing has completed, time to update the prefix.");
+
+    partition_id_update();
+    set_thread_prefix();
+
+    // Something changed, so do a routing policy update.
+    partition_refresh_and_re_evaluate();
+}
+
+static void
+partition_publish_my_prefix()
+{
+    void (*continuation)(void) = NULL;
+    thread_prefix_t *prefix_1 = NULL;
+    thread_prefix_t *prefix_2 = NULL;
+
+    if (adopted_thread_prefix != NULL) {
+        prefix_1 = adopted_thread_prefix;
+        adopted_thread_prefix = NULL;
+    }
+
+    // If we already have a published thread prefix, it really should be my_thread_prefix.
+    if (published_thread_prefix != NULL) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(published_thread_prefix->prefix.s6_addr, prefix_buf);
+        // This should always be false.
+        if (memcmp(&published_thread_prefix->prefix, &my_thread_prefix, 8)) {
+            INFO("partition_publish_my_prefix: Published prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " is not my prefix",
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(published_thread_prefix->prefix.s6_addr, prefix_buf));
+            prefix_2 = published_thread_prefix;
+            published_thread_prefix = NULL;
+            continuation = partition_publish_prefix_finish;
+        } else {
+            INFO("partition_publish_my_prefix: Published prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " is my prefix",
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(published_thread_prefix->prefix.s6_addr, prefix_buf));
+        }
+    }
+    if (published_thread_prefix == NULL) {
+        // Publish the prefix
+        published_thread_prefix = thread_prefix_create(&my_thread_prefix, 64);
+        if (published_thread_prefix == NULL) {
+            ERROR("partition_publish_my_prefix: No memory for locally-advertised thread prefix");
+            goto out;
+        }
+        continuation = partition_publish_prefix_finish;
+        SEGMENTED_IPv6_ADDR_GEN_SRP(my_thread_prefix.s6_addr, prefix_buf);
+        INFO("partition_publish_my_prefix: Publishing my prefix: " PRI_SEGMENTED_IPv6_ADDR_SRP,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(my_thread_prefix.s6_addr, prefix_buf));
+    }
+    partition_remove_all_unwanted_prefixes(continuation, prefix_1, prefix_2);
+out:
+    if (prefix_1 != NULL) {
+        RELEASE_HERE(prefix_1, thread_prefix_finalize);
+    }
+    if (prefix_2 != NULL) {
+        RELEASE_HERE(prefix_2, thread_prefix_finalize);
+    }
+}
+
+static void
+partition_adopt_prefix(thread_prefix_t *prefix)
+{
+    void (*continuation)(void) = NULL;
+    thread_prefix_t *prefix_1 = NULL;
+    thread_prefix_t *prefix_2 = NULL;
+
+    if (published_thread_prefix != NULL) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(published_thread_prefix->prefix.s6_addr, prefix_buf);
+        INFO("partition_adopt_prefix: Removing published prefix " PRI_SEGMENTED_IPv6_ADDR_SRP,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(published_thread_prefix->prefix.s6_addr, prefix_buf));
+        prefix_1 = published_thread_prefix;
+        published_thread_prefix = NULL;
+    }
+
+    // If we already have an advertised thread prefix, it might not have changed.
+    if (adopted_thread_prefix != NULL) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(adopted_thread_prefix->prefix.s6_addr, prefix_buf);
+        if (memcmp(&adopted_thread_prefix->prefix, &prefix->prefix, 8)) {
+            INFO("partition_adopt_prefix: Removing previously adopted prefix " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(adopted_thread_prefix->prefix.s6_addr, prefix_buf));
+            prefix_2 = adopted_thread_prefix;
+            continuation = partition_publish_prefix_finish;
+        } else {
+            INFO("partition_adopt_prefix: Keeping previously adopted prefix " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(adopted_thread_prefix->prefix.s6_addr, prefix_buf));
+        }
+    }
+    if (adopted_thread_prefix == NULL) {
+        // Adopt the prefix
+        adopted_thread_prefix = prefix;
+        RETAIN_HERE(adopted_thread_prefix);
+        continuation = partition_publish_prefix_finish;
+        SEGMENTED_IPv6_ADDR_GEN_SRP(adopted_thread_prefix->prefix.s6_addr, prefix_buf);
+        INFO("partition_adopt_prefix: Adopting prefix " PRI_SEGMENTED_IPv6_ADDR_SRP,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(adopted_thread_prefix->prefix.s6_addr, prefix_buf));
+    }
+    partition_remove_all_unwanted_prefixes(continuation, prefix_1, prefix_2);
+
+    if (prefix_1 != NULL) {
+        RELEASE_HERE(prefix_1, thread_prefix_finalize);
+    }
+    if (prefix_2 != NULL) {
+        RELEASE_HERE(prefix_2, thread_prefix_finalize);
+    }
+}
+
+// Check to see if a specific prefix is still present.
+static bool
+partition_prefix_is_present(struct in6_addr *address, int length)
+{
+    thread_prefix_t *prefix;
+    // For now we assume that the comparison is as a /64.
+    for (prefix = thread_prefixes; prefix; prefix = prefix->next) {
+        if (prefix->prefix_len == length && !memcmp((uint8_t *)&prefix->prefix, (uint8_t *)address, 8)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+// Check to see if a valid pref:id for the specified prefix is present.
+static bool
+partition_pref_id_is_present(struct in6_addr *prefix_addr)
+{
+    thread_pref_id_t *pref_id;
+    uint8_t *prefix_bytes = (uint8_t *)prefix_addr;
+
+    INFO("partition_pref_id_is_present: published_thread_prefix = %p; prefix = %p", published_thread_prefix,
+         prefix_addr);
+
+    // The published prefix's pref:id is always considered present.
+    if (published_thread_prefix != NULL && !memcmp(prefix_addr, &published_thread_prefix->prefix, 8)) {
+        INFO("partition_pref_id_is_present: prefix is published prefix");
+        return true;
+    }
+
+    for (pref_id = thread_pref_ids; pref_id; pref_id = pref_id->next) {
+        // A pref:id is valid if the partition ID matches the current partition ID.
+        // A pref:id matches a prefix if the 40 variable bits in the ULA /48 are the same.
+        if (!memcmp(thread_partition_id, pref_id->partition_id, 4) &&
+            !memcmp(prefix_bytes + 1, pref_id->prefix, 5))
+        {
+            INFO("partition_pref_id_is_present: pref:id is present");
+            return true;
+        } else {
+            IPv6_PREFIX_GEN_SRP(pref_id->prefix, sizeof(pref_id->prefix), pref_id_prefix);
+            if (memcmp(thread_partition_id, pref_id->partition_id, 4)) {
+                INFO("partition_pref_id_is_present: "
+                     "pref:id for " PRI_IPv6_PREFIX_SRP
+                     ":%02x%02x%02x%02x does not match partition id %02x%02x%02x%02x",
+                     IPv6_PREFIX_PARAM_SRP(pref_id_prefix),
+                     pref_id->partition_id[0], pref_id->partition_id[1], pref_id->partition_id[2],
+                     pref_id->partition_id[3],
+                     thread_partition_id[0], thread_partition_id[1], thread_partition_id[2], thread_partition_id[3]);
+            } else {
+                INFO("partition_pref_id_is_present: "
+                     "pref:id for " PRI_IPv6_PREFIX_SRP ":%02x%02x%02x%02x does not match prefix %02x%02x%02x%02x%02x",
+                     IPv6_PREFIX_PARAM_SRP(pref_id_prefix),
+                     pref_id->partition_id[0], pref_id->partition_id[1], pref_id->partition_id[2],
+                     pref_id->partition_id[3],
+                     prefix_bytes[1], prefix_bytes[2], prefix_bytes[3], prefix_bytes[4], prefix_bytes[5]);
+            }
+        }
+    }
+    return false;
+}
+
+// Find the lowest valid prefix present.  The return value may be the published prefix.
+static thread_prefix_t *
+partition_find_lowest_valid_prefix(void)
+{
+    thread_prefix_t *prefix, *lowest = published_thread_prefix;
+
+    // Are there other prefixes published?
+    for (prefix = thread_prefixes; prefix != NULL; prefix = prefix->next) {
+        // The prefix we publish doesn't count.
+        if (published_thread_prefix != NULL && !memcmp(&prefix->prefix, &published_thread_prefix->prefix, 8)) {
+            continue;
+        }
+        if (partition_pref_id_is_present(&prefix->prefix)) {
+            if (lowest == NULL || memcmp(&prefix->prefix, &lowest->prefix, 8) < 0) {
+                lowest = prefix;
+            }
+            break;
+        }
+    }
+    return lowest;
+}
+
+// Find the lowest valid pref:id.  The return value may be the pref:id for the published prefix.
+static thread_pref_id_t *
+partition_find_lowest_valid_pref_id(void)
+{
+    thread_pref_id_t *lowest = NULL;
+    thread_pref_id_t *pref_id;
+
+    for (pref_id = thread_pref_ids; pref_id != NULL; pref_id = pref_id->next) {
+        if (lowest == NULL || memcmp(pref_id->prefix, lowest->prefix, 5) < 0) {
+            lowest = pref_id;
+        }
+    }
+    return lowest;
+}
+
+// The prefix ID timeout has gone off.  At this time we evaluate the state of the network: the fact that we
+// got a wakeup means that there has been no partition event and nothing has changed about the set of
+// prefixes published on the thread mesh since the wakeup was scheduled.  We don't schedule this wakeup unless
+// there is more than one prefix on the thread mesh.   So that means that when the wakeup is called, there
+// is still more than one prefix+pref:id pair active on the link--an undesirable situation.   So we now
+// hold an election.  If we lose, we drop our prefix+pref:id pair in favor of the winner.  If we win,
+// we do nothing--we are expecting the BR(s) publishing the other prefix+pref:id pair(s) to drop them.
+static void
+partition_pref_id_timeout(void *__unused context)
+{
+    thread_prefix_t *prefix = partition_find_lowest_valid_prefix();
+
+    // This should never happen because we wouldn't have set the timeout.
+    if (prefix == NULL) {
+        INFO("partition_pref_id_timeout: no published prefix.");
+        return;
+    }
+
+    // If we won, do nothing.
+    if (published_thread_prefix != NULL && (prefix == published_thread_prefix ||
+                                            !memcmp(&prefix->prefix, &published_thread_prefix->prefix, 8))) {
+        INFO("partition_pref_id_timeout: published prefix is the lowest; keeping it.");
+        return;
+    }
+
+    // published_thread_prefix should never be null here.
+    // If our published prefix is not the lowest prefix, then we should drop it and adopt the lowest prefix.
+    if (published_thread_prefix != NULL && memcmp(&prefix->prefix, &published_thread_prefix->prefix, 8)) {
+        INFO("partition_pref_id_timeout: published prefix is not lowest valid prefix.  Adopting lowest valid prefix.");
+        partition_adopt_prefix(prefix);
+        return;
+    }
+
+    // We should never get here.
+    if (adopted_thread_prefix != NULL) {
+        if (!memcmp(&adopted_thread_prefix->prefix, &prefix->prefix, 8)) {
+            ERROR("partition_pref_id_timeout: no published prefix.  Already adopted lowest.");
+            return;
+        }
+        // Unadopt this prefix since it's not lowest.
+        partition_unpublish_adopted_prefix(false);
+        // adopted_thread_prefix is now NULL
+    }
+
+    // And we should never get here.
+    ERROR("partition_pref_id_timeout: no published prefix.  Adopting lowest.");
+    partition_adopt_prefix(prefix);
+}
+
+// When we see a new partition, if there isn't a prefix to adopt and we aren't publishing one,
+// we wait n seconds to see if some other BR publishes a prefix, but also publish our own pref:id.
+// If no router on the partition is publishing a prefix, then after n seconds we hold an election,
+// choosing the pref:id with the lowest ULA.  If that's this router, then we will publish our prefix,
+// but if not, we want to check after a bit to make sure a prefix /does/ get published.   If after
+// another n seconds, we still don't see a valid prefix+pref:id pair, we publish our own; if this
+// later turns out to have been a mistake, we will hold the election again and remove the one we
+// published if we don't win.
+static void
+partition_post_election_wakeup(void *__unused context)
+{
+    thread_prefix_t *prefix = partition_find_lowest_valid_prefix();
+
+    // There is no valid prefix published.   Publish ours.
+    if (prefix == NULL) {
+        INFO("partition_post_election_wakeup: no valid thread prefix present, publishing mine.");
+        partition_publish_my_prefix();
+        return;
+    }
+
+    // It's perfectly valid to not have adopted the lowest prefix at this point.
+    // However, if we have adopted a prefix, we shouldn't be here because the timeout should have been
+    // canceled.
+    if (adopted_thread_prefix != NULL && memcmp(&adopted_thread_prefix->prefix, &prefix->prefix, 8)) {
+        ERROR("partition_post_election_wakeup: adopted prefix is not lowest.");
+    } else {
+        ERROR("partition_post_election_wakeup: adopted prefix is lowest.");
+    }
+}
+
+// This is the initial wakeup as described under partition_post_election_wakeup.   At this time
+// if there is a valid published pref:id pair, we adopt it; if not, then we hold an election based
+// on all of the on-partition pref:id pairs that we see.   If we win, we publish our prefix; otherwise
+// give the winner time to publish its prefix.
+static void
+partition_post_partition_timeout(void *__unused context)
+{
+    thread_prefix_t *prefix = partition_find_lowest_valid_prefix();
+    thread_pref_id_t *pref_id;
+
+    // Is there a prefix+pref:id published?
+    // Actually at this point we should already have adopted it and the wakeup should have been canceled.
+    if (prefix != NULL) {
+        ERROR("partition_post_partition_timeout: wakeup when there's a valid lowest prefix.");
+        return;
+    }
+
+    // Are there pref:id services published that list a lower ULA than ours?
+    pref_id = partition_find_lowest_valid_pref_id();
+
+    if (pref_id == NULL) {
+        INFO("There are no prefixes published, publishing my prefix.");
+        partition_publish_my_prefix();
+        return;
+    }
+
+    // If not, publish ours.
+    if (memcmp(((uint8_t *)&my_thread_prefix) + 1, pref_id->prefix, 5) < 0) {
+        INFO("partition_post_partition_timeout: my prefix id is lowest, publishing my prefix.");
+        partition_publish_my_prefix();
+        return;
+    }
+
+    // If so, wait another ten seconds to see if one of them publishes a prefix
+    // If we have adopted a prefix, set a timer after which we will drop it and start advertising if nothing has
+    // happened
+    if (partition_post_partition_wakeup != NULL) {  // shouldn't be!
+        ioloop_cancel_wake_event(partition_post_partition_wakeup);
+    } else {
+        partition_post_partition_wakeup = ioloop_wakeup_create();
+        if (partition_post_partition_wakeup == NULL) {
+            ERROR("partition_post_partition_timeout: can't allocate pref:id wait wakeup.");
+            return;
+        }
+    }
+    // Allow ten seconds for the services state to settle, after which time we should either have a pref:id backing
+    // up a prefix, or should advertise a prefix.
+    INFO("partition_post_partition_timeout: waiting for other BR to publish its prefixes.");
+    ioloop_add_wake_event(partition_post_partition_wakeup, NULL, partition_post_election_wakeup, NULL, 10 * 1000);
+}
+
+static void
+partition_proxy_listener_ready(void *__unused context, uint16_t port)
+{
+    INFO("partition_proxy_listener_ready: listening on port %d", port);
+    srp_service_listen_port = port;
+    if (have_non_thread_interface) {
+        partition_can_advertise_service = true;
+        partition_maybe_advertise_service();
+    } else {
+        partition_discontinue_srp_service();
+    }
+}
+
+void
+partition_start_srp_listener(void)
+{
+    const int max_avoid_ports = 100;
+    uint16_t avoid_ports[max_avoid_ports];
+    int num_avoid_ports = 0;
+    thread_service_t *service;
+
+    for (service = thread_services; service; service = service->next) {
+        // Track the port regardless.
+        if (num_avoid_ports < max_avoid_ports) {
+            avoid_ports[num_avoid_ports] = (service->port[0] << 8) | (service->port[1]);
+            num_avoid_ports++;
+        }
+    }
+
+    INFO("partition_start_srp_listener: starting listener.");
+    srp_listener = srp_proxy_listen("local", avoid_ports, num_avoid_ports, partition_proxy_listener_ready);
+    if (srp_listener == NULL) {
+        ERROR("partition_start_srp_listener: Unable to start SRP Proxy listener, so can't advertise it");
+        return;
+    }
+}
+
+static void
+partition_discontinue_srp_service()
+{
+    if (srp_listener != NULL) {
+        srp_proxy_listener_cancel(srp_listener);
+        srp_listener = NULL;
+    }
+
+    // Won't match
+    memset(&srp_listener_ip_address, 0, 16);
+    srp_service_listen_port = 0;
+    partition_can_advertise_service = false;
+
+    // Stop advertising the service, if we are doing so.
+    partition_stop_advertising_service();
+}
+
+// An address on utun0 has changed.  Evaluate what to do with our listener service.
+// This gets called from ifaddr_callback().   If we don't yet have a thread service configured,
+// it should be called for unchanged addresses as well as changed.
+static void
+partition_utun0_address_changed(const struct in6_addr *addr, enum interface_address_change change)
+{
+    thread_prefix_t *advertised_prefix = NULL;
+
+    // Figure out what our current prefix is.
+    if (published_thread_prefix != NULL) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(published_thread_prefix->prefix.s6_addr, prefix_buf);
+        INFO("partition_utun0_address_changed: advertised prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " is my prefix.",
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(published_thread_prefix->prefix.s6_addr, prefix_buf));
+        advertised_prefix = published_thread_prefix;
+    } else if (adopted_thread_prefix != NULL) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(adopted_thread_prefix->prefix.s6_addr, prefix_buf);
+        INFO("partition_utun0_address_changed: advertised prefix " PRI_SEGMENTED_IPv6_ADDR_SRP
+             " is another router's prefix.",
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(adopted_thread_prefix->prefix.s6_addr, prefix_buf));
+        advertised_prefix = adopted_thread_prefix;
+    }
+
+    SEGMENTED_IPv6_ADDR_GEN_SRP(addr, addr_buf);
+    // Is this the address we are currently using?
+    if (!memcmp(&srp_listener_ip_address, addr, 16)) {
+        // Did it go away?   If so, drop the listener.
+        if (change == interface_address_deleted) {
+            INFO("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP ": listener address removed.",
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf));
+            if (srp_listener != NULL) {
+                INFO("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP
+                     ": canceling listener on removed address.", SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf));
+                partition_discontinue_srp_service();
+            }
+        } else {
+            // This should never happen.
+            if (change == interface_address_added) {
+                ERROR("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP
+                      ": address we're listening on was added.", SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf));
+            }
+
+            // Is it on the prefix we're currently publishing?
+            if (advertised_prefix != NULL && !memcmp(&advertised_prefix->prefix, addr, 8)) {
+                INFO("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP
+                     ": listener address is on the advertised prefix--no action needed.",
+                     SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf));
+            } else {
+                // In this case hopefully we'll get a new IP address we _can_ listen on in a subsequent call.
+                INFO("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP
+                     ": listener address is not on the advertised prefix--no action taken.",
+                     SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf));
+            }
+        }
+
+        // In no case can we do anything further.
+        return;
+    }
+
+    // If we have a prefix, see if we need to do anything.
+    if (advertised_prefix != NULL) {
+        // If this is not the address we are currently using, and it showed up, is it on the prefix we
+        // are advertising?
+        if (!memcmp(&advertised_prefix->prefix, addr, 8)) {
+            // If we are not listening on an address, or we are listening on an address that isn't on the
+            // prefix we are advertising, we need to stop, if needed, and start up a new listener.
+            if (srp_listener_ip_address.s6_addr[0] == 0 ||
+                memcmp(&advertised_prefix->prefix, &srp_listener_ip_address, 8))
+            {
+                // See if we already have a listener; if so, stop it.
+                if (srp_listener_ip_address.s6_addr[0] != 0) {
+                    INFO("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP ": stopping old listener.",
+                         SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf));
+                    srp_proxy_listener_cancel(srp_listener);
+                    srp_listener = NULL;
+                }
+                if (srp_listener == NULL) {
+                    if (!have_non_thread_interface) {
+                        INFO("partition_utun0_address_changed: not starting a listener because we have no infrastructure");
+                    } else {
+                        INFO("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP ": starting a new listener.",
+                             SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf));
+                        memcpy(&srp_listener_ip_address, addr, 16);
+                        srp_service_listen_port = 0;
+                        partition_start_srp_listener();
+                    }
+                }
+            }
+        } else {
+            INFO("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP
+                 ": this address not on advertised prefix, so no action to take.",
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf));
+        }
+    } else {
+        INFO("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP
+             ": no advertised prefix, so no action to take.", SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf));
+    }
+}
+
+// We call this function to see if we have a complete, recent set of information; if not, we wait a bit for the set
+// to become complete, but after 500ms we assume it won't be and proceed.
+static bool
+partition_wait_for_prefix_settling(wakeup_callback_t callback, uint64_t now)
+{
+    // Remember when we started waiting for the partition data to settle.
+    if (partition_settle_satisfied) {
+        partition_settle_start = now;
+        partition_settle_satisfied = false;
+    }
+
+    if (partition_settle_wakeup != NULL) {
+        ioloop_cancel_wake_event(partition_settle_wakeup);
+    }
+
+    // If we aren't able to offer service, just wait.
+    if (!partition_may_offer_service) {
+        INFO("partition_wait_for_prefix_settling: not able to offer service--deferring.");
+        return true;
+    }
+
+    // If we've gotten updates on everything, we're good to go.   The reason for comparing against
+    // partition_settle_start is that if we've been seriously throttled for some reason, it might take
+    // more than 500ms to get a callback, even though all the events came in between when we asked
+    // for the initial callback and when we got it.   Tunnel ID shouldn't change after startup.
+    if (partition_last_prefix_set_change >= partition_settle_start &&
+        partition_last_pref_id_set_change >= partition_settle_start &&
+        partition_last_partition_id_change >= partition_settle_start &&
+        partition_last_role_change >= partition_settle_start &&
+        partition_last_state_change >= partition_settle_start && partition_tunnel_name_is_known)
+    {
+        partition_settle_satisfied = true;
+        INFO("partition_wait_for_prefix_settling: satisfied after %llums.", now - partition_settle_start);
+        return false; // means don't wait
+    }
+
+    // If we've waited longer than 500ms and aren't satisfied, complain, but then proceed.
+    if (now - partition_settle_start >= 500) {
+        ERROR("partition_wait_for_prefix_settling: unsatisfied after %llums", now - partition_settle_start);
+        partition_settle_satisfied = true; // not really, but there's always next time.
+        return false; // proceed if possible.
+    }
+
+    // Otherwise, wake up 500ms after we started waiting for things to settle, and reconnoiter.
+    if (partition_settle_wakeup == NULL) {
+        partition_settle_wakeup = ioloop_wakeup_create();
+        if (partition_settle_wakeup == NULL) {
+            ERROR("partition_wait_for_prefix_settling: Unable to postpone partition settlement wakeup: no memory.");
+            partition_settle_satisfied = true;
+            return false;
+        }
+    }
+    ioloop_add_wake_event(partition_settle_wakeup, NULL, callback, NULL, 500 - (int)(now - partition_settle_start));
+    return true;
+}
+
+static void
+partition_got_tunnel_name(void)
+{
+    partition_tunnel_name_is_known = true;
+    refresh_interface_list();
+}
+
+// We have a recent prefix list and either have a recent pref:id list or one probably isn't coming.
+static void
+partition_prefix_list_or_pref_id_list_changed(void *__unused context)
+{
+    // If we haven't had a pref:id update recently, wait a bit to see if one came with the most recent network data.
+    if (partition_wait_for_prefix_settling(partition_prefix_list_or_pref_id_list_changed, ioloop_timenow())) {
+        ERROR("partition_prefix_list_or_pref_id_list_changed: waiting for prefix info to settle.");
+        return;
+    }
+
+    // If we aren't ready to advertise service, do nothing.
+    if (!partition_may_offer_service) {
+        INFO("partition_prefix_list_or_pref_id_list_changed can't offer service yet.");
+        return;
+    }
+
+    // If there are no prefixes, then it doesn't matter what's on the prefix ID list: publish a prefix now.
+    if (thread_prefixes == NULL) {
+        INFO("partition_prefix_list_or_pref_id_list_changed have no prefixes, publishing my prefix");
+        partition_publish_my_prefix();
+        return;
+    }
+
+    // It is a failure of the thread network software for us to get to this point without knowing the thread
+    // partition ID.   We should have received it on startup.   So the case where this would happen would be if
+    // on startup we simply didn't get it, which should never happen.   What we'll do if this happens is make
+    // one up.
+    if (partition_id_is_known == false) {
+        ERROR("partition_prefix_list_or_pref_id_list_changed: partition ID never showed up!");
+    }
+
+    // If we are already publishing a prefix and pref:id, we don't have to do anything to the prefix right now.
+    if (published_thread_prefix != NULL) {
+        // We do need to trigger an interface scan though.
+        refresh_interface_list();
+
+        // Also, if there's more than one prefix present, set a timer for an hour from now, at which point we will
+        // consider dropping our prefix.
+        if (thread_prefixes != NULL && thread_prefixes->next != NULL) {
+            INFO("partition_prefix_list_or_pref_id_list_changed:"
+                 "published prefix is unchanged, setting up the pref:id timer");
+            if (partition_pref_id_wait_wakeup != NULL) {
+                ioloop_cancel_wake_event(partition_pref_id_wait_wakeup);
+            } else {
+                partition_pref_id_wait_wakeup = ioloop_wakeup_create();
+                if (partition_pref_id_wait_wakeup == NULL) {
+                    ERROR("partition_prefix_list_or_pref_id_list_changed: "
+                          "Unable to set a timer to wake up after the an hour to check the partition id.");
+                    return;
+                }
+            }
+            // The thread network can be pretty chaotic right after the BR comes up, so if we see a partition during the
+            // first 60 seconds, don't treat it as a real partition event, and do the re-election in 60 seconds rather
+            // than an hour.
+            uint64_t time_since_zero = ioloop_timenow() - partition_last_state_change;
+            uint32_t pref_id_timeout_time = 3600 * 1000;
+            if (time_since_zero < 60 * 1000) {
+                pref_id_timeout_time = 60 * 1000;
+            }
+            ioloop_add_wake_event(partition_pref_id_wait_wakeup, NULL, partition_pref_id_timeout, NULL,
+                                  pref_id_timeout_time);
+            INFO("added partition pref id timeout");
+        } else {
+            INFO("partition_prefix_list_or_pref_id_list_changed: published prefix is unchanged");
+        }
+        return;
+    }
+
+    // If we have adopted a prefix and the prefix and pref:id are still present, do nothing.
+    if (adopted_thread_prefix != NULL) {
+        if (partition_prefix_is_present(&adopted_thread_prefix->prefix, adopted_thread_prefix->prefix_len) &&
+            partition_pref_id_is_present(&adopted_thread_prefix->prefix))
+        {
+            INFO("partition_prefix_list_or_pref_id_list_changed: adopted prefix is unchanged");
+            return;
+        }
+        // If the adopted prefix is no longer present, stop using it.
+        partition_unpublish_adopted_prefix(false);
+        // adopted_thread_prefix is now NULL.
+    }
+
+    // If there is a prefix present for which there is already a matching pref:id, adopt that prefix and pref:id now.
+    // drop the thread_post_partition_timeout timer.
+    thread_prefix_t *prefix;
+    for (prefix = thread_prefixes; prefix; prefix = prefix->next) {
+        if (partition_pref_id_is_present(&prefix->prefix)) {
+            INFO("partition_prefix_list_or_pref_id_list_changed: adopting new prefix");
+            partition_adopt_prefix(prefix);
+            // When we adopt a prefix, it was already on-link, and quite possibly we already have an address
+            // configured on that prefix on utun0.  Calling refresh_interface_list() will trigger the listener
+            // if in fact that's the case.   If the address hasn't come up on utun0 yet, then when it comes up
+            // that will trigger the listener.
+            refresh_interface_list();
+            return;
+        }
+        if (partition_post_partition_wakeup != NULL) {
+            ioloop_cancel_wake_event(partition_post_partition_wakeup);
+        }
+    }
+
+    // At this point there is a prefix, but no pref:id, and it's /not/ the prefix that we published.  This
+    // means that a partition has happened and the BR that published the prefix is on the other partition,
+    // or else that the BR that published the prefix has gone offline and has been offline for at least
+    // four minutes.
+    // It's possible that either condition will heal, but in the meantime publish a prefix.   The reason for
+    // the urgency is that if we have a partition, and both routers are still online, then routing will be
+    // screwed up until we publish a new prefix and migrate all the accessories on our partition to the
+    // new prefix.
+    INFO("partition_publish_prefix: there is a prefix, but no pref:id, so it's stale. Publishing my prefix.");
+    partition_publish_my_prefix();
+}
+
+// The list of published prefix has changed.  Evaluate what to do with our partition state.
+// Mostly what we do when the prefix list changes is the same as what we do if the pref:id list
+// changes, but if we get an empty prefix list, it doesn't matter what's on the pref:id list,
+// so we act immediately.
+static void
+partition_prefix_set_changed(void)
+{
+    // Time stamp most recent prefix set update.
+    partition_last_prefix_set_change = ioloop_timenow();
+
+    // Otherwise, we have a prefix list and a pref:id list, so we can make decisions.
+    partition_prefix_list_or_pref_id_list_changed(NULL);
+}
+
+// The set of published pref:id's changed.  Evaluate what to do with our pref:id
+static void
+partition_pref_id_set_changed(void)
+{
+    // Time stamp most recent prefix set update.
+    partition_last_prefix_set_change = ioloop_timenow();
+
+    // Otherwise, we have a prefix list and a pref:id list, so we can make decisions.
+    partition_prefix_list_or_pref_id_list_changed(NULL);
+}
+
+// The partition ID changed.
+static void
+partition_id_changed(void)
+{
+    partition_last_partition_id_change = ioloop_timenow();
+
+    // If we've never seen a partition ID before, this is not (necessarily) a partition.
+    if (!partition_id_is_known) {
+        INFO("partition_id_changed: first time through.");
+        partition_id_is_known = true;
+        return;
+    }
+
+    // If we get a partition ID when we aren't a router, we should (I think!) ignore it.
+    if (!partition_can_provide_routing) {
+        INFO("partition_id_changed: we aren't able to offer routing yet, so ignoring.");
+        return;
+    }
+
+    // If we are advertising a prefix, update our pref:id
+    if (published_thread_prefix != NULL) {
+        INFO("partition_id_changed: updating advertised prefix id");
+        partition_id_update();
+        // In principle we didn't change anything material to the routing subsystem, so no need to re-evaluate current
+        // policy.
+        return;
+    }
+
+    // Propose our prefix as a possible lowest prefix in case there's an election.
+    partition_stop_advertising_pref_id();
+    partition_advertise_pref_id(((uint8_t *)(&my_thread_prefix)) + 1);
+
+    // If we have adopted a prefix, set a timer after which we will drop it and start advertising if nothing has
+    // happened
+    if (partition_post_partition_wakeup != NULL) {
+        ioloop_cancel_wake_event(partition_post_partition_wakeup);
+    } else {
+        partition_post_partition_wakeup = ioloop_wakeup_create();
+        if (partition_post_partition_wakeup == NULL) {
+            ERROR("partition_id_changed: can't allocate pref:id wait wakeup.");
+            return;
+        }
+    }
+    // Allow ten seconds for the services state to settle, after which time we should either have a pref:id backing
+    // up a prefix, or should advertise a prefix.
+    INFO("partition_id_changed: waiting for other BRs to propose their prefixes.");
+    ioloop_add_wake_event(partition_post_partition_wakeup, NULL, partition_post_partition_timeout, NULL, 10 * 1000);
+}
+
+static void
+partition_remove_service_done(void *context, cti_status_t status)
+{
+    INFO("partition_remove_service_done: %d", status);
+
+    // Flush any advertisements we're currently doing, since the accessories that advertised them will
+    // notice the service is gone and start advertising with a different service.
+#ifndef OPEN_SOURCE
+    // The conditional test is so that we don't do this twice when we are advertising both services.
+#endif
+    if (context != NULL) {
+        srp_mdns_flush();
+    }
+}
+
+static void
+partition_stop_advertising_service(void)
+{
+    // This should remove any copy of the service that this BR is advertising.
+    INFO("partition_stop_advertising_service: %" PRIu64 "/" PUB_S_SRP, THREAD_ENTERPRISE_NUMBER, "00010001");
+    uint8_t service_info[] = { 0, 0, 0, 1 };
+    int status;
+
+    service_info[0] = THREAD_SRP_SERVER_OPTION & 255;
+    status = cti_remove_service((void *)(ptrdiff_t)1, partition_remove_service_done, dispatch_get_main_queue(),
+                                THREAD_ENTERPRISE_NUMBER, service_info, 1);
+    if (status != kCTIStatus_NoError) {
+        INFO("partition_stop_advertising_service: status %d", status);
+    }
+}
+
+static void
+partition_add_service_callback(void *__unused context, cti_status_t status)
+{
+    if (status != kCTIStatus_NoError) {
+        INFO("partition_add_service_callback: status = %d", status);
+    } else {
+        INFO("partition_add_service_callback: status = %d", status);
+    }
+}
+
+static void
+partition_start_advertising_service(void)
+{
+    uint8_t service_info[] = {0, 0, 0, 1};
+    uint8_t server_info[18];
+    int ret;
+
+    memcpy(&server_info, &srp_listener_ip_address, 16);
+    server_info[16] = (srp_service_listen_port >> 8) & 255;
+    server_info[17] = srp_service_listen_port & 255;
+
+    service_info[0] = THREAD_SRP_SERVER_OPTION & 255;
+    INFO("partition_add_srp_service: %" PRIu64 "/%02x/" PRI_SEGMENTED_IPv6_ADDR_SRP ":%d" ,
+         THREAD_ENTERPRISE_NUMBER, service_info[0],
+         SEGMENTED_IPv6_ADDR_PARAM_SRP(srp_listener_ip_address.s6_addr, server_ip_buf), srp_service_listen_port);
+
+    ret = cti_add_service(NULL, partition_add_service_callback, dispatch_get_main_queue(),
+                              THREAD_ENTERPRISE_NUMBER, service_info, 1, server_info, sizeof server_info);
+    if (ret != kCTIStatus_NoError) {
+        INFO("partition_add_srp_service: status %d", ret);
+    }
+
+    // Wait a while for the service add to be reflected in an event.
+    partition_schedule_service_add_wakeup();
+}
+
+static void
+partition_service_add_wakeup(void *__unused context)
+{
+    partition_service_last_add_time = 0;
+    partition_maybe_advertise_service();
+}
+
+static void
+partition_schedule_service_add_wakeup()
+{
+    if (partition_service_add_pending_wakeup == NULL) {
+        partition_service_add_pending_wakeup = ioloop_wakeup_create();
+        if (partition_service_add_pending_wakeup == NULL) {
+            ERROR("Can't schedule service add pending wakeup: no memory!");
+            return;
+        }
+    } else {
+        ioloop_cancel_wake_event(partition_service_add_pending_wakeup);
+    }
+    // Wait ten seconds.
+    ioloop_add_wake_event(partition_service_add_pending_wakeup, NULL, partition_service_add_wakeup, NULL, 10 * 1000);
+}
+
+static void
+partition_maybe_advertise_service(void)
+{
+    thread_service_t *service, *lowest[2];
+    int num_services = 0;
+    int i;
+    bool should_remove_service = false;
+    bool should_advertise_service = false;
+    int64_t last_add_time;
+
+    // If we aren't ready to advertise a service, there's nothing to do.
+    if (!partition_can_advertise_service) {
+        INFO("partition_maybe_advertise_service: no service to advertise yet.");
+        return;
+    }
+
+    if (partition_service_blocked) {
+        INFO("partition_maybe_advertise_service: service advertising is disabled.");
+        return;
+    }
+
+    for (i = 0; i < 16; i++) {
+        if (srp_listener_ip_address.s6_addr[i] != 0) {
+            break;
+        }
+    }
+    if (i == 16) {
+        INFO("partition_maybe_advertise_service: no listener.");
+    }
+
+    // The add service function requires a remove prior to the add, so if we are doing an add, we need to wait
+    // for things to stabilize before allowing the removal of a service to trigger a re-evaluation.
+    // Therefore, if we've done an add in the past ten seconds, wait ten seconds before trying another add.
+    last_add_time = ioloop_timenow() - partition_service_last_add_time;
+    INFO("partition_maybe_advertise_service: last_add_time = %" PRId64, last_add_time);
+    if (last_add_time < 10 * 1000) {
+        partition_schedule_service_add_wakeup();
+        return;
+    }
+    lowest[0] = NULL;
+    lowest[1] = NULL;
+
+    for (service = thread_services; service; service = service->next) {
+        int port = service->port[0] | (service->port[1] << 8);
+        SEGMENTED_IPv6_ADDR_GEN_SRP(service->address, srv_addr_buf);
+
+        // A service only counts if its prefix is present and its prefix id is present and matches the
+        // current partition id.
+        if (partition_prefix_is_present((struct in6_addr *)service->address, 64)) {
+            if (partition_pref_id_is_present((struct in6_addr *)service->address)) {
+                num_services++;
+                for (i = 0; i < 2; i++) {
+                    if (lowest[i] == NULL) {
+                        lowest[i] = service;
+                        INFO("service " PRI_SEGMENTED_IPv6_ADDR_SRP "%%%d goes in open slot %d.",
+                             SEGMENTED_IPv6_ADDR_PARAM_SRP(service->address, srv_addr_buf), port, i);
+                        break;
+                    } else if (memcmp(service->address, lowest[i]->address, 16) < 0) {
+                        int lowport;
+
+                        if (lowest[1] != NULL) {
+                            lowport = (lowest[1]->port[0] << 8) | lowest[1]->port[1];
+                            SEGMENTED_IPv6_ADDR_GEN_SRP(lowest[1]->address, lowest_1_buf);
+                            INFO("Superseding " PRI_SEGMENTED_IPv6_ADDR_SRP "%%%d in slot 1",
+                                 SEGMENTED_IPv6_ADDR_PARAM_SRP(lowest[1]->address, lowest_1_buf), lowport);
+                        }
+                        if (i == 0) {
+                            lowport = (lowest[0]->port[0] << 8)| lowest[0]->port[1];
+                            SEGMENTED_IPv6_ADDR_GEN_SRP(lowest[0]->address, lowest_0_buf);
+                            INFO("Moving " PRI_SEGMENTED_IPv6_ADDR_SRP "%%%d from slot 0 to slot 1",
+                                 SEGMENTED_IPv6_ADDR_PARAM_SRP(lowest[0]->address, lowest_0_buf), lowport);
+                            lowest[1] = lowest[0];
+                        }
+                        INFO("service " PRI_SEGMENTED_IPv6_ADDR_SRP "%%%d goes in slot %d.",
+                             SEGMENTED_IPv6_ADDR_PARAM_SRP(service->address, srv_addr_buf), port, i);
+                        lowest[i] = service;
+                        break;
+                    }
+                }
+            } else {
+                INFO("service " PRI_SEGMENTED_IPv6_ADDR_SRP "%%%d doesn't count because the pref:id is not present.",
+                     SEGMENTED_IPv6_ADDR_PARAM_SRP(service->address, srv_addr_buf), port);
+            }
+        } else {
+                INFO("service " PRI_SEGMENTED_IPv6_ADDR_SRP "%%%d doesn't count because the prefix is not present.",
+                     SEGMENTED_IPv6_ADDR_PARAM_SRP(service->address, srv_addr_buf), port);
+        }
+    }
+
+    should_remove_service = true;
+    for (i = 0; i < 2; i++) {
+        if (lowest[i] == NULL) {
+            INFO("partition_maybe_advertise_service: adding service because there's an open slot.");
+            should_remove_service = false;
+            should_advertise_service = true;
+            break;
+        } else {
+            int sign = memcmp(((uint8_t *)(&srp_listener_ip_address)), lowest[i]->address, 16);
+            if (sign == 0) {
+                // We're already advertising the service and we win the election.
+                // If the port hasn't changed, don't update the service
+                uint16_t port = (lowest[i]->port[0] << 8) | lowest[i]->port[1];
+                if (port != srp_service_listen_port) {
+                    INFO("partition_maybe_advertise_service: old service was present and prefix would win election.");
+                    should_remove_service = false;
+                    should_advertise_service = true;
+                } else {
+                    INFO("partition_maybe_advertise_service: service already present and would win election.");
+                    should_remove_service = false;
+                    should_advertise_service = false;
+                }
+                break;
+            } else if (sign < 0) {
+                INFO("partition_maybe_advertise_service: service not present but wins election.");
+                should_remove_service = false;
+                should_advertise_service = true;
+                break;
+            } else {
+                INFO("Service would not win election with lowest[%d]", i);
+            }
+        }
+    }
+
+    // Always remove service before adding it, but also remove it if it lost the election.
+    if (should_remove_service) {
+        partition_stop_advertising_service();
+        partition_service_last_add_time = ioloop_timenow();
+    }
+    if (should_advertise_service) {
+        partition_start_advertising_service();
+        partition_service_last_add_time = ioloop_timenow();
+    }
+}
+
+static void
+partition_service_set_changed()
+{
+    partition_pref_id_set_changed();
+    partition_maybe_advertise_service();
+}
+
+static void partition_maybe_enable_services()
+{
+    bool am_associated = current_thread_state == kCTI_NCPState_Associated;
+    if (am_associated) {
+        INFO("partition_maybe_enable_services: "
+             "Enabling service, which was disabled because of the thread role or state.");
+        partition_may_offer_service = true;
+        partition_can_provide_routing = true;
+        refresh_interface_list();
+        partition_prefix_list_or_pref_id_list_changed(NULL);
+        routing_policy_evaluate_all_interfaces(true);
+    } else {
+        INFO("partition_maybe_enable_services: Not enabling service: " PUB_S_SRP,
+             am_associated ? "associated" : "!associated");
+    }
+}
+
+static void partition_disable_service()
+{
+    bool done_something = false;
+
+    // When our node type or state is such that we should no longer be publishing a prefix, the NCP will
+    // automatically remove the published prefix.  In case this happens, we do not want to remember the
+    // prefix as already having been published.  So drop our recollection of the adopted and published
+    // prefixes; this will get cleaned up when the network comes back if there's an inconsistency.
+    if (adopted_thread_prefix != NULL) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(adopted_thread_prefix->prefix.s6_addr, prefix_buf);
+        INFO("partition_disable_service: unadopting prefix " PRI_SEGMENTED_IPv6_ADDR_SRP,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(adopted_thread_prefix->prefix.s6_addr, prefix_buf));
+        RELEASE_HERE(adopted_thread_prefix, thread_prefix_finalize);
+        adopted_thread_prefix = NULL;
+        done_something = true;
+    }
+    if (published_thread_prefix != NULL) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(published_thread_prefix->prefix.s6_addr, prefix_buf);
+        INFO("partition_disable_service: un-publishing prefix " PRI_SEGMENTED_IPv6_ADDR_SRP,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(published_thread_prefix->prefix.s6_addr, prefix_buf));
+        RELEASE_HERE(published_thread_prefix, thread_prefix_finalize);
+        published_thread_prefix = NULL;
+        done_something = true;
+    }
+
+    // We want to always say something when we pass through this state.
+    if (!done_something) {
+           INFO("partition_disable_service: nothing to do.");
+    }
+
+    partition_may_offer_service = false;
+    partition_can_provide_routing = false;
+}
+#endif // RA_TESTER
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 120
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/route.h b/ServiceRegistration/route.h
new file mode 100644 (file)
index 0000000..84d2931
--- /dev/null
@@ -0,0 +1,239 @@
+/* route.h
+ *
+ * Copyright (c) 2019-2020 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __SERVICE_REGISTRATION_ROUTE_H
+#define __SERVICE_REGISTRATION_ROUTE_H
+#if defined(USE_IPCONFIGURATION_SERVICE)
+#include <SystemConfiguration/SystemConfiguration.h>
+#include "IPConfigurationService.h"
+#endif
+
+#define MIN_DELAY_BETWEEN_RAS 4000
+#define MSEC_PER_SEC (NSEC_PER_SEC / NSEC_PER_MSEC)
+#define MAX_ROUTER_RECEIVED_TIME_GAP_BEFORE_STALE 600 * MSEC_PER_SEC
+
+// Fix this
+#ifndef IPV6_ROUTER_MODE_EXCLUSIVE
+#define IPV6_ROUTER_MODE_DISABLED      0
+#define IPV6_ROUTER_MODE_EXCLUSIVE     1
+#define IPV6_ROUTER_MODE_HYBRID                2
+#endif
+
+
+typedef struct interface interface_t;
+typedef struct icmp_message icmp_message_t;
+typedef struct network_link network_link_t;
+struct interface {
+    int ref_count;
+
+    interface_t *NULLABLE next;
+    char *NONNULL name;
+
+    // Wakeup event for next beacon.
+    wakeup_t *NULLABLE beacon_wakeup;
+
+    // Wakeup event called after we're done sending solicits.  At this point we delete all routes more than 10 minutes
+    // old; if none are left, then we assume there's no IPv6 service on the interface.
+    wakeup_t *NULLABLE post_solicit_wakeup;
+
+    // Wakeup event to trigger the next router solicit to be sent.
+    wakeup_t *NULLABLE router_solicit_wakeup;
+
+    // Wakeup event to deconfigure the on-link prefix after it is no longer valid.
+    wakeup_t *NULLABLE deconfigure_wakeup;
+
+    // Wakeup event to detect that vicarious router discovery is complete
+    wakeup_t *NULLABLE vicarious_discovery_complete;
+
+    // List of ICMP messages from different routers.
+    icmp_message_t *NULLABLE routers;
+
+    // The link to which this interface is connected.
+    network_link_t *NULLABLE link;
+
+#if defined(USE_IPCONFIGURATION_SERVICE)
+    // The service used to configure this interface with an address in the on-link prefix
+    IPConfigurationServiceRef NULLABLE ip_configuration_service;
+
+    // SCDynamicStoreRef
+    SCDynamicStoreRef NULLABLE ip_configuration_store;
+#endif
+
+    struct in6_addr link_local;  // Link-local address
+    struct in6_addr ipv6_prefix; // This is the prefix we advertise, if advertise_ipv6_prefix is true.
+
+    // Absolute time of last beacon, and of next beacon.
+    uint64_t last_beacon, next_beacon;
+
+    // Absolute deadline for deprecating the on-link prefix we've been announcing
+    uint64_t deprecate_deadline;
+
+    // Preferred lifetime for the on-link prefix
+    uint32_t preferred_lifetime;
+
+    // Valid lifetime for the on-link prefix
+    uint32_t valid_lifetime;
+
+    // When the interface becomes active, we send up to three solicits.
+    int num_solicits_sent;
+
+    // The interface index according to the operating systme.
+    int index;
+
+    // Number of IPv4 addresses configured on link.
+    int num_ipv4_addresses;
+
+    // Number of beacons sent. After the first three, the inter-beacon interval goes up.
+    int num_beacons_sent;
+
+    // The interface link layer address, if known.
+    uint8_t link_layer[6];
+
+    // True if the interface is not usable.
+    bool inactive;
+
+    // True if this interface can never be used for routing to the thread network (e.g., loopback, tunnels, etc.)
+    bool ineligible;
+
+    // True if we've determined that it's the thread interface.
+    bool is_thread;
+
+    // True if we should advertise an on-link prefix.
+    bool advertise_ipv6_prefix;
+
+    // True if we've gotten a link-layer address.
+    bool have_link_layer_address;
+
+    // True if the on-link prefix is configured on the interface.
+    bool on_link_prefix_configured;
+
+    // True if we've sent our first beacon since the interface came up.
+    bool sent_first_beacon;
+
+    // Indicates whether or not router discovery has completed for this interface.
+    bool router_discovery_complete;
+
+    // Indicates whether we're currently doing router discovery, so that we don't
+    // restart it when we're already doing it.
+    bool router_discovery_in_progress;
+
+    // Indicates that we've received a router discovery message from some other host,
+    // and are waiting 20 seconds to snoop for replies to that RD message that are
+    // multicast.   If we hear no replies during that time, we trigger router discovery.
+    bool vicarious_router_discovery_in_progress;
+};
+
+typedef enum icmp_option_type {
+    icmp_option_source_link_layer_address =  1,
+    icmp_option_target_link_layer_address =  2,
+    icmp_option_prefix_information        =  3,
+    icmp_option_redirected_header         =  4,
+    icmp_option_mtu                       =  5,
+    icmp_option_route_information         = 24,
+} icmp_option_type_t;
+
+typedef enum icmp_type {
+    icmp_type_echo_request           = 128,
+    icmp_type_echo_reply             = 129,
+    icmp_type_router_solicitation    = 133,
+    icmp_type_router_advertisement   = 134,
+    icmp_type_neighbor_solicitation  = 135,
+    icmp_type_neighbor_advertisement = 136,
+    icmp_type_redirect               = 137,
+} icmp_type_t;
+
+typedef struct link_layer_address {
+    uint16_t length;
+    uint8_t address[32];
+} link_layer_address_t;
+
+typedef struct prefix_information {
+    struct in6_addr prefix;
+    uint8_t length;
+    uint8_t flags;
+    uint32_t valid_lifetime;
+    uint32_t preferred_lifetime;
+} prefix_information_t;
+
+typedef struct route_information {
+    struct in6_addr prefix;
+    uint8_t length;
+    uint8_t flags;
+    uint32_t route_lifetime;
+} route_information_t;
+
+typedef struct icmp_option {
+    icmp_option_type_t type;
+    union {
+        link_layer_address_t link_layer_address;
+        prefix_information_t prefix_information;
+        route_information_t route_information;
+    } option;
+} icmp_option_t;
+
+struct icmp_message {
+    icmp_message_t *NULLABLE next;
+    interface_t *NULLABLE interface;
+    icmp_option_t *NULLABLE options;
+    bool new_router;                // If this router information is a newly recevied one.
+    bool received_time_already_adjusted;    // if the received time of the message is already adjusted by
+                                                    // vicarious mode
+    struct in6_addr source;
+    struct in6_addr destination;
+
+    uint64_t received_time;
+
+    uint32_t reachable_time;
+    uint32_t retransmission_timer;
+    uint8_t cur_hop_limit;          // Current hop limit for Router Advertisement messages.
+    uint8_t flags;
+    uint8_t type;
+    uint8_t code;
+    uint16_t checksum;              // We hope the kernel figures this out for us.
+    uint16_t router_lifetime;
+
+    int num_options;
+    int hop_limit;                  // Hop limit provided by the kernel, must be 255.
+};
+
+void ula_generate(void);
+bool start_route_listener(void);
+bool start_icmp_listener(void);
+void icmp_leave_join(int sock, int ifindex, bool join);
+
+#define interface_create(name, iface) interface_create_(name, iface, __FILE__, __LINE__)
+interface_t *NULLABLE interface_create_(const char *NONNULL name, int ifindex,
+                                                      const char *NONNULL file, int line);
+#define interface_retain(interface) interface_retain_(interface, __FILE__, __LINE__)
+void interface_retain_(interface_t *NONNULL interface, const char *NONNULL file, int line);
+#define interface_release(interface) interface_release_(interface, __FILE__, __LINE__)
+void interface_release_(interface_t *NONNULL interface, const char *NONNULL file, int line);
+bool interface_monitor_start(void);
+void thread_network_startup(void);
+void thread_network_shutdown(void);
+void partition_stop_advertising_pref_id(void);
+void partition_start_srp_listener(void);
+#endif // __SERVICE_REGISTRATION_ROUTE_H
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 120
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/sign-macos.c b/ServiceRegistration/sign-macos.c
new file mode 100644 (file)
index 0000000..3c1ee30
--- /dev/null
@@ -0,0 +1,312 @@
+/* sign.c
+ *
+ * Copyright (c) 2018-2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * DNS SIG(0) signature generation for DNSSD SRP using mbedtls.
+ *
+ * Functions required for loading, saving, and generating public/private keypairs, extracting the public key
+ * into KEY RR data, and computing signatures.
+ *
+ * This is the implementation for Mac OS X.
+ */
+
+
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <sys/random.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <dns_sd.h>
+
+#include "srp.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#define SRP_CRYPTO_MACOS_INTERNAL
+#include "srp-crypto.h"
+
+// Key is stored in an opaque data structure, for mbedtls this is an mbedtls_pk_context.
+// Function to read a public key from a KEY record
+// Function to validate a signature given some data and a public key (not required on client)
+
+// Function to free a key
+void
+srp_keypair_free(srp_key_t *key)
+{
+    free(key);
+}
+
+uint16_t
+srp_random16()
+{
+    return arc4random_uniform(65536);
+}
+
+static void
+srp_sec_error_print(const char *reason, OSStatus status)
+{
+    const char *utf8 = NULL;
+    CFStringRef err = SecCopyErrorMessageString(status, NULL);
+    if (err != NULL) {
+        utf8 = CFStringGetCStringPtr(err, kCFStringEncodingUTF8);
+    }
+    if (utf8 != NULL) {
+        ERROR(PUB_S_SRP ": " PUB_S_SRP, reason, utf8);
+    } else {
+        ERROR(PUB_S_SRP ": %d", reason, (int)status);
+    }
+    if (err != NULL) {
+        CFRelease(err);
+    }
+}
+
+// Function to generate a key
+static srp_key_t *
+srp_get_key_internal(const char *key_name, bool delete)
+{
+    long two56 = 256;
+    srp_key_t *key = NULL;
+    OSStatus status;
+
+    CFMutableDictionaryRef key_parameters = CFDictionaryCreateMutable(NULL, 8,
+                                                                      &kCFTypeDictionaryKeyCallBacks,
+                                                                      &kCFTypeDictionaryValueCallBacks);
+    CFMutableDictionaryRef pubkey_parameters;
+
+    if (key_parameters != NULL) {
+        CFDictionaryAddValue(key_parameters, kSecAttrIsPermanent, kCFBooleanTrue);
+        CFDictionaryAddValue(key_parameters, kSecAttrKeyType, kSecAttrKeyTypeECSECPrimeRandom);
+        CFNumberRef num = CFNumberCreate(NULL, kCFNumberLongType, &two56);
+        CFDictionaryAddValue(key_parameters, kSecAttrKeySizeInBits, num);
+        CFRelease(num);
+        CFStringRef str = CFStringCreateWithCString(NULL, key_name, kCFStringEncodingUTF8);
+        CFDictionaryAddValue(key_parameters, kSecAttrLabel, str);
+        CFRelease(str);
+        CFDictionaryAddValue(key_parameters, kSecReturnRef, kCFBooleanTrue);
+        CFDictionaryAddValue(key_parameters, kSecMatchLimit, kSecMatchLimitOne);
+        CFDictionaryAddValue(key_parameters, kSecClass, kSecClassKey);
+        pubkey_parameters = CFDictionaryCreateMutableCopy(NULL, 8, key_parameters);
+        if (pubkey_parameters != NULL) {
+            CFDictionaryAddValue(key_parameters, kSecAttrKeyClass, kSecAttrKeyClassPrivate);
+            CFDictionaryAddValue(pubkey_parameters, kSecAttrKeyClass, kSecAttrKeyClassPublic);
+            CFDictionaryAddValue(pubkey_parameters, kSecAttrIsExtractable, kCFBooleanTrue);
+            CFDictionaryAddValue(key_parameters, kSecAttrIsExtractable, kCFBooleanTrue);
+#if !defined(OPEN_SOURCE) && TARGET_OS_TV
+            CFDictionaryAddValue(pubkey_parameters, kSecAttrAccessible,
+                                 kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate);
+#else
+            CFDictionaryAddValue(pubkey_parameters, kSecAttrAccessible,
+                                 kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly);
+#endif
+            if (delete) {
+                status = SecItemDelete(key_parameters);
+                if (status == errSecSuccess) {
+                    status = SecItemDelete(pubkey_parameters);
+                }
+                key = NULL;
+            } else {
+                key = calloc(1, sizeof(*key));
+
+                if (key != NULL) {
+                    CFErrorRef error = NULL;
+
+                    // See if the key is already on the keychain.
+                    status = SecItemCopyMatching(key_parameters, (CFTypeRef *)&key->private);
+                    if (status == errSecSuccess) {
+                        status = SecItemCopyMatching(pubkey_parameters, (CFTypeRef *)&key->public);
+                    } else {
+                        key->private = SecKeyCreateRandomKey(key_parameters, &error);
+                        if (key->private != NULL) {
+                            key->public = SecKeyCopyPublicKey(key->private);
+                        }
+                    }
+                    if (key->public == NULL || key->private == NULL) {
+                        if (error != NULL) {
+                            CFShow(error);
+                        } else {
+                            srp_sec_error_print("Failed to get key pair", status);
+                        }
+                        free(key);
+                        key = NULL;
+                    }
+                }
+            }
+            CFRelease(key_parameters);
+            CFRelease(pubkey_parameters);
+        }
+    }
+    return key;
+}
+
+srp_key_t *
+srp_get_key(const char *key_name, void *__unused os_context)
+{
+    return srp_get_key_internal(key_name, false);
+}
+
+// Remove an existing key
+int
+srp_reset_key(const char *key_name, void *__unused os_context)
+{
+    srp_get_key_internal(key_name, true);
+    return kDNSServiceErr_NoError;
+}
+
+// void to get the length of the public key
+size_t
+srp_pubkey_length(srp_key_t *key)
+{
+    (void)key;
+    return ECDSA_KEY_SIZE;
+}
+
+int
+srp_key_algorithm(srp_key_t *key)
+{
+    (void)key;
+    return dnssec_keytype_ecdsa;
+}
+
+size_t
+srp_signature_length(srp_key_t *key)
+{
+    (void)key;
+    return ECDSA_KEY_SIZE;
+}
+
+// Function to copy out the public key as binary data
+int
+srp_pubkey_copy(uint8_t *buf, size_t max, srp_key_t *key)
+{
+    CFErrorRef error = NULL;
+    int ret = 0;
+    CFDataRef pubkey = SecKeyCopyExternalRepresentation(key->public, &error);
+    if (pubkey == NULL) {
+        if (error != NULL) {
+            CFShow(error);
+        } else {
+            ERROR("Unknown failure in SecKeyCopyExternalRepresentation");
+        }
+    } else {
+        const uint8_t *bytes = CFDataGetBytePtr(pubkey);
+        unsigned long len = CFDataGetLength(pubkey);
+
+        // Should be 04 | X | Y
+        if (bytes[0] != 4) {
+            ERROR("Unexpected preamble to public key: %d", bytes[0]);
+        } else if (len - 1 > max) {
+            ERROR("Not enough room for public key in buffer: %ld > %zd", len - 1, max);
+        } else if (len - 1 != ECDSA_KEY_SIZE) {
+            ERROR("Unexpected key size %ld", len - 1);
+        } else {
+            memcpy(buf, bytes + 1, len - 1);
+            ret = ECDSA_KEY_SIZE;
+        }
+        CFRelease(pubkey);
+    }
+    return ret;
+}
+
+// Function to generate a signature given some data and a private key
+int
+srp_sign(uint8_t *output, size_t max, uint8_t *message, size_t msglen,
+         uint8_t *rr, size_t rdlen, srp_key_t *key)
+{
+    CFMutableDataRef payload = NULL;
+    CFDataRef signature = NULL;
+    CFErrorRef error = NULL;
+    const uint8_t *bytes;
+    unsigned long len;
+    int ret = 0;
+
+    typedef struct {
+        SecAsn1Item r, s;
+    } raw_signature_data_t;
+    raw_signature_data_t raw_signature;
+
+    ECDSA_SIG_TEMPLATE(sig_template);
+
+    if (max < ECDSA_SHA256_SIG_SIZE) {
+        ERROR("srp_sign: not enough space in output buffer (%lu) for signature (%d).",
+              (unsigned long)max, ECDSA_SHA256_SIG_SIZE);
+        return 0;
+    }
+
+    payload = CFDataCreateMutable(NULL, msglen + rdlen);
+    if (payload == NULL) {
+        ERROR("srp_sign: CFDataCreateMutable failed on length %zd", msglen + rdlen);
+        return 0;
+    }
+    CFDataAppendBytes(payload, rr, rdlen);
+    CFDataAppendBytes(payload, message, msglen);
+
+    signature = SecKeyCreateSignature(key->private,
+                                      kSecKeyAlgorithmECDSASignatureMessageX962SHA256, payload, &error);
+    CFRelease(payload);
+    if (error != NULL) {
+        CFRelease(signature);
+        CFShow(error);
+        return 0;
+    }
+    if (signature == NULL) {
+        ERROR("No error, but no signature.");
+        return 0;
+    }
+
+    SecAsn1CoderRef decoder;
+    OSStatus status = SecAsn1CoderCreate(&decoder);
+    if (status == errSecSuccess) {
+        len = CFDataGetLength(signature);
+        bytes = CFDataGetBytePtr(signature);
+
+        status = SecAsn1Decode(decoder, bytes, len, sig_template, &raw_signature);
+        if (status == errSecSuccess) {
+            if (raw_signature.r.Length + raw_signature.s.Length > ECDSA_SHA256_SIG_SIZE) {
+                ERROR("Unexpected length %zd + %zd is not %d", raw_signature.r.Length,
+                      raw_signature.s.Length, ECDSA_SHA256_SIG_SIZE);
+            } else {
+                unsigned long diff = ECDSA_SHA256_SIG_PART_SIZE - raw_signature.r.Length;
+                if (diff > 0) {
+                    memset(output, 0, diff);
+                }
+                memcpy(output + diff, raw_signature.r.Data, raw_signature.r.Length);
+                diff = ECDSA_SHA256_SIG_PART_SIZE - raw_signature.s.Length;
+                if (diff > 0) {
+                    memset(output + ECDSA_SHA256_SIG_PART_SIZE, 0, diff);
+                }
+                memcpy(output + ECDSA_SHA256_SIG_PART_SIZE + diff, raw_signature.s.Data, raw_signature.s.Length);
+                ret = 1;
+            }
+        }
+        SecAsn1CoderRelease(decoder);
+    }
+    if (status != errSecSuccess) {
+        srp_sec_error_print("srp_sign", status);
+    }
+    CFRelease(signature);
+    return ret;
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
index 2455d990a28e5f3c80e1a8a385d7d16a57810187..99d661320b427a881d7db5e8e226c8e76139aad8 100644 (file)
@@ -1,6 +1,6 @@
-/* sign.c
+/* sign-mbedtls.c
  *
- * Copyright (c) 2018 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2018-2019 Apple Computer, Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  *
  * Functions required for loading, saving, and generating public/private keypairs, extracting the public key
  * into KEY RR data, and computing signatures.
+ *
+ * This is the implementation for mbedtls, e.g. on Thread Devices, Linux, and OpenWRT.
  */
 
 #include <stdio.h>
+#ifdef THREAD_DEVKIT_ADK
+#include <openthread/random_noncrypto.h>
+#include "HAPPlatformRandomNumber.h"
+#else
 #include <arpa/inet.h>
+#ifdef LINUX_GETENTROPY
+#define _GNU_SOURCE
+#include <linux/random.h>
+#include <sys/syscall.h>
+#else
+#include <sys/random.h>
+#endif // LINUX_GETENTROPY
+#endif // THREAD_DEVKIT_ADK
 #include <string.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <fcntl.h>
-#include <sys/random.h>
-#include <sys/errno.h>
+#include <errno.h>
 
 #include "srp.h"
 #include "dns-msg.h"
-#define SRP_CRYPTO_MBEDTLS_INTERNAL
+#define SRP_CRYPTO_MBEDTLS_INTERNAL 1
 #include "srp-crypto.h"
+#include "dns_sd.h"
 
 // For debugging
 #ifdef DEBUG_SHA256
 int
-srp_mbedtls_sha256_update_ret(mbedtls_sha256_context *sha, uint8_t *data, size_t len)
+srp_mbedtls_sha256_update_ret(const char *thing_name,
+                              mbedtls_sha256_context *sha, uint8_t *data, size_t len)
 {
     int i;
-    fprintf(stderr, "data %lu: ", (unsigned long)len);
+    fprintf(stderr, "%s %lu: ", thing_name, (unsigned long)len);
+    if (len > 400) {
+        len = 400;
+    }
+
     for (i = 0; i < len; i++) {
         fprintf(stderr, "%02x", data[i]);
     }
@@ -70,17 +89,27 @@ srp_mbedtls_sha256_finish_ret(mbedtls_sha256_context *sha, uint8_t *hash)
 void
 srp_keypair_free(srp_key_t *key)
 {
+#ifndef EXCLUDE_CRYPTO
     mbedtls_pk_free(&key->key);
-    mbedtls_entropy_free(&key->entropy);
-    mbedtls_ctr_drbg_free(&key->ctr);
+#endif
     free(key);
 }
 
-// Needed to see the RNG with good entropy data.
+#ifndef EXCLUDE_CRYPTO
+// Needed to seed the RNG with good entropy data.
 static int
 get_entropy(void *data, unsigned char *output, size_t len, size_t *outlen)
 {
+#ifdef THREAD_DEVKIT_ADK
+    HAPPlatformRandomNumberFill(output, len);
+    *outlen = len;
+    return 0;
+#else
+#ifdef LINUX_GETENTROPY
+    int result = syscall(SYS_getrandom, output, len, GRND_RANDOM);
+#else
     int result = getentropy(output, len);
+#endif
     (void)data;
 
     if (result != 0) {
@@ -88,78 +117,133 @@ get_entropy(void *data, unsigned char *output, size_t len, size_t *outlen)
         return MBEDTLS_ERR_ENTROPY_SOURCE_FAILED;
     }
     *outlen = len;
+#endif // THREAD_DEVKIT_ADK
     return 0;
 }
 
+// mbedtls on embedded devices seems to react poorly to multiple rng contexts, so we create just
+// one and keep it around.   It would be nice if this got fixed, but it's actually more efficient
+// to have one context, so not something we need to fix.
+typedef struct rng_state {
+    mbedtls_entropy_context entropy_context;
+    mbedtls_ctr_drbg_context rng_context;
+    char errbuf[64];
+} rng_state_t;
+
+static rng_state_t *rng_state;
+
+bool
+rng_state_fetch(void)
+{
+    int status;
+
+    if (rng_state == NULL) {
+        rng_state = calloc(1, sizeof *rng_state);
+        if (rng_state == NULL) {
+            ERROR("srp_random16(): no memory for state.");
+            goto fail;
+        }
+
+        mbedtls_entropy_init(&rng_state->entropy_context);
+        status = mbedtls_entropy_add_source(&rng_state->entropy_context, get_entropy,
+                                            NULL, 1, MBEDTLS_ENTROPY_SOURCE_STRONG);
+        if (status != 0) {
+            mbedtls_strerror(status, rng_state->errbuf, sizeof rng_state->errbuf);
+            ERROR("mbedtls_entropy_add_source failed: %s", rng_state->errbuf);
+            goto fail;
+        }
+
+        mbedtls_ctr_drbg_init(&rng_state->rng_context);
+        status = mbedtls_ctr_drbg_seed(&rng_state->rng_context,
+                                       mbedtls_entropy_func, &rng_state->entropy_context, NULL, 0);
+
+        if (status != 0) {
+            mbedtls_strerror(status, rng_state->errbuf, sizeof rng_state->errbuf);
+            ERROR("mbedtls_ctr_drbg_seed failed: %s", rng_state->errbuf);
+        fail:
+            free(rng_state);
+            rng_state = NULL;
+            return false;
+        }
+    }
+    return true;
+}
+#endif // EXCLUDE_CRYPTO
+
 static srp_key_t *
 srp_key_setup(void)
 {
-    int status;
-    srp_key_t *key = calloc(sizeof *key, 1);
-    char errbuf[64];
+    srp_key_t *key = calloc(1, sizeof(*key));
 
     if (key == NULL) {
         return key;
     }
-    
+
+#ifndef EXCLUDE_CRYPTO
     mbedtls_pk_init(&key->key);
-    mbedtls_entropy_init(&key->entropy);
-    if ((status = mbedtls_entropy_add_source(&key->entropy, get_entropy,
-                                             NULL, 1, MBEDTLS_ENTROPY_SOURCE_STRONG)) != 0) {
-        mbedtls_strerror(status, errbuf, sizeof errbuf);
-        ERROR("mbedtls_entropy_add_source failed: %s", errbuf);
-    } else if ((status = mbedtls_ctr_drbg_seed(&key->ctr, mbedtls_entropy_func, &key->entropy, NULL, 0)) != 0) {
-        mbedtls_strerror(status, errbuf, sizeof errbuf);
-        ERROR("mbedtls_ctr_drbg_seed failed: %s", errbuf);
-    } else {
+    if (rng_state_fetch()) {
         return key;
     }
     mbedtls_pk_free(&key->key);
-    mbedtls_entropy_free(&key->entropy);
     free(key);
     return NULL;
+#else
+    return key;
+#endif
 }
 
-// Function to read a keypair from a file
-srp_key_t *
-srp_load_keypair(const char *file)
+uint16_t
+srp_random16()
 {
-    int fd = open(file, O_RDONLY);
-    unsigned char buf[256];
-    ssize_t rv;
-    srp_key_t *key;
+#ifdef EXCLUDE_CRYPTO
+    // Note that this is the wrong thing to return here and needs to be fixed.
+    return otRandomNonCryptoGetUint16();
+#else
     int status;
+    uint16_t ret;
     char errbuf[64];
-
-    if (fd < 0) {
-        if (errno != ENOENT) {
-            ERROR("Unable to open srp.key: %s", strerror(errno));
-            return NULL;
+    if (rng_state_fetch()) {
+        status = mbedtls_ctr_drbg_random(&rng_state->rng_context, (unsigned char *)&ret, sizeof ret);
+        if (status != 0) {
+            mbedtls_strerror(status, errbuf, sizeof errbuf);
+            ERROR("mbedtls_ctr_drbg_random failed: %s", errbuf);
+            return 0xffff;
         }
-        return NULL;
-    }        
-
-    // The key is of limited size, so there's no reason to get fancy.
-    rv = read(fd, buf, sizeof buf);
-    close(fd);
-    if (rv == sizeof buf) {
-        ERROR("key file is unreasonably large.");
-        return NULL;
+        return ret;
     }
+    return 0xffff;
+#endif
+}
+
+srp_key_t *
+srp_load_key_from_buffer(const uint8_t *buffer, size_t length)
+{
+    srp_key_t *key;
+#ifndef EXCLUDE_CRYPTO
+    int status;
+    char errbuf[64];
+#endif
 
     key = srp_key_setup();
     if (key == NULL) {
         return NULL;
     }
 
-    if ((status = mbedtls_pk_parse_key(&key->key, buf, rv, NULL, 0)) != 0) {
+#ifndef EXCLUDE_CRYPTO
+    if ((status = mbedtls_pk_parse_key(&key->key, buffer, length, NULL, 0)) != 0) {
         mbedtls_strerror(status, errbuf, sizeof errbuf);
         ERROR("mbedtls_pk_parse_key failed: %s", errbuf);
     } else if (!mbedtls_pk_can_do(&key->key, MBEDTLS_PK_ECDSA)) {
-        ERROR("%s does not contain a usable ECDSA key.", file);
+        ERROR("Buffer does not contain a usable ECDSA key.");
     } else {
         return key;
     }
+#else
+    if (length == ECDSA_KEY_SIZE) {
+        memcpy(key->key, buffer, length);
+        return key;
+    }
+#endif
     srp_keypair_free(key);
     return NULL;
 }
@@ -168,64 +252,118 @@ srp_load_keypair(const char *file)
 srp_key_t *
 srp_generate_key(void)
 {
+    srp_key_t *key;
+#ifndef EXCLUDE_CRYPTO
     int status;
     char errbuf[64];
-    srp_key_t *key = srp_key_setup();
-    const mbedtls_pk_info_t *key_type = mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY);
+    const mbedtls_pk_info_t *key_type;
+#endif
 
-    if (key == NULL || key_type == NULL) {
+    INFO("srp_key_setup");
+    key = srp_key_setup();
+    if (key == NULL) {
+        ERROR("srp_key_setup() failed.");
         return NULL;
     }
-    
+#ifndef EXCLUDE_CRYPTO
+    key_type = mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY);
+    if (key_type == NULL) {
+        INFO("mbedtls_pk_info_from_type failed");
+        return NULL;
+    }
+
+    INFO("mbedtls_pk_setup");
     if ((status = mbedtls_pk_setup(&key->key, key_type)) != 0) {
         mbedtls_strerror(status, errbuf, sizeof errbuf);
         ERROR("mbedtls_pk_setup failed: %s", errbuf);
-    } else if ((status = mbedtls_ecdsa_genkey(mbedtls_pk_ec(key->key), MBEDTLS_ECP_DP_SECP256R1,
-                                              mbedtls_ctr_drbg_random, &key->ctr)) != 0) {
-        mbedtls_strerror(status, errbuf, sizeof errbuf);
-        ERROR("mbedtls_ecdsa_genkey failed: %s", errbuf);
     } else {
-        return key;
+        INFO("mbedtls_pk_ecdsa_genkey");
+        if ((status = mbedtls_ecdsa_genkey(mbedtls_pk_ec(key->key), MBEDTLS_ECP_DP_SECP256R1,
+                                           mbedtls_ctr_drbg_random, &rng_state->rng_context)) != 0) {
+            mbedtls_strerror(status, errbuf, sizeof errbuf);
+            ERROR("mbedtls_ecdsa_genkey failed: %s", errbuf);
+        } else {
+            return key;
+        }
     }
     srp_keypair_free(key);
     return NULL;
+#else
+    HAPPlatformRandomNumberFill(key->key, sizeof key->key);
+    return key;
+#endif
 }
 
-// Function to write a keypair to a file
-int
-srp_write_key_to_file(const char *file, srp_key_t *key)
+// Copy an srp_key_t into a buffer.   Key is not necessarily aligned with the beginning of the
+// buffer; the return value, if not NULL, is the beginning of the key.   If NULL, the buffer wasn't
+// big enough.
+uint8_t *
+srp_store_key_to_buffer(uint8_t *buffer, size_t *length, srp_key_t *key)
 {
-    int fd;
-    unsigned char buf[256];
-    ssize_t rv;
-    int len;
+#ifdef EXCLUDE_CRYPTO
+    if (*length < ECDSA_KEY_SIZE) {
+        return NULL;
+    }
+    if (*length > sizeof key->key) {
+        *length = sizeof key->key;
+    }
+    memcpy(buffer, key->key, *length);
+    return buffer;
+#else
+    size_t len = mbedtls_pk_write_key_der(&key->key, buffer, *length);
+    uint8_t *ret;
     char errbuf[64];
-
-    len = mbedtls_pk_write_key_der(&key->key, buf, sizeof buf);
     if (len <= 0) {
         mbedtls_strerror(len, errbuf, sizeof errbuf);
         ERROR("mbedtls_pk_write_key_der failed: %s", errbuf);
-        return 0;
+        return NULL;
     }
-
-#ifndef O_DIRECT
-#define O_DIRECT 0
+    ret = &buffer[*length - len];
+    *length = len;
+    return ret;
 #endif
-    fd = open(file, O_CREAT | O_EXCL | O_WRONLY | O_DIRECT, 0700);
-    if (fd < 0) {
-        ERROR("Unable to create srp.key: %s", strerror(errno));
-        return 0;
-    }        
+}
 
-    rv = write(fd, &buf[sizeof buf - len], len);
-    close(fd);
-    if (rv != len) {
-        ERROR("key file write truncated.");
-        unlink(file);
-        return 0;
-    }
+srp_key_t *
+srp_get_key(const char *key_name, void *os_context)
+{
+    uint8_t buf[256];
+    uint16_t buf_length;
+    uint8_t *key_bytes;
+    size_t keydata_length;
+    int err;
+    srp_key_t *key;
 
-    return 1;
+    err = srp_load_key_data(os_context, key_name, buf, &buf_length, sizeof buf);
+    if (err == kDNSServiceErr_NoError) {
+        key = srp_load_key_from_buffer(buf, buf_length);
+        if (key == NULL) {
+            INFO("load key fail");
+            return NULL;
+        }
+        // Otherwise we have a key.
+    } else if (err == kDNSServiceErr_NoSuchKey) {
+        key = srp_generate_key();
+        if (key == NULL) {
+            INFO("gen key fail");
+            return NULL;
+        }
+        keydata_length = sizeof buf;
+        if ((key_bytes = srp_store_key_to_buffer(buf, &keydata_length, key)) == NULL) {
+            INFO("store key fail");
+            return NULL;
+        }
+        // Note that it's possible for key_bytes != buf.
+        err = srp_store_key_data(os_context, key_name, key_bytes, (uint16_t)keydata_length);
+        if (err != kDNSServiceErr_NoError) {
+            INFO("store key data fail");
+            return NULL;
+        }
+    } else {
+        INFO("srp_get_key: weird error %d", err);
+        return NULL;
+    }
+    return key;
 }
 
 // Function to get the length of the public key
@@ -251,6 +389,7 @@ srp_signature_length(srp_key_t *key)
 int
 srp_pubkey_copy(uint8_t *buf, size_t max, srp_key_t *key)
 {
+#ifndef EXCLUDE_CRYPTO
     mbedtls_ecp_keypair *ecp = mbedtls_pk_ec(key->key);
     char errbuf[64];
     int status;
@@ -266,6 +405,14 @@ srp_pubkey_copy(uint8_t *buf, size_t max, srp_key_t *key)
         ERROR("mbedtls_mpi_write_binary: %s", errbuf);
         return 0;
     }
+#else
+    if (max < ECDSA_KEY_SIZE) {
+        return 0;
+    }
+
+    memcpy(buf, key->key, ECDSA_KEY_SIZE);
+    return ECDSA_KEY_SIZE;
+#endif // EXCLUDE_CRYPTO
 
 #ifdef MBEDTLS_PUBKEY_DUMP
     int i;
@@ -274,8 +421,7 @@ srp_pubkey_copy(uint8_t *buf, size_t max, srp_key_t *key)
         fprintf(stderr, "%02x", buf[i]);
     }
     putc('\n', stderr);
-#endif
-
+#endif // EXCLUDE_CRYPTO
     return ECDSA_KEY_SIZE;
 }
 
@@ -283,41 +429,76 @@ srp_pubkey_copy(uint8_t *buf, size_t max, srp_key_t *key)
 int
 srp_sign(uint8_t *output, size_t max, uint8_t *message, size_t msglen, uint8_t *rr, size_t rdlen, srp_key_t *key)
 {
+#ifdef EXCLUDE_CRYPTO
+    return 1;
+#else
+    int success = 1;
     int status;
     unsigned char hash[ECDSA_SHA256_HASH_SIZE];
     char errbuf[64];
-    mbedtls_sha256_context sha;
+    mbedtls_sha256_context *sha;
+    uint8_t shabuf[16 + sizeof(*sha)];
+    uint32_t *sbp;
     mbedtls_ecp_keypair *ecp = mbedtls_pk_ec(key->key);
     mbedtls_mpi r, s;
 
+#ifdef THREAD_DEVKIT_ADK
+    int i;
+    INFO("srp_sign(output=%p  max=%d  message=%p  msglen=%d  rr=%p  rdlen=%d)",
+         output, max, message, msglen, rr, rdlen);
+#endif
+
     if (max < ECDSA_SHA256_SIG_SIZE) {
         ERROR("srp_sign: not enough space in output buffer (%lu) for signature (%d).",
               (unsigned long)max, ECDSA_SHA256_SIG_SIZE);
         return 0;
     }
 
-    mbedtls_sha256_init(&sha);
+    sbp = (uint32_t *)shabuf;
+    sha = (mbedtls_sha256_context *)sbp;
+    mbedtls_sha256_init(sha);
+#ifdef THREAD_DEVKIT_ADK
+    for (i = 0; i < sizeof shabuf; i += 16) {
+        INFO("%03x: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
+             i, shabuf[i], shabuf[i + 1], shabuf[i + 2], shabuf[i + 3], shabuf[i + 4], shabuf[i + 5], shabuf[i + 6],
+             shabuf[i + 7], shabuf[i + 8], shabuf[i + 9], shabuf[i + 10], shabuf[i + 11], shabuf[i + 12],
+             shabuf[i + 13], shabuf[i + 14], shabuf[i + 15]);
+    }
+    INFO("1 rdlen=%d", rdlen);
+#endif
     memset(hash, 0, sizeof hash);
     mbedtls_mpi_init(&r);
     mbedtls_mpi_init(&s);
 
     // Calculate the hash across first the SIG RR (minus the signature) and then the message
     // up to but not including the SIG RR.
-    if ((status = mbedtls_sha256_starts_ret(&sha, 0)) != 0 ||
-        (status = srp_mbedtls_sha256_update_ret(&sha, rr, rdlen) != 0) ||
-        (status = srp_mbedtls_sha256_update_ret(&sha, message, msglen)) != 0 ||
-        (status = srp_mbedtls_sha256_finish_ret(&sha, hash)) != 0) {
+    status = mbedtls_sha256_starts_ret(sha, 0);
+#ifdef THREAD_DEVKIT_ADK
+    INFO("5 rdlen=%d", rdlen);
+#endif
+    if (status == 0) {
+        status = srp_mbedtls_sha256_update_ret("rr", sha, rr, rdlen);
+    }
+    if (status == 0) {
+        status = srp_mbedtls_sha256_update_ret("message", sha, message, msglen);
+    }
+    if (status == 0) {
+        status = srp_mbedtls_sha256_finish_ret(sha, hash);
+    }
+    if (status != 0) {
         mbedtls_strerror(status, errbuf, sizeof errbuf);
         ERROR("mbedtls_sha_256 hash failed: %s", errbuf);
-        return 0;
+        success = 0;
+        goto cleanup;
     }
 
     status = mbedtls_ecdsa_sign(&ecp->grp, &r, &s, &ecp->d, hash, sizeof hash,
-                                mbedtls_ctr_drbg_random, &key->ctr);
+                                mbedtls_ctr_drbg_random, &rng_state->rng_context);
     if (status != 0) {
         mbedtls_strerror(status, errbuf, sizeof errbuf);
         ERROR("mbedtls_ecdsa_sign failed: %s", errbuf);
-        return 0;
+        success = 0;
+        goto cleanup;
     }
 
     if ((status = mbedtls_mpi_write_binary(&r, output, ECDSA_SHA256_SIG_PART_SIZE)) != 0 ||
@@ -325,11 +506,16 @@ srp_sign(uint8_t *output, size_t max, uint8_t *message, size_t msglen, uint8_t *
                                            ECDSA_SHA256_SIG_PART_SIZE)) != 0) {
         mbedtls_strerror(status, errbuf, sizeof errbuf);
         ERROR("mbedtls_ecdsa_sign failed: %s", errbuf);
-        return 0;
+        success = 0;
+        goto cleanup;
     }
-    return 1;
+cleanup:
+    mbedtls_mpi_free(&r);
+    mbedtls_mpi_free(&s);
+    return success;
+#endif
 }
-    
+
 // Local Variables:
 // mode: C
 // tab-width: 4
diff --git a/ServiceRegistration/srp-api.h b/ServiceRegistration/srp-api.h
new file mode 100644 (file)
index 0000000..cee1ea9
--- /dev/null
@@ -0,0 +1,155 @@
+/* srp-api.h
+ *
+ * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Structure definitions for the Service Registration Protocol gateway.
+ */
+
+#include "srp.h"
+
+typedef void (*srp_hostname_conflict_callback_t)(const char *NONNULL hostname);
+typedef void (*srp_wakeup_callback_t)(void *NONNULL state);
+typedef void (*srp_datagram_callback_t)(void *NONNULL state, void *NONNULL message, size_t message_length);
+
+// The below functions provide a way for the host to inform the SRP service of the state of the network.
+
+// Call this before calling anything else.   Context will be passed back whenever the srp code
+// calls any of the host functions.
+int srp_host_init(void *NULLABLE host_context);
+
+// Call this to reset the host key (e.g. on factory reset)
+int srp_host_key_reset(void);
+
+// This function can be called by accessories that have different requirements for lease intervals.
+// Normally new_lease_time would be 3600 (1 hour) and new_key_lease_type would be 604800 (7 days).
+int srp_set_lease_times(uint32_t new_lease_time, uint32_t new_key_lease_time);
+
+// Called when a new address is configured that should be advertised.  This can be called during a refresh,
+// in which case it doesn't mark the network state as changed if the address was already present.
+int srp_add_interface_address(uint16_t rrtype, const uint8_t *NONNULL rdata, uint16_t rdlen);
+
+// Called whenever the SRP server address changes or the SRP server becomes newly reachable.  This can be
+// called during a refresh, in which case it doesn't mark the network state as changed if the address was
+// already present.
+int srp_add_server_address(const uint8_t *NONNULL port, uint16_t rrtype, const uint8_t *NONNULL rdata, uint16_t rdlen);
+
+// Called when the node knows its hostname (usually once).   The callback is called if we try to do an SRP
+// update and find out that the hostname is in use; in this case, the callback is expected to generate a new
+// hostname and re-register it.   It is permitted to call srp_set_hostname() from the callback.
+// If the hostname is changed by the callback, then it is used immediately on return from the callback;
+// if the hostname is changed in any other situation, nothing is done with the new name until
+// srp_network_state_stable() is called.
+int srp_set_hostname(const char *NONNULL hostname, srp_hostname_conflict_callback_t NONNULL callback);
+
+// Called when a network state change is complete (that is, all new addresses have been saved and
+// any update to the SRP server address has been provided).   This is only needed when not using the
+// refresh mechanism.
+int srp_network_state_stable(void);
+
+// Delete a previously-configured SRP server address.  This should not be done during a refresh.
+int srp_delete_interface_address(uint16_t rrtype, const uint8_t *NONNULL rdata, uint16_t rdlen);
+
+// Delete a previously-configured SRP server address.  This should not be done during a refresh.
+int srp_delete_server_address(uint16_t rrtype, const uint8_t *NONNULL port, const uint8_t *NONNULL rdata,
+                              uint16_t rdlen);
+
+// Call this to start an address refresh.   This makes sense to do in cases where the caller
+// is not tracking changes, but rather is just doing a full refresh whenever the network state
+// is seen to have changed.   When the refresh is done, if any addresses were added or removed,
+// network_state_changed will be true, and so a call to dnssd_network_state_change_finished()
+// will trigger an update; if nothing changed, no update will be sent.
+int srp_start_address_refresh(void);
+
+// Call this when the address refresh is done.   This invokes srp_network_state_stable().
+int srp_finish_address_refresh(void);
+
+// Call this to deregister everything that's currently registered.  A return value other than kDNSServiceErr_NoError
+// means that there's nothing to deregister.
+int srp_deregister(void *NULLABLE os_context);
+
+// The below functions must be provided by the host.
+
+// This function fetches a key with the specified name for use in signing SRP updates.
+// At present, only ECDSA is supported.   If a key with the specified name doesn't exist,
+// the host is expected to generate and store it.
+srp_key_t *NULLABLE srp_get_key(const char *NONNULL key_name, void *NULLABLE host_context);
+
+// This function clears the key with the specified name.
+int srp_reset_key(const char *NONNULL key_name, void *NULLABLE host_context);
+
+// This function fetches the IP address type (rrtype), address (rrdata x rdlength) and port (port[0],
+// port[1]) of the most recent server with which the SRP client has successfully registered from stable
+// storage.  If the fetch is successful and there was a server recorded in stable storage, it returns true;
+// otherwise it returns false. A false status can mean that there's no way to fetch this information, that
+// no registration has happened in the past, or that there was some other error accessing stable storage.
+bool srp_get_last_server(uint16_t *NONNULL rrtype, uint8_t *NONNULL rrdata, uint16_t rdlength,
+                         uint8_t *NONNULL port, void *NULLABLE host_context);
+
+// This function stores the IP address type (rrtype), address (rrdata x rdlength) and port (port[0],
+// port[1]) of the most recent server with which the SRP client has successfully registered to stable
+// storage.  If the store is successful, it returns true; otherwise it returns false. A false status can
+// mean that there's no way to store this information, or that there was an error writing this information
+// to stable storage.
+bool srp_save_last_server(uint16_t rrtype, uint8_t *NONNULL rrdata, uint16_t rdlength,
+                          uint8_t *NONNULL port, void *NULLABLE host_context);
+
+// This is called to create a context for sending and receiving UDP messages to and from a specified
+// remote host address and port.  The context passed is to be used whenever the srp host implementation
+// does a callback, e.g. when a datagram arrives or when a wakeup occurs (see srp_set_wakeup()).
+// The context is not actually connected to a specific address and port until srp_connect_udp() is
+// invoked on it.
+int srp_make_udp_context(void *NULLABLE host_context, void *NULLABLE *NONNULL p_context,
+                         srp_datagram_callback_t NONNULL callback, void *NONNULL context);
+
+// Connect a udp context to a particular destination.  The context has to have already been created by
+// srp_make_udp_context().  When packets are received, they will be passed to the callback set
+// in srp_make_udp_context().   This must not be called on a context that is already bound to
+// some other destination--call srp_disconnect_udp() first if reusing.
+int
+srp_connect_udp(void *NONNULL context, const uint8_t *NONNULL port, uint16_t address_type,
+                const uint8_t *NONNULL address, uint16_t addrlen);
+
+// Disconnect a udp context.  This is used to dissociate from the udp context state that was created
+// by a previous call to srp_connect_udp
+int
+srp_disconnect_udp(void *NONNULL context);
+
+// This gets rid of the UDP context, frees any associated memory, cancels any outstanding wakeups.
+// The freeing may occur later than the deactivating, depending on how the underlying event loop
+// works.
+int srp_deactivate_udp_context(void *NONNULL host_context, void *NONNULL context);
+
+// This is called to send a datagram to a UDP connection.   The UDP connection is identified by the
+// anonymous pointer that was returned by srp_make_udp_context().
+int srp_send_datagram(void *NULLABLE host_context,
+                      void *NONNULL context, void *NONNULL message, size_t message_length);
+
+// This is called with the context returned by srp_make_udp_context.  The caller is expected to schedule
+// a wakeup event <milliseconds> in the future, when when that event occurs, it's expected to call the
+// callback with the context that was passed to srp_make_udp_context.
+int srp_set_wakeup(void *NULLABLE host_context,
+                   void *NONNULL context, int milliseconds, srp_wakeup_callback_t NONNULL callback);
+
+// This is called to cancel a wakeup, and should not fail even if there is no wakeup pending.
+int srp_cancel_wakeup(void *NULLABLE host_context, void *NONNULL context);
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/srp-client-entitlements.plist b/ServiceRegistration/srp-client-entitlements.plist
new file mode 100644 (file)
index 0000000..3e1bdcd
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+  <dict>
+    <key>application-identifier</key>
+    <string>com.apple.srp-client</string>
+    <key>com.apple.security.network.client</key>
+    <true/>
+    <key>com.apple.srp-mdns-proxy.proxy</key>
+    <true/>
+  </dict>
+</plist>
diff --git a/ServiceRegistration/srp-client.c b/ServiceRegistration/srp-client.c
new file mode 100644 (file)
index 0000000..917d574
--- /dev/null
@@ -0,0 +1,1434 @@
+/* srp-client.c
+ *
+ * Copyright (c) 2018-2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SRP Client
+ *
+ * DNSServiceRegister API for SRP.   See dns_sd.h for details on the API.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#ifdef THREAD_DEVKIT
+#include "../mDNSShared/dns_sd.h"
+#else
+#include <dns_sd.h>
+#include <arpa/inet.h>
+#endif
+#include "srp.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "srp-crypto.h"
+
+typedef struct client_state client_state_t;
+
+typedef struct service_addr service_addr_t;
+struct service_addr {
+    service_addr_t *NULLABLE next;
+    dns_rr_t rr;
+    uint8_t port[2];
+};
+
+typedef struct _DNSServiceRef_t reg_state_t;
+typedef struct update_context {
+    void *udp_context;
+    void *message;
+    client_state_t *NONNULL client;
+    service_addr_t *server;
+    service_addr_t *interfaces;
+    size_t message_length;
+    uint32_t next_retransmission_time;
+    uint32_t next_attempt_time;
+    uint32_t lease_time;
+    uint32_t key_lease_time;
+    uint32_t serial;
+    bool notified;  // Callers have been notified.
+    bool connected; // UDP context is connected.
+    bool removing;  // We are removing the current registration(s)
+} update_context_t;
+
+struct _DNSServiceRef_t {
+    reg_state_t *NULLABLE next;
+    uint32_t serial;
+    DNSServiceFlags flags;
+    uint32_t interfaceIndex;
+    char *NULLABLE name;
+    char *NULLABLE regtype;
+    char *NULLABLE domain;
+    char *NULLABLE host;
+    int port;
+    uint16_t txtLen;
+    void *NULLABLE txtRecord;
+    DNSServiceRegisterReply callback;
+    bool succeeded;
+    bool called_back;
+    void *NULLABLE context;
+};
+
+struct client_state {
+    client_state_t *next;
+    reg_state_t *registrations;
+    char *hostname;
+    int hostname_rename_number; // If we've had a naming conflict, this will be nonzero.
+    srp_hostname_conflict_callback_t hostname_conflict_callback;
+    srp_key_t *key;
+    void *os_context;
+    uint32_t lease_time;
+    uint32_t key_lease_time;
+    uint32_t registration_serial;
+    uint32_t srp_max_attempt_interval;
+    uint32_t srp_max_retry_interval;
+    service_addr_t stable_server;
+    bool srp_server_synced;
+
+    int client_serial;
+
+    // Currently we only ever have one update in flight.  If we decide we need to send another,
+    // we need to cancel the one we're currently doing.
+    update_context_t *active_update;
+};
+
+// Implementation of SRP network entry points, which can be called by the network implementation on the
+// hosting platform.
+
+static bool network_state_changed = false;
+static bool doing_refresh = false;
+static service_addr_t *interfaces;
+static service_addr_t *servers;
+static service_addr_t *interface_refresh_state;
+static service_addr_t *server_refresh_state;
+static uint8_t no_port[2];
+
+client_state_t *clients;
+client_state_t *current_client;
+
+// Forward references
+static int do_srp_update(client_state_t *client, bool definite);
+static void udp_response(void *v_update_context, void *v_message, size_t message_length);
+static dns_wire_t *NULLABLE generate_srp_update(client_state_t *client, uint32_t update_lease_time,
+                                                uint32_t update_key_lease_time, size_t *NONNULL p_length,
+                                                service_addr_t *NONNULL server, uint32_t serial, bool remove);
+static bool srp_is_network_active(void);
+
+#define VALIDATE_IP_ADDR                                         \
+    if ((rrtype != dns_rrtype_a && rrtype != dns_rrtype_aaaa) || \
+        (rrtype == dns_rrtype_a && rdlen != 4) ||                \
+        (rrtype == dns_rrtype_aaaa && rdlen != 16)) {            \
+        return kDNSServiceErr_Invalid;                           \
+    }
+
+// Call this before calling anything else.   Context will be passed back whenever the srp code
+// calls any of the host functions.
+int
+srp_host_init(void *context)
+{
+    client_state_t *new_client = calloc(1, sizeof(*new_client));
+    if (new_client == NULL) {
+        return kDNSServiceErr_NoMemory;
+    }
+    new_client->os_context = context;
+    new_client->lease_time = 3600;       // 1 hour for registration leases
+    new_client->key_lease_time = 604800; // 7 days for key leases
+    new_client->registration_serial = 0;
+    new_client->srp_max_attempt_interval = 1000 * 60 * 60; // By default, never wait longer than an hour to do another
+                                                           // registration attempt.
+    new_client->srp_max_retry_interval = 1000 * 15;        // Default retry interval is 15 seconds--three attempts.
+
+    current_client = new_client;
+    new_client->next = clients;
+    clients = current_client;
+    return kDNSServiceErr_NoError;
+}
+
+int
+srp_host_key_reset(void)
+{
+    if (current_client->key != NULL) {
+        srp_keypair_free(current_client->key);
+        current_client->key = NULL;
+    }
+    return srp_reset_key("com.apple.srp-client.host-key", current_client->os_context);
+}
+
+int
+srp_set_lease_times(uint32_t new_lease_time, uint32_t new_key_lease_time)
+{
+    current_client->lease_time = new_lease_time;
+    current_client->key_lease_time = new_key_lease_time;
+    return kDNSServiceErr_NoError;
+}
+
+static void
+sync_from_stable_storage(update_context_t *update)
+{
+    service_addr_t *server;
+    client_state_t *client = update->client;
+    if (!client->srp_server_synced) {
+        client->srp_server_synced =
+            srp_get_last_server(&client->stable_server.rr.type, (uint8_t *)&client->stable_server.rr.data,
+                                sizeof(client->stable_server.rr.data), &client->stable_server.port[0],
+                                client->os_context);
+        // Nothing read.
+        if (!client->srp_server_synced) {
+            return;
+        }
+    } else {
+        if (update->server != NULL) {
+            return;
+        }
+    }
+
+    // See if one of the advertised servers is the one we last updated.
+    for (server = servers; server; server = server->next) {
+        if (server->rr.type == client->stable_server.rr.type &&
+            !memcmp(&server->port, &client->stable_server.port, 2) &&
+            ((server->rr.type == dns_rrtype_a && !memcmp(&server->rr.data, &client->stable_server.rr.data, 4)) ||
+             (server->rr.type == dns_rrtype_aaaa && !memcmp(&server->rr.data, &client->stable_server.rr.data, 16))))
+        {
+            update->server = server;
+            return;
+        }
+    }
+}
+
+static void
+sync_to_stable_storage(update_context_t *update)
+{
+    client_state_t *client = update->client;
+    if (!client->srp_server_synced) {
+        client->srp_server_synced =
+            srp_save_last_server(client->stable_server.rr.type, (uint8_t *)&client->stable_server.rr.data,
+                                 client->stable_server.rr.type == dns_rrtype_a ? 4 : 16,
+                                 client->stable_server.port, client->os_context);
+    }
+}
+
+// Find an address on a list of addresses.
+static service_addr_t **
+find_address(service_addr_t **addrs, const uint8_t *port, uint16_t rrtype, const uint8_t *rdata, uint16_t rdlen)
+{
+    service_addr_t *addr, **p_addr = addrs;
+
+    while (*p_addr != NULL) {
+        addr = *p_addr;
+        if (addr->rr.type == rrtype && !memcmp(&addr->rr.data, rdata, rdlen) && !memcmp(addr->port, port, 2)) {
+            break;
+        }
+        p_addr = &addr->next;
+    }
+    return p_addr;
+}
+
+// Worker function to add an address and notice whether the network state has changed (so as to trigger a
+// refresh).
+static int
+add_address(service_addr_t **list, service_addr_t **refresh,
+            const uint8_t *port, uint16_t rrtype, const uint8_t *rdata, uint16_t rdlen)
+{
+    service_addr_t *addr, **p_addr, **p_refresh;
+
+    VALIDATE_IP_ADDR;
+
+    // See if the address is on the refresh list.
+    p_refresh = find_address(refresh, port, rrtype, rdata, rdlen);
+
+    // See also if it's on the address list (shouldn't be on both).  This also finds the end of the list.
+    p_addr = find_address(list, port, rrtype, rdata, rdlen);
+    if (*p_addr != NULL) {
+        return kDNSServiceErr_NoError;
+    }
+
+    if (*p_refresh != NULL) {
+        addr = *p_refresh;
+
+        // This shouldn't happen, but if it does, free the old address.
+        if (*p_addr != NULL) {
+            ERROR("duplicate address during refresh!");
+            free(addr);
+            return kDNSServiceErr_NoError;
+        }
+
+        *p_refresh = addr->next;
+        addr->next = NULL;
+        *p_addr = addr;
+
+        // In this case, the network state has not changed.
+        return kDNSServiceErr_NoError;
+    }
+
+    addr = calloc(1, sizeof *addr);
+    if (addr == NULL) {
+        return kDNSServiceErr_NoMemory;
+    }
+    addr->rr.type = rrtype;
+    addr->rr.qclass = dns_qclass_in;
+    memcpy(&addr->rr.data, rdata, rdlen);
+    memcpy(&addr->port, port, 2);
+    *p_addr = addr;
+    network_state_changed = true;
+
+    // Print IPv6 address directly here because the code has to be portable for ADK, and OpenThread environment
+    // has no support for INET6_ADDRSTRLEN.
+    INFO("added " PUB_S_SRP
+         " address: %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x port %u (%04x)",
+         *list == servers ? "server" : "interface",
+         rdata[0], rdata[1], rdata[2], rdata[3], rdata[4], rdata[5], rdata[6], rdata[7],
+         rdata[8], rdata[9], rdata[10], rdata[11], rdata[12], rdata[13], rdata[14], rdata[15],
+         port != NULL ? (port[0] << 8 | port[1]) : 0, port != NULL ? (port[0] << 8 | port[1]) : 0);
+
+    return kDNSServiceErr_NoError;
+}
+
+// Called when a new address is configured that should be advertised.  This can be called during a refresh,
+// in which case it doesn't mark the network state as changed if the address was already present.
+int
+srp_add_interface_address(uint16_t rrtype, const uint8_t *NONNULL rdata, uint16_t rdlen)
+{
+    return add_address(&interfaces, &interface_refresh_state, no_port, rrtype, rdata, rdlen);
+}
+
+// Called whenever the SRP server address changes or the SRP server becomes newly reachable.  This can be
+// called during a refresh, in which case it doesn't mark the network state as changed if the address was
+// already present.
+int
+srp_add_server_address(const uint8_t *port, uint16_t rrtype, const uint8_t *NONNULL rdata, uint16_t rdlen)
+{
+    VALIDATE_IP_ADDR;
+
+    return add_address(&servers, &server_refresh_state, port, rrtype, rdata, rdlen);
+}
+
+// Called when the node knows its hostname (usually once).   The callback is called if we try to do an SRP
+// update and find out that the hostname is in use; in this case, the callback is expected to generate a new
+// hostname and re-register it.   It is permitted to call srp_set_hostname() from the callback.
+// If the hostname is changed by the callback, then it is used immediately on return from the callback;
+// if the hostname is changed in any other situation, nothing is done with the new name until
+// srp_network_state_stable() is called.
+int
+srp_set_hostname(const char *NONNULL name, srp_hostname_conflict_callback_t callback)
+{
+    if (current_client->hostname != NULL) {
+        free(current_client->hostname);
+    }
+    current_client->hostname = strdup(name);
+    if (current_client->hostname == NULL) {
+        return kDNSServiceErr_NoMemory;
+    }
+    current_client->hostname_conflict_callback = callback;
+    network_state_changed = true;
+    return kDNSServiceErr_NoError;
+}
+
+// Called when a network state change is complete (that is, all new addresses have been saved and
+// any update to the SRP server address has been provided).   This is only needed when not using the
+// refresh mechanism.
+static bool
+srp_is_network_active(void)
+{
+    INFO("srp_is_network_active:  nsc = %d servers = %p interfaces = %p, hostname = " PRI_S_SRP,
+             network_state_changed, servers, interfaces,
+         current_client->hostname ? current_client->hostname : "<not set>");
+    return servers != NULL && interfaces != NULL && current_client->hostname != NULL;
+}
+
+int
+srp_network_state_stable(void)
+{
+    client_state_t *client;
+    int status = kDNSServiceErr_NoError;
+    if (network_state_changed && srp_is_network_active()) {
+        network_state_changed = false;
+        for (client = clients; client; client = client->next) {
+            int ret = do_srp_update(client, false);
+            // In the normal case, there will only be one client, and therefore one return status.  For testing,
+            // we allow more than one client; if we get an error here, we return it, but we still launch all the
+            // updates.
+            if (ret != kDNSServiceErr_NoError && status == kDNSServiceErr_NoError) {
+                status = ret;
+            }
+        }
+    }
+    return kDNSServiceErr_NoError;
+}
+
+// Worker function to delete a server or interface address that was previously configured.
+static int
+delete_address(service_addr_t **list, const uint8_t *port, uint16_t rrtype, const uint8_t *NONNULL rdata,
+               uint16_t rdlen)
+{
+    service_addr_t *addr, **p_addr;
+
+    // Delete API and refresh API are incompatible.
+    if (doing_refresh) {
+        return kDNSServiceErr_BadState;
+    }
+    VALIDATE_IP_ADDR;
+
+    // See if we know this address.
+    p_addr = find_address(list, port, rrtype, rdata, rdlen);
+    if (*p_addr != NULL) {
+        addr = *p_addr;
+        *p_addr = addr->next;
+        free(addr);
+        network_state_changed = true;
+        return kDNSServiceErr_NoError;
+    }
+    return kDNSServiceErr_NoSuchRecord;
+}
+
+// Delete a previously-configured SRP server address.  This should not be done during a refresh.
+int
+srp_delete_interface_address(uint16_t rrtype, const uint8_t *NONNULL rdata, uint16_t rdlen)
+{
+    return delete_address(&interfaces, no_port, rrtype, rdata, rdlen);
+}
+
+// Delete a previously-configured SRP server address.  This should not be done during a refresh.
+int
+srp_delete_server_address(uint16_t rrtype, const uint8_t *port, const uint8_t *NONNULL rdata, uint16_t rdlen)
+{
+    return delete_address(&servers, port, rrtype, rdata, rdlen);
+}
+
+// Call this to start an address refresh.   This makes sense to do in cases where the caller
+// is not tracking changes, but rather is just doing a full refresh whenever the network state
+// is seen to have changed.   When the refresh is done, if any addresses were added or removed,
+// network_state_changed will be true, and so a call to dnssd_network_state_change_finished()
+// will trigger an update; if nothing changed, no update will be sent.
+int
+srp_start_address_refresh(void)
+{
+    if (doing_refresh) {
+        return kDNSServiceErr_BadState;
+    }
+    doing_refresh = true;
+    interface_refresh_state = interfaces;
+    server_refresh_state = servers;
+    interfaces = NULL;
+    servers = NULL;
+    network_state_changed = false;
+    return kDNSServiceErr_NoError;
+}
+
+// Call this when the address refresh is done.   This invokes srp_network_state_stable().
+int
+srp_finish_address_refresh(void)
+{
+    service_addr_t *addr, *next;
+    int i;
+    if (!doing_refresh) {
+        return kDNSServiceErr_BadState;
+    }
+    for (i = 0; i < 2; i++) {
+        if (i == 0) {
+            next = server_refresh_state;
+            server_refresh_state = NULL;
+        } else {
+            next = interface_refresh_state;
+            interface_refresh_state = NULL;
+        }
+        if (next != NULL) {
+            network_state_changed = true;
+        }
+        while (next) {
+            uint8_t *rdata = (uint8_t *)&next->rr.data;
+            // Print IPv6 address directly here because the code has to be portable for ADK, and OpenThread environment
+            // has no support for INET6_ADDRSTRLEN.
+            INFO("deleted " PUB_S_SRP
+                 " address: %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x port %u (%x)",
+                 i ? "interface" : "server",
+                 rdata[0], rdata[1], rdata[2], rdata[3], rdata[4], rdata[5], rdata[6], rdata[7],
+                 rdata[8], rdata[9], rdata[10], rdata[11], rdata[12], rdata[13], rdata[14], rdata[15],
+                 (next->port[0] << 8) | next->port[1], (next->port[0] << 8) | next->port[1]);
+            addr = next;
+            next = addr->next;
+            free(addr);
+        }
+    }
+    doing_refresh = false;
+    return srp_network_state_stable();
+}
+
+// Implementation of the API that the application will call to update the TXT record after having registered
+// a service previously with a different TXT record.   In principle this can also update a record added with
+// DNSServiceAddRecord or DNSServiceRegisterRecord, but we don't support those APIs at present.
+
+DNSServiceErrorType
+DNSServiceUpdateRecord(DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags,
+                       uint16_t rdlen, const void *rdata, uint32_t ttl)
+{
+    reg_state_t *registration;
+    void *txtRecord = NULL;
+
+    (void)RecordRef;
+    (void)flags;
+    (void)ttl;
+
+    if (sdRef == NULL || RecordRef != NULL || rdata == NULL) {
+        return kDNSServiceErr_Invalid;
+    }
+
+    // Add it to the list (so it will appear valid to DNSServiceRefDeallocate()).
+    for (registration = current_client->registrations; registration != NULL; registration = registration->next) {
+        if (registration == sdRef) {
+            break;
+        }
+    }
+    if (registration == NULL) {
+        return kDNSServiceErr_BadReference;
+    }
+
+    if (rdlen != 0) {
+        txtRecord = malloc(rdlen);
+        if (txtRecord == NULL) {
+            return kDNSServiceErr_NoMemory;
+        }
+        memcpy(txtRecord, rdata, rdlen);
+    } else {
+        registration->txtRecord = NULL;
+    }
+
+    if (registration->txtRecord != NULL) {
+        free(registration->txtRecord);
+    }
+
+    registration->txtRecord = txtRecord;
+    registration->txtLen = rdlen;
+    network_state_changed = true;
+    return kDNSServiceErr_NoError;
+}
+
+// Implementation of the API that applications will call to register services.   This is independent of the
+// hosting platform API.
+DNSServiceErrorType
+DNSServiceRegister(DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
+                   const char *NULLABLE name, const char *NULLABLE regtype, const char *NULLABLE domain,
+                   const char *NULLABLE host, uint16_t port,
+                   uint16_t txtLen, const void *txtRecord,
+                   DNSServiceRegisterReply callBack, void *context)
+{
+    reg_state_t **rp, *reg = calloc(1, sizeof *reg);
+    if (reg == NULL) {
+        return kDNSServiceErr_NoMemory;
+    }
+
+    // Add it to the list (so it will appear valid to DNSServiceRefDeallocate()).
+    rp = &current_client->registrations;
+    while (*rp) {
+        rp = &((*rp)->next);
+    }
+    *rp = reg;
+
+    // If we don't already have a hostname, use the one from the registration.
+    if (current_client->hostname == NULL) {
+        srp_set_hostname(host, NULL);
+    }
+
+    reg->serial = current_client->registration_serial++;
+    reg->flags = flags;
+    reg->interfaceIndex = interfaceIndex;
+    reg->called_back = true;
+#define stashName(thing)                        \
+    if (thing != NULL) {                        \
+        reg->thing = strdup(thing);             \
+        if (reg->thing == NULL) {               \
+            DNSServiceRefDeallocate(reg);       \
+            return kDNSServiceErr_NoMemory;     \
+        }                                       \
+    } else {                                    \
+        reg->thing = NULL;                      \
+    }
+    stashName(name);
+    stashName(regtype);
+    stashName(domain);
+    stashName(host);
+    reg->port = port;
+    reg->txtLen = txtLen;
+    if (txtLen != 0) {
+        reg->txtRecord = malloc(txtLen);
+        if (reg->txtRecord == NULL) {
+            DNSServiceRefDeallocate(reg);
+            return kDNSServiceErr_NoMemory;
+        }
+        memcpy(reg->txtRecord, txtRecord, txtLen);
+    } else {
+        reg->txtRecord = NULL;
+    }
+    reg->callback = callBack;
+    reg->context = context;
+    *sdRef = reg;
+    network_state_changed = true;
+    return kDNSServiceErr_NoError;
+}
+
+void
+DNSServiceRefDeallocate(DNSServiceRef sdRef)
+{
+    reg_state_t **rp, *reg = NULL;
+    bool found = false;
+    client_state_t *client;
+
+    if (sdRef == NULL) {
+        return;
+    }
+
+    for (client = clients; client; client = client->next) {
+        // Remove it from the list.
+        rp = &client->registrations;
+        reg = *rp;
+        while (*rp) {
+            if (reg == sdRef) {
+                *rp = reg->next;
+                found = true;
+                break;
+            }
+            rp = &((*rp)->next);
+            reg = *rp;
+        }
+
+        if (found) {
+            break;
+        }
+    }
+
+    // This avoids a bogus free.
+    if (!found || reg == NULL) {
+        return;
+    }
+    if (reg->name != NULL) {
+        free(reg->name);
+    }
+    if (reg->regtype != NULL) {
+        free(reg->regtype);
+    }
+    if (reg->domain != NULL) {
+        free(reg->domain);
+    }
+    if (reg->host != NULL) {
+        free(reg->host);
+    }
+    if (reg->txtRecord != NULL) {
+        free(reg->txtRecord);
+    }
+    free(reg);
+}
+
+static void
+update_finalize(update_context_t *update)
+{
+    client_state_t *client = update->client;
+    if (update->udp_context != NULL) {
+        srp_deactivate_udp_context(client->os_context, update->udp_context);
+    }
+    if (update->message != NULL) {
+        free(update->message);
+    }
+    if (update->interfaces != NULL) {
+        free(update->interfaces);
+    }
+    free(update);
+}
+
+static void
+do_callbacks(client_state_t *client, uint32_t serial, int err, bool succeeded)
+{
+    reg_state_t *rp;
+    bool work;
+
+    // The callback can modify the list, so we use a marker to remember where we are in the list rather
+    // than remembering a pointer which could be invalidated.  If a callback adds a registration, that
+    // registration doesn't get called because called_back is set to true when a registration is added.
+    for (rp = client->registrations; rp; rp = rp->next) {
+        if (rp->serial <= serial) {
+            rp->called_back = false;
+        }
+    }
+    do {
+        work = false;
+        for (rp = client->registrations; rp; rp = rp->next) {
+            if (rp->serial > serial || rp->callback == NULL || rp->called_back) {
+                continue;
+            }
+            work = true;
+            rp->called_back = true;
+            if (rp->callback != NULL) {
+                rp->callback(rp, kDNSServiceFlagsAdd, err, rp->name, rp->regtype, rp->domain, rp->context);
+            }
+            if (succeeded) {
+                rp->succeeded = true;
+            }
+        }
+    } while (work);
+}
+
+static void
+udp_retransmit(void *v_update_context)
+{
+    update_context_t *context = v_update_context;
+    client_state_t *client;
+    service_addr_t *next_server = NULL;
+    int err;
+
+    client = context->client;
+
+    if (!srp_is_network_active()) {
+        INFO("udp_retransmit: network is down, discontinuing renewals.");
+        if (client->active_update != NULL) {
+            update_finalize(client->active_update);
+            client->active_update = NULL;
+        }
+        return;
+    }
+    // It shouldn't be possible for this to happen.
+    if (client->active_update == NULL) {
+        INFO("udp_retransmit: no active update for " PRI_S_SRP " (%p).",
+             client->hostname ? client->hostname : "<null>", client);
+        return;
+    }
+    INFO("udp_retransmit: next_attempt %" PRIu32 " next_retransmission %" PRIu32 " for " PRI_S_SRP " (%p)",
+         context->next_attempt_time, context->next_retransmission_time,
+         client->hostname ? client->hostname : "<null>", client);
+
+    // If next retransmission time is zero, this means that we gave up our last attempt to register, and have
+    // now waited long enough to try again.  We will then use an exponential backoff for 90 seconds before giving
+    // up again; if we give up again, we will wait longer to retry, up to an hour.
+    if (context->next_retransmission_time == 0) {
+        // If there are no servers, we don't need to schedule a re-attempt: when a server is seen, we will do
+        // an update immediately.
+        if (servers == NULL) {
+            return;
+        }
+        next_server = servers;
+
+        // If this attempt fails, don't try again for a while longer, but limit the retry interval to an hour.
+        context->next_attempt_time *= 2;
+        if (context->next_attempt_time > client->srp_max_attempt_interval) {
+            context->next_attempt_time = client->srp_max_attempt_interval;
+        }
+        context->next_retransmission_time = 2000; // Next retry will be in two seconds.
+    }
+    // If this would be our fourth retry on a particular server, try the next server.
+    else if (context->next_retransmission_time > client->srp_max_retry_interval) {
+        // If we are removing, there is no point in trying the next server--just give up and report a timeout.
+        if (context->removing) {
+            do_callbacks(client, context->serial, kDNSServiceErr_Timeout, false);
+            // Once the goodbye retransmission has timed out, we're done.
+            return;
+        }
+        for (next_server = servers; next_server; next_server = next_server->next) {
+            if (next_server == context->server) {
+                // We're going to use the next server after the one we just tried.  If we run out of servers,
+                // we'll give up for a while.
+                next_server = next_server->next;
+                break;
+            }
+        }
+        
+        // If we run off the end of the list, give up for a bit.
+        if (next_server == NULL) {
+            context->next_retransmission_time = 0;
+        } else {
+            context->next_retransmission_time = 2000;
+        }
+    }
+    // Otherwise, we are still trying to win with a particular server, so back off exponentially.
+    else {
+        context->next_retransmission_time *= 2;
+    }
+    
+    // If we are giving up on the current server, get rid of any udp state.
+    if (context->next_retransmission_time == 0 || next_server != NULL) {
+        if (next_server != NULL) {
+            context->server = next_server;
+        }
+        srp_disconnect_udp(context->udp_context);
+        context->connected = false;
+        if (context->message != NULL) {
+            free(context->message);
+        }
+        context->message = NULL;
+        context->message_length = 0;
+    }
+
+    // If we are not giving up, send the next packet.
+    if (context->server != NULL && context->next_retransmission_time != 0) {
+        if (!context->connected) {
+            // Create a UDP context for this transaction.
+            err = srp_connect_udp(context->udp_context, context->server->port, servers->rr.type,
+                                  (uint8_t *)&context->server->rr.data,
+                                  context->server->rr.type == dns_rrtype_a ? 4 : 16);
+            // In principle if it fails here, it might succeed later, so we just don't send a packet and let
+            // the timeout take care of it.
+            if (err != kDNSServiceErr_NoError) {
+                ERROR("udp_retransmit: error %d creating udp context.", err);
+            } else {
+                context->connected = true;
+            }
+        }
+
+        if (context->message == NULL) {
+            context->message = generate_srp_update(client, client->lease_time, client->key_lease_time, &context->message_length,
+                                                   context->server, context->serial, context->removing);
+            if (context->message == NULL) {
+                ERROR("No memory for message.");
+                return;
+            }
+        }
+
+        if (context->connected) {
+            // Send the datagram to the server
+            err = srp_send_datagram(client->os_context, context->udp_context, context->message, context->message_length);
+            if (err != kDNSServiceErr_NoError) {
+                ERROR("udp_retransmit: error %d sending a datagram.", err);
+            }
+        }
+    }
+
+    // If we've given up for now, schedule a next attempt; otherwise, schedule the next retransmission.
+    if (context->next_retransmission_time == 0) {
+        err = srp_set_wakeup(client->os_context, context->udp_context, context->next_attempt_time, udp_retransmit);
+    } else {
+        err = srp_set_wakeup(client->os_context, context->udp_context,
+                             context->next_retransmission_time - 512 + srp_random16() % 1024, udp_retransmit);
+    }
+    if (err != kDNSServiceErr_NoError) {
+        INFO("udp_retransmit: error %d setting wakeup", err);
+        // what to do?
+    }
+}
+
+static void
+renew_callback(void *v_update_context)
+{
+    update_context_t *context = v_update_context;
+    client_state_t *client = context->client;
+    INFO("renew callback");
+    do_srp_update(client, true);
+}
+
+// This function will, if hostname_rename_number is nonzero, create a hostname using the chosen hostname plus
+// space plus the number as ascii text.   The caller is responsible for freeing the return value if it's not NULL.
+static char *
+conflict_print(client_state_t *client, dns_towire_state_t *towire, char **return_hostname, char *chosen_hostname)
+{
+    char *conflict_hostname;
+    size_t hostname_len;
+
+    if (client->hostname_rename_number == 0) {
+        *return_hostname = chosen_hostname;
+        return NULL;
+    }
+
+    hostname_len = strlen(chosen_hostname);
+    // 7 is max length of decimal short (5) plus space plus NUL
+    if (hostname_len + 7 > DNS_MAX_LABEL_SIZE) {
+        hostname_len = DNS_MAX_LABEL_SIZE - 7;
+    }
+    conflict_hostname = malloc(hostname_len + 7);
+    if (conflict_hostname == NULL) {
+        if (towire != NULL) {
+            towire->line = __LINE__;
+            towire->outer_line = -1;
+            towire->error = true;
+        }
+        *return_hostname = chosen_hostname;
+        return NULL;
+    }
+
+    memcpy(conflict_hostname, chosen_hostname, hostname_len);
+    snprintf(conflict_hostname + hostname_len, 7, " %d", client->hostname_rename_number);
+    *return_hostname = conflict_hostname;
+    return conflict_hostname;
+}
+
+static void
+udp_response(void *v_update_context, void *v_message, size_t message_length)
+{
+    update_context_t *context = v_update_context;
+    client_state_t *client = context->client;
+    dns_wire_t *message = v_message;
+    int err;
+    int rcode = dns_rcode_get(message);
+    (void)message_length;
+    uint32_t new_lease_time;
+    reg_state_t *registration;
+    const uint8_t *p = message->data;
+    const uint8_t *end = (const uint8_t *)v_message + message_length;
+    bool resolve_name_conflict = false;
+    char *conflict_hostname = NULL, *chosen_hostname;
+    uint32_t retry_time;
+
+    INFO("Got a response for %p, rcode = %d", client, dns_rcode_get(message));
+
+    // Cancel the retransmit wakeup.
+    err = srp_cancel_wakeup(client->os_context, context->udp_context);
+    if (err != kDNSServiceErr_NoError) {
+        INFO("udp_response: %d", err);
+    }
+    // We want a different UDP source port for each transaction, so cancel the current UDP state.
+    srp_disconnect_udp(context->udp_context);
+    context->connected = false;
+
+    // When we are doing a remove, we don't actually care what the result is--if we get back an answer, we call
+    // the callback.
+    if (context->removing) {
+        do_callbacks(client, context->serial, kDNSServiceErr_NoSuchRecord, false);
+        return;
+    }
+
+    // Deal with the response.
+    switch (rcode) {
+    case dns_rcode_noerror:
+        // Remember the server we connected with.  active_update and active_update->server should always be
+        // non-NULL here.
+        if (client->active_update != NULL && client->active_update->server != NULL) {
+            // If the new server is not the one that's mentioned in stable_server, then update the one
+            // in stable_server.
+            if (client->active_update->server->rr.type != client->stable_server.rr.type ||
+                (client->stable_server.rr.type == dns_rrtype_a
+                 ? memcmp(&client->stable_server.rr.data, &client->active_update->server->rr.data, 4)
+                 : (client->stable_server.rr.type == dns_rrtype_aaaa
+                    ? memcmp(&client->stable_server.rr.data, &client->active_update->server->rr.data, 16)
+                    : true)) ||
+                memcmp(client->stable_server.port, client->active_update->server->port, 2))
+            {
+                memcpy(&client->stable_server, client->active_update->server, sizeof(client->stable_server));
+                client->srp_server_synced = false;
+            }
+            sync_to_stable_storage(client->active_update);
+        }
+
+        // Get the renewal time
+        // At present, there's no code to actually parse a real DNS packet in the client, so
+        // we rely on the server returning just an EDNS0 option; if this assumption fails, we
+        // are out of luck.
+        if (message->qdcount == 0 && message->ancount == 0 &&
+            message->nscount == 0 && ntohs(message->arcount) == 1 &&
+
+            // We expect the edns0 option to be:
+            // root label - 1 byte
+            // type = 2 bytes
+            // class = 2 bytes
+            // ttl = 4 bytes
+            // rdlength = 2 bytes
+            // lease option code = 2
+            // lease option length = 2
+            // lease = 4
+            // key lease = 4
+            // total = 23
+            end - p == 23 && // right number of bytes for an EDNS0 OPT containing an update lease option
+            *p == 0 &&       // root label
+            p[1] == (dns_rrtype_opt >> 8) && p[2] == (dns_rrtype_opt & 255) &&
+            // skip class and ttl, we don't care
+            p[9] == 0 && p[10] == 12 && // rdlength
+            p[11] == (dns_opt_update_lease >> 8) && p[12] == (dns_opt_update_lease & 255) &&
+            p[13] == 0 && p[14] == 8) // opt_length
+        {
+            new_lease_time = (((uint32_t)p[15] << 12) | ((uint32_t)p[16] << 8) |
+                              ((uint32_t)p[17] << 8) | ((uint32_t)p[18]));
+            INFO("Lease time set to %" PRIu32, new_lease_time);
+        } else {
+            new_lease_time = context->lease_time;
+            INFO("Lease time defaults to %" PRIu32, new_lease_time);
+            DEBUG("len %ld qd %d an %d ns %d ar %d data %02x %02x %02x %02x %02x %02x %02x %02x %02x"
+                  " %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
+                  (unsigned long)(end - p), ntohs(message->qdcount), ntohs(message->ancount),
+                  ntohs(message->nscount), ntohs(message->arcount),
+                  p[0], p[1], p[2], p[3], p[3], p[5], p[6], p[7], p[8], p[9], p[10], p[11],
+                  p[12], p[13], p[14], p[15], p[16], p[17], p[18], p[19], p[20], p[21], p[22]);
+        }
+
+        // Set up to renew.  Time is in milliseconds, and we want to renew at 80% of the lease time.
+        srp_set_wakeup(client->os_context, context->udp_context, (new_lease_time * 1000) * 8 / 10, renew_callback);
+
+        do_callbacks(client, context->serial, kDNSServiceErr_NoError, true);
+        break;
+    case dns_rcode_yxdomain:
+        // Get the actual hostname that we sent.
+        if (client->hostname_conflict_callback != NULL && client->hostname != NULL) {
+            conflict_hostname = conflict_print(client, NULL, &chosen_hostname, client->hostname);
+            client->hostname_conflict_callback(chosen_hostname);
+            if (conflict_hostname != NULL) {
+                free(conflict_hostname);
+            }
+        } else {
+            resolve_name_conflict = true;
+        }
+
+        bool resolve_with_callback = true;
+        for (registration = client->registrations; registration; registration = registration->next) {
+            if (registration->callback != NULL && registration->serial <= context->serial &&
+                !(registration->flags & kDNSServiceFlagsNoAutoRename))
+            {
+                resolve_with_callback = false;
+            }
+        }
+        if (resolve_with_callback) {
+            do_callbacks(client, context->serial, kDNSServiceErr_NameConflict, false);
+        } else {
+            resolve_name_conflict = true;
+        }
+
+        if (resolve_name_conflict) {
+            // If we get a name conflict, try using a low number to rename, but only twice; it's time consuming to do
+            // this, so if we get two conflicts, we switch to using a random number.
+            if (client->hostname_rename_number < 2) {
+                client->hostname_rename_number++;
+            } else {
+                client->hostname_rename_number = srp_random16();
+            }
+            // When we get a name conflict response, we need to re-do the update immediately
+            // (with a 0-500ms delay of course).
+            do_srp_update(client, true);
+        }
+        break;
+
+    default:
+        // Any other response has to be treated as a transient failure, so we need to retry
+        // This sort of failure is essentially unacceptable in a real deployment--it indicates
+        // that something is seriously broken.   But it's not up to the client to debug that.
+        retry_time = context->lease_time * 4 / 5;
+        // We don't want to retry _too_ often.
+        if (retry_time < 120) {
+            retry_time = 120;
+        }
+        srp_set_wakeup(client->os_context, context->udp_context, retry_time * 1000, renew_callback);
+        break;
+    }
+}
+
+// Generate a new SRP update message
+static dns_wire_t *
+generate_srp_update(client_state_t *client, uint32_t update_lease_time, uint32_t update_key_lease_time,
+                    size_t *NONNULL p_length, service_addr_t *server, uint32_t serial, bool removing)
+{
+    dns_wire_t *message;
+    const char *zone_name = "default.service.arpa";
+    const char *service_type = "_ipps._tcp";
+    const char *txt_record = "0";
+    uint16_t key_tag;
+    dns_towire_state_t towire;
+    dns_name_pointer_t p_host_name;
+    dns_name_pointer_t p_zone_name;
+    dns_name_pointer_t p_service_name;
+    dns_name_pointer_t p_service_instance_name;
+    int line, pass;
+    service_addr_t *addr;
+    reg_state_t *reg;
+    char *conflict_hostname = NULL, *chosen_hostname;
+
+#define INCREMENT(x) (x) = htons(ntohs(x) + 1)
+    memset(&towire, 0, sizeof towire);
+
+    // Get the key if we don't already have it.
+    if (client->key == NULL) {
+        client->key = srp_get_key("com.apple.srp-client.host-key", client->os_context);
+        if (client->key == NULL) {
+            INFO("No key gotten.");
+            return NULL;
+        }
+    }
+
+#define CH if (towire.error) { line = __LINE__; goto fail; }
+
+    if (client->hostname == NULL) {
+        ERROR("generate_srp_update called with NULL hostname.");
+        return NULL;
+    }
+
+    // Allocate a message buffer.
+    message = calloc(1, sizeof *message);
+    if (message == NULL) {
+        return NULL;
+    }
+    towire.p = &message->data[0];               // We start storing RR data here.
+    towire.lim = &message->data[DNS_DATA_SIZE]; // This is the limit to how much we can store.
+    towire.message = message;
+
+    // Generate a random UUID.
+    message->id = srp_random16();
+    message->bitfield = 0;
+    dns_qr_set(message, dns_qr_query);
+    dns_opcode_set(message, dns_opcode_update);
+
+    message->qdcount = 0;
+    // Copy in Zone name (and save pointer)
+    // ZTYPE = SOA
+    // ZCLASS = IN
+    dns_full_name_to_wire(&p_zone_name, &towire, zone_name); CH;
+    dns_u16_to_wire(&towire, dns_rrtype_soa); CH;
+    dns_u16_to_wire(&towire, dns_qclass_in); CH;
+    INCREMENT(message->qdcount);
+
+    message->ancount = 0;
+    // PRCOUNT = 0
+
+    message->nscount = 0;
+    // UPCOUNT = ...
+
+    // Host Description:
+    //  * Delete all RRsets from <hostname>; remember the pointer to hostname
+    //      NAME = hostname label followed by pointer to SOA name.
+    //      TYPE = ANY
+    //      CLASS = ANY
+    //      TTL = 0
+    //      RDLENGTH = 0
+
+    conflict_hostname = conflict_print(client, &towire, &chosen_hostname, client->hostname); CH;
+    dns_name_to_wire(&p_host_name, &towire, chosen_hostname); CH;
+    dns_pointer_to_wire(&p_host_name, &towire, &p_zone_name); CH;
+    dns_u16_to_wire(&towire, dns_rrtype_any); CH;
+    dns_u16_to_wire(&towire, dns_qclass_any); CH;
+    dns_ttl_to_wire(&towire, 0); CH;
+    dns_u16_to_wire(&towire, 0); CH;
+    INCREMENT(message->nscount);
+
+    //  * Add addresses: A and/or AAAA RRsets, each of which contains one
+    //    or more A or AAAA RRs.
+    //      NAME = pointer to hostname from Delete (above)
+    //      TYPE = A or AAAA
+    //      CLASS = IN
+    //      TTL = 3600 ?
+    //      RDLENGTH = number of RRs * RR length (4 or 16)
+    //      RDATA = <the data>
+    for (pass = 0; pass < 2; pass++) {
+        bool have_good_address = false;
+
+        for (addr = interfaces; addr; addr = addr->next) {
+            // If we have an IPv6 address that's on the same prefix as the server's address, send only that
+            // IPv6 address.
+            if (addr->rr.type != dns_rrtype_aaaa ||
+                (addr->rr.type == server->rr.type && !memcmp(&addr->rr.data, &server->rr.data, 8)))
+            {
+                have_good_address = true;
+            }
+            if (have_good_address || pass == 1) {
+                dns_pointer_to_wire(NULL, &towire, &p_host_name); CH;
+                dns_u16_to_wire(&towire, addr->rr.type); CH;
+                dns_u16_to_wire(&towire, dns_qclass_in); CH;
+                dns_ttl_to_wire(&towire, 3600); CH;
+                dns_rdlength_begin(&towire); CH;
+                dns_rdata_raw_data_to_wire(&towire, &addr->rr.data,
+                                           addr->rr.type == dns_rrtype_a ? 4 : 16); CH;
+                dns_rdlength_end(&towire); CH;
+                INCREMENT(message->nscount);
+            }
+            if (have_good_address) {
+                break;
+            }
+        }
+    }
+
+    //  * Exactly one KEY RR:
+    //      NAME = pointer to hostname from Delete (above)
+    //      TYPE = KEY
+    //      CLASS = IN
+    //      TTL = 3600
+    //      RDLENGTH = length of key + 4 (32 bits)
+    //      RDATA = <flags(16) = 0000 0010 0000 0001, protocol(8) = 3, algorithm(8) = 8?, public key(variable)>
+    dns_pointer_to_wire(NULL, &towire, &p_host_name); CH;
+    dns_u16_to_wire(&towire, dns_rrtype_key); CH;
+    dns_u16_to_wire(&towire, dns_qclass_in); CH;
+    dns_ttl_to_wire(&towire, 3600); CH;
+    dns_rdlength_begin(&towire); CH;
+    key_tag = dns_rdata_key_to_wire(&towire, 0, 2, 1, client->key); CH;
+    dns_rdlength_end(&towire); CH;
+    INCREMENT(message->nscount);
+
+    // Emit any registrations.
+    for (reg = client->registrations; reg; reg = reg->next) {
+        // Only remove the registrations that are actually registered. Normally this will be all of them, but it's
+        // possible for a registration to be added but not to have been updated yet, and then for us to get a remove
+        // call, in which case we don't need to remove it.
+        if (removing && reg->serial > serial) {
+            continue;
+        }
+
+        // Service:
+        //   * Update PTR RR
+        //     NAME = service name (_a._b.service.arpa)
+        //     TYPE = PTR
+        //     CLASS = IN
+        //     TTL = 3600
+        //     RDLENGTH = 2
+        //     RDATA = service instance name
+        dns_name_to_wire(&p_service_name, &towire, reg->regtype == NULL ? service_type : reg->regtype); CH;
+        dns_pointer_to_wire(&p_service_name, &towire, &p_zone_name); CH;
+        dns_u16_to_wire(&towire, dns_rrtype_ptr); CH;
+        dns_u16_to_wire(&towire, dns_qclass_in); CH;
+        dns_ttl_to_wire(&towire, 3600); CH;
+        dns_rdlength_begin(&towire); CH;
+        if (reg->name != NULL) {
+            char *service_instance_name, *to_free = conflict_print(client, &towire, &service_instance_name, reg->name);
+            dns_name_to_wire(&p_service_instance_name, &towire, service_instance_name); CH;
+            if (to_free != NULL) {
+                free(to_free);
+            }
+        } else {
+            dns_name_to_wire(&p_service_instance_name, &towire, chosen_hostname); CH;
+        }
+        dns_pointer_to_wire(&p_service_instance_name, &towire, &p_service_name); CH;
+        dns_rdlength_end(&towire); CH;
+        INCREMENT(message->nscount);
+
+        // Service Instance:
+        //   * Delete all RRsets from service instance name
+        //      NAME = service instance name (save pointer to service name, which is the second label)
+        //      TYPE = ANY
+        //      CLASS = ANY
+        //      TTL = 0
+        //      RDLENGTH = 0
+        dns_pointer_to_wire(NULL, &towire, &p_service_instance_name); CH;
+        dns_u16_to_wire(&towire, dns_rrtype_any); CH;
+        dns_u16_to_wire(&towire, dns_qclass_any); CH;
+        dns_ttl_to_wire(&towire, 0); CH;
+        dns_u16_to_wire(&towire, 0); CH;
+        INCREMENT(message->nscount);
+
+        //   * Add one SRV RRset pointing to Host Description
+        //      NAME = pointer to service instance name from above
+        //      TYPE = SRV
+        //      CLASS = IN
+        //      TTL = 3600
+        //      RDLENGTH = 8
+        //      RDATA = <priority(16) = 0, weight(16) = 0, port(16) = service port, target = pointer to hostname>
+        dns_pointer_to_wire(NULL, &towire, &p_service_instance_name); CH;
+        dns_u16_to_wire(&towire, dns_rrtype_srv); CH;
+        dns_u16_to_wire(&towire, dns_qclass_in); CH;
+        dns_ttl_to_wire(&towire, 3600); CH;
+        dns_rdlength_begin(&towire); CH;
+        dns_u16_to_wire(&towire, 0); CH; // priority
+        dns_u16_to_wire(&towire, 0); CH; // weight
+        dns_u16_to_wire(&towire, reg->port); CH; // port
+        dns_pointer_to_wire(NULL, &towire, &p_host_name); CH;
+        dns_rdlength_end(&towire); CH;
+        INCREMENT(message->nscount);
+
+        //   * Add one or more TXT records
+        //      NAME = pointer to service instance name from above
+        //      TYPE = TXT
+        //      CLASS = IN
+        //      TTL = 3600
+        //      RDLENGTH = <length of text>
+        //      RDATA = <text>
+        dns_pointer_to_wire(NULL, &towire, &p_service_instance_name); CH;
+        dns_u16_to_wire(&towire, dns_rrtype_txt); CH;
+        dns_u16_to_wire(&towire, dns_qclass_in); CH;
+        dns_ttl_to_wire(&towire, 3600); CH;
+        dns_rdlength_begin(&towire); CH;
+        if (reg->txtRecord != NULL) {
+            dns_rdata_raw_data_to_wire(&towire, reg->txtRecord, reg->txtLen);
+        } else {
+            dns_rdata_txt_to_wire(&towire, txt_record); CH;
+        }
+        dns_rdlength_end(&towire); CH;
+        INCREMENT(message->nscount);
+    }
+
+    // What about services with more than one name?   Are these multiple service descriptions?
+
+    // ARCOUNT = 2
+    //   EDNS(0) options
+    //     ...
+    //   SIG(0)
+
+    message->arcount = 0;
+    dns_edns0_header_to_wire(&towire, DNS_MAX_UDP_PAYLOAD, 0, 0, 1); CH; // XRCODE = 0; VERSION = 0; DO=1
+    dns_rdlength_begin(&towire); CH;
+    dns_u16_to_wire(&towire, dns_opt_update_lease); CH;  // OPTION-CODE
+    dns_edns0_option_begin(&towire); CH;                 // OPTION-LENGTH
+    if (removing) {
+        // If we are removing the record, lease time should be zero. Key_lease_time can be nonzero, but we
+        // aren't currently offering a way to do that.
+        dns_u32_to_wire(&towire, 0); CH;
+        dns_u32_to_wire(&towire, 0); CH;
+    } else {
+        dns_u32_to_wire(&towire, update_lease_time); CH;     // LEASE (e.g. 1 hour)
+        dns_u32_to_wire(&towire, update_key_lease_time); CH; // KEY-LEASE (7 days)
+    }
+    dns_edns0_option_end(&towire); CH;                   // Now we know OPTION-LENGTH
+    dns_rdlength_end(&towire); CH;
+    INCREMENT(message->arcount);
+
+    // The signature must be computed before counting the signature RR in the header counts.
+    dns_sig0_signature_to_wire(&towire, client->key, key_tag, &p_host_name, chosen_hostname, zone_name); CH;
+    INCREMENT(message->arcount);
+    *p_length = towire.p - (uint8_t *)message;
+
+    if (conflict_hostname != NULL) {
+        free(conflict_hostname);
+    }
+    return message;
+
+fail:
+    if (conflict_hostname != NULL) {
+        free(conflict_hostname);
+    }
+
+    if (towire.error) {
+        ERROR("Ran out of message space at srp-client.c:%d (%d, %d)",
+              line, towire.line, towire.outer_line);
+    }
+    if (client->active_update != NULL) {
+        update_finalize(client->active_update);
+        client->active_update = NULL;
+    }
+    if (message != NULL) {
+        free(message);
+    }
+    return NULL;
+}
+
+// Send SRP updates for host records that have changed.
+static int
+do_srp_update(client_state_t *client, bool definite)
+{
+    int err;
+    service_addr_t *server;
+    service_addr_t *interface, *registered;
+
+    // Cancel any ongoing active update.
+    if (!definite && client->active_update != NULL) {
+        bool server_changed = true;
+        bool interface_changed = false;
+        for (server = servers; server != NULL; server = server->next) {
+            if (server == client->active_update->server) {
+                server_changed = false;
+            }
+        }
+        for (registered = client->active_update->interfaces; registered != NULL; registered = registered->next) {
+            for (interface = interfaces; interface; interface = interface->next) {
+                if (interface == registered) {
+                    break;
+                }
+            }
+            if (interface == NULL) {
+                interface_changed = true;
+                break;
+            }
+        }
+        if (!interface_changed && !server_changed) {
+            INFO("do_srp_update: addresses to register are the same; server is the same.");
+            return kDNSServiceErr_NoError;
+        }
+    }
+
+    // Get rid of the previous update, if any.
+    if (client->active_update != NULL) {
+        update_finalize(client->active_update);
+        client->active_update = NULL;
+    }
+
+    // Make an update context.
+    update_context_t *active_update = calloc(1, sizeof(*active_update));
+    if (active_update == NULL) {
+        err = kDNSServiceErr_NoMemory;
+    } else {
+        // If possible, use the server we used last time.
+        active_update->client = client;
+        sync_from_stable_storage(active_update);
+        if (active_update->server == NULL) {
+            active_update->server = servers;
+        }
+        active_update->serial = client->registration_serial;
+        active_update->message = NULL;
+        active_update->message_length = 0;
+        // If the initial transmission times out, start retrying at ~two seconds.
+        active_update->next_retransmission_time = 2000;
+        // If this update times out, try again in two minutes, backing off to an hour exponentially.
+        active_update->next_attempt_time = 1000 * 2 * 60;
+        active_update->lease_time = client->lease_time;
+        active_update->key_lease_time = client->key_lease_time;
+        active_update->interfaces = calloc(1, sizeof(*active_update->interfaces));
+        if (active_update->interfaces == NULL) {
+            err = kDNSServiceErr_NoMemory;
+            INFO("No memory for interface address");
+        } else {
+            memcpy(active_update->interfaces, interfaces, sizeof(*interfaces));
+            err = srp_make_udp_context(client->os_context, &active_update->udp_context, udp_response, active_update);
+        }
+    }
+    if (err == kDNSServiceErr_NoError) {
+        // XXX use some random jitter on these times.
+        active_update->next_retransmission_time = 2000;
+        err = srp_set_wakeup(client->os_context, active_update->udp_context, srp_random16() % 1023, udp_retransmit);
+    }
+    if (err != kDNSServiceErr_NoError) {
+        if (active_update != NULL) {
+            update_finalize(active_update);
+            active_update = NULL;
+        }
+    }
+    client->active_update = active_update;
+    return err;
+}
+
+// Deregister all existing registrations.
+int
+srp_deregister(void *os_context)
+{
+    reg_state_t *rp;
+    bool something_to_deregister = false;
+    client_state_t *client;
+
+    for (client = clients; client; client = client->next) {
+        if (client->os_context == os_context) {
+            break;
+        }
+    }
+    if (client == NULL) {
+        return kDNSServiceErr_Invalid;
+    }
+
+    if (client->active_update == NULL) {
+        INFO("srp_deregister: no active update.");
+        return kDNSServiceErr_NoSuchRecord;
+    }
+
+    // See if there are any registrations that have succeeded.
+    for (rp = client->registrations; rp; rp = rp->next) {
+        if (rp->serial <= client->active_update->serial && rp->succeeded) {
+            something_to_deregister = true;
+        }
+    }
+
+    // If so, start a deregistration update; otherwise return NoSuchRecord.
+    if (something_to_deregister) {
+        if (client->active_update->message) {
+            free(client->active_update->message);
+            client->active_update->message = NULL;
+        }
+        client->active_update->removing = true;
+        client->active_update->next_retransmission_time = 2000;
+        client->active_update->next_attempt_time = 1000 * 2 * 60;
+        udp_retransmit(client->active_update);
+        return kDNSServiceErr_NoError;
+    } else {
+        return kDNSServiceErr_NoSuchRecord;
+    }
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
index 0065dd90a03dbca111794a7d743b86e0f40da30f..70d8e8e4e27ed93c00f677738e11781d973631c4 100644 (file)
@@ -1,6 +1,6 @@
-/* srp-key.h
+/* srp-crypto.h
  *
- * Copyright (c) 2018 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2018-2020 Apple Computer, Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #ifndef __SRP_CRYPTO_H
 #define __SRP_CRYPTO_H
+
+#include "srp.h"
+
 // Anonymous key structure, depends on the target.
 typedef struct srp_key srp_key_t;
+typedef struct hmac_key hmac_key_t;
+struct hmac_key {
+    int algorithm;
+    dns_name_t *NONNULL name;
+    uint8_t *NONNULL secret;
+    int length;
+};
+
+#define ECDSA_KEY_SIZE             64
+#define ECDSA_KEY_PART_SIZE        32
+#define ECDSA_SHA256_HASH_SIZE     32
+#define ECDSA_SHA256_SIG_SIZE      64
+#define ECDSA_SHA256_SIG_PART_SIZE 32
+
+#define SIG_HEADERLEN    11
+#define SIG_STATIC_RDLEN 18
+
+#define dnssec_keytype_ecdsa 13
+
+#define SRP_SHA256_DIGEST_SIZE 32
+#define SRP_SHA256_BLOCK_SIZE  64
+#define SRP_HMAC_TYPE_SHA256   1
+
+#ifdef SRP_CRYPTO_MACOS_INTERNAL
+#include <CoreFoundation/CoreFoundation.h>
+#include <Security/Security.h>
+// #include <Security/SecTransform.h>
+#include <CoreServices/CoreServices.h>
+#include <Security/SecAsn1Coder.h>
+#include <Security/SecAsn1Templates.h>
+#ifndef OPEN_SOURCE
+#include <Security/SecItemPriv.h>
+#endif
+
+struct srp_key {
+    SecKeyRef NONNULL public;
+    SecKeyRef NONNULL private;
+};
+
+// An ECDSASHA256 signature in ASN.1 DER format is 0x30 | x | 0x02 | y | r | 0x02 | z | s, where x is the
+// length of the whole sequence (minus the first byte), y is the encoded length of r, and z is
+// the encoded length of s.
+       // type               offset in output buffer      sub-template    size of output buffer
+       // ----               -----------------------      ------------    ---------------------
+#define ECDSA_SIG_TEMPLATE(name)                                                                          \
+    static const SecAsn1Template sig_template[] = {                                                       \
+        { SEC_ASN1_SEQUENCE, 0,                                 NULL,     sizeof(raw_signature_data_t) }, \
+        { SEC_ASN1_INTEGER,  offsetof(raw_signature_data_t, r), NULL,     0 },                            \
+        { SEC_ASN1_INTEGER,  offsetof(raw_signature_data_t, s), NULL,     0 },                            \
+        { 0,                 0,                           NULL,           0 }                             \
+    };
+
+#if !TARGET_OS_IPHONE && !TARGET_OS_TV && !TARGET_OS_WATCH
+# define SECTRANSFORM_AVAILABLE 1
+#endif // MACOS only
+#endif // SRP_CRYPTO_MACOS_INTERNAL
 
 #ifdef SRP_CRYPTO_MBEDTLS_INTERNAL
 #include <mbedtls/error.h>
 #include <mbedtls/pk.h>
+#include <mbedtls/md.h>
 #include <mbedtls/ecp.h>
 #include <mbedtls/ecdsa.h>
 #include <mbedtls/entropy.h>
@@ -35,53 +95,78 @@ typedef struct srp_key srp_key_t;
 #include <mbedtls/sha256.h>
 #include <mbedtls/base64.h>
 
+#ifdef THREAD_DEVKIT_ADK
+#ifndef EXCLUDE_CRYPTO
+// Defines EXCLUDE_CRYPTO to skip all crypto operations.
+// #define EXCLUDE_CRYPTO // rdar://57313692
+#endif
+#endif
+
+// Works just fine with mbedtls.
+#define KEYCOPY_WORKS 1
+
 // The SRP key includes both the ecdsa key and the pseudo-random number generator context, so that we can
 // use the PRNG for signing as well as generating keys.   The PRNG is seeded with a high-entropy data source.
 // This structure assumes that we are just using this one key; if we want to support multiple keys then
 // the entropy source and PRNG should be shared by all keys (of course, that's not thread-safe, so...)
 struct srp_key {
+#ifndef EXCLUDE_CRYPTO
     mbedtls_pk_context key;
-    mbedtls_entropy_context entropy;
-    mbedtls_ctr_drbg_context ctr;
-};    
+#else
+    uint8_t key[ECDSA_KEY_SIZE];
+#endif
+};
 
+#define DEBUG_SHA256
 #ifdef DEBUG_SHA256
-int srp_mbedtls_sha256_update_ret(mbedtls_sha256_context *NONNULL sha, uint8_t *NONNULL message, size_t msglen);
+int srp_mbedtls_sha256_update_ret(const char *NONNULL thing_name,
+                                  mbedtls_sha256_context *NONNULL sha, uint8_t *NONNULL message, size_t msglen);
 int srp_mbedtls_sha256_finish_ret(mbedtls_sha256_context *NONNULL sha, uint8_t *NONNULL hash);
 #else
-#define srp_mbedtls_sha256_update_ret mbedtls_sha256_update_ret
+#define srp_mbedtls_sha256_update_ret(name, ...) mbedtls_sha256_update_ret(##__VA_ARGS__)
 #define srp_mbedtls_sha256_finish_ret mbedtls_sha256_finish_ret
 #endif // DEBUG_SHA256
-#endif // SRP_CRYPTO_MBEDTLS_INTERNAL
+#ifdef THREAD_DEVKIT_ADK
+#define mbedtls_strerror(code, buf, bufsize) snprintf(buf, bufsize, "%d", (int)(code))
+#endif
 
-#define ECDSA_KEY_SIZE 64
-#define ECDSA_KEY_PART_SIZE 32
-#define ECDSA_SHA256_HASH_SIZE 32
-#define ECDSA_SHA256_SIG_SIZE 64
-#define ECDSA_SHA256_SIG_PART_SIZE 32
+// The following entry points must be provided by the host for hosts that use mbedtls signing.
 
-#define SIG_HEADERLEN 11
-#define SIG_STATIC_RDLEN 18
+// The SRP host is expected to load the SRP-specific host key out of stable storage.
+// If no key has previously been stored, this function must return kDNSServiceErr_NoSuchKey.
+// If the key doesn't fit in the buffer, this function must return kDNSServiceErr_NoMemory.
+// Otherwise, the function is expected to copy the key into the buffer and store the key length
+// through the length pointer, and return kDNSServiceErr_NoError.
+int srp_load_key_data(void *NULLABLE host_context, const char *NONNULL key_name,
+                      uint8_t *NONNULL buffer, uint16_t *NONNULL length, uint16_t buffer_size);
 
+// The SRP host is expected to store the SRP-specific host key in stable storage.
+// If the key store fails, the server returns a relevant kDNSServiceErr_* error,
+// such as kDNSServiceErr_NoMemory.  Otherwise, the function returns kDNSServiceErr_NoError.
+// It is generally expected that storing the key will not fail--if it does fail, SRP can't
+// function.
+int srp_store_key_data(void *NULLABLE host_context, const char *NONNULL key_name, uint8_t *NONNULL buffer,
+                       uint16_t length);
 
-#define dnssec_keytype_ecdsa  13
+#endif // SRP_CRYPTO_MBEDTLS_INTERNAL
 
 // sign_*.c:
 void srp_keypair_free(srp_key_t *NONNULL key);
-srp_key_t *NULLABLE srp_load_keypair(const char *NONNULL file);
-srp_key_t *NULLABLE srp_generate_key(void);
-int srp_write_key_to_file(const char *NONNULL file, srp_key_t *NONNULL key);
+uint16_t srp_random16(void);
 int srp_key_algorithm(srp_key_t *NONNULL key);
 size_t srp_pubkey_length(srp_key_t *NONNULL key);
 size_t srp_signature_length(srp_key_t *NONNULL key);
 int srp_pubkey_copy(uint8_t *NONNULL buf, size_t max, srp_key_t *NONNULL key);
-int srp_sign(uint8_t *NONNULL output, size_t max,
-            uint8_t *NONNULL message, size_t msglen, uint8_t *NONNULL rdata, size_t rdlen, srp_key_t *NONNULL key);
+int srp_sign(uint8_t *NONNULL output, size_t max, uint8_t *NONNULL message, size_t msglen,
+             uint8_t *NONNULL rdata, size_t rdlen, srp_key_t *NONNULL key);
 
 // verify_*.c:
 bool srp_sig0_verify(dns_wire_t *NONNULL message, dns_rr_t *NONNULL key, dns_rr_t *NONNULL signature);
 void srp_print_key(srp_key_t *NONNULL key);
 
+// hash_*.c:
+void srp_hmac_iov(hmac_key_t *NONNULL key, uint8_t *NONNULL output, size_t max, struct iovec *NONNULL iov, int count);
+int srp_base64_parse(char *NONNULL src, size_t *NONNULL len_ret, uint8_t *NONNULL buf, size_t buflen);
 #endif // __SRP_CRYPTO_H
 
 // Local Variables:
diff --git a/ServiceRegistration/srp-dns-proxy.c b/ServiceRegistration/srp-dns-proxy.c
new file mode 100644 (file)
index 0000000..e1b1490
--- /dev/null
@@ -0,0 +1,1186 @@
+/* srp-gw.c
+ *
+ * Copyright (c) 2018-2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This is a DNSSD Service Registration Protocol gateway.   The purpose of this is to make it possible
+ * for SRP clients to update DNS servers that don't support SRP.
+ *
+ * The way it works is that this gateway listens on port ANY:53 and forwards either to another port on
+ * the same host (not recommended) or to any port (usually 53) on a different host.   Requests are accepted
+ * over both TCP and UDP in principle, but UDP requests should be from constrained nodes, and rely on
+ * network topology for authentication.
+ *
+ * Note that this is not a full DNS proxy, so you can't just put it in front of a DNS server.
+ */
+
+// Get DNS server IP address
+// Get list of permitted source subnets for TCP updates
+// Get list of permitted source subnet/interface tuples for UDP updates
+// Set up UDP listener
+// Set up TCP listener (no TCP Fast Open)
+// Event loop
+// Transaction processing:
+//   1. If UDP, validate that it's from a subnet that is valid for the interface on which it was received.
+//   2. If TCP, validate that it's from a permitted subnet
+//   3. Check that the message is a valid SRP update according to the rules
+//   4. Check the signature
+//   5. Do a DNS Update with prerequisites to prevent overwriting a host record with the same owner name but
+//      a different key.
+//   6. Send back the response
+
+#define __APPLE_USE_RFC_3542
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <dns_sd.h>
+
+#include "srp.h"
+#include "dns-msg.h"
+#include "srp-crypto.h"
+#include "ioloop.h"
+#include "srp-gw.h"
+#include "config-parse.h"
+#include "srp-proxy.h"
+
+static addr_t dns_server;
+static dns_name_t *service_update_zone; // The zone to update when we receive an update for default.service.arpa.
+static hmac_key_t *key;
+
+static int
+usage(const char *progname)
+{
+    ERROR("usage: %s -s <addr> <port> -k <key-file> -t <subnet> ... -u <ifname> <subnet> ...", progname);
+    ERROR("  -s can only appear once.");
+    ERROR("  -k can appear once.");
+    ERROR("  -t can only appear once, and is followed by one or more subnets.");
+    ERROR("  -u can appear more than once, is followed by one interface name, and");
+    ERROR("     one or more subnets.");
+    ERROR("  <addr> is an IPv4 address or IPv6 address.");
+    ERROR("  <port> is a UDP port number.");
+    ERROR("  <key-file> is a file containing an HMAC-SHA256 key for authenticating updates to the auth server.");
+    ERROR("  <subnet> is an IP address followed by a slash followed by the prefix width.");
+    ERROR("  <ifname> is the printable name of the interface.");
+    ERROR("ex: srp-gw -s 2001:DB8::1 53 -k srp.key -t 2001:DB8:1300::/48 -u en0 2001:DB8:1300:1100::/56");
+    return 1;
+}
+
+// Free the data structures into which the SRP update was parsed.   The pointers to the various DNS objects that these
+// structures point to are owned by the parsed DNS message, and so these do not need to be freed here.
+void
+update_free_parts(service_instance_t *service_instances, service_instance_t *added_instances,
+                  service_t *services, dns_host_description_t *host_description)
+{
+    service_instance_t *sip;
+    service_t *sp;
+
+    for (sip = service_instances; sip; ) {
+        service_instance_t *next = sip->next;
+        free(sip);
+        sip = next;
+    }
+    for (sip = added_instances; sip; ) {
+        service_instance_t *next = sip->next;
+        free(sip);
+        sip = next;
+    }
+    for (sp = services; sp; ) {
+        service_t *next = sp->next;
+        free(sp);
+        sp = next;
+    }
+    if (host_description != NULL) {
+        free(host_description);
+    }
+}
+
+// Free all the stuff that we accumulated while processing the SRP update.
+void
+update_free(update_t *update)
+{
+    // Free all of the structures we collated RRs into:
+    update_free_parts(update->instances, update->added_instances, update->services, update->host);
+    // We don't need to free the zone name: it's either borrowed from the message,
+    // or it's service_update_zone, which is static.
+    message_free(update->message);
+    dns_message_free(update->parsed_message);
+    free(update);
+}
+
+
+#define name_to_wire(towire, name) name_to_wire_(towire, name, __LINE__)
+void
+name_to_wire_(dns_towire_state_t *towire, dns_name_t *name, int line)
+{
+    // Does compression...
+    dns_concatenate_name_to_wire_(towire, name, NULL, NULL, line);
+}
+
+void
+rdata_to_wire(dns_towire_state_t *towire, dns_rr_t *rr)
+{
+    dns_rdlength_begin(towire);
+
+    // These are the only types we expect to see.  If something else were passed, it would be written as rdlen=0.
+    switch(rr->type) {
+    case dns_rrtype_ptr:
+        name_to_wire(towire, rr->data.ptr.name);
+        break;
+
+    case dns_rrtype_srv:
+        dns_u16_to_wire(towire, rr->data.srv.priority);
+        dns_u16_to_wire(towire, rr->data.srv.weight);
+        dns_u16_to_wire(towire, rr->data.srv.port);
+        name_to_wire(towire, rr->data.srv.name);
+        break;
+
+    case dns_rrtype_txt:
+        dns_rdata_raw_data_to_wire(towire, rr->data.txt.data, rr->data.txt.len);
+        break;
+
+    case dns_rrtype_key:
+        dns_u16_to_wire(towire, rr->data.key.flags);
+        dns_u8_to_wire(towire, rr->data.key.protocol);
+        dns_u8_to_wire(towire, rr->data.key.algorithm);
+        dns_rdata_raw_data_to_wire(towire, rr->data.key.key, rr->data.key.len);
+        break;
+
+    case dns_rrtype_a:
+        dns_rdata_raw_data_to_wire(towire, &rr->data.a, sizeof rr->data.a);
+        break;
+
+    case dns_rrtype_aaaa:
+        dns_rdata_raw_data_to_wire(towire, &rr->data.aaaa, sizeof rr->data.aaaa);
+        break;
+    }
+
+    dns_rdlength_end(towire);
+}
+
+// We only list the types we are using--there are other types that we don't support.
+typedef enum prereq_type prereq_type_t;
+enum prereq_type {
+    update_rrset_equals,     // RFC 2136 section 2.4.2: RRset Exists (Value Dependent)
+    update_name_not_in_use,  // RFC 2136 section 2.4.5: Name Is Not In Use
+};
+
+void
+add_prerequisite(dns_wire_t *msg, dns_towire_state_t *towire, prereq_type_t ptype, dns_name_t *name, dns_rr_t *rr)
+{
+    char namebuf[DNS_MAX_NAME_SIZE + 1];
+    if (ntohs(msg->nscount) != 0 || ntohs(msg->arcount) != 0) {
+        ERROR("%s: adding prerequisite after updates", dns_name_print(name, namebuf, sizeof namebuf));
+        towire->truncated = true;
+    }
+    name_to_wire(towire, name);
+    switch(ptype) {
+    case update_rrset_equals:
+        dns_u16_to_wire(towire, rr->type);
+        dns_u16_to_wire(towire, rr->qclass);
+        dns_ttl_to_wire(towire, 0);
+        rdata_to_wire(towire, rr);
+        break;
+    case update_name_not_in_use:
+        dns_u16_to_wire(towire, dns_rrtype_any);   // TYPE
+        dns_u16_to_wire(towire, dns_qclass_none);  // CLASS
+        dns_ttl_to_wire(towire, 0);                // TTL
+        dns_u16_to_wire(towire, 0);                // RDLEN
+        break;
+    }
+    msg->ancount = htons(ntohs(msg->ancount) + 1);
+}
+
+// We actually only support one type of delete, so it's a bit silly to specify it, but in principle we might
+// want more later.
+typedef enum delete_type delete_type_t;
+enum delete_type {
+    delete_name, // RFC 2136 section 2.5.3: Delete all RRsets from a name
+};
+
+void
+add_delete(dns_wire_t *msg, dns_towire_state_t *towire, delete_type_t dtype, dns_name_t *name)
+{
+    name_to_wire(towire, name);
+    switch(dtype) {
+    case delete_name:
+        dns_u16_to_wire(towire, dns_rrtype_any);   // TYPE
+        dns_u16_to_wire(towire, dns_qclass_any);   // CLASS
+        dns_ttl_to_wire(towire, 0);                // TTL
+        dns_u16_to_wire(towire, 0);                // RDLEN
+        break;
+    }
+    msg->nscount = htons(ntohs(msg->nscount) + 1);
+}
+
+// Copy the RR we received in the SRP update out in wire format.
+
+void
+add_rr(dns_wire_t *msg, dns_towire_state_t *towire, dns_name_t *name, dns_rr_t *rr)
+{
+    if (rr != NULL) {
+        name_to_wire(towire, name);
+        dns_u16_to_wire(towire, rr->type);   // TYPE
+        dns_u16_to_wire(towire, rr->qclass); // CLASS
+        dns_ttl_to_wire(towire, rr->ttl);    // TTL
+        rdata_to_wire(towire, rr);           // RDLEN
+        msg->nscount = htons(ntohs(msg->nscount) + 1);
+    }
+}
+
+// Construct an update of the specified type, assuming that the record being updated
+// either exists or does not exist, depending on the value of exists.   Actual records
+// to be update are taken from the update_t.
+//
+// Analysis:
+//
+// The goal of the update is to either bring the zone to the state described in the SRP update, or
+// determine that the state described in the SRP update conflicts with what is already present in
+// the zone.
+//
+// Possible scenarios:
+// 1. Update and Zone are the same (A and AAAA records may differ):
+//    Prerequisites:
+//    a. for each instance: KEY RR exists on instance name and is the same
+//    b. for host: KEY RR exists on host name and is the same
+//    Update:
+//    a. for each instance: delete all records on instance name, add KEY RR, add SRV RR, add TXT RR
+//    b. for host: delete host instance, add A, AAAA and KEY RRs
+//    c. for each service: add PTR record pointing on service name to service instance name
+//
+// We should try 1 first, because it should be the steady state case; that is, it should be what happens
+// most of the time.
+// If 1 fails, then we could have some service instances present and others not.   There is no way to
+// know without trying.   We can at this point either try to add each service instance in a separate update,
+// or assume that none are present and add them all at once, and then if this fails add them individually.
+// I think that it makes sense to try them all first, because that should be the second most common case:
+//
+// 2. Nothing in update is present in zone:
+//    Prerequisites:
+//    a. For each instance: instance name is not in use
+//    b. Host name is not in use
+//    Update:
+//    a. for each instance: add KEY RR, add SRV RR, add TXT RR on instance name
+//    b. for host: add A, AAAA and KEY RRs on host name
+//    c. for each service: add PTR record pointing on service name to service instance name
+//
+// If either (1) or (2) works, we're done.   If both fail, then we need to do the service instance updates
+// and host update one by one.   This is a bit nasty because we actually have to try twice: once assuming
+// the RR exists, and once assuming it doesn't.   If any of the instance updates fail, or the host update
+// fails, we delete all the ones that succeeded.
+//
+// In the cases other than (1) and (2), we can add all the service PTRs in the host update, because they're
+// only added if the host update succeeds; if it fails, we have to go back and remove all the service
+// instances.
+//
+// One open question for the SRP document: we probably want to signal whether the conflict is with the
+// hostname or one of the service instance names.   We can do this with an EDNS(0) option.
+//
+// The flow will be:
+// - Try to update assuming everything is there already (case 1)
+// - Try to update assuming nothing is there already (case 2)
+// - For each service instance:
+//   - Try to update assuming it's not there; if this succeeds, add this instance to the list of
+//     instances that have been added. If not:
+//     - Try to update assuming it is there
+//     - If this fails, go to fail
+// - Try to update the host (and also services) assuming the host is not there.   If this fails:
+//   - Try to update the host (and also services) assuming the host is there.  If this succeeds:
+//     - return success
+// fail:
+// - For each service instance in the list of instances that have been added:
+//   - delete all records on the instance name.
+//
+// One thing that isn't accounted for here: it's possible that a previous update added some but not all
+// instances in the current update.  Subsequently, some other device may have claimed an instance that is
+// present but in conflict in the current update.   In this case, all of the instances prior to that one
+// in the update will actually have been updated by this update, but then the update as a whole will fail.
+// I think this is unlikely to be an actual problem, and there's no way to address it without a _lot_ of
+// complexity.
+
+bool
+construct_update(update_t *update)
+{
+    dns_towire_state_t towire;
+    dns_wire_t *msg = update->update; // Solely to reduce the amount of typing.
+    service_instance_t *instance;
+    service_t *service;
+    host_addr_t *host_addr;
+
+    // Set up the message constructor
+    memset(&towire, 0, sizeof towire);
+    towire.p = &msg->data[0];  // We start storing RR data here.
+    towire.lim = &msg->data[0] + update->update_max; // This is the limit to how much we can store.
+    towire.message = msg;
+
+    // Initialize the update message...
+    memset(msg, 0, DNS_HEADER_SIZE);
+    dns_qr_set(msg, dns_qr_query);
+    dns_opcode_set(msg, dns_opcode_update);
+    msg->id = srp_random16();
+
+    // An update always has one question, which is the zone name.
+    msg->qdcount = htons(1);
+    name_to_wire(&towire, update->zone_name);
+    dns_u16_to_wire(&towire, dns_rrtype_soa);
+    dns_u16_to_wire(&towire, dns_qclass_in);
+
+    switch(update->state) {
+    case connect_to_server:
+        ERROR("Update construction requested when still connecting.");
+        update->update_length = 0;
+        return false;
+
+        // Do a DNS Update for a service instance
+    case refresh_existing:
+        // Add a "KEY exists and is <x> and a PTR exists and is <x> prerequisite for each instance being updated.
+        for (instance = update->instances; instance; instance = instance->next) {
+            add_prerequisite(msg, &towire, update_rrset_equals, instance->name, update->host->key);
+        }
+        add_prerequisite(msg, &towire, update_rrset_equals, update->host->name, update->host->key);
+        // Now add a delete for each service instance
+        for (instance = update->instances; instance; instance = instance->next) {
+            add_delete(msg, &towire, delete_name, instance->name);
+        }
+        add_delete(msg, &towire, delete_name, update->host->name);
+
+    add_instances:
+        // Now add the update for each instance.
+        for (instance = update->instances; instance; instance = instance->next) {
+            add_rr(msg, &towire, instance->name, update->host->key);
+            add_rr(msg, &towire, instance->name, instance->srv);
+            add_rr(msg, &towire, instance->name, instance->txt);
+        }
+        // Add the update for each service
+        for (service = update->services; service; service = service->next) {
+            add_rr(msg, &towire, service->rr->name, service->rr);
+        }
+        // Add the host records...
+        add_rr(msg, &towire, update->host->name, update->host->key);
+        for (host_addr = update->host->addrs; host_addr; host_addr = host_addr->next) {
+            add_rr(msg, &towire, update->host->name, &host_addr->rr);
+        }
+        break;
+
+    case create_nonexistent:
+        // Add a "name not in use" prerequisite for each instance being updated.
+        for (instance = update->instances; instance; instance = instance->next) {
+            add_prerequisite(msg, &towire, update_name_not_in_use, instance->name, (dns_rr_t *)NULL);
+        }
+        add_prerequisite(msg, &towire, update_name_not_in_use, update->host->name, (dns_rr_t *)NULL);
+        goto add_instances;
+
+    case create_nonexistent_instance:
+        // The only prerequisite is that this specific service instance doesn't exist.
+        add_prerequisite(msg, &towire, update_name_not_in_use, update->instance->name, (dns_rr_t *)NULL);
+        goto add_instance;
+
+    case refresh_existing_instance:
+        // If instance already exists, prerequisite is that it has the same key, and we also have to
+        // delete all RRs on the name before adding our RRs, in case they have changed.
+        add_prerequisite(msg, &towire, update_rrset_equals, update->instance->name, update->host->key);
+        add_delete(msg, &towire, delete_name, update->instance->name);
+    add_instance:
+        add_rr(msg, &towire, update->instance->name, update->host->key);
+        add_rr(msg, &towire, update->instance->name, update->instance->srv);
+        add_rr(msg, &towire, update->instance->name, update->instance->txt);
+        break;
+
+    case create_nonexistent_host:
+        add_prerequisite(msg, &towire, update_name_not_in_use, update->host->name, (dns_rr_t *)NULL);
+        goto add_host;
+
+    case refresh_existing_host:
+        add_prerequisite(msg, &towire, update_rrset_equals, update->host->name, update->host->key);
+        add_delete(msg, &towire, delete_name, update->host->name);
+        // Add the service PTRs here--these don't need to be in a separate update, because if we get here
+        // the only thing that can make adding them not okay is if adding the host fails.
+        // Add the update for each service
+        for (service = update->services; service; service = service->next) {
+            add_rr(msg, &towire, service->rr->name, service->rr);
+        }
+    add_host:
+        // Add the host records...
+        add_rr(msg, &towire, update->host->name, update->host->key);
+        for (host_addr = update->host->addrs; host_addr; host_addr = host_addr->next) {
+            add_rr(msg, &towire, update->host->name, &host_addr->rr);
+        }
+        break;
+
+    case delete_failed_instance:
+        // Delete all the instances we successfull added before discovering a problem.
+        // It is possible in principle that these could have been overwritten by some other
+        // process and we could be deleting the wrong stuff, but in practice this should
+        // never happen if these are legitimately managed by SRP.   Once a name has been
+        // claimed by SRP, it should continue to be managed by SRP until its lease expires
+        // and SRP deletes it, at which point it is of course fair game.
+        for (instance = update->instances; instance; instance = instance->next) {
+            add_delete(msg, &towire, delete_name, instance->name);
+        }
+        break;
+    }
+    if (towire.error != 0) {
+        ERROR("construct_update: error %s while generating update at line %d", strerror(towire.error), towire.line);
+        return false;
+    }
+    update->update_length = towire.p - (uint8_t *)msg;
+    return true;
+}
+
+void
+update_finished(update_t *update, int rcode)
+{
+    comm_t *comm = update->client;
+    struct iovec iov;
+    dns_wire_t response;
+    INFO("Update Finished, rcode = " PUB_S_SRP, dns_rcode_name(rcode));
+
+    memset(&response, 0, DNS_HEADER_SIZE);
+    response.id = update->message->wire.id;
+    response.bitfield = update->message->wire.bitfield;
+    dns_rcode_set(&response, rcode);
+    dns_qr_set(&response, dns_qr_response);
+
+    iov.iov_base = &response;
+    iov.iov_len = DNS_HEADER_SIZE;
+
+    comm->send_response(comm, update->message, &iov, 1);
+
+    // If success, construct a response
+    // If fail, send a quick status code
+    // Signal host name conflict and instance name conflict using different rcodes (?)
+    // Okay, so if there's a host name/instance name conflict, and the host name has the right key, then
+    // the instance name is actually bogus and should be overwritten.
+    // If the host has the wrong key, and the instance is present, then the instance is also bogus.
+    // So in each of these cases, perhaps we should just gc the instance.
+    // This would mean that there is nothing to signal: either the instance is a mismatch, and we
+    // overwrite it and return success, or the host is a mismatch and we gc the instance and return failure.
+    ioloop_close(&update->server->io);
+    update_free(update);
+}
+
+void
+update_send(update_t *update)
+{
+    struct iovec iov[4];
+    dns_towire_state_t towire;
+    dns_wire_t *msg = update->update;
+    struct timeval tv;
+    uint8_t *p_mac;
+#ifdef DEBUG_DECODE_UPDATE
+    dns_message_t *decoded;
+#endif
+
+    // Set up the message constructor
+    memset(&towire, 0, sizeof towire);
+    towire.p = (uint8_t *)msg + update->update_length;  // We start storing RR data here.
+    towire.lim = &msg->data[0] + update->update_max;    // This is the limit to how much we can store.
+    towire.message = msg;
+    towire.p_rdlength = NULL;
+    towire.p_opt = NULL;
+
+    // If we have a key, sign the message with the key using TSIG HMAC-SHA256.
+    if (key != NULL) {
+        // Maintain an IOV with the bits of the message that we need to sign.
+        iov[0].iov_base = msg;
+
+        name_to_wire(&towire, key->name);
+        iov[0].iov_len = towire.p - (uint8_t *)iov[0].iov_base;
+        dns_u16_to_wire(&towire, dns_rrtype_tsig);            // RRTYPE
+        iov[1].iov_base = towire.p;
+        dns_u16_to_wire(&towire, dns_qclass_any);             // CLASS
+        dns_ttl_to_wire(&towire, 0);                          // TTL
+        iov[1].iov_len = towire.p - (uint8_t *)iov[1].iov_base;
+        // The message digest skips the RDLEN field.
+        dns_rdlength_begin(&towire);                          // RDLEN
+        iov[2].iov_base = towire.p;
+        dns_full_name_to_wire(NULL, &towire, "hmac-sha256."); // Algorithm Name
+        gettimeofday(&tv, NULL);
+        dns_u48_to_wire(&towire, tv.tv_sec);                  // Time since epoch
+        dns_u16_to_wire(&towire, 300);                        // Fudge interval
+                                                              // (clocks can be skewed by up to 5 minutes)
+        // Message digest doesn't cover MAC size or MAC fields, for obvious reasons, nor original message ID.
+        iov[2].iov_len = towire.p - (uint8_t *)iov[2].iov_base;
+        dns_u16_to_wire(&towire, SRP_SHA256_DIGEST_SIZE);       // MAC Size
+        p_mac = towire.p;                                     // MAC
+        if (!towire.error) {
+            if (towire.p + SRP_SHA256_DIGEST_SIZE >= towire.lim) {
+                towire.error = ENOBUFS;
+                towire.truncated = true;
+                towire.line = __LINE__;
+            } else {
+                towire.p += SRP_SHA256_DIGEST_SIZE;
+            }
+        }
+        // We have to copy the message ID into the tsig signature; this is because in some cases, although not this one,
+        // the message ID will be overwritten.   So the copy of the ID is what's validated, but it's copied into the
+        // header for validation, so we don't include it when generating the hash.
+        dns_rdata_raw_data_to_wire(&towire, &msg->id, sizeof msg->id);
+        iov[3].iov_base = towire.p;
+        dns_u16_to_wire(&towire, 0);                     // TSIG Error (always 0 on send).
+        dns_u16_to_wire(&towire, 0);                     // Other Len (MBZ?)
+        iov[3].iov_len = towire.p - (uint8_t *)iov[3].iov_base;
+        dns_rdlength_end(&towire);
+
+        // Okay, we have stored the TSIG signature, now compute the message digest.
+        srp_hmac_iov(key, p_mac, SRP_SHA256_DIGEST_SIZE, &iov[0], 4);
+        msg->arcount = htons(ntohs(msg->arcount) + 1);
+        update->update_length = towire.p - (const uint8_t *)msg;
+    }
+
+    if (towire.error != 0) {
+        ERROR("update_send: error \"%s\" while generating update at line %d",
+              strerror(towire.error), towire.line);
+        update_finished(update, dns_rcode_servfail);
+        return;
+    }
+
+#ifdef DEBUG_DECODE_UPDATE
+    if (!dns_wire_parse(&decoded, msg, update->update_length)) {
+        ERROR("Constructed message does not successfully parse.");
+        update_finished(update, dns_rcode_servfail);
+        return;
+    }
+#endif
+
+    // Transmit the update
+    iov[0].iov_base = update->update;
+    iov[0].iov_len = update->update_length;
+    update->server->send_response(update->server, update->message, iov, 1);
+}
+
+void
+update_connect_callback(comm_t *comm)
+{
+    update_t *update = comm->context;
+
+    // Once we're connected, construct the first update.
+    INFO("Connected to " PUB_S_SRP ".", comm->name);
+    // STATE CHANGE: connect_to_server -> refresh_existing
+    update->state = refresh_existing;
+    if (!construct_update(update)) {
+        update_finished(update, dns_rcode_servfail);
+        return;
+    }
+    update_send(update);
+}
+
+const char *NONNULL
+update_state_name(update_state_t state)
+{
+    switch(state) {
+    case connect_to_server:
+        return "connect_to_server";
+    case create_nonexistent:
+        return "create_nonexistent";
+    case refresh_existing:
+        return "refresh_existing";
+    case create_nonexistent_instance:
+        return "create_nonexistent_instance";
+    case refresh_existing_instance:
+        return "refresh_existing_instance";
+    case create_nonexistent_host:
+        return "create_nonexistent_host";
+    case refresh_existing_host:
+        return "refresh_existing_host";
+    case delete_failed_instance:
+        return "delete_failed_instance";
+    }
+    return "unknown state";
+}
+
+void
+update_finalize(io_t *context)
+{
+}
+
+void
+update_disconnect_callback(comm_t *comm, int error)
+{
+    update_t *update = comm->context;
+
+    if (update->state == connect_to_server) {
+        INFO(PUB_S_SRP " disconnected: " PUB_S_SRP, comm->name, strerror(error));
+        update_finished(update, dns_rcode_servfail);
+    } else {
+        // This could be bad if any updates succeeded.
+        ERROR("%s disconnected during update in state %s: %s",
+              comm->name, update_state_name(update->state), strerror(error));
+        update_finished(update, dns_rcode_servfail);
+    }
+}
+
+void
+update_reply_callback(comm_t *comm)
+{
+    update_t *update = comm->context;
+    dns_wire_t *wire = &comm->message->wire;
+    char namebuf[DNS_MAX_NAME_SIZE + 1], namebuf1[DNS_MAX_NAME_SIZE + 1];
+    service_instance_t **pinstance;
+    update_state_t initial_state;
+    service_instance_t *initial_instance;
+
+    initial_instance = update->instance;
+    initial_state = update->state;
+
+    INFO("Message from " PUB_S_SRP " in state " PUB_S_SRP ", rcode = " PUB_S_SRP ".", comm->name,
+        update_state_name(update->state), dns_rcode_name(dns_rcode_get(wire)));
+
+    // Sanity check the response
+    if (dns_qr_get(wire) == dns_qr_query) {
+        ERROR("Received a query from the authoritative server!");
+        update_finished(update, dns_rcode_servfail);
+        return;
+    }
+    if (dns_opcode_get(wire) != dns_opcode_update) {
+        ERROR("Received a response with opcode %d from the authoritative server!",
+              dns_opcode_get(wire));
+        update_finished(update, dns_rcode_servfail);
+        return;
+    }
+    if (update->update == NULL) {
+        ERROR("Received a response from auth server when no update has been sent yet.");
+        update_finished(update, dns_rcode_servfail);
+    }
+    // This isn't an error in the protocol, because we might be pipelining.   But we _aren't_ pipelining,
+    // so there is only one message in flight.   So the message IDs should match.
+    if (update->update->id != wire->id) {
+        ERROR("Response doesn't have the expected id: %x != %x.", wire->id, update->update->id);
+        update_finished(update, dns_rcode_servfail);
+    }
+
+    // Handle the case where the update succeeded.
+    switch(dns_rcode_get(wire)) {
+    case dns_rcode_noerror:
+        switch(update->state) {
+        case connect_to_server:  // Can't get a response when connecting.
+        invalid:
+            ERROR("Invalid rcode \"%s\" for state %s",
+                  dns_rcode_name(dns_rcode_get(wire)), update_state_name(update->state));
+            update_finished(update, dns_rcode_servfail);
+            return;
+
+        case create_nonexistent:
+            DM_NAME_GEN_SRP(update->host->name, freshly_added_name_buf);
+            INFO("SRP Update for host " PRI_DM_NAME_SRP " was freshly added.",
+                     DM_NAME_PARAM_SRP(update->host->name, freshly_added_name_buf));
+            update_finished(update, dns_rcode_noerror);
+            return;
+
+        case refresh_existing:
+            DM_NAME_GEN_SRP(update->host->name, refreshed_name_buf);
+            INFO("SRP Update for host " PRI_DM_NAME_SRP " was refreshed.",
+                 DM_NAME_PARAM_SRP(update->host->name, refreshed_name_buf));
+            update_finished(update, dns_rcode_noerror);
+            return;
+
+        case create_nonexistent_instance:
+            DM_NAME_GEN_SRP(update->instance->name, create_instance_buf);
+            INFO("Instance create for " PRI_DM_NAME_SRP " succeeded",
+                 DM_NAME_PARAM_SRP(update->instance->name, create_instance_buf));
+            // If we created a new instance, we need to remember it in case we have to undo it.
+            // To do that, we have to take it off the list.
+            for (pinstance = &update->instances; *pinstance != NULL; pinstance = &((*pinstance)->next)) {
+                if (*pinstance == update->instance) {
+                    break;
+                }
+            }
+            *pinstance = update->instance->next;
+            // If there are no more instances to update, then do the host add.
+            if (*pinstance == NULL) {
+                // STATE CHANGE: create_nonexistent_instance -> create_nonexistent_host
+                update->state = create_nonexistent_host;
+            } else {
+                // Not done yet, do the next one.
+                update->instance = *pinstance;
+            }
+            break;
+
+        case refresh_existing_instance:
+            DM_NAME_GEN_SRP(update->instance->name, refreshed_instance_buf);
+            INFO("Instance refresh for " PRI_S_SRP " succeeded",
+                 DM_NAME_PARAM_SRP(update->instance->name, refreshed_instance_buf));
+
+            // Move on to the next instance to update.
+            update->instance = update->instance->next;
+            // If there are no more instances to update, then do the host add.
+            if (update->instance == NULL) {
+                // STATE CHANGE: refresh_existing_instance -> create_nonexistent_host
+                update->state = create_nonexistent_host;
+            } else {
+                // Not done yet, do the next one.
+                // STATE CHANGE: refresh_existing_instance -> create_nonexistent_instance
+                update->state = create_nonexistent_instance;
+            }
+            break;
+
+        case create_nonexistent_host:
+            DM_NAME_GEN_SRP(update->instance->name, new_host_buf);
+            INFO("SRP Update for new host " PRI_S_SRP " was successful.",
+                 DM_NAME_PARAM_SRP(update->instance->name, new_host_buf));
+            update_finished(update, dns_rcode_noerror);
+            return;
+
+        case refresh_existing_host:
+            DM_NAME_GEN_SRP(update->instance->name, existing_host_buf);
+            INFO("SRP Update for existing host " PRI_S_SRP " was successful.",
+                 DM_NAME_PARAM_SRP(update->instance->name, existing_host_buf));
+            update_finished(update, dns_rcode_noerror);
+            return;
+
+        case delete_failed_instance:
+            DM_NAME_GEN_SRP(update->host->name, failed_instance_buf);
+            INFO("Instance deletes for host %s succeeded",
+                 DM_NAME_PARAM_SRP(update->host->name, failed_instance_buf));
+            update_finished(update, update->fail_rcode);
+            return;
+        }
+        break;
+
+        // We will get NXRRSET if we were adding an existing host with the prerequisite that a KEY
+        // RR exist on the name with the specified value.  Some other KEY RR may exist, or there may
+        // be no such RRSET; we can't tell from this response.
+    case dns_rcode_nxrrset:
+        switch(update->state) {
+        case connect_to_server:           // Can't get a response while connecting.
+        case create_nonexistent:          // Can't get nxdomain when creating.
+        case create_nonexistent_instance: // same
+        case create_nonexistent_host:     // same
+        case delete_failed_instance:      // There are no prerequisites for deleting failed instances, so
+                                          // in principle this should never fail.
+            goto invalid;
+
+        case refresh_existing:
+            // If we get an NXDOMAIN when doing a refresh, it means either that there is a conflict,
+            // or that one of the instances we are refreshing doesn't exist.   So now do the instances
+            // one at a time.
+
+            // STATE CHANGE: refresh_existing -> create_nonexistent
+            update->state = create_nonexistent;
+            update->instance = update->instances;
+            break;
+
+        case refresh_existing_instance:
+            // In this case, we tried to update an existing instance and found that the prerequisite
+            // didn't match.   This means either that there is a conflict, or else that the instance
+            // expired and was deleted between the time that we attempted to create it and the time
+            // we attempted to update it.  We could account for this with an create_nonexistent_instance_again
+            // state, but currently do not.
+
+            // If we have added some instances, we need to delete them before we send the fail response.
+            if (update->added_instances != NULL) {
+                // STATE CHANGE: refresh_existing_instance -> delete_failed_instance
+                update->state = delete_failed_instance;
+            delete_added_instances:
+                update->instance = update->added_instances;
+                update->fail_rcode = dns_rcode_get(wire);
+                break;
+            } else {
+                update_finished(update, dns_rcode_get(wire));
+                return;
+            }
+
+        case refresh_existing_host:
+            // In this case, there is a conflicting host entry.  This means that all the service
+            // instances that exist and are owned by the key we are using are bogus, whether we
+            // created them or they were already there.  However, it is not our mission to remove
+            // pre-existing messes here, so we'll just delete the ones we added.
+            if (update->added_instances != NULL) {
+                // STATE CHANGE: refresh_existing_host -> delete_failed_instance
+                update->state = delete_failed_instance;
+                goto delete_added_instances;
+            }
+            update_finished(update, dns_rcode_get(wire));
+            return;
+        }
+        break;
+        // We get YXDOMAIN if we specify a prerequisite that the name not exist, but it does exist.
+    case dns_rcode_yxdomain:
+       switch(update->state) {
+        case connect_to_server:         // We can't get a response while connecting.
+        case refresh_existing:          // If we are refreshing, our prerequisites are all looking for
+        case refresh_existing_instance: // a specific RR with a specific value, so we can never get
+        case refresh_existing_host:     // YXDOMAIN.
+        case delete_failed_instance:    // And if we are deleting failed instances, we should never get an error.
+            goto invalid;
+
+        case create_nonexistent:
+            // If we get an NXDOMAIN when doing a refresh, it means either that there is a conflict,
+            // or that one of the instances we are refreshing doesn't exist.   So now do the instances
+            // one at a time.
+
+            // STATE CHANGE: create_nonexistent -> create_nonexistent_instance
+            update->state = create_nonexistent_instance;
+            update->instance = update->instances;
+            break;
+
+        case create_nonexistent_instance:
+            // STATE CHANGE: create_nonexistent_instance -> refresh_existing_instance
+            update->state = refresh_existing_instance;
+            break;
+
+        case create_nonexistent_host:
+            // STATE CHANGE: create_nonexistent_host -> refresh_existing_host
+            update->state = refresh_existing_host;
+            break;
+       }
+       break;
+
+    case dns_rcode_notauth:
+        ERROR("DNS Authoritative server does not think we are authorized to update it, please fix.");
+        update_finished(update, dns_rcode_servfail);
+        return;
+
+        // We may want to return different error codes or do more informative logging for some of these:
+    case dns_rcode_formerr:
+    case dns_rcode_servfail:
+    case dns_rcode_notimp:
+    case dns_rcode_refused:
+    case dns_rcode_yxrrset:
+    case dns_rcode_notzone:
+    case dns_rcode_dsotypeni:
+    default:
+        goto invalid;
+    }
+
+    if (update->state != initial_state) {
+        INFO("Update state changed from " PUB_S_SRP " to " PUB_S_SRP, update_state_name(initial_state),
+             update_state_name(update->state));
+    }
+    if (update->instance != initial_instance) {
+        DM_NAME_GEN_SRP(initial_instance->name, initial_name_buf);
+        DM_NAME_GEN_SRP(update->instance->name, updated_name_buf);
+        INFO("Update instance changed from " PRI_DM_NAME_SRP " to " PRI_DM_NAME_SRP,
+             DM_NAME_PARAM_SRP(initial_instance->name, initial_name_buf),
+             DM_NAME_PARAM_SRP(update->instance->name, updated_name_buf));
+    }
+    if (construct_update(update)) {
+        update_send(update);
+    } else {
+        ERROR("Failed to construct update");
+        update_finished(update, dns_rcode_servfail);
+    }
+     return;
+}
+
+bool
+srp_update_start(comm_t *connection, dns_message_t *parsed_message, dns_host_description_t *host,
+                 service_instance_t *instance, service_t *service, dns_name_t *update_zone,
+                 uint32_t lease_time, uint32_t key_lease_time)
+{
+    update_t *update;
+
+    // Allocate the data structure
+    update = calloc(1, sizeof *update);
+    if (update == NULL) {
+        ERROR("start_dns_update: unable to allocate update structure!");
+        return false;
+    }
+    // Allocate the buffer in which updates will be constructed.
+    update->update = calloc(1, DNS_MAX_UDP_PAYLOAD);
+    if (update->update == NULL) {
+        ERROR("start_dns_update: unable to allocate update message buffer.");
+        return false;
+    }
+    update->update_max = DNS_DATA_SIZE;
+
+    // Retain the stuff we're supposed to send.
+    update->host = host;
+    update->instances = instance;
+    update->services = service;
+    update->parsed_message = parsed_message;
+    update->message = connection->message;
+    update->state = connect_to_server;
+    update->zone_name = update_zone;
+    update->client = connection;
+
+    // Start the connection to the server
+    update->server = ioloop_connect(&dns_server, false, true, update_reply_callback,
+                                    update_connect_callback, update_disconnect_callback, update_finalize, update);
+    if (update->server == NULL) {
+        free(update);
+        return false;
+    }
+    INFO("Connecting to auth server.");
+    return true;
+}
+
+static bool
+key_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
+{
+    hmac_key_t *key = context;
+    long val;
+    char *endptr;
+    size_t len;
+    uint8_t keybuf[SRP_SHA256_DIGEST_SIZE];
+    int error;
+
+    // Validate the constant-size stuff first.
+    if (strcasecmp(hunks[1], "in")) {
+        ERROR("Expecting tsig key class IN, got %s.", hunks[1]);
+        return false;
+    }
+
+    if (strcasecmp(hunks[2], "key")) {
+        ERROR("expecting tsig key type KEY, got %s", hunks[2]);
+        return false;
+    }
+
+    // There's not much meaning to be extracted from the flags.
+    val = strtol(hunks[3], &endptr, 10);
+    if (*endptr != 0 || endptr == hunks[3]) {
+        ERROR("Invalid key flags: %s", hunks[3]);
+        return false;
+    }
+
+    // The protocol number as produced by BIND will always be 3, meaning DNSSEC, but of
+    // course we aren't using this key for DNSSEC, so it's not clear that we should take
+    // this seriously; hence we just check to see that it's a number.
+    val = strtol(hunks[4], &endptr, 10);
+    if (*endptr != 0 || endptr == hunks[4]) {
+        ERROR("Invalid protocol number: %s", hunks[4]);
+        return false;
+    }
+
+    // The key algorithm should be HMAC-SHA253.  BIND uses 163, but this is not registered
+    // with IANA.   So again, we don't actually require this, but we do validate it so that
+    // if someone generated the wrong key type, they'll get a message.
+    val = strtol(hunks[5], &endptr, 10);
+    if (*endptr != 0 || endptr == hunks[5]) {
+        ERROR("Invalid protocol number: %s", hunks[5]);
+        return false;
+    }
+    if (val != 163) {
+        INFO("Warning: Protocol number for HMAC-SHA256 TSIG KEY is not 163, but %ld", val);
+    }
+
+    key->name = dns_pres_name_parse(hunks[0]);
+    if (key->name == NULL) {
+        ERROR("Invalid key name: %s", hunks[0]);
+        return false;
+    }
+
+    error = srp_base64_parse(hunks[6], &len, keybuf, sizeof keybuf);
+    if (error != 0) {
+        ERROR("Invalid HMAC-SHA256 key: %s", strerror(errno));
+        goto fail;
+    }
+
+    // The key should be 32 bytes (256 bits).
+    if (len == 0) {
+        ERROR("Invalid (null) secret for key %s", hunks[0]);
+        goto fail;
+    }
+    key->secret = malloc(len);
+    if (key->secret == NULL) {
+        ERROR("Unable to allocate space for secret for key %s", hunks[0]);
+    fail:
+        dns_name_free(key->name);
+        key->name = NULL;
+        return false;
+    }
+    memcpy(key->secret, keybuf, len);
+    key->length = len;
+    key->algorithm = SRP_HMAC_TYPE_SHA256;
+    return true;
+}
+
+config_file_verb_t key_verbs[] = {
+    { NULL, 7, 7, key_handler }
+};
+#define NUMKEYVERBS ((sizeof key_verbs) / sizeof (config_file_verb_t))
+
+hmac_key_t *
+parse_hmac_key_file(const char *filename)
+{
+    hmac_key_t *key = calloc(1, sizeof *key);
+    if (key == NULL) {
+        ERROR("No memory for tsig key structure.");
+        return NULL;
+    }
+    if (!config_parse(key, filename, key_verbs, NUMKEYVERBS)) {
+        ERROR("Failed to parse key file.");
+        free(key);
+        return NULL;
+    }
+    return key;
+}
+
+int
+main(int argc, char **argv)
+{
+    int i;
+    subnet_t *tcp_validators = NULL;
+    udp_validator_t *udp_validators = NULL;
+    udp_validator_t *NULLABLE *NONNULL up = &udp_validators;
+    subnet_t *NULLABLE *NONNULL nt = &tcp_validators;
+    subnet_t *NULLABLE *NONNULL sp;
+    addr_t pref;
+    uint16_t port;
+    socklen_t len, prefalen;
+    char *s, *p;
+    int width;
+    bool got_server = false;
+
+    // Read the configuration from the command line.
+    for (i = 1; i < argc; i++) {
+        if (!strcmp(argv[i], "-s")) {
+            if (got_server) {
+                ERROR("only one authoritative server can be specified.");
+                return usage(argv[0]);
+            }
+            if (++i == argc) {
+                ERROR("-s is missing dns server IP address.");
+                return usage(argv[0]);
+            }
+            len = getipaddr(&dns_server, argv[i]);
+            if (!len) {
+                ERROR("Invalid IP address: %s.", argv[i]);
+                return usage(argv[0]);
+            }
+            if (++i == argc) {
+                ERROR("-s is missing dns server port.");
+                return usage(argv[0]);
+            }
+            port = strtol(argv[i], &s, 10);
+            if (s == argv[i] || s[0] != '\0') {
+                ERROR("Invalid port number: %s", argv[i]);
+                return usage(argv[0]);
+            }
+            if (dns_server.sa.sa_family == AF_INET) {
+                dns_server.sin.sin_port = htons(port);
+            } else {
+                dns_server.sin6.sin6_port = htons(port);
+            }
+            got_server = true;
+        } else if (!strcmp(argv[i], "-k")) {
+            if (++i == argc) {
+                ERROR("-k is missing key file name.");
+                return usage(argv[0]);
+            }
+            key = parse_hmac_key_file(argv[i]);
+            // Someething should already have printed the error message.
+            if (key == NULL) {
+                return 1;
+            }
+        } else if (!strcmp(argv[i], "-t") || !strcmp(argv[i], "-u")) {
+            if (!strcmp(argv[i], "-u")) {
+                if (++i == argc) {
+                    ERROR("-u is missing interface name.");
+                    return usage(argv[0]);
+                }
+                *up = calloc(1, sizeof **up);
+                if (*up == NULL) {
+                    ERROR("udp_validators: out of memory.");
+                    return usage(argv[0]);
+                }
+                (*up)->ifname = strdup(argv[i]);
+                if ((*up)->ifname == NULL) {
+                    ERROR("udp validators: ifname: out of memory.");
+                    return usage(argv[0]);
+                }
+                sp = &((*up)->subnets);
+            } else {
+                sp = nt;
+            }
+
+            if (++i == argc) {
+                ERROR("%s requires at least one prefix.", argv[i - 1]);
+                return usage(argv[0]);
+            }
+            s = strchr(argv[i], '/');
+            if (s == NULL) {
+                ERROR("%s is not a prefix.", argv[i]);
+                return usage(argv[0]);
+            }
+            *s = 0;
+            ++s;
+            prefalen = getipaddr(&pref, argv[i]);
+            if (!prefalen) {
+                ERROR("%s is not a valid prefix address.", argv[i]);
+                return usage(argv[0]);
+            }
+            width = strtol(s, &p, 10);
+            if (s == p || p[0] != '\0') {
+                ERROR("%s (prefix width) is not a number.", p);
+                return usage(argv[0]);
+            }
+            if (width < 0 ||
+                (pref.sa.sa_family == AF_INET && width > 32) ||
+                (pref.sa.sa_family == AF_INET6 && width > 64)) {
+                ERROR("%s is not a valid prefix length for %s", p,
+                        pref.sa.sa_family == AF_INET ? "IPv4" : "IPv6");
+                return usage(argv[0]);
+            }
+
+            *nt = calloc(1, sizeof **nt);
+            if (!*nt) {
+                ERROR("tcp_validators: out of memory.");
+                return 1;
+            }
+
+            (*nt)->preflen = width;
+            (*nt)->family = pref.sa.sa_family;
+            if (pref.sa.sa_family == AF_INET) {
+                memcpy((*nt)->bytes, &pref.sin.sin_addr, 4);
+            } else {
+                memcpy((*nt)->bytes, &pref.sin6.sin6_addr, 8);
+            }
+
+            // *up will be non-null for -u and null for -t.
+            if (*up) {
+                up = &((*up)->next);
+            } else {
+                nt = sp;
+            }
+        }
+    }
+    if (!got_server) {
+        ERROR("No authoritative DNS server specified to take updates!");
+        return 1;
+    }
+
+    if (!ioloop_init()) {
+        return 1;
+    }
+
+    if (!srp_proxy_listen("home.arpa")) {
+        return 1;
+    }
+
+    // For now, hardcoded, should be configurable
+    service_update_zone = dns_pres_name_parse("home.arpa");
+
+    do {
+        int something = 0;
+        something = ioloop_events(0);
+        INFO("dispatched %d events.", something);
+    } while (1);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
index add308673592ee9aff26f2e55abcd385a40075dc..9ec3733878f6732249b25ad505a9d205084ed388 100644 (file)
@@ -1,6 +1,6 @@
 /* srp-gw.c
  *
- * Copyright (c) 2018 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2018-2019 Apple Computer, Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *
- * This is a DNSSD Service Registration Protocol gateway.   The purpose of this is to make it possible
+ * This is a DNSSD Service Registration Protocol update proxy.   The purpose of this is to make it possible
  * for SRP clients to update DNS servers that don't support SRP.
  *
  * The way it works is that this gateway listens on port ANY:53 and forwards either to another port on
 #include <string.h>
 #include <stdio.h>
 #include <unistd.h>
-#include <sys/errno.h>
+#include <errno.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
-#include <sys/event.h>
 #include <fcntl.h>
 #include <sys/time.h>
 
 #include "srp-crypto.h"
 #include "ioloop.h"
 #include "dnssd-proxy.h"
+#include "srp-gw.h"
+#include "config-parse.h"
 
-#pragma mark structures
-
-typedef struct subnet subnet_t;
-struct subnet {
-    subnet_t *NULLABLE next;
-    uint8_t preflen;
-    uint8_t family;
-    char bytes[8];
-};
-
-typedef struct udp_validator udp_validator_t;
-struct udp_validator {
-    udp_validator_t *NULLABLE next;
-    char *NONNULL ifname;
-    int ifindex;
-    subnet_t *subnets;
-};
+static addr_t dns_server;
+static hmac_key_t *key;
 
 static int
 usage(const char *progname)
 {
-    ERROR("usage: %s -s <addr> <port> -t <subnet> ... -u <ifname> <subnet> ...", progname);
+    ERROR("usage: %s -s <addr> <port> -k <key-file> -t <subnet> ... -u <ifname> <subnet> ...", progname);
     ERROR("  -s can only appear once.");
+    ERROR("  -k can appear once.");
     ERROR("  -t can only appear once, and is followed by one or more subnets.");
     ERROR("  -u can appear more than once, is followed by one interface name, and");
     ERROR("     one or more subnets.");
     ERROR("  <addr> is an IPv4 address or IPv6 address.");
     ERROR("  <port> is a UDP port number.");
+    ERROR("  <key-file> is a file containing an HMAC-SHA256 key for authenticating updates to the auth server.");
     ERROR("  <subnet> is an IP address followed by a slash followed by the prefix width.");
     ERROR("  <ifname> is the printable name of the interface.");
-    ERROR("ex: srp-gw -s 2001:DB8::1 53 -t 2001:DB8:1300::/48 -u en0 2001:DB8:1300:1100::/56");
+    ERROR("ex: srp-gw -s 2001:DB8::1 53 -k srp.key -t 2001:DB8:1300::/48 -u en0 2001:DB8:1300:1100::/56");
     return 1;
 }
 
-typedef struct delete delete_t;
-struct delete {
-    delete_t *next;
-    dns_name_t *name;
-};
+#define name_to_wire(towire, name) name_to_wire_(towire, name, __LINE__)
+void
+name_to_wire_(dns_towire_state_t *towire, dns_name_t *name, int line)
+{
+    // Does compression...
+    dns_concatenate_name_to_wire_(towire, name, NULL, NULL, line);
+}
 
-typedef struct dns_host_description dns_host_description_t;
-struct dns_host_description {
-    dns_name_t *name;
-    dns_rr_t *a, *aaaa, *key;
-    delete_t *delete;
-    int num_instances;
-};
+void
+rdata_to_wire(dns_towire_state_t *towire, dns_rr_t *rr)
+{
+    dns_txt_element_t *txt;
+
+    dns_rdlength_begin(towire);
 
-typedef struct service_instance service_instance_t;
-struct service_instance {
-    service_instance_t *next;
-    dns_host_description_t *host_description;
-    dns_name_t *name;
-    delete_t *delete;
-    int num_instances;
-    dns_rr_t *srv, *txt;
+    // These are the only types we expect to see.  If something else were passed, it would be written as rdlen=0.
+    switch(rr->type) {
+    case dns_rrtype_ptr:
+        name_to_wire(towire, rr->data.ptr.name);
+        break;
+
+    case dns_rrtype_srv:
+        dns_u16_to_wire(towire, rr->data.srv.priority);
+        dns_u16_to_wire(towire, rr->data.srv.weight);
+        dns_u16_to_wire(towire, rr->data.srv.port);
+        name_to_wire(towire, rr->data.srv.name);
+        break;
+
+    case dns_rrtype_txt:
+        for (txt = rr->data.txt; txt != NULL; txt = txt->next) {
+            dns_u8_to_wire(towire, txt->len);
+            dns_rdata_raw_data_to_wire(towire, txt->data, txt->len);
+        }
+        break;
+
+    case dns_rrtype_key:
+        dns_u16_to_wire(towire, rr->data.key.flags);
+        dns_u8_to_wire(towire, rr->data.key.protocol);
+        dns_u8_to_wire(towire, rr->data.key.algorithm);
+        dns_rdata_raw_data_to_wire(towire, rr->data.key.key, rr->data.key.len);
+        break;
+
+    case dns_rrtype_a:
+        dns_rdata_raw_data_to_wire(towire, &rr->data.a, sizeof rr->data.a);
+        break;
+
+    case dns_rrtype_aaaa:
+        dns_rdata_raw_data_to_wire(towire, &rr->data.aaaa, sizeof rr->data.aaaa);
+        break;
+    }
+
+    dns_rdlength_end(towire);
+}
+
+// We only list the types we are using--there are other types that we don't support.
+typedef enum prereq_type prereq_type_t;
+enum prereq_type {
+    update_rrset_equals,     // RFC 2136 section 2.4.2: RRset Exists (Value Dependent)
+    update_name_not_in_use,  // RFC 2136 section 2.4.5: Name Is Not In Use
 };
 
-typedef struct service service_t;
-struct service {
-    service_t *next;
-    service_instance_t *instance;
-    dns_name_t *name;
-    dns_rr_t *rr;
+void
+add_prerequisite(dns_wire_t *msg, dns_towire_state_t *towire, prereq_type_t ptype, dns_name_t *name, dns_rr_t *rr)
+{
+    char namebuf[DNS_MAX_NAME_SIZE + 1];
+    if (ntohs(msg->nscount) != 0 || ntohs(msg->arcount) != 0) {
+        ERROR("%s: adding prerequisite after updates", dns_name_print(name, namebuf, sizeof namebuf));
+        towire->truncated = true;
+    }
+    name_to_wire(towire, name);
+    switch(ptype) {
+    case update_rrset_equals:
+        dns_u16_to_wire(towire, rr->type);
+        dns_u16_to_wire(towire, rr->qclass);
+        dns_ttl_to_wire(towire, 0);
+        rdata_to_wire(towire, rr);
+        break;
+    case update_name_not_in_use:
+        dns_u16_to_wire(towire, dns_rrtype_any);   // TYPE
+        dns_u16_to_wire(towire, dns_qclass_none);  // CLASS
+        dns_ttl_to_wire(towire, 0);                // TTL
+        dns_u16_to_wire(towire, 0);                // RDLEN
+        break;
+    }
+    msg->ancount = htons(ntohs(msg->ancount) + 1);
+}
+
+// We actually only support one type of delete, so it's a bit silly to specify it, but in principle we might
+// want more later.
+typedef enum delete_type delete_type_t;
+enum delete_type {
+    delete_name, // RFC 2136 section 2.5.3: Delete all RRsets from a name
 };
 
-bool
-srp_relay(comm_t *comm, dns_message_t *message)
+void
+add_delete(dns_wire_t *msg, dns_towire_state_t *towire, delete_type_t dtype, dns_name_t *name)
 {
-    dns_name_t *update_zone;
-    bool updating_services_dot_arpa = false;
-    int i;
-    dns_host_description_t *host_description = NULL;
-    delete_t *deletes = NULL, *dp, **dpp = &deletes;
-    service_instance_t *service_instances = NULL, *sip, **sipp = &service_instances;
-    service_t *services = NULL, *sp, **spp = &services;
-    dns_rr_t *signature;
-    char namebuf[DNS_MAX_NAME_SIZE + 1], namebuf1[DNS_MAX_NAME_SIZE + 1];
-    bool ret = false;
-    struct timeval now;
+    name_to_wire(towire, name);
+    switch(dtype) {
+    case delete_name:
+        dns_u16_to_wire(towire, dns_rrtype_any);   // TYPE
+        dns_u16_to_wire(towire, dns_qclass_any);   // CLASS
+        dns_ttl_to_wire(towire, 0);                // TTL
+        dns_u16_to_wire(towire, 0);                // RDLEN
+        break;
+    }
+    msg->nscount = htons(ntohs(msg->nscount) + 1);
+}
 
-    // Update requires a single SOA record as the question
-    if (message->qdcount != 1) {
-        ERROR("srp_relay: update received with qdcount > 1");
-        return false;
+// Copy the RR we received in the SRP update out in wire format.
+
+void
+add_rr(dns_wire_t *msg, dns_towire_state_t *towire, dns_name_t *name, dns_rr_t *rr)
+{
+    if (rr != NULL) {
+        name_to_wire(towire, name);
+        dns_u16_to_wire(towire, rr->type);   // TYPE
+        dns_u16_to_wire(towire, rr->qclass); // CLASS
+        dns_ttl_to_wire(towire, rr->ttl);    // TTL
+        rdata_to_wire(towire, rr);           // RDLEN
+        msg->nscount = htons(ntohs(msg->nscount) + 1);
     }
+}
+
+// Free all the stuff that we accumulated while processing the SRP update.
+void
+srp_update_free(update_t *update)
+{
+    // Free all of the structures we collated RRs into:
+    srp_update_free_parts(update->instances, update->added_instances, update->services, update->host);
+    // We don't need to free the zone name: it's either borrowed from the message,
+    // or it's service_update_zone, which is static.
+    ioloop_message_release(update->message);
+    dns_message_free(update->parsed_message);
+    free(update);
+}
+
+// Construct an update of the specified type, assuming that the record being updated
+// either exists or does not exist, depending on the value of exists.   Actual records
+// to be update are taken from the update_t.
+//
+// Analysis:
+//
+// The goal of the update is to either bring the zone to the state described in the SRP update, or
+// determine that the state described in the SRP update conflicts with what is already present in
+// the zone.
+//
+// Possible scenarios:
+// 1. Update and Zone are the same (A and AAAA records may differ):
+//    Prerequisites:
+//    a. for each instance: KEY RR exists on instance name and is the same
+//    b. for host: KEY RR exists on host name and is the same
+//    Update:
+//    a. for each instance: delete all records on instance name, add KEY RR, add SRV RR, add TXT RR
+//    b. for host: delete host instance, add A, AAAA and KEY RRs
+//    c. for each service: add PTR record pointing on service name to service instance name
+//
+// We should try 1 first, because it should be the steady state case; that is, it should be what happens
+// most of the time.
+// If 1 fails, then we could have some service instances present and others not.   There is no way to
+// know without trying.   We can at this point either try to add each service instance in a separate update,
+// or assume that none are present and add them all at once, and then if this fails add them individually.
+// I think that it makes sense to try them all first, because that should be the second most common case:
+//
+// 2. Nothing in update is present in zone:
+//    Prerequisites:
+//    a. For each instance: instance name is not in use
+//    b. Host name is not in use
+//    Update:
+//    a. for each instance: add KEY RR, add SRV RR, add TXT RR on instance name
+//    b. for host: add A, AAAA and KEY RRs on host name
+//    c. for each service: add PTR record pointing on service name to service instance name
+//
+// If either (1) or (2) works, we're done.   If both fail, then we need to do the service instance updates
+// and host update one by one.   This is a bit nasty because we actually have to try twice: once assuming
+// the RR exists, and once assuming it doesn't.   If any of the instance updates fail, or the host update
+// fails, we delete all the ones that succeeded.
+//
+// In the cases other than (1) and (2), we can add all the service PTRs in the host update, because they're
+// only added if the host update succeeds; if it fails, we have to go back and remove all the service
+// instances.
+//
+// One open question for the SRP document: we probably want to signal whether the conflict is with the
+// hostname or one of the service instance names.   We can do this with an EDNS(0) option.
+//
+// The flow will be:
+// - Try to update assuming everything is there already (case 1)
+// - Try to update assuming nothing is there already (case 2)
+// - For each service instance:
+//   - Try to update assuming it's not there; if this succeeds, add this instance to the list of
+//     instances that have been added. If not:
+//     - Try to update assuming it is there
+//     - If this fails, go to fail
+// - Try to update the host (and also services) assuming the host is not there.   If this fails:
+//   - Try to update the host (and also services) assuming the host is there.  If this succeeds:
+//     - return success
+// fail:
+// - For each service instance in the list of instances that have been added:
+//   - delete all records on the instance name.
+//
+// One thing that isn't accounted for here: it's possible that a previous update added some but not all
+// instances in the current update.  Subsequently, some other device may have claimed an instance that is
+// present but in conflict in the current update.   In this case, all of the instances prior to that one
+// in the update will actually have been updated by this update, but then the update as a whole will fail.
+// I think this is unlikely to be an actual problem, and there's no way to address it without a _lot_ of
+// complexity.
+
+bool
+construct_update(update_t *update)
+{
+    dns_towire_state_t towire;
+    dns_wire_t *msg = update->update; // Solely to reduce the amount of typing.
+    service_instance_t *instance;
+    service_t *service;
+    dns_addr_reg_t *reg;
+
+    // Set up the message constructor
+    memset(&towire, 0, sizeof towire);
+    towire.p = &msg->data[0];  // We start storing RR data here.
+    towire.lim = &msg->data[0] + update->update_max; // This is the limit to how much we can store.
+    towire.message = msg;
 
-    // Update should contain zero answers.
-    if (message->ancount != 0) {
-        ERROR("srp_relay: update received with ancount > 0");
+    // Initialize the update message...
+    memset(msg, 0, DNS_HEADER_SIZE);
+    dns_qr_set(msg, dns_qr_query);
+    dns_opcode_set(msg, dns_opcode_update);
+    msg->id = srp_random16();
+
+    // An update always has one question, which is the zone name.
+    msg->qdcount = htons(1);
+    name_to_wire(&towire, update->zone_name);
+    dns_u16_to_wire(&towire, dns_rrtype_soa);
+    dns_u16_to_wire(&towire, dns_qclass_in);
+
+    switch(update->state) {
+    case connect_to_server:
+        ERROR("Update construction requested when still connecting.");
+        update->update_length = 0;
         return false;
-    }
 
-    if (message->questions[0].type != dns_rrtype_soa) {
-        ERROR("srp_relay: update received with rrtype %d instead of SOA in question section.",
-              message->questions[0].type);
+        // Do a DNS Update for a service instance
+    case refresh_existing:
+        // Add a "KEY exists and is <x> and a PTR exists and is <x> prerequisite for each instance being updated.
+        for (instance = update->instances; instance; instance = instance->next) {
+            add_prerequisite(msg, &towire, update_rrset_equals, instance->name, update->host->key);
+        }
+        add_prerequisite(msg, &towire, update_rrset_equals, update->host->name, update->host->key);
+        // Now add a delete for each service instance
+        for (instance = update->instances; instance; instance = instance->next) {
+            add_delete(msg, &towire, delete_name, instance->name);
+        }
+        add_delete(msg, &towire, delete_name, update->host->name);
+
+    add_instances:
+        // Now add the update for each instance.
+        for (instance = update->instances; instance; instance = instance->next) {
+            add_rr(msg, &towire, instance->name, update->host->key);
+            add_rr(msg, &towire, instance->name, instance->srv);
+            add_rr(msg, &towire, instance->name, instance->txt);
+        }
+        // Add the update for each service
+        for (service = update->services; service; service = service->next) {
+            add_rr(msg, &towire, service->rr->name, service->rr);
+        }
+        // Add the host records...
+        add_rr(msg, &towire, update->host->name, update->host->key);
+        for (reg = update->host->a; reg; reg = reg->next) {
+            add_rr(msg, &towire, update->host->name, reg->rr);
+        }
+        for (reg = update->host->aaaa; reg; reg = reg->next) {
+            add_rr(msg, &towire, update->host->name, reg->rr);
+        }
+        break;
+
+    case create_nonexistent:
+        // Add a "name not in use" prerequisite for each instance being updated.
+        for (instance = update->instances; instance; instance = instance->next) {
+            add_prerequisite(msg, &towire, update_name_not_in_use, instance->name, (dns_rr_t *)NULL);
+        }
+        add_prerequisite(msg, &towire, update_name_not_in_use, update->host->name, (dns_rr_t *)NULL);
+        goto add_instances;
+
+    case create_nonexistent_instance:
+        // The only prerequisite is that this specific service instance doesn't exist.
+        add_prerequisite(msg, &towire, update_name_not_in_use, update->instance->name, (dns_rr_t *)NULL);
+        goto add_instance;
+
+    case refresh_existing_instance:
+        // If instance already exists, prerequisite is that it has the same key, and we also have to
+        // delete all RRs on the name before adding our RRs, in case they have changed.
+        add_prerequisite(msg, &towire, update_rrset_equals, update->instance->name, update->host->key);
+        add_delete(msg, &towire, delete_name, update->instance->name);
+    add_instance:
+        add_rr(msg, &towire, update->instance->name, update->host->key);
+        add_rr(msg, &towire, update->instance->name, update->instance->srv);
+        add_rr(msg, &towire, update->instance->name, update->instance->txt);
+        break;
+
+    case create_nonexistent_host:
+        add_prerequisite(msg, &towire, update_name_not_in_use, update->host->name, (dns_rr_t *)NULL);
+        goto add_host;
+
+    case refresh_existing_host:
+        add_prerequisite(msg, &towire, update_rrset_equals, update->host->name, update->host->key);
+        add_delete(msg, &towire, delete_name, update->host->name);
+        // Add the service PTRs here--these don't need to be in a separate update, because if we get here
+        // the only thing that can make adding them not okay is if adding the host fails.
+        // Add the update for each service
+        for (service = update->services; service; service = service->next) {
+            add_rr(msg, &towire, service->rr->name, service->rr);
+        }
+    add_host:
+        // Add the host records...
+        add_rr(msg, &towire, update->host->name, update->host->key);
+        for (reg = update->host->a; reg; reg = reg->next) {
+            add_rr(msg, &towire, update->host->name, reg->rr);
+        }
+        for (reg = update->host->aaaa; reg; reg = reg->next) {
+            add_rr(msg, &towire, update->host->name, reg->rr);
+        }
+        break;
+
+    case delete_failed_instance:
+        // Delete all the instances we successfull added before discovering a problem.
+        // It is possible in principle that these could have been overwritten by some other
+        // process and we could be deleting the wrong stuff, but in practice this should
+        // never happen if these are legitimately managed by SRP.   Once a name has been
+        // claimed by SRP, it should continue to be managed by SRP until its lease expires
+        // and SRP deletes it, at which point it is of course fair game.
+        for (instance = update->instances; instance; instance = instance->next) {
+            add_delete(msg, &towire, delete_name, instance->name);
+        }
+        break;
+    }
+    if (towire.error != 0) {
+        ERROR("construct_update: error %s while generating update at line %d", strerror(towire.error), towire.line);
         return false;
     }
-    update_zone = message->questions[0].name;
+    update->update_length = towire.p - (uint8_t *)msg;
+    return true;
+}
 
-    // What zone are we updating?
-    if (dns_names_equal_text(update_zone, "services.arpa")) {
-        updating_services_dot_arpa = true;
-    }
+void
+update_finished(update_t *update, int rcode)
+{
+    comm_t *comm = update->client;
+    struct iovec iov;
+    dns_wire_t response;
+    INFO("Update Finished, rcode = " PUB_S_SRP, dns_rcode_name(rcode));
 
-    // Scan over the authority RRs; do the delete consistency check.  We can't do other consistency checks
-    // because we can't assume a particular order to the records other than that deletes have to come before
-    // adds.
-    for (i = 0; i < message->nscount; i++) {
-        dns_rr_t *rr = &message->authority[i];
+    memset(&response, 0, DNS_HEADER_SIZE);
+    response.id = update->message->wire.id;
+    response.bitfield = update->message->wire.bitfield;
+    dns_rcode_set(&response, rcode);
 
-        // If this is a delete for all the RRs on a name, record it in the list of deletes.
-        if (rr->type == dns_rrtype_any && rr->qclass == dns_qclass_any && rr->ttl == 0) {
-            for (dp = deletes; dp; dp = dp->next) {
-                if (dns_names_equal(dp->name, rr->name)) {
-                    ERROR("srp_relay: two deletes for the same name: %s",
-                          dns_name_print(rr->name, namebuf, sizeof namebuf));
-                    goto out;
-                }
-            }
-            dp = calloc(sizeof *dp, 1);
-            if (!dp) {
-                ERROR("srp_relay: no memory.");
-                goto out;
+    iov.iov_base = &response;
+    iov.iov_len = DNS_HEADER_SIZE;
+
+    comm->send_response(comm, update->message, &iov, 1);
+
+    // If success, construct a response
+    // If fail, send a quick status code
+    // Signal host name conflict and instance name conflict using different rcodes (?)
+    // Okay, so if there's a host name/instance name conflict, and the host name has the right key, then
+    // the instance name is actually bogus and should be overwritten.
+    // If the host has the wrong key, and the instance is present, then the instance is also bogus.
+    // So in each of these cases, perhaps we should just gc the instance.
+    // This would mean that there is nothing to signal: either the instance is a mismatch, and we
+    // overwrite it and return success, or the host is a mismatch and we gc the instance and return failure.
+    ioloop_close(&update->server->io);
+    srp_update_free(update);
+}
+
+void
+update_send(update_t *update)
+{
+    struct iovec iov[4];
+    dns_towire_state_t towire;
+    dns_wire_t *msg = update->update;
+    struct timeval tv;
+    uint8_t *p_mac;
+#ifdef DEBUG_DECODE_UPDATE
+    dns_message_t *decoded;
+#endif
+
+    // Set up the message constructor
+    memset(&towire, 0, sizeof towire);
+    towire.p = (uint8_t *)msg + update->update_length;  // We start storing RR data here.
+    towire.lim = &msg->data[0] + update->update_max;    // This is the limit to how much we can store.
+    towire.message = msg;
+    towire.p_rdlength = NULL;
+    towire.p_opt = NULL;
+
+    // If we have a key, sign the message with the key using TSIG HMAC-SHA256.
+    if (key != NULL) {
+        // Maintain an IOV with the bits of the message that we need to sign.
+        iov[0].iov_base = msg;
+
+        name_to_wire(&towire, key->name);
+        iov[0].iov_len = towire.p - (uint8_t *)iov[0].iov_base;
+        dns_u16_to_wire(&towire, dns_rrtype_tsig);            // RRTYPE
+        iov[1].iov_base = towire.p;
+        dns_u16_to_wire(&towire, dns_qclass_any);             // CLASS
+        dns_ttl_to_wire(&towire, 0);                          // TTL
+        iov[1].iov_len = towire.p - (uint8_t *)iov[1].iov_base;
+        // The message digest skips the RDLEN field.
+        dns_rdlength_begin(&towire);                          // RDLEN
+        iov[2].iov_base = towire.p;
+        dns_full_name_to_wire(NULL, &towire, "hmac-sha256."); // Algorithm Name
+        gettimeofday(&tv, NULL);
+        dns_u48_to_wire(&towire, tv.tv_sec);                  // Time since epoch
+        dns_u16_to_wire(&towire, 300);                        // Fudge interval
+                                                              // (clocks can be skewed by up to 5 minutes)
+        // Message digest doesn't cover MAC size or MAC fields, for obvious reasons, nor original message ID.
+        iov[2].iov_len = towire.p - (uint8_t *)iov[2].iov_base;
+        dns_u16_to_wire(&towire, SRP_SHA256_DIGEST_SIZE);       // MAC Size
+        p_mac = towire.p;                                     // MAC
+        if (!towire.error) {
+            if (towire.p + SRP_SHA256_DIGEST_SIZE >= towire.lim) {
+                towire.error = ENOBUFS;
+                towire.truncated = true;
+                towire.line = __LINE__;
+            } else {
+                towire.p += SRP_SHA256_DIGEST_SIZE;
             }
-            dp->name = rr->name;
-            *dpp = dp;
-            dpp = &dp->next;
         }
+        // We have to copy the message ID into the tsig signature; this is because in some cases, although not this one,
+        // the message ID will be overwritten.   So the copy of the ID is what's validated, but it's copied into the
+        // header for validation, so we don't include it when generating the hash.
+        dns_rdata_raw_data_to_wire(&towire, &msg->id, sizeof msg->id);
+        iov[3].iov_base = towire.p;
+        dns_u16_to_wire(&towire, 0);                     // TSIG Error (always 0 on send).
+        dns_u16_to_wire(&towire, 0);                     // Other Len (MBZ?)
+        iov[3].iov_len = towire.p - (uint8_t *)iov[3].iov_base;
+        dns_rdlength_end(&towire);
 
-        // Otherwise if it's an A or AAAA record, it's part of a hostname entry.
-        else if (rr->type == dns_rrtype_a || rr->type == dns_rrtype_aaaa || rr->type == dns_rrtype_key) {
-            // Allocate the hostname record
-            if (!host_description) {
-                host_description = calloc(sizeof *host_description, 1);
-                if (!host_description) {
-                    ERROR("srp_relay: no memory");
-                    goto out;
-                }
-            }
+        // Okay, we have stored the TSIG signature, now compute the message digest.
+        srp_hmac_iov(key, p_mac, SRP_SHA256_DIGEST_SIZE, &iov[0], 4);
+        msg->arcount = htons(ntohs(msg->arcount) + 1);
+        update->update_length = towire.p - (const uint8_t *)msg;
+    }
 
-            // Make sure it's preceded by a deletion of all the RRs on the name.
-            if (!host_description->delete) {
-                for (dp = deletes; dp; dp = dp->next) {
-                    if (dns_names_equal(dp->name, rr->name)) {
-                        break;
-                    }
-                }
-                if (dp == NULL) {
-                    ERROR("srp_relay: ADD for hostname %s without a preceding delete.",
-                          dns_name_print(rr->name, namebuf, sizeof namebuf));
-                    goto out;
-                }
-                host_description->delete = dp;
-                host_description->name = dp->name;
-            }
-                          
-            if (rr->type == dns_rrtype_a) {
-                if (host_description->a != NULL) {
-                    ERROR("srp_relay: more than one A rrset received for name: %s",
-                          dns_name_print(rr->name, namebuf, sizeof namebuf));
-                    goto out;
-                }
-                host_description->a = rr;
-            } else if (rr->type == dns_rrtype_aaaa) {
-                if (host_description->aaaa != NULL) {
-                    ERROR("srp_relay: more than one AAAA rrset received for name: %s",
-                          dns_name_print(rr->name, namebuf, sizeof namebuf));
-                    goto out;
-                }
-                host_description->aaaa = rr;
-            } else if (rr->type == dns_rrtype_key) {
-                if (host_description->key != NULL) {
-                    ERROR("srp_relay: more than one KEY rrset received for name: %s",
-                          dns_name_print(rr->name, namebuf, sizeof namebuf));
-                    goto out;
-                }
-                host_description->key =  rr;
-            }
+    if (towire.error != 0) {
+        ERROR("update_send: error \"%s\" while generating update at line %d",
+              strerror(towire.error), towire.line);
+        update_finished(update, dns_rcode_servfail);
+        return;
+    }
+
+#ifdef DEBUG_DECODE_UPDATE
+    if (!dns_wire_parse(&decoded, msg, update->update_length)) {
+        ERROR("Constructed message does not successfully parse.");
+        update_finished(update, dns_rcode_servfail);
+        return;
+    }
+#endif
+
+    // Transmit the update
+    iov[0].iov_base = update->update;
+    iov[0].iov_len = update->update_length;
+    update->server->send_response(update->server, update->message, iov, 1);
+}
+
+void
+update_connect_callback(comm_t *comm)
+{
+    update_t *update = comm->context;
+
+    // Once we're connected, construct the first update.
+    INFO("Connected to " PUB_S_SRP ".", comm->name);
+    // STATE CHANGE: connect_to_server -> refresh_existing
+    update->state = refresh_existing;
+    if (!construct_update(update)) {
+        update_finished(update, dns_rcode_servfail);
+        return;
+    }
+    update_send(update);
+}
+
+const char *NONNULL
+update_state_name(update_state_t state)
+{
+    switch(state) {
+    case connect_to_server:
+        return "connect_to_server";
+    case create_nonexistent:
+        return "create_nonexistent";
+    case refresh_existing:
+        return "refresh_existing";
+    case create_nonexistent_instance:
+        return "create_nonexistent_instance";
+    case refresh_existing_instance:
+        return "refresh_existing_instance";
+    case create_nonexistent_host:
+        return "create_nonexistent_host";
+    case refresh_existing_host:
+        return "refresh_existing_host";
+    case delete_failed_instance:
+        return "delete_failed_instance";
+    }
+    return "unknown state";
+}
+
+void
+update_disconnect_callback(comm_t *comm, int error)
+{
+    update_t *update = comm->context;
+
+    if (update->state == connect_to_server) {
+        INFO(PUB_S_SRP " disconnected: " PUB_S_SRP, comm->name, strerror(error));
+        update_finished(update, dns_rcode_servfail);
+    } else {
+        // This could be bad if any updates succeeded.
+        ERROR("%s disconnected during update in state %s: %s",
+              comm->name, update_state_name(update->state), strerror(error));
+        update_finished(update, dns_rcode_servfail);
+    }
+}
+
+void
+update_reply_callback(comm_t *comm)
+{
+    update_t *update = comm->context;
+    dns_wire_t *wire = &comm->message->wire;
+    char namebuf[DNS_MAX_NAME_SIZE + 1], namebuf1[DNS_MAX_NAME_SIZE + 1];
+    service_instance_t **pinstance;
+    update_state_t initial_state;
+    service_instance_t *initial_instance;
+
+    initial_instance = update->instance;
+    initial_state = update->state;
+
+    INFO("Message from " PUB_S_SRP " in state " PUB_S_SRP ", rcode = " PUB_S_SRP ".", comm->name,
+         update_state_name(update->state), dns_rcode_name(dns_rcode_get(wire)));
+
+    // Sanity check the response
+    if (dns_qr_get(wire) == dns_qr_query) {
+        ERROR("Received a query from the authoritative server!");
+        update_finished(update, dns_rcode_servfail);
+        return;
+    }
+    if (dns_opcode_get(wire) != dns_opcode_update) {
+        ERROR("Received a response with opcode %d from the authoritative server!",
+              dns_opcode_get(wire));
+        update_finished(update, dns_rcode_servfail);
+        return;
+    }
+    if (update->update == NULL) {
+        ERROR("Received a response from auth server when no update has been sent yet.");
+        update_finished(update, dns_rcode_servfail);
+    }
+    // This isn't an error in the protocol, because we might be pipelining.   But we _aren't_ pipelining,
+    // so there is only one message in flight.   So the message IDs should match.
+    if (update->update->id != wire->id) {
+        ERROR("Response doesn't have the expected id: %x != %x.", wire->id, update->update->id);
+        update_finished(update, dns_rcode_servfail);
+    }
+
+    // Handle the case where the update succeeded.
+    switch(dns_rcode_get(wire)) {
+    case dns_rcode_noerror:
+        switch(update->state) {
+        case connect_to_server:  // Can't get a response when connecting.
+        invalid:
+            ERROR("Invalid rcode \"%s\" for state %s",
+                  dns_rcode_name(dns_rcode_get(wire)), update_state_name(update->state));
+            update_finished(update, dns_rcode_servfail);
+            return;
+
+        case create_nonexistent: {
+            DM_NAME_GEN_SRP(update->host->name, name_buf);
+            INFO("SRP Update for host " PRI_DM_NAME_SRP " was freshly added.",
+                 DM_NAME_PARAM_SRP(update->host->name, name_buf));
+            update_finished(update, dns_rcode_noerror);
+            return;
         }
 
-        // Otherwise if it's an SRV entry, that should be a service instance name.
-        else if (rr->type == dns_rrtype_srv || rr->type == dns_rrtype_txt) {
-            // Should be a delete that precedes this service instance.
-            for (dp = deletes; dp; dp = dp->next) {
-                if (dns_names_equal(dp->name, rr->name)) {
-                    break;
-                }
-            }
-            if (dp == NULL) {
-                ERROR("srp_relay: ADD for service instance not preceded by delete: %s",
-                      dns_name_print(rr->name, namebuf, sizeof namebuf));
-                goto out;
-            }
-            for (sip = service_instances; sip; sip = sip->next) {
-                if (dns_names_equal(sip->name, rr->name)) {
+        case refresh_existing: {
+            DM_NAME_GEN_SRP(update->host->name, name_buf);
+            INFO("SRP Update for host " PRI_DM_NAME_SRP " was refreshed.",
+                 DM_NAME_PARAM_SRP(update->host->name, name_buf));
+            update_finished(update, dns_rcode_noerror);
+            return;
+        }
+
+        case create_nonexistent_instance: {
+            DM_NAME_GEN_SRP(update->instances->name, name_buf);
+            INFO("Instance create for " PRI_DM_NAME_SRP " succeeded",
+                 DM_NAME_PARAM_SRP(update->instances->name, name_buf));
+            // If we created a new instance, we need to remember it in case we have to undo it.
+            // To do that, we have to take it off the list.
+            for (pinstance = &update->instances; *pinstance != NULL; pinstance = &((*pinstance)->next)) {
+                if (*pinstance == update->instance) {
                     break;
                 }
             }
-            if (!sip) {
-                sip = calloc(sizeof *sip, 1);
-                if (sip == NULL) {
-                    ERROR("srp_relay: no memory");
-                    goto out;
-                }
-                sip->delete = dp;
-                sip->name = dp->name;
-                *sipp = sip;
-                sipp = &sip->next;
-            }
-            if (rr->type == dns_rrtype_srv) {
-                if (sip->srv != NULL) {
-                    ERROR("srp_relay: more than one SRV rr received for service instance: %s",
-                          dns_name_print(rr->name, namebuf, sizeof namebuf));
-                    goto out;
-                }
-                sip->srv = rr;
-            } else if (rr->type == dns_rrtype_txt) {
-                if (sip->txt != NULL) {
-                    ERROR("srp_relay: more than one SRV rr received for service instance: %s",
-                          dns_name_print(rr->name, namebuf, sizeof namebuf));
-                }
-                sip->txt = rr;
+            *pinstance = update->instance->next;
+            // If there are no more instances to update, then do the host add.
+            if (*pinstance == NULL) {
+                // STATE CHANGE: create_nonexistent_instance -> create_nonexistent_host
+                update->state = create_nonexistent_host;
+            } else {
+                // Not done yet, do the next one.
+                update->instance = *pinstance;
             }
+            break;
         }
 
-        // Otherwise if it's a PTR entry, that should be a service name
-        else if (rr->type == dns_rrtype_ptr) {
-            sp = calloc(sizeof *sp, 1);
-            if (sp == NULL) {
-                ERROR("srp_relay: no memory");
-                goto out;
-            }
-            sp->rr = rr;
-            *spp = sp;
-            spp = &sp->next;
-        }            
-
-        // Otherwise it's not a valid update
-        else {
-            ERROR("srp_relay: unexpected rrtype %d on %s in update.", rr->type,
-                      dns_name_print(rr->name, namebuf, sizeof namebuf));
-            goto out;
-        }
-    }
+        case refresh_existing_instance: {
+            DM_NAME_GEN_SRP(update->instance->name, name_buf);
+            INFO("Instance refresh for " PRI_DM_NAME_SRP " succeeded",
+                 DM_NAME_PARAM_SRP(update->instances->name, name_buf));
 
-    // Now that we've scanned the whole update, do the consistency checks for updates that might
-    // not have come in order.
-    
-    // First, make sure there's a host description.
-    if (host_description == NULL) {
-        ERROR("srp_relay: SRP update does not include a host description.");
-        goto out;
-    }
-
-    // Make sure that each service add references a service instance that's in the same update.
-    for (sp = services; sp; sp = sp->next) {
-        for (sip = service_instances; sip; sip = sip->next) {
-            if (dns_names_equal(sip->name, sp->rr->data.ptr.name)) {
-                // Note that we have already verified that there is only one service instance
-                // with this name, so this could only ever happen once in this loop even without
-                // the break statement.
-                sp->instance = sip;
-                sip->num_instances++;
-                break;
+            // Move on to the next instance to update.
+            update->instance = update->instance->next;
+            // If there are no more instances to update, then do the host add.
+            if (update->instance == NULL) {
+                // STATE CHANGE: refresh_existing_instance -> create_nonexistent_host
+                update->state = create_nonexistent_host;
+            } else {
+                // Not done yet, do the next one.
+                // STATE CHANGE: refresh_existing_instance -> create_nonexistent_instance
+                update->state = create_nonexistent_instance;
             }
+            break;
         }
-        // If this service doesn't point to a service instance that's in the update, then the
-        // update fails validation.
-        if (sip == NULL) {
-            ERROR("srp_relay: service %s points to an instance that's not included: %s",
-                  dns_name_print(sp->name, namebuf, sizeof namebuf),
-                  dns_name_print(sip->name, namebuf1, sizeof namebuf1));
-            goto out;
+
+        case create_nonexistent_host: {
+            DM_NAME_GEN_SRP(update->host->name, name_buf);
+            INFO("SRP Update for new host " PRI_DM_NAME_SRP " was successful.",
+                 DM_NAME_PARAM_SRP(update->host->name, name_buf));
+            update_finished(update, dns_rcode_noerror);
+            return;
         }
-    }
 
-    for (sip = service_instances; sip; sip = sip->next) {
-        // For each service instance, make sure that at least one service references it
-        if (sip->num_instances == 0) {
-            ERROR("srp_relay: service instance update for %s is not referenced by a service update.",
-                  dns_name_print(sip->name, namebuf, sizeof namebuf));
-            goto out;
+        case refresh_existing_host: {
+            DM_NAME_GEN_SRP(update->host->name, name_buf);
+            INFO("SRP Update for existing host " PRI_DM_NAME_SRP " was successful.",
+                 DM_NAME_PARAM_SRP(update->host->name, name_buf));
+            update_finished(update, dns_rcode_noerror);
+            return;
         }
 
-        // For each service instance, make sure that it references the host description
-        if (dns_names_equal(host_description->name, sip->srv->data.srv.name)) {
-            sip->host_description = host_description;
-            host_description->num_instances++;
+        case delete_failed_instance: {
+            DM_NAME_GEN_SRP(update->host->name, name_buf);
+            INFO("Instance deletes for host " PRI_DM_NAME_SRP " succeeded",
+                 DM_NAME_PARAM_SRP(update->host->name, name_buf));
+            update_finished(update, update->fail_rcode);
+            return;
         }
-    }
+        break;
+
+        // We will get NXRRSET if we were adding an existing host with the prerequisite that a KEY
+        // RR exist on the name with the specified value.  Some other KEY RR may exist, or there may
+        // be no such RRSET; we can't tell from this response.
+    case dns_rcode_nxrrset:
+        switch(update->state) {
+        case connect_to_server:           // Can't get a response while connecting.
+        case create_nonexistent:          // Can't get nxdomain when creating.
+        case create_nonexistent_instance: // same
+        case create_nonexistent_host:     // same
+        case delete_failed_instance:      // There are no prerequisites for deleting failed instances, so
+                                          // in principle this should never fail.
+            goto invalid;
+
+        case refresh_existing:
+            // If we get an NXDOMAIN when doing a refresh, it means either that there is a conflict,
+            // or that one of the instances we are refreshing doesn't exist.   So now do the instances
+            // one at a time.
+
+            // STATE CHANGE: refresh_existing -> create_nonexistent
+            update->state = create_nonexistent;
+            update->instance = update->instances;
+            break;
+
+        case refresh_existing_instance:
+            // In this case, we tried to update an existing instance and found that the prerequisite
+            // didn't match.   This means either that there is a conflict, or else that the instance
+            // expired and was deleted between the time that we attempted to create it and the time
+            // we attempted to update it.  We could account for this with an create_nonexistent_instance_again
+            // state, but currently do not.
+
+            // If we have added some instances, we need to delete them before we send the fail response.
+            if (update->added_instances != NULL) {
+                // STATE CHANGE: refresh_existing_instance -> delete_failed_instance
+                update->state = delete_failed_instance;
+            delete_added_instances:
+                update->instance = update->added_instances;
+                update->fail_rcode = dns_rcode_get(wire);
+                break;
+            } else {
+                update_finished(update, dns_rcode_get(wire));
+                return;
+            }
 
-    // Make sure that at least one service instance references the host description
-    if (host_description->num_instances == 0) {
-        ERROR("srp_relay: host description %s is not referenced by any service instances.",
-              dns_name_print(host_description->name, namebuf, sizeof namebuf));
-        goto out;
-    }
-
-    // Make sure the host description has at least one address record.
-    if (host_description->a == NULL && host_description->aaaa == NULL) {
-        ERROR("srp_relay: host description %s doesn't contain any IP addresses.",
-              dns_name_print(host_description->name, namebuf, sizeof namebuf));
-        goto out;
-    }
-    // And make sure it has a key record
-    if (host_description->key == NULL) {
-        ERROR("srp_relay: host description %s doesn't contain a key.",
-              dns_name_print(host_description->name, namebuf, sizeof namebuf));
-        goto out;
-    }
-
-    // The signature should be the last thing in the additional section.   Even if the signature
-    // is valid, if it's not at the end we reject it.   Note that we are just checking for SIG(0)
-    // so if we don't find what we're looking for, we forward it to the DNS auth server which
-    // will either accept or reject it.
-    if (message->arcount < 1) {
-        ERROR("srp_relay: signature not present");
-        goto out;
-    }
-    signature = &message->additional[message->arcount -1];
-    if (signature->type != dns_rrtype_sig) {
-        ERROR("srp_relay: signature is not at the end or is not present");
-        goto out;
-    }
-
-    // Make sure that the signer name is the hostname.   If it's not, it could be a legitimate
-    // update with a different key, but it's not an SRP update, so we pass it on.
-    if (!dns_names_equal(signature->data.sig.signer, host_description->name)) {
-        ERROR("srp_relay: signer %s doesn't match host %s", 
-              dns_name_print(signature->data.sig.signer, namebuf, sizeof namebuf),
-              dns_name_print(host_description->name, namebuf1, sizeof namebuf1));
-        goto out;
-    }
-    
-    // Make sure we're in the time limit for the signature.   Zeroes for the inception and expiry times
-    // mean the host that send this doesn't have a working clock.   One being zero and the other not isn't
-    // valid unless it's 1970.
-    if (signature->data.sig.inception != 0 || signature->data.sig.expiry != 0) {
-        gettimeofday(&now, NULL);
-        // The sender does the bracketing, so we can just do a simple comparison.
-        if (now.tv_sec > signature->data.sig.expiry || now.tv_sec < signature->data.sig.inception) {
-            ERROR("signature is not timely: %lu < %lu < %lu does not hold",
-                  (unsigned long)signature->data.sig.inception, (unsigned long)now.tv_sec,
-                  (unsigned long)signature->data.sig.expiry);
-            goto badsig;
+        case refresh_existing_host:
+            // In this case, there is a conflicting host entry.  This means that all the service
+            // instances that exist and are owned by the key we are using are bogus, whether we
+            // created them or they were already there.  However, it is not our mission to remove
+            // pre-existing messes here, so we'll just delete the ones we added.
+            if (update->added_instances != NULL) {
+                // STATE CHANGE: refresh_existing_host -> delete_failed_instance
+                update->state = delete_failed_instance;
+                goto delete_added_instances;
+            }
+            update_finished(update, dns_rcode_get(wire));
+            return;
         }
+        break;
+        // We get YXDOMAIN if we specify a prerequisite that the name not exist, but it does exist.
+    case dns_rcode_yxdomain:
+       switch(update->state) {
+        case connect_to_server:         // We can't get a response while connecting.
+        case refresh_existing:          // If we are refreshing, our prerequisites are all looking for
+        case refresh_existing_instance: // a specific RR with a specific value, so we can never get
+        case refresh_existing_host:     // YXDOMAIN.
+        case delete_failed_instance:    // And if we are deleting failed instances, we should never get an error.
+            goto invalid;
+
+        case create_nonexistent:
+            // If we get an NXDOMAIN when doing a refresh, it means either that there is a conflict,
+            // or that one of the instances we are refreshing doesn't exist.   So now do the instances
+            // one at a time.
+
+            // STATE CHANGE: create_nonexistent -> create_nonexistent_instance
+            update->state = create_nonexistent_instance;
+            update->instance = update->instances;
+            break;
+
+        case create_nonexistent_instance:
+            // STATE CHANGE: create_nonexistent_instance -> refresh_existing_instance
+            update->state = refresh_existing_instance;
+            break;
+
+        case create_nonexistent_host:
+            // STATE CHANGE: create_nonexistent_host -> refresh_existing_host
+            update->state = refresh_existing_host;
+            break;
+       }
+       break;
+
+    case dns_rcode_notauth:
+        ERROR("DNS Authoritative server does not think we are authorized to update it, please fix.");
+        update_finished(update, dns_rcode_servfail);
+        return;
+
+        // We may want to return different error codes or do more informative logging for some of these:
+    case dns_rcode_formerr:
+    case dns_rcode_servfail:
+    case dns_rcode_notimp:
+    case dns_rcode_refused:
+    case dns_rcode_yxrrset:
+    case dns_rcode_notzone:
+    case dns_rcode_dsotypeni:
+    default:
+        goto invalid;
     }
 
-    // Now that we have the key, we can validate the signature.   If the signature doesn't validate,
-    // there is no need to pass the message on.
-    if (!srp_sig0_verify(message->wire, host_description->key, signature)) {
-        ERROR("signature is not valid");
-        goto badsig;
+    if (update->state != initial_state) {
+        INFO("Update state changed from " PUB_S_SRP " to " PUB_S_SRP, update_state_name(initial_state),
+             update_state_name(update->state));
+    }
+    if (update->instance != initial_instance) {
+        DM_NAME_GEN_SRP(initial_instance->name, initial_name_buf);
+        DM_NAME_GEN_SRP(update->instance->name, updated_name_buf);
+        INFO("Update instance changed from " PRI_DM_NAME_SRP " to " PRI_DM_NAME_SRP,
+             DM_NAME_PARAM_SRP(initial_instance->name, initial_name_buf),
+             DM_NAME_PARAM_SRP(update->instance->name, updated_name_buf));
     }
+    if (construct_update(update)) {
+        update_send(update);
+    } else {
+        ERROR("Failed to construct update");
+        update_finished(update, dns_rcode_servfail);
+    }
+     return;
+}
 
-badsig:
-    // True means we consumed it, not that it was valid.
-    ret = true;
+bool
+srp_update_start(comm_t *connection, dns_message_t *parsed_message, dns_host_description_t *host,
+                 service_instance_t *instance, service_t *service, dns_name_t *update_zone)
+{
+    update_t *update;
 
-out:
-    // free everything we allocated but (it turns out) aren't going to use
-    for (dp = deletes; dp; ) {
-        delete_t *next = dp->next;
-        free(dp);
-        dp = next;
-    }
-    for (sip = service_instances; sip; ) {
-        service_instance_t *next = sip->next;
-        free(sip);
-        sip = next;
+    // Allocate the data structure
+    update = calloc(1, sizeof *update);
+    if (update == NULL) {
+        ERROR("start_dns_update: unable to allocate update structure!");
+        return false;
     }
-    for (sp = services; sp; ) {
-        service_t *next = sp->next;
-        free(sp);
-        sp = next;
+    // Allocate the buffer in which updates will be constructed.
+    update->update = calloc(1, DNS_MAX_UDP_PAYLOAD);
+    if (update->update == NULL) {
+        ERROR("start_dns_update: unable to allocate update message buffer.");
+        return false;
     }
-    if (host_description != NULL) {
-        free(host_description);
+    update->update_max = DNS_DATA_SIZE;
+
+    // Retain the stuff we're supposed to send.
+    update->host = host;
+    update->instances = instance;
+    update->services = service;
+    update->parsed_message = parsed_message;
+    update->message = connection->message;
+    update->state = connect_to_server;
+    update->zone_name = update_zone;
+    update->client = connection;
+
+    // Start the connection to the server
+    update->server = connect_to_host(&dns_server, false, update_reply_callback,
+                                     update_connect_callback, update_disconnect_callback, update);
+    if (update->server == NULL) {
+        free(update);
+        return false;
     }
-    return ret;
+    return true;
 }
 
-void
-dns_evaluate(comm_t *comm)
+static bool
+key_handler(void *context, const char *filename, char **hunks, int num_hunks, int lineno)
 {
-    dns_message_t *message;
+    hmac_key_t *key = context;
+    long val;
+    char *endptr;
+    size_t len;
+    uint8_t keybuf[SRP_SHA256_DIGEST_SIZE];
+    int error;
 
-    // Drop incoming responses--we're a server, so we only accept queries.
-    if (dns_qr_get(&comm->message->wire) == dns_qr_response) {
-        return;
+    // Validate the constant-size stuff first.
+    if (strcasecmp(hunks[1], "in")) {
+        ERROR("Expecting tsig key class IN, got %s.", hunks[1]);
+        return false;
     }
 
-    // Forward incoming messages that are queries but not updates.
-    // XXX do this later--for now we operate only as a translator, not a proxy.
-    if (dns_opcode_get(&comm->message->wire) != dns_opcode_update) {
-        return;
+    if (strcasecmp(hunks[2], "key")) {
+        ERROR("expecting tsig key type KEY, got %s", hunks[2]);
+        return false;
     }
-    
-    // Parse the UPDATE message.
-    if (!dns_wire_parse(&message, &comm->message->wire, comm->message->length)) {
-        ERROR("dns_wire_parse failed.");
-        return;
+
+    // There's not much meaning to be extracted from the flags.
+    val = strtol(hunks[3], &endptr, 10);
+    if (*endptr != 0 || endptr == hunks[3]) {
+        ERROR("Invalid key flags: %s", hunks[3]);
+        return false;
+    }
+
+    // The protocol number as produced by BIND will always be 3, meaning DNSSEC, but of
+    // course we aren't using this key for DNSSEC, so it's not clear that we should take
+    // this seriously; hence we just check to see that it's a number.
+    val = strtol(hunks[4], &endptr, 10);
+    if (*endptr != 0 || endptr == hunks[4]) {
+        ERROR("Invalid protocol number: %s", hunks[4]);
+        return false;
+    }
+
+    // The key algorithm should be HMAC-SHA253.  BIND uses 163, but this is not registered
+    // with IANA.   So again, we don't actually require this, but we do validate it so that
+    // if someone generated the wrong key type, they'll get a message.
+    val = strtol(hunks[5], &endptr, 10);
+    if (*endptr != 0 || endptr == hunks[5]) {
+        ERROR("Invalid protocol number: %s", hunks[5]);
+        return false;
     }
-    
-    // We need the wire message to validate the signature...
-    message->wire = &comm->message->wire;
-    if (!srp_relay(comm, message)) {
-        // The message wasn't invalid, but wasn't an SRP message.
-        // dns_forward(comm)
+    if (val != 163) {
+        INFO("Warning: Protocol number for HMAC-SHA256 TSIG KEY is not 163, but %ld", val);
     }
-    // But we don't save it.
-    message->wire = NULL;
 
-    //dns_message_free(message);
+    key->name = dns_pres_name_parse(hunks[0]);
+    if (key->name == NULL) {
+        ERROR("Invalid key name: %s", hunks[0]);
+        return false;
+    }
+
+    error = srp_base64_parse(hunks[6], &len, keybuf, sizeof keybuf);
+    if (error != 0) {
+        ERROR("Invalid HMAC-SHA256 key: %s", strerror(errno));
+        goto fail;
+    }
+
+    // The key should be 32 bytes (256 bits).
+    if (len == 0) {
+        ERROR("Invalid (null) secret for key %s", hunks[0]);
+        goto fail;
+    }
+    key->secret = malloc(len);
+    if (key->secret == NULL) {
+        ERROR("Unable to allocate space for secret for key %s", hunks[0]);
+    fail:
+        dns_name_free(key->name);
+        key->name = NULL;
+        return false;
+    }
+    memcpy(key->secret, keybuf, len);
+    key->length = len;
+    key->algorithm = SRP_HMAC_TYPE_SHA256;
+    return true;
 }
 
-void dns_input(comm_t *comm)
+config_file_verb_t key_verbs[] = {
+    { NULL, 7, 7, key_handler }
+};
+#define NUMKEYVERBS ((sizeof key_verbs) / sizeof (config_file_verb_t))
+
+hmac_key_t *
+parse_hmac_key_file(const char *filename)
 {
-    dns_evaluate(comm);
-    message_free(comm->message);
-    comm->message = NULL;
+    hmac_key_t *key = calloc(1, sizeof *key);
+    if (key == NULL) {
+        ERROR("No memory for tsig key structure.");
+        return NULL;
+    }
+    if (!config_parse(key, filename, key_verbs, NUMKEYVERBS)) {
+        ERROR("Failed to parse key file.");
+        free(key);
+        return NULL;
+    }
+    return key;
 }
 
 int
@@ -493,30 +1010,31 @@ main(int argc, char **argv)
     udp_validator_t *NULLABLE *NONNULL up = &udp_validators;
     subnet_t *NULLABLE *NONNULL nt = &tcp_validators;
     subnet_t *NULLABLE *NONNULL sp;
-    addr_t server, pref;
+    addr_t pref;
     uint16_t port;
     socklen_t len, prefalen;
     char *s, *p;
     int width;
-    uint16_t listen_port;
-
-    listen_port = htons(53);
+    bool got_server = false;
 
     // Read the configuration from the command line.
     for (i = 1; i < argc; i++) {
         if (!strcmp(argv[i], "-s")) {
-            if (i++ == argc) {
-                ERROR("-s is missing server IP address.");
+            if (got_server) {
+                ERROR("only one authoritative server can be specified.");
+                return usage(argv[0]);
+            }
+            if (++i == argc) {
+                ERROR("-s is missing dns server IP address.");
                 return usage(argv[0]);
             }
-            len = getipaddr(&server, argv[i]);
+            len = getipaddr(&dns_server, argv[i]);
             if (!len) {
                 ERROR("Invalid IP address: %s.", argv[i]);
                 return usage(argv[0]);
             }
-            server.sa.sa_len = len;
-            if (i++ == argc) {
-                ERROR("-s is missing server port.");
+            if (++i == argc) {
+                ERROR("-s is missing dns server port.");
                 return usage(argv[0]);
             }
             port = strtol(argv[i], &s, 10);
@@ -524,15 +1042,25 @@ main(int argc, char **argv)
                 ERROR("Invalid port number: %s", argv[i]);
                 return usage(argv[0]);
             }
-            if (server.sa.sa_family == AF_INET) {
-                server.sin.sin_port = htons(port);
+            if (dns_server.sa.sa_family == AF_INET) {
+                dns_server.sin.sin_port = htons(port);
             } else {
-                server.sin6.sin6_port = htons(port);
+                dns_server.sin6.sin6_port = htons(port);
+            }
+            got_server = true;
+        } else if (!strcmp(argv[i], "-k")) {
+            if (++i == argc) {
+                ERROR("-k is missing key file name.");
+                return usage(argv[0]);
+            }
+            key = parse_hmac_key_file(argv[i]);
+            // Someething should already have printed the error message.
+            if (key == NULL) {
+                return 1;
             }
-            i += 2;
         } else if (!strcmp(argv[i], "-t") || !strcmp(argv[i], "-u")) {
             if (!strcmp(argv[i], "-u")) {
-                if (i++ == argc) {
+                if (++i == argc) {
                     ERROR("-u is missing interface name.");
                     return usage(argv[0]);
                 }
@@ -551,7 +1079,7 @@ main(int argc, char **argv)
                 sp = nt;
             }
 
-            if (i++ == argc) {
+            if (++i == argc) {
                 ERROR("%s requires at least one prefix.", argv[i - 1]);
                 return usage(argv[0]);
             }
@@ -602,29 +1130,19 @@ main(int argc, char **argv)
             }
         }
     }
-
-    if (!ioloop_init()) {
+    if (!got_server) {
+        ERROR("No authoritative DNS server specified to take updates!");
         return 1;
     }
 
-    // Set up listeners
-    if (!setup_listener_socket(AF_INET, IPPROTO_UDP, listen_port, "UDPv4 listener", dns_input, 0, 0)) {
-        ERROR("UDPv4 listener: fail.");
-        return 1;
-    }
-    if (!setup_listener_socket(AF_INET6, IPPROTO_UDP, listen_port, "UDPv6 listener", dns_input, 0, 0)) {
-        ERROR("UDPv6 listener: fail.");
-        return 1;
-    }
-    if (!setup_listener_socket(AF_INET, IPPROTO_TCP, listen_port, "TCPv4 listener", dns_input, 0, 0)) {
-        ERROR("TCPv4 listener: fail.");
+    if (!ioloop_init()) {
         return 1;
     }
-    if (!setup_listener_socket(AF_INET6, IPPROTO_TCP, listen_port, "TCPv6 listener", dns_input, 0, 0)) {
-        ERROR("TCPv4 listener: fail.");
+
+    if (!srp_proxy_listen()) {
         return 1;
     }
-    
+
     do {
         int something = 0;
         something = ioloop_events(0);
diff --git a/ServiceRegistration/srp-gw.h b/ServiceRegistration/srp-gw.h
new file mode 100644 (file)
index 0000000..5b6f950
--- /dev/null
@@ -0,0 +1,123 @@
+/* srp-gw.c
+ *
+ * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Structure definitions for the Service Registration Protocol gateway.
+ */
+
+typedef struct subnet subnet_t;
+struct subnet {
+    subnet_t *NULLABLE next;
+    uint8_t preflen;
+    uint8_t family;
+    char bytes[8];
+};
+
+typedef struct udp_validator udp_validator_t;
+struct udp_validator {
+    udp_validator_t *NULLABLE next;
+    char *NONNULL ifname;
+    int ifindex;
+    subnet_t *NONNULL subnets;
+};
+
+typedef struct delete delete_t;
+struct delete {
+    delete_t *NULLABLE next;
+    dns_name_t *NONNULL name;
+    dns_name_t *NONNULL zone;
+    bool consumed;
+};
+
+typedef struct host_addr host_addr_t;
+struct host_addr {
+    host_addr_t *NULLABLE next;
+    dns_rr_t rr;
+};
+typedef struct dns_host_description dns_host_description_t;
+struct dns_host_description {
+    dns_name_t *NONNULL name;
+    host_addr_t *NULLABLE addrs;
+    dns_rr_t *NULLABLE key;
+    delete_t *NULLABLE delete;
+    int num_instances;
+};
+
+typedef struct service service_t;
+struct service {
+    service_t *NULLABLE next;
+    dns_rr_t *NONNULL rr; // The service name is rr->name.
+    dns_name_t *NONNULL zone;
+};
+
+typedef struct service_instance service_instance_t;
+struct service_instance {
+    service_instance_t *NULLABLE next;
+    dns_host_description_t *NULLABLE host;
+    dns_name_t *NONNULL name;
+    delete_t *NULLABLE delete;
+    service_t *NONNULL service;
+    int num_instances;
+    dns_rr_t *NULLABLE srv, *NULLABLE txt;
+};
+
+// The update_t structure is used to maintain the ongoing state of a particular DNS Update.
+
+typedef enum update_state update_state_t;
+enum update_state {
+    connect_to_server,              // Establish a connection with the auth server.
+    create_nonexistent,             // Update service instance assuming it's not already there (case 1).
+    refresh_existing,               // Update service instance assuming it's already there and the same (case 2).
+    create_nonexistent_instance,    // Update service instance assuming it's not there
+    refresh_existing_instance,      // Update host assuming it's there and the same
+    create_nonexistent_host,        // Update a host that's not present (and also the services)
+    refresh_existing_host,          // Update a host that's present (and also the services)
+    delete_failed_instance,         // The update failed, so delete service instances that were successfully added.
+};
+
+typedef enum update_event update_event_t;
+enum update_event {
+    update_event_connected,
+    update_event_disconnected,
+    update_event_response_received
+};
+
+typedef struct update update_t;
+struct update {
+    comm_t *NONNULL server;                       // Connection to authoritative server
+    comm_t *NONNULL client;                       // Connection to SRP client (which might just be a UDP socket).
+    update_state_t state;
+    dns_host_description_t *NONNULL host;
+    service_instance_t *NONNULL instances;
+    service_instance_t *NULLABLE instance;        // If we are updating instances one at a time.
+    service_instance_t *NULLABLE added_instances; // Instances we have successfully added.
+    service_t *NONNULL services;
+    dns_name_t *NULLABLE zone_name;               // If NULL, we are processing an update for services.arpa.
+    message_t *NONNULL message;
+    dns_message_t *NONNULL parsed_message;
+    dns_wire_t *NONNULL update;                   // The current update...
+    size_t update_length;
+    size_t update_max;
+    uint8_t fail_rcode;                           // rcode to return after deleting added service instances.
+};
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/srp-ioloop.c b/ServiceRegistration/srp-ioloop.c
new file mode 100644 (file)
index 0000000..70c2ce4
--- /dev/null
@@ -0,0 +1,782 @@
+/* srp-ioloop.c
+ *
+ * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * srp host API implementation for Posix using ioloop primitives.
+ */
+
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <dns_sd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "srp.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "srp-crypto.h"
+#include "ioloop.h"
+
+#include "cti-services.h"
+#if !defined(OPEN_SOURCE) && defined(TARGET_OS_TV)
+#define LIVE_TRANSACTION_CLEANUP 1
+#include "advertising_proxy_services.h"
+#endif
+
+static int lease_time = 0;
+static bool random_leases = false;
+static bool delete_registrations = false;
+static bool use_thread_services = false;
+#ifdef LIVE_TRANSACTION_CLEANUP
+static bool live_transaction_cleanup = false;
+static int live_transaction_cleanup_time;
+static wakeup_t *live_transaction_wakeup;
+static advertising_proxy_conn_ref live_transaction_cref;
+#endif
+static int num_clients = 1;
+static int bogusify_signatures = false;
+
+const uint64_t thread_enterprise_number = 52627;
+
+cti_connection_t thread_service_context;
+
+#define SRP_IO_CONTEXT_MAGIC 0xFEEDFACEFADEBEEFULL  // BEES!   Everybody gets BEES!
+typedef struct io_context {
+    uint64_t magic_cookie1;
+    wakeup_t *wakeup;
+    void *NONNULL srp_context;
+    comm_t *NULLABLE connection;
+    srp_wakeup_callback_t wakeup_callback;
+    srp_datagram_callback_t datagram_callback;
+    uint64_t magic_cookie2;
+} io_context_t;
+wakeup_t *remove_wakeup;
+
+typedef struct srp_client {
+    DNSServiceRef sdref;
+    int index;
+    wakeup_t *wakeup;
+    char *name;
+} srp_client_t;
+
+static int
+validate_io_context(io_context_t **dest, void *src)
+{
+    io_context_t *context = src;
+    if (context->magic_cookie1 == SRP_IO_CONTEXT_MAGIC &&
+        context->magic_cookie2 == SRP_IO_CONTEXT_MAGIC)
+   {
+        *dest = context;
+        return kDNSServiceErr_NoError;
+    }
+    return kDNSServiceErr_BadState;
+}
+
+static void
+datagram_callback(comm_t *connection, message_t *message, void *context)
+{
+    (void)connection;
+    io_context_t *io_context = context;
+    io_context->datagram_callback(io_context->srp_context,
+                                  &message->wire, message->length);
+}
+
+static void
+wakeup_callback(void *context)
+{
+    io_context_t *io_context;
+    if (validate_io_context(&io_context, context) == kDNSServiceErr_NoError) {
+        INFO("wakeup on context %p srp_context %p", io_context, io_context->srp_context);
+        io_context->wakeup_callback(io_context->srp_context);
+    } else {
+        INFO("wakeup with invalid context: %p", context);
+    }
+}
+
+int
+srp_deactivate_udp_context(void *host_context, void *in_context)
+{
+    io_context_t *io_context;
+    int err;
+    (void)host_context;
+
+    err = validate_io_context(&io_context, in_context);
+    if (err == kDNSServiceErr_NoError) {
+        if (io_context->connection) {
+            ioloop_comm_release(io_context->connection);
+        }
+        free(io_context);
+    }
+    return err;
+}
+
+int
+srp_disconnect_udp(void *context)
+{
+    io_context_t *io_context;
+    int err;
+
+    err = validate_io_context(&io_context, context);
+    if (err == kDNSServiceErr_NoError) {
+        if (io_context->connection) {
+            ioloop_comm_cancel(io_context->connection);
+            ioloop_comm_release(io_context->connection);
+            io_context->connection = NULL;
+        }
+    }
+    return err;
+}
+
+int
+srp_connect_udp(void *context, const uint8_t *port, uint16_t address_type, const uint8_t *address, uint16_t addrlen)
+{
+    io_context_t *io_context;
+    addr_t remote;
+    int err;
+
+    err = validate_io_context(&io_context, context);
+    if (err == kDNSServiceErr_NoError) {
+        if (io_context->connection) {
+            ERROR("srp_connect_udp called with non-null I/O context.");
+            return kDNSServiceErr_Invalid;
+        }
+
+        if (address_type == dns_rrtype_a) {
+            if (addrlen != 4) {
+                return kDNSServiceErr_Invalid;
+            }
+            remote.sa.sa_family = AF_INET;
+            memcpy(&remote.sin.sin_addr, address, addrlen);
+#ifndef NOT_HAVE_SA_LEN
+            remote.sa.sa_len = sizeof remote.sin;
+#endif
+            memcpy(&remote.sin.sin_port, port, 2);
+        } else {
+            if (addrlen != 16) {
+                return kDNSServiceErr_Invalid;
+            }
+            remote.sa.sa_family = AF_INET6;
+            memcpy(&remote.sin6.sin6_addr, address, addrlen);
+#ifndef NOT_HAVE_SA_LEN
+            remote.sa.sa_len = sizeof remote.sin;
+#endif
+            memcpy(&remote.sin6.sin6_port, port, 2);
+        }
+
+        io_context->connection = ioloop_connection_create(&remote, false, false, datagram_callback,
+                                                          NULL, NULL, NULL, io_context);
+        if (io_context->connection == NULL) {
+            return kDNSServiceErr_NoMemory;
+        }
+    }
+    return err;
+}
+
+int
+srp_make_udp_context(void *host_context, void **p_context, srp_datagram_callback_t callback, void *context)
+{
+    (void)host_context;
+
+    io_context_t *io_context = calloc(1, sizeof *io_context);
+    if (io_context == NULL) {
+        return kDNSServiceErr_NoMemory;
+    }
+    io_context->magic_cookie1 = io_context->magic_cookie2 = SRP_IO_CONTEXT_MAGIC;
+    io_context->datagram_callback = callback;
+    io_context->srp_context = context;
+
+    io_context->wakeup = ioloop_wakeup_create();
+    if (io_context->wakeup == NULL) {
+        free(io_context);
+        return kDNSServiceErr_NoMemory;
+    }
+
+    *p_context = io_context;
+    return kDNSServiceErr_NoError;
+}
+
+int
+srp_set_wakeup(void *host_context, void *context, int milliseconds, srp_wakeup_callback_t callback)
+{
+    int err;
+    io_context_t *io_context;
+    (void)host_context;
+
+    err = validate_io_context(&io_context, context);
+    if (err == kDNSServiceErr_NoError) {
+        io_context->wakeup_callback = callback;
+        INFO("srp_set_wakeup on context %p, srp_context %p", io_context, io_context->srp_context);
+        ioloop_add_wake_event(io_context->wakeup, io_context, wakeup_callback, NULL, milliseconds);
+    }
+    return err;
+}
+
+int
+srp_cancel_wakeup(void *host_context, void *context)
+{
+    int err;
+    io_context_t *io_context;
+    (void)host_context;
+
+    err = validate_io_context(&io_context, context);
+    if (err == kDNSServiceErr_NoError) {
+        ioloop_cancel_wake_event(io_context->wakeup);
+    }
+    return err;
+}
+
+int
+srp_send_datagram(void *host_context, void *context, void *message, size_t message_length)
+{
+    int err;
+    struct iovec iov;
+    io_context_t *io_context;
+    (void)host_context;
+
+    memset(&iov, 0, sizeof iov);
+    iov.iov_base = message;
+    iov.iov_len = message_length;
+
+    if (bogusify_signatures) {
+        ((uint8_t *)message)[message_length - 10] = ~(((uint8_t *)message)[message_length - 10]);
+    }
+
+    err = validate_io_context(&io_context, context);
+    if (err == kDNSServiceErr_NoError) {
+        if (!ioloop_send_message(io_context->connection, message, &iov, 1)) {
+            return kDNSServiceErr_Unknown;
+        }
+    }
+    return err;
+}
+
+static bool
+srp_load_file_data(void *host_context, const char *filename, uint8_t *buffer, uint16_t *length, uint16_t buffer_size)
+{
+    off_t flen;
+    ssize_t len;
+    int file;
+    (void)host_context;
+
+    file = open(filename, O_RDONLY);
+    if (file < 0) {
+        ERROR("srp_load_file_data: %s: open: %s", filename, strerror(errno));
+        return false;
+    }
+
+    // Get the length of the file.
+    flen = lseek(file, 0, SEEK_END);
+    lseek(file, 0, SEEK_SET);
+    if (flen > buffer_size) {
+        ERROR("srp_load_file_data: %s: lseek: %s", filename, strerror(errno));
+        close(file);
+        return false;
+    }
+    len = read(file, buffer, flen);
+    if (len < 0 || len != flen) {
+        if (len < 0) {
+            ERROR("srp_load_file_data: %s: read: %s", filename, strerror(errno));
+        } else {
+            ERROR("srp_load_file_data: %s: short read %d out of %d", filename, (int)len, (int)flen);
+        }
+        close(file);
+        return false;
+    }
+    close(file);
+    *length = (uint16_t)len;
+    return true;
+}
+
+static bool
+srp_store_file_data(void *host_context, const char *filename, uint8_t *buffer, uint16_t length)
+{
+    ssize_t len;
+    int file;
+    (void)host_context;
+    file = open(filename, O_WRONLY | O_CREAT, 0600);
+    if (file < 0) {
+        ERROR("srp_store_file_data: %s: %s", filename, strerror(errno));
+        return false;
+   }
+    len = write(file, buffer, length);
+    if (len < 0 || len != length) {
+        if (len < 0) {
+            ERROR("srp_store_file_data: " PUB_S_SRP ": " PUB_S_SRP, filename, strerror(errno));
+        } else {
+            ERROR("srp_store_file_data: short write %d out of %d on file %s", (int)len, (int)length, filename);
+        }
+        unlink(filename);
+        close(file);
+        return kDNSServiceErr_Unknown;
+    }
+    close(file);
+    return kDNSServiceErr_NoError;
+}
+
+
+bool
+srp_get_last_server(uint16_t *NONNULL rrtype, uint8_t *NONNULL rdata, uint16_t rdlim,
+                    uint8_t *NONNULL port, void *NULLABLE host_context)
+{
+    uint8_t buffer[22];
+    unsigned offset = 0;
+    uint16_t length;
+    uint16_t rdlength;
+
+    if (!srp_load_file_data(host_context, "/var/run/srp-last-server", buffer, &length, sizeof(buffer))) {
+        return false;
+    }
+    if (length < 10) { // rrtype + rdlength + ipv4 address + port
+        ERROR("srp_get_last_server: stored server data is too short: %d", length);
+        return false;
+    }
+    *rrtype = (((uint16_t)buffer[offset]) << 8) | buffer[offset + 1];
+    offset += 2;
+    rdlength = (((uint16_t)buffer[offset]) << 8) | buffer[offset + 1];
+    offset += 2;
+    if ((*rrtype == dns_rrtype_a && rdlength != 4) || (*rrtype == dns_rrtype_aaaa && rdlength != 16)) {
+        ERROR("srp_get_last_server: invalid rdlength %d for %s record",
+              rdlength, *rrtype == dns_rrtype_a ? "A" : "AAAA");
+        return false;
+    }
+    if (length < rdlength + 6) { // rrtype + rdlength + address + port
+        ERROR("srp_get_last_server: stored server data length %d is too short", length);
+        return false;
+    }
+    if (rdlength > rdlim) {
+        ERROR("srp_get_last_server: no space for %s data in provided buffer size %d",
+              *rrtype == dns_rrtype_a ? "A" : "AAAA", rdlim);
+        return false;
+    }
+    memcpy(rdata, &buffer[offset], rdlength);
+    offset += rdlength;
+    memcpy(port, &buffer[offset], 2);
+    return true;
+}
+
+bool
+srp_save_last_server(uint16_t rrtype, uint8_t *NONNULL rdata, uint16_t rdlength,
+                     uint8_t *NONNULL port, void *NULLABLE host_context)
+{
+    dns_towire_state_t towire;
+    uint8_t buffer[24];
+    size_t length;
+    memset(&towire, 0, sizeof(towire));
+    towire.p = buffer;
+    towire.lim = towire.p + sizeof(buffer);
+
+    if (rdlength != 4 && rdlength != 16) {
+        ERROR("srp_save_last_server: invalid IP address length %d", rdlength);
+        return false;
+    }
+    dns_u16_to_wire(&towire, rrtype);
+    dns_u16_to_wire(&towire, rdlength);
+    dns_rdata_raw_data_to_wire(&towire, rdata, rdlength);
+    dns_rdata_raw_data_to_wire(&towire, port, 2);
+
+    if (towire.error) {
+        ERROR("srp_save_last_server: " PUB_S_SRP " at %d (%p:%p:%p) while constructing output buffer",
+              strerror(towire.error), towire.line, towire.p, towire.lim, buffer);
+        return false;
+    }
+
+    length = towire.p - buffer;
+    if (!srp_store_file_data(host_context, "/var/run/srp-last-server", buffer, length)) {
+        return false;
+    }
+    return true;
+}
+
+#ifdef NO_KEYCHAIN
+int
+srp_load_key_data(void *host_context, const char *key_name, uint8_t *buffer, uint16_t *length, uint16_t buffer_size)
+{
+    if (srp_load_file_data(key_name, buffer, length, buffer_size)) {
+        return kDNSServiceErr_NoError;
+    }
+    return kDNSServiceErr_Unknown;
+}
+
+int
+srp_store_key_data(void *host_context, const char *key_name, uint8_t *buffer, uint16_t length)
+{
+    if (!srp_store_file_data(host_context, key_name, buffer, length)) {
+        return kDNSServiceErr_Unknown;
+    return kDNSServiceErr_NoError;
+}
+#endif // NO_KEYCHAIN
+
+static void
+interface_callback(void *context, const char *NONNULL name,
+                   const addr_t *NONNULL address, const addr_t *NONNULL netmask,
+                   uint32_t flags, enum interface_address_change event_type)
+{
+    bool drop = false;
+    uint8_t *rdata;
+    uint16_t rdlen;
+    uint16_t rrtype;
+    cti_service_vec_t *cti_services = context;
+
+    (void)netmask;
+    (void)index;
+    (void)event_type;
+
+    if (address->sa.sa_family == AF_INET) {
+        rrtype = dns_rrtype_a;
+        rdata = (uint8_t *)&address->sin.sin_addr;
+        rdlen = 4;
+
+        // Should use IN_LINKLOCAL and IN_LOOPBACK macros here, but for some reason they are not present on
+        // OpenWRT.
+        if (rdata[0] == 127) {
+            drop = true;
+        } else if (rdata[0] == 169 && rdata[1] == 254) {
+            drop = true;
+        }
+    } else if (address->sa.sa_family == AF_INET6) {
+        rrtype = dns_rrtype_aaaa;
+        rdata = (uint8_t *)&address->sin6.sin6_addr;
+        rdlen = 16;
+        if (IN6_IS_ADDR_LOOPBACK(&address->sin6.sin6_addr)) {
+            drop = true;
+        } else if (IN6_IS_ADDR_LINKLOCAL(&address->sin6.sin6_addr)) {
+            drop = true;
+        }
+    } else {
+        return;
+    }
+    if (drop) {
+        if (address->sa.sa_family == AF_INET) {
+            IPv4_ADDR_GEN_SRP(rdata, ipv4_rdata_buf);
+            DEBUG("interface_callback: ignoring " PUB_S_SRP " " PRI_IPv4_ADDR_SRP, name,
+                  IPv4_ADDR_PARAM_SRP(rdata, ipv4_rdata_buf));
+        } else if (address->sa.sa_family == AF_INET6) {
+            SEGMENTED_IPv6_ADDR_GEN_SRP(rdata, ipv6_rdata_buf);
+            DEBUG("interface_callback: ignoring " PUB_S_SRP " " PRI_SEGMENTED_IPv6_ADDR_SRP, name,
+                  SEGMENTED_IPv6_ADDR_PARAM_SRP(rdata, ipv6_rdata_buf));
+        } else {
+            INFO("interface_callback: ignoring with non-v4/v6 address" PUB_S_SRP, name);
+        }
+        return;
+    }
+
+    if (address->sa.sa_family == AF_INET) {
+        IPv4_ADDR_GEN_SRP(rdata, ipv4_rdata_buf);
+        DEBUG("interface_callback: " PUB_S_SRP " " PRI_IPv4_ADDR_SRP " %x", name,
+              IPv4_ADDR_PARAM_SRP(rdata, ipv4_rdata_buf), flags);
+    } else if (address->sa.sa_family == AF_INET6) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(rdata, ipv6_rdata_buf);
+        DEBUG("interface_callback: " PUB_S_SRP " " PRI_SEGMENTED_IPv6_ADDR_SRP " %x", name,
+              SEGMENTED_IPv6_ADDR_PARAM_SRP(rdata, ipv6_rdata_buf), flags);
+    } else {
+        DEBUG("interface_callback: " PUB_S_SRP "<none IPv4/IPv6 address> %x", name, flags);
+    }
+
+    // This is a workaround for a bug in the utun0 code, where packets sent to the IP address of the local
+    // thread interface are dropped and do not reach the SRP server. To address this, if we find a service
+    // that is on a local IPv6 address, we replace the address with ::1.
+    if (cti_services != NULL && rrtype == dns_rrtype_aaaa) {
+        size_t i;
+        for (i = 0; i < cti_services->num; i++) {
+            cti_service_t *cti_service = cti_services->services[i];
+            // Look for SRP service advertisements only.
+            if (IS_SRP_SERVICE(cti_service)) {
+                // Local IP address?
+                if (!memcmp(cti_service->server, rdata, 16)) {
+                    // ::
+                    memset(cti_service->server, 0, 15);
+                    // 1
+                    cti_service->server[15] = 1;
+                }
+            }
+        }
+    }
+
+    srp_add_interface_address(rrtype, rdata, rdlen);
+}
+
+static void
+remove_callback(void *context)
+{
+    srp_client_t *client = context;
+    srp_deregister(client);
+}
+
+static void
+register_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
+                  const char *name, const char *regtype, const char *domain, void *context)
+{
+    srp_client_t *client = context;
+
+    (void)sdRef;
+    (void)regtype;
+    (void)flags;
+    (void)name;
+    (void)regtype;
+    (void)domain;
+    INFO("Register Reply for %s: %d", client->name, errorCode);
+
+    if (errorCode == kDNSServiceErr_NoError && delete_registrations) {
+        client->wakeup = ioloop_wakeup_create();
+        if (client->wakeup == NULL) {
+            ERROR("Unable to allocate a wakeup for %s.", client->name);
+            exit(1);
+        }
+
+        // Do a remove in five seconds.
+        ioloop_add_wake_event(client->wakeup, client, remove_callback, NULL, 5000);
+    }
+}
+
+static void
+usage(void)
+{
+    fprintf(stderr,
+            "srp-client [--lease-time <seconds>] [--client-count <client count>] [--server <address>%%<port>]\n"
+#ifdef LIVE_TRANSACTION_CLEANUP
+            "           [--live-transaction-cleanup <milliseconds>]\n"
+#endif
+            "           [--random-leases] [--delete-registrations] [--use-thread-services] [--bogusify-signatures]\n");
+    exit(1);
+}
+
+#ifdef LIVE_TRANSACTION_CLEANUP
+static void
+flushed_callback(advertising_proxy_conn_ref cref, xpc_object_t response, advertising_proxy_error_type err)
+{
+    INFO("flushed: cref %p  response %p   err %d.", cref, response, err);
+    if (err != kDNSSDAdvertisingProxyStatus_NoError) {
+        exit(1);
+    }
+    // We don't need to wait around after flushing.
+    exit(0);
+}
+
+static void
+live_transaction_wakeup_callback(void *__unused context)
+{
+    int err = advertising_proxy_flush_entries(&live_transaction_cref, dispatch_get_main_queue(), flushed_callback);
+    if (err != kDNSSDAdvertisingProxyStatus_NoError) {
+        ERROR("live_transaction_wakeup_callback: advertising_proxy_flush_entries failed: %d", err);
+    }
+}
+
+// This is a test to see if, if we have an update pending, we can safely remove all hosts without there being a
+// problem.
+static void
+maybe_schedule_live_transaction_cleanup(void)
+{
+    if (live_transaction_cleanup) {
+        live_transaction_wakeup = ioloop_wakeup_create();
+        if (live_transaction_wakeup == NULL) {
+            ERROR("maybe_schedule_live_transaction_cleanup: unable to allocate wakeup!");
+            exit(1);
+        }
+        // Schedule the wakeup for 100ms in the future.
+        ioloop_add_wake_event(live_transaction_wakeup, NULL, live_transaction_wakeup_callback, NULL, live_transaction_cleanup_time);
+    }
+}
+#endif
+
+static void
+cti_service_list_callback(void *__unused context, cti_service_vec_t *services, cti_status_t status)
+{
+    size_t i;
+
+    if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) {
+        INFO("cti_get_service_list_callback: disconnected");
+        exit(1);
+    }
+
+    srp_start_address_refresh();
+    ioloop_map_interface_addresses(services, interface_callback);
+    for (i = 0; i < services->num; i++) {
+        cti_service_t *cti_service = services->services[i];
+        // Look for SRP service advertisements only.
+        if (IS_SRP_SERVICE(cti_service)) {
+            srp_add_server_address(&cti_service->server[16], dns_rrtype_aaaa, cti_service->server, 16);
+        }
+    }
+    srp_finish_address_refresh();
+    srp_network_state_stable();
+#ifdef LIVE_TRANSACTION_CLEANUP
+    maybe_schedule_live_transaction_cleanup();
+#endif
+}
+
+int
+main(int argc, char **argv)
+{
+
+    uint8_t server_address[16] = { 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1 };
+    uint8_t bogus_address[16] = { 0xfc,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1 };
+        // { 0x26, 0x20, 0x01, 0x49, 0x00, 0x0f, 0x1a, 0x4d, 0x04, 0xff, 0x61, 0x5a, 0xa2, 0x2a, 0xab, 0xe8 };
+    uint8_t port[2];
+    uint16_t iport;
+    int err;
+    DNSServiceRef sdref;
+    int *nump;
+    char *end;
+    (void)argc;
+    (void)argv;
+    int i;
+    bool have_server_address = false;
+
+    ioloop_init();
+
+    for (i = 1; i < argc; i++) {
+        if (!strcmp(argv[i], "--lease-time")) {
+            nump = &lease_time;
+        number:
+            if (i + 1 == argc) {
+                usage();
+            }
+            *nump = (uint32_t)strtoul(argv[i + 1], &end, 10);
+            if (end == argv[i + 1] || end[0] != 0) {
+                usage();
+            }
+            i++;
+        } else if (!strcmp(argv[i], "--client-count")) {
+            nump = &num_clients;
+            goto number;
+        } else if (!strcmp(argv[i], "--server")) {
+            char *percent;
+            int server_port;
+            uint8_t addrbuf[16];
+            uint16_t addrtype = dns_rrtype_aaaa;
+            int addrlen = 16;
+
+            if (i + 1 == argc) {
+                usage();
+           }
+            percent = strchr(argv[i + 1], '%');
+            if (percent == NULL || percent[1] == 0) {
+                usage();
+            }
+            *percent = 0;
+            percent++;
+
+            server_port = (uint32_t)strtoul(percent, &end, 10);
+            if (end == percent || end[0] != 0) {
+                usage();
+            }
+            port[0] = server_port >> 8;
+            port[1] = server_port & 255;
+
+            if (inet_pton(AF_INET6, argv[i + 1], addrbuf) < 1) {
+                if (inet_pton(AF_INET, argv[i + 1], addrbuf) < 1) {
+                    usage();
+                } else {
+                    addrtype = dns_rrtype_a;
+                    addrlen = 4;
+                }
+            }
+            srp_add_server_address(port, addrtype, addrbuf, addrlen);
+            have_server_address = true;
+            i++;
+        } else if (!strcmp(argv[i], "--random-leases")) {
+            random_leases = true;
+        } else if (!strcmp(argv[i], "--delete-registrations")) {
+            delete_registrations = true;
+        } else if (!strcmp(argv[i], "--use-thread-services")) {
+            use_thread_services = true;
+        } else if (!strcmp(argv[i], "--bogusify-signatures")) {
+            bogusify_signatures = true;
+#ifdef LIVE_TRANSACTION_CLEANUP
+        } else if (!strcmp(argv[i], "--live-transaction-cleanup")) {
+            nump = &live_transaction_cleanup_time;
+            live_transaction_cleanup = true;
+            goto number;
+#endif
+        } else {
+            usage();
+        }
+    }
+
+    if (!use_thread_services) {
+        ioloop_map_interface_addresses(NULL, interface_callback);
+    }
+
+    if (!have_server_address && !use_thread_services) {
+        port[0] = 0;
+        port[1] = 53;
+        srp_add_server_address(port, dns_rrtype_aaaa, bogus_address, 16);
+        srp_add_server_address(port, dns_rrtype_aaaa, server_address, 16);
+    }
+
+    for (i = 0; i < num_clients; i++) {
+        srp_client_t *client;
+        char hnbuf[128];
+
+        client = calloc(1, sizeof(*client));
+        if (client == NULL) {
+            ERROR("no memory for client %d", i);
+            exit(1);
+        }
+
+        if (num_clients == 1) {
+            strcpy(hnbuf, "srp-api-test");
+        } else {
+            snprintf(hnbuf, sizeof(hnbuf), "srp-api-test-%d", i);
+        }
+        client->name = strdup(hnbuf);
+        if (client->name == NULL) {
+            ERROR("No memory for client name %s", hnbuf);
+            exit(1);
+        }
+        client->index = i;
+
+        srp_host_init(client);
+        srp_set_hostname(hnbuf, NULL);
+
+        if (random_leases) {
+            int random_lease_time = 30 + srp_random16() % 1800; // random
+            INFO("Client %d, lease time = %d", i, random_lease_time);
+            srp_set_lease_times(random_lease_time, 7 * 24 * 3600); // random host lease, 7 day key lease
+        } else if (lease_time > 0) {
+            srp_set_lease_times(lease_time, 7 * 24 * 3600); // specified host lease, 7 day key lease
+        }
+
+        memcpy(&iport, port, 2);
+        err = DNSServiceRegister(&sdref, 0, 0, hnbuf, "_ipps._tcp",
+                                 NULL, NULL, iport, 0, NULL, register_callback, client);
+        if (err != kDNSServiceErr_NoError) {
+            ERROR("DNSServiceRegister failed: %d", err);
+            exit(1);
+        }
+    }
+
+    if (use_thread_services) {
+        cti_get_service_list(&thread_service_context, NULL, cti_service_list_callback, dispatch_get_main_queue());
+    } else {
+        srp_network_state_stable();
+#ifdef LIVE_TRANSACTION_CLEANUP
+        maybe_schedule_live_transaction_cleanup();
+#endif
+    }
+    ioloop();
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/srp-mdns-proxy.c b/ServiceRegistration/srp-mdns-proxy.c
new file mode 100644 (file)
index 0000000..c3de424
--- /dev/null
@@ -0,0 +1,2511 @@
+/* srp-mdns-proxy.c
+ *
+ * Copyright (c) 2019-2020 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the SRP Advertising Proxy, which is an SRP Server
+ * that offers registered addresses using mDNS.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <dns_sd.h>
+#include <net/if.h>
+#include <sys/resource.h>
+
+#include "srp.h"
+#include "dns-msg.h"
+#include "srp-crypto.h"
+#include "ioloop.h"
+#include "dnssd-proxy.h"
+#include "srp-gw.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "config-parse.h"
+#include "route.h"
+
+#ifdef IOLOOP_MACOS
+#include "xpc_client_advertising_proxy.h"
+#include "advertising_proxy_services.h"
+#include <os/transaction_private.h>
+#endif
+
+// Server internal state
+struct {
+    struct in6_addr addr;
+    int width;
+} *preferred_prefix;
+
+typedef struct srp_xpc_client srp_xpc_client_t;
+struct srp_xpc_client {
+    srp_xpc_client_t *next;
+    xpc_connection_t connection;
+    bool connection_canceled; // If true, we've initiated an xpc_connection_cancel on this client.
+    bool enabler; // If true, this client has asked to enable the proxy.
+};
+
+typedef struct srp_wanted_state srp_wanted_state_t;
+struct srp_wanted_state {
+    int ref_count;
+    os_transaction_t transaction;
+};
+
+adv_host_t *hosts;
+int advertise_interface = kDNSServiceInterfaceIndexAny;
+
+const char local_suffix_ld[] = ".local";
+const char *local_suffix = &local_suffix_ld[1];
+uint32_t max_lease_time = 3600 * 27; // One day plus 20%
+
+static xpc_connection_t xpc_listener;
+
+srp_wanted_state_t *srp_wanted;
+srp_xpc_client_t *srp_xpc_clients;
+
+// Forward references...
+static void try_new_hostname(adv_host_t *host);
+static void register_host_completion(DNSServiceRef sdref, DNSRecordRef rref,
+                                     DNSServiceFlags flags, DNSServiceErrorType error_code, void *context);
+static void register_instance_completion(DNSServiceRef sdref, DNSServiceFlags flags, DNSServiceErrorType error_code,
+                                         const char *name, const char *regtype, const char *domain, void *context);
+static void update_from_host(adv_host_t *host);
+static void start_host_update(adv_host_t *host);
+static void prepare_update(adv_host_t *host);
+static void lease_callback(void *context);
+
+
+static void
+adv_address_finalize(adv_address_t *address)
+{
+    free(address);
+}
+
+static void
+adv_instance_finalize(adv_instance_t *instance)
+{
+    if (instance->txn != NULL) {
+        ioloop_dnssd_txn_release(instance->txn);
+    }
+    if (instance->txt_data != NULL) {
+        free(instance->txt_data);
+    }
+    if (instance->instance_name != NULL) {
+        free(instance->instance_name);
+    }
+    if (instance->service_type != NULL) {
+        free(instance->service_type);
+    }
+    free(instance);
+}
+
+static adv_instance_vec_t *
+adv_instance_vec_create(int size)
+{
+    adv_instance_vec_t *vec;
+
+    vec = calloc(1, sizeof(*vec));
+    if (vec != NULL) {
+        if (size == 0) {
+            size = 1;
+        }
+        vec->vec = calloc(size, sizeof (*(vec->vec)));
+        if (vec->vec == NULL) {
+            free(vec);
+            vec = NULL;
+        } else {
+            RETAIN_HERE(vec);
+        }
+    }
+    return vec;
+}
+
+static adv_instance_vec_t *
+adv_instance_vec_copy(adv_instance_vec_t *vec)
+{
+    adv_instance_vec_t *new_vec;
+    int i;
+
+    new_vec = adv_instance_vec_create(vec->num);
+    if (new_vec != NULL) {
+        RETAIN_HERE(new_vec);
+        for (i = 0; i < vec->num; i++) {
+            if (vec->vec[i] != NULL) {
+                new_vec->vec[i] = vec->vec[i];
+                RETAIN_HERE(new_vec->vec[i]);
+            }
+        }
+        new_vec->num = vec->num;
+    }
+    return new_vec;
+}
+
+static void
+adv_instance_vec_finalize(adv_instance_vec_t *vec)
+{
+    int i;
+
+    for (i = 0; i < vec->num; i++) {
+        if (vec->vec[i] != NULL) {
+            RELEASE_HERE(vec->vec[i], adv_instance_finalize);
+            vec->vec[i] = NULL;
+        }
+    }
+    free(vec->vec);
+    free(vec);
+}
+
+static bool
+same_prefix(void *ai, void *bi, int width)
+{
+    int bite;
+    uint8_t *a = ai, *b = bi;
+    static int masks[] = {0xff, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe};
+    static int mask;
+    for (bite = 0; bite * 8 < width; bite++) {
+        if (a[bite] != b[bite]) {
+            return false;
+        }
+    }
+    if ((width % 8) == 0) {
+        return true;
+    }
+    mask = masks[width % 8];
+    if ((a[bite] & mask) == (b[bite] & mask)) {
+        return true;
+    }
+    return false;
+}
+
+// We call advertise_finished when a client request has finished, successfully or otherwise.
+static void
+advertise_finished(comm_t *connection, message_t *message, int rcode, client_update_t *client)
+{
+    struct iovec iov;
+    dns_wire_t response;
+    INFO("advertise_finished: rcode = " PUB_S_SRP, dns_rcode_name(rcode));
+
+    memset(&response, 0, DNS_HEADER_SIZE);
+    response.id = message->wire.id;
+    response.bitfield = message->wire.bitfield;
+    dns_rcode_set(&response, rcode);
+    dns_qr_set(&response, dns_qr_response);
+
+    iov.iov_base = &response;
+    // If this was a successful update, send back the lease time, which will either
+    // be what the client asked for, or a shorter lease, depending on what limit has
+    // been set.
+    if (client != NULL) {
+        dns_towire_state_t towire;
+        memset(&towire, 0, sizeof towire);
+        towire.p = &response.data[0];               // We start storing RR data here.
+        towire.lim = &response.data[DNS_DATA_SIZE]; // This is the limit to how much we can store.
+        towire.message = &response;
+        response.qdcount = 0;
+        response.ancount = 0;
+        response.nscount = 0;
+        response.arcount = htons(1);
+        dns_edns0_header_to_wire(&towire, DNS_MAX_UDP_PAYLOAD, 0, 0, 1);
+        dns_rdlength_begin(&towire);
+        dns_u16_to_wire(&towire, dns_opt_update_lease);  // OPTION-CODE
+        dns_edns0_option_begin(&towire);                 // OPTION-LENGTH
+        dns_u32_to_wire(&towire, client->host_lease);    // LEASE (e.g. 1 hour)
+        dns_u32_to_wire(&towire, client->key_lease);     // KEY-LEASE (7 days)
+        dns_edns0_option_end(&towire);                   // Now we know OPTION-LENGTH
+        dns_rdlength_end(&towire);
+        // It should not be possible for this to happen; if it does, the client
+        // might not renew its lease in a timely manner.
+        if (towire.error) {
+            ERROR("advertise_finished: unexpectedly failed to send EDNS0 lease option.");
+            iov.iov_len = DNS_HEADER_SIZE;
+        } else {
+            iov.iov_len = towire.p - (uint8_t *)&response;
+        }
+    } else {
+        iov.iov_len = DNS_HEADER_SIZE;
+    }
+    ioloop_send_message(connection, message, &iov, 1);
+}
+
+static void
+host_txn_finalize_callback(void *context)
+{
+    adv_host_t *host = context;
+    host->txn = NULL;
+    host->rref = NULL;
+}
+
+static void
+instance_txn_finalize_callback(void *context)
+{
+    adv_instance_t *instance = context;
+    instance->txn = NULL;
+}
+
+static void
+retry_callback(void *context)
+{
+    adv_host_t *host = (adv_host_t *)context;
+    if (host->updates == NULL && host->clients == NULL) {
+        update_from_host(host);
+    } else {
+        start_host_update(host);
+    }
+}
+
+static void
+wait_retry(adv_host_t *host)
+{
+#define MIN_HOST_RETRY_INTERVAL 15
+#define MAX_HOST_RETRY_INTERVAL 120
+    // If we've been retrying long enough for the lease to expire, give up.
+    if (!host->lease_expiry || host->lease_expiry >= ioloop_timenow()) {
+        lease_callback(host);
+    }
+    if (host->retry_interval == 0) {
+        host->retry_interval = MIN_HOST_RETRY_INTERVAL;
+    } else if (host->retry_interval < MAX_HOST_RETRY_INTERVAL) {
+        host->retry_interval *= 2;
+    }
+    INFO("wait_retry: waiting %d seconds...", host->retry_interval);
+    ioloop_add_wake_event(host->retry_wakeup, host, retry_callback, NULL, host->retry_interval * 1000);
+}
+
+// When the connection to mDNSResponder has died, the registration dies with it.
+// In order to get back to where we were, we use the contents of the host to create an
+// update.  We push that update in front of any update that was pending, and restart
+// the registration.  There is no guarantee that this will succeed.   If it fails, then
+// the registration is abandoned.
+
+static void
+service_disconnected(adv_host_t *host)
+{
+    // If we don't have any updates we can do, this host is dead.
+    if (host->updates == NULL) {
+        // lease_callback will get rid of this host.
+        lease_callback(host);
+    } else {
+        wait_retry(host);
+    }
+}
+
+static void
+client_finalize(client_update_t *client)
+{
+    srp_update_free_parts(client->instances, NULL, client->services, client->host);
+    if (client->parsed_message != NULL) {
+        dns_message_free(client->parsed_message);
+    }
+    if (client->message != NULL) {
+        ioloop_message_release(client->message);
+    }
+    if (client->connection != NULL) {
+        ioloop_comm_release(client->connection);
+    }
+    free(client);
+}
+
+static void
+update_finalize(adv_update_t *NONNULL update)
+{
+    adv_host_t *host = update->host;
+    int i;
+    if (host != NULL) {
+        adv_update_t **p_updates = &host->updates;
+
+        // Once the update is done, we want to make sure that any results that come in on the host registration do not
+        // reference the update, which will we are about to free.  So get rid of the update pointer that's in the
+        // host address transaction aux data.
+        if (update->host->txn != NULL) {
+            adv_update_t *host_txn_update = ioloop_dnssd_txn_get_aux_pointer(host->txn);
+            // If we are retrying an update, the update on the host DNSServiceRegisterRecord transaction might not be
+            // the transaction we are finalizing, but if it is, we definitely want to make it go away.
+            if (host_txn_update == update) {
+                ioloop_dnssd_txn_set_aux_pointer(host->txn, NULL);
+            }
+        }
+        INFO("finalizing update %p for host " PRI_S_SRP, update, host->registered_name);
+
+        // Take this update off the host's update list (it might already be gone)
+        while (*p_updates != NULL) {
+            if (*p_updates == update) {
+                *p_updates = update->next;
+                break;
+            } else {
+                p_updates = &((*p_updates)->next);
+            }
+        }
+    } else {
+        INFO("finalizing update with no host.");
+    }
+
+    // Release instances and instance vectors.
+    if (update->add_instances != NULL) {
+        RELEASE_HERE(update->add_instances, adv_instance_vec_finalize);
+    }
+
+    if (update->update_instances != NULL) {
+        RELEASE_HERE(update->update_instances, adv_instance_vec_finalize);
+    }
+
+    if (update->remove_instances != NULL) {
+        RELEASE_HERE(update->remove_instances, adv_instance_vec_finalize);
+    }
+
+    if (update->add_addresses != NULL) {
+        for (i = 0; i < update->num_add_addresses; i++) {
+            if (update->add_addresses[i] != NULL) {
+                RELEASE_HERE(update->add_addresses[i], adv_address_finalize);
+            }
+        }
+        free(update->add_addresses);
+    }
+
+    if (update->remove_addresses != NULL) {
+        for (i = 0; i < update->num_remove_addresses; i++) {
+            if (update->remove_addresses[i] != NULL) {
+                RELEASE_HERE(update->remove_addresses[i], adv_address_finalize);
+            }
+        }
+        free(update->remove_addresses);
+    }
+
+    if (update->selected_addr != NULL) {
+        RELEASE_HERE(update->selected_addr, adv_address_finalize);
+    }
+
+    free(update);
+}
+
+static void
+update_failed(adv_update_t *update, int rcode, bool expire)
+{
+    // If we still have a client waiting for the result of this update, tell it we failed.
+    // Updates that have never worked are abandoned when the client is notified.
+    if (update->client != NULL) {
+        adv_host_t *host = update->host;
+        client_update_t *client = update->client;
+        advertise_finished(client->connection, client->message, rcode, NULL);
+        update_finalize(update);
+        client_finalize(client);
+        // If we don't have a lease yet, or the old lease has expired, remove the host.
+        // However, if the expire flag is false, it's because we're already finalizing the
+        // host, so doing an expiry here would double free the host. In this case, we leave
+        // it to the caller to do the expiry (really, to finalize the host).
+        if (expire && (host->lease_expiry == 0 || host->lease_expiry <= ioloop_timenow())) {
+            lease_callback(host);
+        }
+        return;
+    }
+
+    // The only time that we will not have a client waiting for a result from an update is if
+    // we are trying to recover from an mDNSResponder crash.  mDNSResponder doesn't crash much,
+    // so the likelihood of us even finding out if this behavior is correct is pretty low.
+    // That said, we do take this possibility into account; if it happens, we try to restore
+    // all the registrations that were present prior to the crash.
+
+    // In the case of an update that previously succeeded, but that now has failed because of a naming
+    // conflict (yxdomain), we have to abandon it, even though we have no way to notify the owner.
+    if (rcode == dns_rcode_yxdomain) {
+        update_finalize(update);
+        return;
+    }
+}
+
+static void
+host_addr_free(adv_host_t *host)
+{
+    int i;
+
+    if (host->addresses != NULL) {
+       for (i = 0; i < host->num_addresses; i++) {
+           if (host->addresses[i] != NULL) {
+               RELEASE_HERE(host->addresses[i], adv_address_finalize);
+           }
+       }
+       free(host->addresses);
+    }
+    host->addresses = NULL;
+    host->num_addresses = 0;
+}
+
+static void
+host_finalize(adv_host_t *host)
+{
+    int i;
+
+    // Get rid of the host wake events.
+    if (host->lease_wakeup != NULL) {
+        ioloop_cancel_wake_event(host->lease_wakeup);
+        ioloop_wakeup_release(host->lease_wakeup);
+    }
+    if (host->retry_wakeup != NULL) {
+        ioloop_cancel_wake_event(host->retry_wakeup);
+        ioloop_wakeup_release(host->retry_wakeup);
+    }
+
+
+    // Remove all the advertised address records (currently only one).
+    if (host->txn != NULL) {
+        if (host->txn->sdref == NULL) {
+            INFO("host_finalize: releasing DNSSD transaction for " PRI_S_SRP ", but there's no sdref.", host->name);
+        } else {
+            INFO("host_finalize: removing AAAA record(s) for " PRI_S_SRP, host->registered_name);
+        }
+        ioloop_dnssd_txn_release(host->txn);
+    } else {
+        INFO("host_finalize: no host address transaction for " PRI_S_SRP, host->registered_name);
+    }
+
+    // Remove the address records.
+    host_addr_free(host);
+
+    // Remove the services.
+
+    if (host->instances != NULL) {
+        for (i = 0; i < host->instances->num; i++) {
+            if (host->instances->vec[i] != NULL) {
+                if (host->instances->vec[i]->txn) {
+                    ioloop_dnssd_txn_release(host->instances->vec[i]->txn);
+                }
+            }
+        }
+        RELEASE_HERE(host->instances, adv_instance_vec_finalize);
+    }
+
+    // At this point we could claim the key, but for now just get rid of the host.
+    if (host->key_rdata != NULL) {
+        free(host->key_rdata);
+    }
+    INFO("host_finalize: removed " PRI_S_SRP ", key_id %x", host->name ? host->name : "<null>", host->key_id);
+
+    // In the default case, host->name and host->registered_name point to the same memory: we don't want a double free.
+    if (host->registered_name == host->name) {
+        host->registered_name = NULL;
+    }
+    if (host->name != NULL) {
+        free(host->name);
+    }
+    if (host->registered_name != NULL) {
+        free(host->registered_name);
+    }
+    free(host);
+}
+
+static void
+lease_callback(void *context)
+{
+    adv_host_t **p_hosts, *host = context;
+
+    // Find the host on the list of hosts.
+    for (p_hosts = &hosts; *p_hosts != NULL; p_hosts = &(*p_hosts)->next) {
+        if (*p_hosts == host) {
+            break;
+        }
+    }
+    if (*p_hosts == NULL) {
+        ERROR("lease-callback: called with nonexistent host.");
+        return;
+    }
+
+    // It's possible that we got an update to this host, but haven't processed it yet.  In this
+    // case, we don't want to get rid of the host, but we do want to get rid of it later if the
+    // update fails.  So postpone the removal for another lease interval.
+    if (host->updates != NULL || host->clients != NULL) {
+        INFO("lease_callback: reached with pending updates on host " PRI_S_SRP ".", host->registered_name);
+        ioloop_add_wake_event(host->lease_wakeup, host, lease_callback, NULL, host->lease_interval * 1000);
+        host->lease_expiry = ioloop_timenow() + host->lease_interval * 1000;
+        return;
+    }
+
+    // De-link the host.
+    *p_hosts = host->next;
+
+    // Get rid of any transactions attached to the host, any timer events, and any other associated data.
+    host_finalize(host);
+}
+
+static void
+update_finished(adv_update_t *update)
+{
+    adv_host_t *host = update->host;
+    client_update_t *client = update->client;
+    int num_addresses = 0;
+    adv_address_t **addresses = NULL;
+    int num_instances = 0;
+    adv_instance_vec_t *instances = NULL;
+    int i, j;
+    int num_host_addresses = 0;
+    int num_add_addresses = 0;
+    int num_host_instances = 0;
+    int num_add_instances = 0;
+    uint8_t *rdata;
+    adv_update_t **p_update;
+
+    // Reset the retry interval, since we succeeded in updating.
+    host->retry_interval = 0;
+
+    // Once an update has finished, we need to apply all of the proposed changes to the host object.
+    if (host->addresses != NULL) {
+        for (i = 0; i < host->num_addresses; i++) {
+            if (host->addresses[i] != NULL &&
+                (update->remove_addresses == NULL || update->remove_addresses[i] == NULL))
+            {
+                num_host_addresses++;
+            }
+        }
+    }
+
+    if (update->add_addresses != NULL) {
+        for (i = 0; i < update->num_add_addresses; i++) {
+            if (update->add_addresses[i] != NULL) {
+                num_add_addresses++;
+            }
+        }
+    }
+
+    num_addresses = num_host_addresses + num_add_addresses;
+    if (num_addresses > 0) {
+        addresses = calloc(num_addresses, sizeof *addresses);
+        if (addresses == NULL) {
+            update_failed(update, dns_rcode_servfail, true);
+            return;
+        }
+
+        j = 0;
+        addresses[j] = update->selected_addr;
+        RETAIN_HERE(addresses[j]);
+        j++;
+
+        rdata = update->selected_addr->rdata;
+        SEGMENTED_IPv6_ADDR_GEN_SRP(rdata, rdata_buf);
+        INFO("update_finished: selected " PRI_SEGMENTED_IPv6_ADDR_SRP " on host " PRI_S_SRP,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(rdata, rdata_buf), host->registered_name);
+
+        if (host->addresses != NULL) {
+            for (i = 0; i < host->num_addresses; i++) {
+                if (host->addresses[i] != NULL && host->addresses[i] != update->selected_addr &&
+                    (update->remove_addresses == NULL || update->remove_addresses[i] == NULL))
+                {
+#ifdef DEBUG_VERBOSE
+                    uint8_t *rdp = host->addresses[i]->rdata;
+                    SEGMENTED_IPv6_ADDR_GEN_SRP(rdp, rdp_buf);
+                    INFO("update_finished: retaining " PRI_SEGMENTED_IPv6_ADDR_SRP "on host " PRI_S_SRP,
+                         SEGMENTED_IPv6_ADDR_PARAM_SRP(rdp, rdp_buf), host->registered_name);
+#endif
+                    addresses[j] = host->addresses[i];
+                    RETAIN_HERE(addresses[j]);
+                    j++;
+                }
+            }
+        }
+        if (update->add_addresses != NULL) {
+            for (i = 0; i < update->num_add_addresses; i++) {
+                if (update->add_addresses[i] != NULL) {
+                    if (update->add_addresses[i] != update->selected_addr) {
+#ifdef DEBUG_VERBOSE
+                        uint8_t *rdp = update->add_addresses[i]->rdata;
+                        SEGMENTED_IPv6_ADDR_GEN_SRP(rdp, rdp_buf);
+                        INFO("update_finished: adding " PRI_SEGMENTED_IPv6_ADDR_SRP "to host " PRI_S_SRP,
+                             SEGMENTED_IPv6_ADDR_PARAM_SRP(rdp, rdp_buf), host->registered_name);
+#endif
+                        addresses[j] = update->add_addresses[i];
+                        RETAIN_HERE(addresses[j]);
+                        j++;
+                    }
+                    RELEASE_HERE(update->add_addresses[i], adv_address_finalize);
+                    update->add_addresses[i] = NULL;
+                }
+            }
+        }
+        if (update->selected_addr != NULL) {
+            RELEASE_HERE(update->selected_addr, adv_address_finalize);
+            update->selected_addr = NULL;
+        }
+    }
+
+    // Do the same for instances.
+    for (i = 0; i < host->instances->num; i++) {
+        if (host->instances->vec[i] != NULL &&
+            (update->remove_instances == NULL || update->remove_instances->vec[i] == NULL))
+        {
+            num_host_instances++;
+        }
+    }
+
+    if (update->add_instances != NULL) {
+        for (i = 0; i < update->add_instances->num; i++) {
+            if (update->add_instances->vec[i] != NULL) {
+                num_add_instances++;
+            }
+        }
+    }
+
+    num_instances = num_add_instances + num_host_instances;
+    instances = adv_instance_vec_create(num_instances);
+    if (instances == NULL) {
+        if (addresses != NULL) {
+            for (i = 0; i < num_addresses; i++) {
+                if (addresses[i] != NULL) {
+                    RELEASE_HERE(addresses[i], adv_address_finalize);
+                }
+            }
+            free(addresses);
+        }
+        update_failed(update, dns_rcode_servfail, true);
+        return;
+    }
+    instances->num = num_instances;
+
+    j = 0;
+    for (i = 0; i < host->instances->num; i++) {
+        if (update->remove_instances != NULL && update->remove_instances->vec[i] == NULL) {
+            if (update->update_instances->vec[i] != NULL) {
+                adv_instance_t *instance = update->update_instances->vec[i];
+                INFO("update_finished: updated instance " PRI_S_SRP " " PRI_S_SRP " %d",
+                      instance->instance_name, instance->service_type, instance->port);
+                // Implicit RETAIN/RELEASE
+                instances->vec[j] = instance;
+                update->update_instances->vec[i] = NULL;
+            } else {
+                if (host->instances->vec[i] != NULL) {
+                    adv_instance_t *instance = host->instances->vec[i];
+                    INFO("update_finished: retained instance " PRI_S_SRP " " PRI_S_SRP " %d",
+                          instance->instance_name, instance->service_type, instance->port);
+                    instances->vec[j++] = instance;
+                    RETAIN_HERE(instance);
+                }
+            }
+        }
+    }
+    if (update->add_instances != NULL) {
+        for (i = 0; i < update->add_instances->num; i++) {
+            adv_instance_t *instance = update->add_instances->vec[i];
+            if (instance != NULL) {
+                INFO("update_finished: added instance " PRI_S_SRP " " PRI_S_SRP " %d",
+                      instance->instance_name, instance->service_type, instance->port);
+                // Implicit RETAIN/RELEASE
+                instances->vec[j++] = instance;
+                update->add_instances->vec[i] = NULL;
+            }
+        }
+    }
+
+    // At this point we can safely modify the host object because we aren't doing any more
+    // allocations.
+    host_addr_free(host);
+    RELEASE_HERE(host->instances, adv_instance_vec_finalize);
+
+    host->addresses = addresses;
+    host->num_addresses = num_addresses;
+    host->instances = instances;
+
+    if (client) {
+        advertise_finished(client->connection, client->message, dns_rcode_noerror, client);
+        client_finalize(client);
+        update->client = NULL;
+    }
+
+    // The update should still be on the host.
+    for (p_update = &host->updates; *p_update != NULL; p_update = &(*p_update)->next) {
+        if (*p_update == update) {
+            break;
+        }
+    }
+
+    if (*p_update == NULL) {
+        ERROR("update_finished: p_update is null.");
+    } else {
+        *p_update = update->next;
+    }
+
+    // If we have another prepared update to do, apply it first.
+    if (host->updates) {
+        start_host_update(host);
+        goto out;
+    }
+
+    // If we have an update that hasn't yet been prepared, prepare it and apply it.
+    if (host->clients) {
+        prepare_update(host);
+        goto out;
+    }
+
+    // If we got a late name conflict while processing the previous update, try to get a new hostname.
+    // We won't get here if the update caused the host to be reregistered--in that case we will either
+    // return a failure to the client and delete the host, or else we'll have resolved the conflict.
+    if (host->hostname_update_pending) {
+        try_new_hostname(host);
+    }
+
+    // Otherwise, there's no work left to do, so just wait until the lease expires.
+    host->lease_interval = update->host_lease;
+    host->key_lease = update->key_lease;
+
+    if (update->lease_expiry != 0) {
+        uint64_t now = ioloop_timenow();
+        if (update->lease_expiry < now) {
+            ERROR("update_finished: lease expiry for host %s happened %" PRIu64 " milliseconds ago.",
+                  host->registered_name, now - update->lease_expiry);
+            // Expire the lease in 1000 ms.
+            ioloop_add_wake_event(host->lease_wakeup, host, lease_callback, NULL, 1000);
+        } else {
+            uint64_t when = update->lease_expiry - now;
+            if (when > INT32_MAX) {
+                when = INT32_MAX;
+            }
+            ioloop_add_wake_event(host->lease_wakeup, host, lease_callback, NULL, (uint32_t)when);
+            host->lease_expiry = update->lease_expiry;
+        }
+    } else {
+        ioloop_add_wake_event(host->lease_wakeup, host, lease_callback, NULL, host->lease_interval * 1000);
+        host->lease_expiry = ioloop_timenow() + host->lease_interval * 1000;
+    }
+out:
+    update_finalize(update);
+}
+
+// When the host registration has completed, we get this callback.   Completion either means that we succeeded in
+// registering the record, or that something went wrong and the registration has failed.
+static void
+register_instance_completion(DNSServiceRef sdref, DNSServiceFlags flags, DNSServiceErrorType error_code,
+                             const char *name, const char *regtype, const char *domain, void *context)
+{
+    (void)flags;
+    (void)sdref;
+    (void)name;
+    (void)regtype;
+    adv_instance_t *instance = context;
+    adv_update_t *update = instance->update;
+    adv_host_t *host = instance->host;
+
+    // It's possible that we could restart a host update due to an error while a callback is still pending on a stale
+    // update.  In this case, we just cancel all of the work that's been done on the stale update (it's probably already
+    // moot anyway.
+    if (update != NULL && host->updates != update) {
+        INFO("register_instance_completion: registration for service " PRI_S_SRP "." PRI_S_SRP
+             " completed with invalid state.", name, regtype);
+        update_finalize(update);
+        return;
+    }
+
+    // We will generally get a callback on success or failure of the initial registration; this is what causes
+    // the update to complete or fail. We may get subsequent callbacks because of name conflicts. So the first
+    // time we get a callback, instance->update will always be valid; thereafter, it will not, so null it out.
+    instance->update = NULL;
+
+    if (error_code == kDNSServiceErr_NoError) {
+        INFO("register_instance_completion: registration for service " PRI_S_SRP "." PRI_S_SRP "." PRI_S_SRP " -> "
+             PRI_S_SRP " has completed.", instance->instance_name, instance->service_type, domain,
+             host->registered_name);
+        INFO("register_instance_completion: registration is under " PRI_S_SRP "." PRI_S_SRP PRI_S_SRP, name, regtype,
+             domain);
+
+        // In principle update->instance should always be non-NULL here because a no-error response should
+        // only happen once or not at all. But just to be safe...
+        if (update != NULL) {
+            update->num_instances_completed++;
+            if (update->num_instances_completed == update->num_instances_started) {
+                // We have successfully advertised the service.
+                update_finished(update);
+            }
+        } else {
+            ERROR("register_instance_completion: no error, but update is NULL for instance " PRI_S_SRP " (" PRI_S_SRP
+                  " " PRI_S_SRP " " PRI_S_SRP ")", instance->instance_name, name, regtype, domain);
+        }
+    } else {
+        INFO("register_instance_completion: registration for service " PRI_S_SRP "." PRI_S_SRP "." PRI_S_SRP " -> "
+             PRI_S_SRP " failed with code %d", instance->instance_name, instance->service_type, domain,
+             host->registered_name, error_code);
+
+        // If this is the immediate result of a registration, we can inform the SRP client that it failed.
+        if (update != NULL) {
+            // At present we will never get this error because mDNSResponder will just choose a new name.
+            if (error_code == kDNSServiceErr_NameConflict) {
+                update_failed(update, dns_rcode_yxdomain, true);
+            } else {
+                update_failed(update, dns_rcode_servfail, true);
+            }
+        } else {
+            ERROR("Late failure for instance " PRI_S_SRP "--can't update client.", instance->instance_name);
+        }
+
+        if (error_code == kDNSServiceErr_ServiceNotRunning || error_code == kDNSServiceErr_DefunctConnection) {
+            service_disconnected(host);
+        }
+    }
+}
+
+static void
+extract_instance_name(char *instance_name, int instance_name_max,
+                      char *service_name, int service_name_max, service_instance_t *instance)
+{
+    dns_name_t *end_of_service_name = instance->service->rr->name->next;
+    if (end_of_service_name != NULL) {
+        if (end_of_service_name->next != NULL) {
+            end_of_service_name = end_of_service_name->next;
+        }
+    }
+    dns_name_print_to_limit(instance->service->rr->name, end_of_service_name, service_name, service_name_max);
+
+    // Make a presentation-format version of the service instance name.
+    dns_name_print_to_limit(instance->name, instance->name != NULL ? instance->name->next : NULL,
+                            instance_name, instance_name_max);
+}
+
+static bool
+register_instance(adv_instance_t *instance)
+{
+    int err;
+    DNSServiceRef sdref;
+
+    INFO("DNSServiceRegister(" PRI_S_SRP ", " PRI_S_SRP ", " PRI_S_SRP ", %d)",
+         instance->instance_name, instance->service_type, instance->host->registered_name, instance->port);
+    err = DNSServiceRegister(&sdref, kDNSServiceFlagsUnique, advertise_interface,
+                             instance->instance_name, instance->service_type, local_suffix,
+                             instance->host->registered_name, htons(instance->port), instance->txt_length,
+                             instance->txt_data, register_instance_completion, instance);
+    // This would happen if we pass NULL for regtype, which we don't, or if we run out of memory, or if
+    // the server isn't running; in the second two cases, we can always try again later.
+    if (err != kDNSServiceErr_NoError) {
+        // If we can, always send status to the client.
+        if (instance->update != NULL) {
+            if (instance->update->client == NULL &&
+                (err == kDNSServiceErr_ServiceNotRunning || err == kDNSServiceErr_DefunctConnection))
+            {
+                INFO("DNSServiceRegister failed: " PUB_S_SRP ,
+                     err == kDNSServiceErr_ServiceNotRunning ? "not running" : "defunct");
+                service_disconnected(instance->host);
+            } else {
+                INFO("DNSServiceRegister failed: %d", err);
+                update_failed(instance->update, dns_rcode_servfail, true);
+            }
+        }
+        return false;
+    }
+    instance->txn = ioloop_dnssd_txn_add(sdref, instance, instance_txn_finalize_callback);
+    if (instance->txn == NULL) {
+        ERROR("register_instance: no memory.");
+        DNSServiceRefDeallocate(sdref);
+        return false;
+    }
+    if (instance->update != NULL) {
+        instance->update->num_instances_started++;
+    }
+    return true;
+}
+
+#ifdef UNUSED
+// When an update fails on some record, abandon_update is called to stop advertising the other records
+// that were proposed in the update.  The state associated with the update is then freed.  The caller is
+// responsible for sending the result back to the SRP client.   If anything was deleted by the update, it's
+// also abandoned, which is somewhat problematic.
+static void
+abandon_update(adv_host_t *host)
+{
+    (void)host;
+}
+
+// When a registration that's been successfully added in the past is attempted, and fails in a way
+// that indicates a conflict or unrecoverable error, we have to abandon it.  abandon_registration
+// takes care of that.
+static void
+abandon_registration(adv_host_t *host)
+{
+    (void)host;
+}
+#endif
+
+static void
+start_service_updates(adv_host_t *host)
+{
+    int i;
+    adv_update_t *update = host->updates;
+
+    if (update == NULL) {
+        ERROR("start_service_updates: no work to do.");
+        return;
+    }
+
+    // For each service instance that's being added, register it.
+    for (i = 0; i < update->add_instances->num; i++) {
+        if (update->add_instances->vec[i] != NULL) {
+            if (!register_instance(update->add_instances->vec[i])) {
+                return;
+            }
+        }
+    }
+    // For each service instance that's being updated or deleted, delete it.
+    if (update->update_instances->num != host->instances->num) {
+        ERROR("start_service_updates: update instance count %d differs from host instance count %d",
+              update->update_instances->num, host->instances->num);
+        update_failed(update, dns_rcode_servfail, true);
+        return;
+    }
+    if (update->remove_instances->num != host->instances->num) {
+        ERROR("start_service_updates: delete instance count %d differs from host instance count %d",
+              update->remove_instances->num, host->instances->num);
+        update_failed(update, dns_rcode_servfail, true);
+        return;
+    }
+    for (i = 0; i < host->instances->num; i++) {
+        if (update->update_instances->vec[i] != NULL || update->remove_instances->vec[i] != NULL) {
+            if (host->instances->vec[i]->txn != NULL) {
+                ioloop_dnssd_txn_release(host->instances->vec[i]->txn);
+                host->instances->vec[i]->txn = NULL;
+            }
+        }
+        if (update->update_instances->vec[i] != NULL) {
+            if (!register_instance(update->update_instances->vec[i])) {
+                INFO("start_service_update: register instance failed.");
+                return;
+            }
+        }
+    }
+    if (update->num_instances_started == 0) {
+        INFO("start_service_update: no service updates, so we're finished.");
+        update_finished(update);
+    }
+}
+
+// When we get a late name conflict on the hostname, we need to update the host registration and all of the
+// service registrations. To do this, we construct an update and then apply it. If there is already an update
+// in progress, we put this update at the end of the list.
+static void
+update_from_host(adv_host_t *host)
+{
+    adv_update_t *update = NULL;
+    int i;
+
+    // Allocate the update structure.
+    update = calloc(1, sizeof *update);
+    if (update == NULL) {
+        ERROR("update_from_host: no memory for update.");
+        goto fail;
+    }
+
+    update->add_addresses = calloc(host->num_addresses, sizeof (*update->add_addresses));
+    if (update->add_addresses == NULL) {
+        ERROR("update_from_host: no memory for addresses");
+        goto fail;
+    }
+    update->num_add_addresses = 0;
+
+    // Copy all of the addresses in the host into add_addresses
+    for (i = 0; i < host->num_addresses; i++) {
+        if (host->addresses[i] != NULL) {
+            update->add_addresses[update->num_add_addresses] = host->addresses[i];
+            RETAIN_HERE(update->add_addresses[update->num_add_addresses]);
+            update->num_add_addresses++;
+        }
+    }
+
+    // We can never update more instances than currently exist for this host.
+    update->update_instances = adv_instance_vec_copy(host->instances);
+    for (i = 0; i < update->update_instances->num; i++) {
+        if (update->update_instances->vec[i] != NULL) {
+           update->update_instances->vec[i]->update = update;
+        }
+    }
+
+    // We aren't actually adding or deleting any instances, but...
+    update->remove_instances = adv_instance_vec_create(host->instances->num);
+    if (update->remove_instances == NULL) {
+        ERROR("update_from_host: no memory for remove_instances");
+        goto fail;
+    }
+    update->remove_instances->num = host->instances->num;
+
+    update->add_instances = adv_instance_vec_create(host->instances->num);
+    if (update->add_instances == NULL) {
+        ERROR("update_from_host: no memory for add_instances");
+        goto fail;
+    }
+    update->add_instances->num = host->instances->num;
+
+    // At this point we have figured out all the work we need to do, so hang it off an update structure.
+    update->host = host;
+    update->num_remove_addresses = 0;
+    update->num_add_addresses = host->num_addresses;
+    update->host_lease = host->lease_interval;
+    update->key_lease = host->key_lease;
+    update->lease_expiry = host->lease_expiry;
+
+    // The only time we can expect this to happen is as a result of a retry, in which case we want to
+    // get the host back to its correct state before continuing.
+    update->next = host->updates;
+    host->updates = update;
+    start_host_update(host);
+    return;
+
+fail:
+    update_finalize(update);
+    wait_retry(host);
+    return;
+}
+
+// If we get a name conflict, we need to choose a new name for the host.
+static void
+try_new_hostname(adv_host_t *host)
+{
+    char *s, *t;
+    char separator = '-';
+    char namebuf[DNS_MAX_LABEL_SIZE_ESCAPED];
+
+    t = namebuf;
+    for (s = host->name; *s; s++) {
+        if (*s == '.') {
+            *t = 0;
+            break;
+        }
+        if (t - namebuf >= DNS_MAX_LABEL_SIZE_ESCAPED - 13) { // 13: "-12345.local\0"
+            *t = 0;
+            ERROR("try_new_hostname: truncating " PRI_S_SRP " to " PRI_S_SRP, host->name, namebuf);
+            break;
+        }
+        if (*s == '\\') {
+            if (s[1] == 0 || s[2] == 0 || s[3] == 0) {
+                ERROR("try_new_hostname: escaped hostname " PRI_S_SRP " is invalid", host->name);
+                *t = 0;
+                break;
+            }
+            *t++ = *s++;
+            *t++ = *s++;
+            *t++ = *s++;
+            *t++ = *s;
+            continue;
+        }
+        if (*s == ' ' || *s == '-' || *s == '_') {
+            separator = *s;
+        }
+        *t++ = *s;
+    }
+    INFO("try_new_hostname: using base name %s", namebuf);
+    // Append a random number to the end of the name.
+    host->name_serial = srp_random16();
+    snprintf(t, 13, "-%d.local", host->name_serial);
+    INFO("try_new_hostname: using full name %s", namebuf);
+
+    if (host->registered_name != host->name) {
+        free(host->registered_name);
+    }
+    host->registered_name = strdup(namebuf);
+    if (host->registered_name == NULL) {
+        ERROR("try_new_hostname: No memory for alternative name for " PRI_S_SRP ": " PRI_S_SRP, host->name, namebuf);
+        // We pretty much can't do anything at this point.
+        lease_callback(host);
+        return;
+    }
+
+    // Generate an update from the host entry and do it.
+    update_from_host(host);
+}
+
+// When the host registration has completed, we get this callback.   Completion either means that we succeeded in
+// registering the record, or that something went wrong and the registration has failed.
+static void
+register_host_completion(DNSServiceRef sdref, DNSRecordRef rref,
+                         DNSServiceFlags flags, DNSServiceErrorType error_code, void *context)
+{
+    dnssd_txn_t *txn = context;
+    adv_update_t *update = ioloop_dnssd_txn_get_aux_pointer(txn);
+    adv_host_t *host = ioloop_dnssd_txn_get_context(txn);
+    (void)sdref;
+    (void)rref;
+    (void)error_code;
+    (void)flags;
+
+    // It's possible that we could restart a host update due to an error while a callback is still pending on a stale
+    // update.  In this case, we just cancel all of the work that's been done on the stale update (it's probably already
+    // moot anyway.
+    if (update != NULL && (host == NULL || host->updates != update)) {
+        INFO("register_host_completion: registration for host completed with invalid state.");
+        update_finalize(update);
+        return;
+    }
+
+    if (host == NULL) {
+        ERROR("register_host_completion called on null host.");
+        return;
+    }
+
+    if (error_code == kDNSServiceErr_NoError) {
+        // If we get here while a hostname update is pending, it means that the conflict was resolved when
+        // we re-registered the host as a side effect of the update, so we no longer need to update the
+        // hostname.
+        host->hostname_update_pending = false;
+
+        // Now that the hostname has been registered, we can register services that point at it.
+        INFO("register_host_completion: registration for host " PRI_S_SRP " has completed.", host->registered_name);
+        start_service_updates(host);
+    } else {
+        INFO("register_host_completion: registration for host " PRI_S_SRP " failed, status = %d", host->registered_name,
+             error_code);
+        if (update == NULL) {
+            // We shouldn't get any error here other than a name conflict or daemon not running.
+            // If we get a name conflict, that means that some other BR or host on the network has
+            // started advertising the hostname we chose, so we need to choose a new name and fix
+            // all of the service registrations.
+            if (error_code == kDNSServiceErr_NameConflict) {
+                if (host->updates != NULL || host->clients != NULL) {
+                    host->hostname_update_pending = true;
+                } else {
+                    try_new_hostname(host);
+                }
+            }
+        } else {
+            if (error_code == kDNSServiceErr_NameConflict) {
+                update_failed(update, dns_rcode_yxdomain, true);
+            } else {
+                update_failed(update, dns_rcode_servfail, true);
+            }
+        }
+    }
+}
+
+static adv_instance_t *
+adv_instance_create(service_instance_t *raw, adv_host_t *host, adv_update_t *update)
+{
+    char service_type[DNS_MAX_LABEL_SIZE_ESCAPED * 2 + 2]; // sizeof '.' + sizeof '\0'.
+    char instance_name[DNS_MAX_NAME_SIZE_ESCAPED + 1];
+    char *txt_data;
+
+    // Allocate the raw registration
+    adv_instance_t *instance = calloc(1, sizeof *instance);
+    if (instance == NULL) {
+        ERROR("adv_instance:create: unable to allocate raw registration struct.");
+        return NULL;
+    }
+    RETAIN_HERE(instance);
+    instance->host = host;
+    instance->update = update;
+    // SRV records have priority, weight and port, but DNSServiceRegister only uses port.
+    instance->port = (raw->srv == NULL) ? 0 : raw->srv->data.srv.port;
+
+    // Make a presentation-format version of the service name.
+    extract_instance_name(instance_name, sizeof instance_name, service_type, sizeof service_type, raw);
+    instance->instance_name = strdup(instance_name);
+    if (instance->instance_name == NULL) {
+        ERROR("adv_instance:create: unable to allocate instance name.");
+        RELEASE_HERE(instance, adv_instance_finalize);
+        return NULL;
+    }
+    instance->service_type = strdup(service_type);
+    if (instance->service_type == NULL) {
+        ERROR("adv_instance:create: unable to allocate instance type.");
+        RELEASE_HERE(instance, adv_instance_finalize);
+        return NULL;
+    }
+
+    // Allocate the text record buffer
+    if (raw->txt != NULL) {
+    txt_data = malloc(raw->txt->data.txt.len);
+        if (txt_data == NULL) {
+            RELEASE_HERE(instance, adv_instance_finalize);
+            ERROR("adv_instance:create: unable to allocate txt_data buffer");
+            return NULL;
+        }
+        // Format the txt buffer as required by DNSServiceRegister().
+        memcpy(txt_data, raw->txt->data.txt.data, raw->txt->data.txt.len);
+        instance->txt_data = txt_data;
+        instance->txt_length = raw->txt->data.txt.len;
+    } else {
+        instance->txt_data = NULL;
+        instance->txt_length = 0;
+    }
+    return instance;
+}
+
+#define adv_address_create(addr, host) \
+    adv_address_create_(addr, host, __FILE__, __LINE__)
+static adv_address_t *
+adv_address_create_(host_addr_t *addr, adv_host_t *host, const char *file, int line)
+{
+
+    adv_address_t *new_addr = calloc(1, sizeof *new_addr);
+    if (new_addr == NULL) {
+        ERROR("adv_address_create: no memory for new_addr");
+        return NULL;
+    }
+    new_addr->host = host;
+    new_addr->rrtype = addr->rr.type;
+    new_addr->rdlen = addr->rr.type == dns_rrtype_a ? 4 : 16;
+    memcpy(new_addr->rdata, &addr->rr.data, new_addr->rdlen);
+    RETAIN(new_addr);
+    return new_addr;
+}
+
+// When we need to register a host with mDNSResponder, start_host_update is called.   This can be either because
+// we just got a new registration for a host, or if the daemon dies and we need to re-do the host registration.
+// This just registers the host; if that succeeds, then we register the service instances.
+static void
+start_host_update(adv_host_t *host)
+{
+    adv_update_t *update = host->updates;
+    int err;
+    bool remove_preexisting;
+    uint8_t *add_rdata;
+    uint16_t add_rrtype;
+    uint16_t add_rdlen;
+    adv_address_t *selected_addr = NULL, *preferred_addr = NULL;
+    int i;
+    adv_address_t *addr;
+
+    // No work to do?
+    if (host->updates == NULL) {
+        ERROR("start_host_update: no work to do for host " PRI_S_SRP, host->registered_name);
+        return;
+    }
+
+    // If we haven't published an address yet, or if we are going to remove the currently published address,
+    // or if we have new addresses to add, then choose the address that is best suited, preferring recently
+    // added addresses over existing addresses.
+    remove_preexisting = false;
+    add_rdata = NULL;
+    add_rrtype = 0;
+    add_rdlen = 0;
+
+    // If the host address isn't about to be removed, and is an IPv6 address on the preferred prefix,
+    // store it in preferred_addr for comparison and possible retention.
+    if (preferred_prefix != NULL &&
+        host->num_addresses > 0 && host->addresses[0] != NULL &&
+        update->remove_addresses != NULL && update->remove_addresses[0] == NULL &&
+        host->addresses[0]->rrtype == dns_rrtype_aaaa &&
+        same_prefix(host->addresses[0]->rdata, &preferred_prefix->addr, preferred_prefix->width))
+    {
+        preferred_addr = host->addresses[0];
+    }
+
+    if ((host->num_addresses != 0 && update->remove_addresses != NULL && update->remove_addresses[0] != NULL) ||
+        update->num_add_addresses != 0)
+    {
+        if (preferred_prefix != NULL) {
+            for (i = 0; i < update->num_add_addresses; i++) {
+                addr = update->add_addresses[i];
+                if (// there is an address
+                    addr != NULL && addr->rrtype == dns_rrtype_aaaa &&
+                    // it's on the preferred prefix
+                    same_prefix(addr->rdata, &preferred_prefix->addr, preferred_prefix->width))
+                {
+                    // If this address is the same as the currently selected address, we don't need
+                    // to add it.  Preferred_addr is always a AAAA.
+                    if (preferred_addr != NULL && preferred_addr->rrtype == dns_rrtype_aaaa &&
+                        !memcmp(addr->rdata, preferred_addr->rdata, 16))
+                    {
+                        selected_addr = preferred_addr;
+                    }
+                    // But if the current address is not preferred, or there isn't one, then choose this
+                    // address.   If there's more than one address in the preferred prefix, we choose the first
+                    // one we come to.  SRP client should not be sending temporary addresses or deprecated addresses.
+                    else {
+                        selected_addr = addr;
+                        remove_preexisting = true;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    // If none of the new addresses were different than the current address and in the preferred prefix,
+    // keep the current address.
+    if (selected_addr == NULL &&
+        host->num_addresses > 0 && host->addresses[0] != NULL &&
+        update->remove_addresses != NULL && update->remove_addresses[0] == NULL)
+    {
+        selected_addr = host->addresses[0];
+        remove_preexisting = false;
+        update->registering_key = false;
+    }
+
+    // If the current address is being removed, and we don't have a new address in the preferred
+    // prefix, just use the first address in the add list.
+    if (selected_addr == NULL) {
+        for (i = 0; i < update->num_add_addresses; i++) {
+            if (update->add_addresses[i] != NULL) {
+                selected_addr = update->add_addresses[i];
+                remove_preexisting = true;
+                break;
+            }
+        }
+        // If there's no new address, see if there's an old address we didn't use before.
+        if (selected_addr == NULL) {
+            for (i = 1; i < host->num_addresses; i++) {
+                addr = host->addresses[i];
+                if (addr != NULL)
+                {
+                    selected_addr = addr;
+                    remove_preexisting = true;
+                    break;
+                }
+            }
+        }
+    }
+
+    // If we didn't find an address, we're just deleting the address.
+    if (selected_addr == NULL) {
+        INFO("start_host_update: No address was selected.");
+        remove_preexisting = true;
+        add_rdata = host->key_rdata;
+        add_rrtype = dns_rrtype_key;
+        add_rdlen = host->key_rdlen;
+        update->registering_key = true;
+    } else {
+        // We don't need to update the host address if it didn't change.
+        if (remove_preexisting) {
+            if (host->num_addresses > 0) {
+                INFO("start_host_update: Replacing existing address %p.", selected_addr);
+            } else {
+                INFO("start_host_update: Adding new address %p.", selected_addr);
+            }
+            add_rdata = selected_addr->rdata;
+            add_rrtype = selected_addr->rrtype;
+            add_rdlen = selected_addr->rdlen;
+        } else {
+            INFO("start_host_update: Retaining existing address.");
+        }
+        update->registering_key = false;
+        if (update->selected_addr != NULL) {
+            RELEASE_HERE(update->selected_addr, adv_address_finalize);
+        }
+        update->selected_addr = selected_addr;
+        RETAIN_HERE(update->selected_addr);
+    }
+    INFO("update->selected_addr = %p", update->selected_addr);
+
+    // At present, the DNSService* API doesn't provide a clean way to update a record registered with
+    // DNSServiceRegisterRecord, because it assumes that you'd only ever want to update it with a record
+    // of the same type.   We can't use that, so we just remove the record (if it exists) and then add
+    // the intended record.
+    if (remove_preexisting && host->txn != NULL) {
+        ioloop_dnssd_txn_release(host->txn);
+        host->txn = NULL;
+    }
+
+    // If we don't think we have a connection, make one.
+    if (host->txn == NULL) {
+        DNSServiceRef sdref;
+        err = DNSServiceCreateConnection(&sdref);
+        // In principle the only way this can fail is if the daemon isn't running.
+        if (err != kDNSServiceErr_NoError) {
+            // If this is a new update, just send a response to the client.  Otherwise maybe try to re-add it.
+            if (update->client != NULL) {
+                ERROR("DNSServiceCreateConnection: something went wrong: %d.", err);
+                update_failed(update, dns_rcode_servfail, true);
+            } else if (err == kDNSServiceErr_DefunctConnection || err == kDNSServiceErr_ServiceNotRunning) {
+                ERROR("DNSServiceCreateConnection: " PUB_S_SRP ".",
+                      err == kDNSServiceErr_DefunctConnection ? "defunct connection" : "service not running");
+                service_disconnected(host);
+            } else if (err != kDNSServiceErr_NoError) {
+                ERROR("DNSServiceCreateConnection: something went wrong: %d.", err);
+                wait_retry(host);
+            }
+            return;
+        }
+        INFO("Adding transaction %p", sdref);
+        host->txn = ioloop_dnssd_txn_add(sdref, host, host_txn_finalize_callback);
+        if (host->txn == NULL) {
+            ERROR("start_host_update: no memory for host transaction");
+            if (update->client != NULL) {
+                update_failed(update, dns_rcode_servfail, true);
+            } else {
+                wait_retry(host);
+            }
+            return;
+        }
+    }
+
+    if (add_rdata != NULL) {
+        INFO("start_host_update: DNSServiceRegisterRecord(%p %p %d %d %p %d %d %d %p %d %p %p)",
+             host ? (host->txn ? host->txn->sdref : 0) : 0, host ? &host->rref : 0,
+             kDNSServiceFlagsUnique | kDNSServiceFlagsNoAutoRename,
+             advertise_interface, host ? host->registered_name : 0,
+             add_rrtype, dns_qclass_in, add_rdlen, add_rdata, 3600,
+             register_host_completion, host ? host->txn : 0);
+        ioloop_dnssd_txn_set_aux_pointer(host->txn, update);
+        err = DNSServiceRegisterRecord(host->txn->sdref, &host->rref,
+                                       kDNSServiceFlagsUnique | kDNSServiceFlagsNoAutoRename,
+                                       advertise_interface, host->registered_name,
+                                       add_rrtype, dns_qclass_in, add_rdlen, add_rdata, 3600,
+                                       register_host_completion, host->txn);
+        if (err != kDNSServiceErr_NoError) {
+            ERROR("start_host_update: DNSServiceRegisterRecord failed on host: %d", err);
+            if (update->client != NULL) {
+                update_failed(update, dns_rcode_servfail, true);
+                return;
+            } else if (err == kDNSServiceErr_DefunctConnection || err == kDNSServiceErr_ServiceNotRunning) {
+                service_disconnected(host);
+                return;
+            }
+        }
+    }
+    // If we didn't have to do an add, start the service updates immediately.
+    else {
+        INFO("start_host_update: no host address rdata, so no host update");
+        start_service_updates(host);
+    }
+    return;
+}
+
+// When a host has no update in progress, and there is a client update ready to process, we need to analyze
+// the client update to see what work needs to be done.  This work is constructed as an translation from the
+// raw update sent by the client (host->clients) into a prepared update that can be used directly to
+// register the information with mDNSResponder.
+//
+// Normally a host will only have one prepared update in progress; however, if we lose our connection to
+// mDNSResponder, then we need to re-create the host advertisement.  If there was an update in progress when
+// this happened, we then need to reapply that as well.  In this case an update is constructed from the host, to
+// get the host into the intended state, and the in-progress update is pushed below that; when the host has
+// been re-created on the daemon, the pending update is popped back off the stack and restarted.
+static void
+prepare_update(adv_host_t *host)
+{
+    host_addr_t *addr;
+    int i, j;
+    service_instance_t *instance;
+    adv_address_t **remove_addrs = NULL;
+    int num_remove_addrs = 0;
+    adv_address_t **add_addrs = NULL;
+    int num_add_addrs = 0;
+    int num_update_instances = 0;
+    int num_add_instances = 0;
+    int num_remove_instances = 0;
+    adv_instance_vec_t *update_instances = NULL, *add_instances = NULL, *remove_instances = NULL;
+    client_update_t *client_update = host->clients;
+    adv_update_t *update = NULL;
+
+    // Work to do:
+    // - Figure out what address records to add and what address records to delete.
+    // - Because we can only have one address record at a time currently, figure out which address record we want
+    // - If we already have an address record published, and it's the same, do nothing
+    //   - else if we already have an address record published, and it's changed to a different address, do an update
+    //   - else if we have a new address record, publish it
+    //   - else publish the key to hold the name
+    // - Go through the set of service instances, identifying deletes, changes and adds
+    //   - We don't currently allow deletes, but what that would look like would be an instance with no SRV or TXT
+    //     record.
+    //   - What about a delete that keeps the name but un-advertises the service?   How would we indicate that?
+    //     Maybe if there's no service PTR for the service?
+    //   - Changes means that the contents of the text record changed, or the contents of the SRV record
+    //     changed (but not the hostname) or both.
+    //   - New means that we don't have a service with that service instance name on the host (and we previously
+    //     eliminated the possibility that it exists on some other host).
+
+    // Allocate the update structure.
+    update = calloc(1, sizeof *update);
+    if (update == NULL) {
+        ERROR("prepare_update: no memory for update.");
+        goto fail;
+    }
+
+    // The maximum number of addresses we could be deleting is all the ones the host currently has.
+    num_remove_addrs = host->num_addresses;
+    if (num_remove_addrs != 0) {
+        remove_addrs = calloc(num_remove_addrs, sizeof *remove_addrs);
+        // If we can't allocate space, just wait a bit.
+        if (remove_addrs == NULL) {
+            ERROR("prepare_update: no memory for remove_addrs");
+            goto fail;
+        }
+    } else {
+        remove_addrs = NULL;
+    }
+
+    num_add_addrs = 0;
+    for (addr = client_update->host->addrs; addr != NULL; addr = addr->next) {
+        num_add_addrs++;
+    }
+    add_addrs = calloc(num_add_addrs, sizeof *add_addrs);
+    if (add_addrs == NULL) {
+        ERROR("prepare_update: no memory for add_addrs");
+        goto fail;
+    }
+
+    // Copy all of the addresses in the update into add_addresses
+    num_add_addrs = 0;
+    for (addr = client_update->host->addrs; addr; addr = addr->next) {
+        adv_address_t *prepared_address = adv_address_create(addr, host);
+        if (prepared_address == NULL) {
+            ERROR("prepare_update: No memory for prepared address");
+            goto fail;
+        }
+        add_addrs[num_add_addrs++] = prepared_address;
+    }
+
+    // For every host address, see if it's in add_addresses.   If it's not, it needs to be removed.
+    // If it is, it doesn't need to be added.
+    if (num_remove_addrs != 0) {
+        memcpy(remove_addrs, host->addresses, num_remove_addrs * sizeof *remove_addrs);
+        for (i = 0; i < num_remove_addrs; i++) {
+            if (host->addresses[i] != NULL) {
+                remove_addrs[i] = host->addresses[i];
+                RETAIN_HERE(remove_addrs[i]);
+            }
+            for (j = 0; j < num_add_addrs; j++) {
+                // If the address is present in both places, remove it from the list of addresses to
+                // add, and also remove it from the list of addresses to remove.   When we're done,
+                // all that will be remaining in the list to remove will be addresses that weren't present
+                // in the add list.
+                if (remove_addrs[i] != NULL && add_addrs[j] != NULL &&
+                    add_addrs[j]->rrtype == remove_addrs[i]->rrtype &&
+                    !memcmp(&add_addrs[j]->rdata, remove_addrs[i]->rdata, remove_addrs[i]->rdlen))
+                {
+                    RELEASE_HERE(remove_addrs[i], adv_address_finalize);
+                    remove_addrs[i] = NULL;
+                    RELEASE_HERE(add_addrs[j], adv_address_finalize);
+                    add_addrs[j] = NULL;
+                }
+            }
+        }
+    }
+
+    // We can never update more instances than currently exist for this host.
+    num_update_instances = host->instances->num;
+    num_remove_instances = host->instances->num;
+
+    update_instances = adv_instance_vec_create(num_update_instances);
+    if (update_instances == NULL) {
+        ERROR("prepare_update: no memory for update_instances");
+        goto fail;
+    }
+    update_instances->num = num_update_instances;
+
+    // We aren't actually deleting any instances, but...
+    remove_instances = adv_instance_vec_create(num_remove_instances);
+    if (remove_instances == NULL) {
+        ERROR("prepare_update: no memory for remove_instances");
+        goto fail;
+    }
+    remove_instances->num = num_remove_instances;
+
+    // The number of instances to add can be as many as there are instances in the update.
+    num_add_instances = 0;
+    for (instance = client_update->instances; instance; instance = instance->next) {
+        num_add_instances++;
+    }
+    add_instances = adv_instance_vec_create(num_add_instances);
+    if (add_instances == NULL) {
+        ERROR("prepare_update: no memory for add_instances");
+        goto fail;
+    }
+
+    // Convert all of the instances in the client update to adv_instance_t structures for easy comparison.
+    // Any that are unchanged will have to be freed--oh well.
+    i = 0;
+    for (instance = client_update->instances; instance != NULL; instance = instance->next) {
+        adv_instance_t *prepared_instance = adv_instance_create(instance, host, update);
+        if (prepared_instance == NULL) {
+            // prepare_instance logs.
+            goto fail;
+        }
+        if (i >= num_add_instances) {
+            ERROR("prepare_update: while preparing client update instances, i >= num_add_instances");
+            goto fail;
+        }
+        add_instances->vec[i++] = prepared_instance;
+    }
+    add_instances->num = i;
+
+    // The instances in the update are now in add_instances.  If they are updates, move them to update_instances.
+    // If they are unchanged, free them and null them out.   If they are adds, leave them.
+    for (i = 0; i < num_add_instances; i++) {
+        adv_instance_t *add_instance = add_instances->vec[i];
+
+        for (j = 0; j < host->instances->num; j++) {
+            adv_instance_t *host_instance = host->instances->vec[j];
+
+            // See if the instance names match.
+            if (!strcmp(add_instance->instance_name, host_instance->instance_name) &&
+                !strcmp(add_instance->service_type, host_instance->service_type))
+            {
+                // If the rdata is the same, it's not an add or an update.
+                if (add_instance->txt_length == host_instance->txt_length &&
+                    add_instance->port == host_instance->port &&
+                    (add_instance->txt_length == 0 ||
+                     !memcmp(add_instance->txt_data, host_instance->txt_data, add_instance->txt_length)))
+                {
+                    RELEASE_HERE(add_instance, adv_instance_finalize);
+                } else {
+                    // Implicit RETAIN/RELEASE
+                    update_instances->vec[j] = add_instance;
+                }
+                add_instances->vec[i] = NULL;
+                break;
+            }
+        }
+    }
+
+    // At this point we have figured out all the work we need to do, so hang it off an update structure.
+    update->host = host;
+    update->client = client_update;
+    host->clients = client_update->next;
+    update->num_remove_addresses = num_remove_addrs;
+    update->remove_addresses = remove_addrs;
+    update->num_add_addresses = num_add_addrs;
+    update->add_addresses = add_addrs;
+    update->remove_instances = remove_instances;
+    update->add_instances = add_instances;
+    update->update_instances = update_instances;
+    update->host_lease = client_update->host_lease;
+    update->key_lease = client_update->key_lease;
+
+    update->next = host->updates;
+    host->updates = update;
+
+    start_host_update(host);
+    return;
+
+fail:
+    if (remove_addrs != NULL) {
+        // Addresses in remove_addrs are owned by the host and don't need to be freed.
+        free(remove_addrs);
+    }
+    if (add_addrs != NULL) {
+        // Addresses in add_addrs are ours, so we have to free them.
+        for (i = 0; i < num_add_addrs; i++) {
+            if (add_addrs[i] != NULL) {
+                RELEASE_HERE(add_addrs[i], adv_address_finalize);
+            }
+        }
+        free(add_addrs);
+    }
+    if (add_instances != NULL) {
+        RELEASE_HERE(add_instances, adv_instance_vec_finalize);
+        add_instances = NULL;
+    }
+    if (remove_instances != NULL) {
+        RELEASE_HERE(remove_instances, adv_instance_vec_finalize);
+        remove_instances = NULL;
+    }
+    if (update_instances != NULL) {
+        RELEASE_HERE(update_instances, adv_instance_vec_finalize);
+        update_instances = NULL;
+    }
+    if (update) {
+        if (update->client != NULL) {
+            update->client->next = host->clients;
+            host->clients = update->client;
+        }
+        free(update);
+    }
+
+    // Try again sometime later.
+    wait_retry(host);
+}
+
+typedef enum { missed, match, conflict } instance_outcome_t;
+static instance_outcome_t
+compare_instance(adv_instance_t *instance,
+                 dns_host_description_t *new_host, adv_host_t *host,
+                 char *instance_name, char *service_type)
+{
+    if (instance == NULL) {
+        return missed;
+    }
+    if (!strcmp(instance_name, instance->instance_name) && !strcmp(service_type, instance->service_type)) {
+        if (!dns_names_equal_text(new_host->name, host->name)) {
+            return conflict;
+        }
+        return match;
+    }
+    return missed;
+}
+
+bool
+srp_update_start(comm_t *connection, dns_message_t *parsed_message, message_t *raw_message,
+                 dns_host_description_t *new_host, service_instance_t *instances, service_t *services,
+                 dns_name_t *update_zone, uint32_t lease_time, uint32_t key_lease_time)
+{
+    adv_host_t *host, **p_hosts = NULL;
+    char pres_name[DNS_MAX_NAME_SIZE_ESCAPED + 1];
+    int i;
+    service_instance_t *new_instance, *client_instance;
+    instance_outcome_t outcome = missed;
+    adv_update_t *update;
+    client_update_t *client_update, **p_client_update;
+    char instance_name[DNS_MAX_LABEL_SIZE_ESCAPED + 1];
+    char service_type[DNS_MAX_LABEL_SIZE_ESCAPED * 2 + 2];
+    uint32_t key_id = 0;
+    char new_host_name[DNS_MAX_NAME_SIZE_ESCAPED + 1];
+    host_addr_t *addr;
+    const bool remove = lease_time == 0;
+    const char *updatestr = lease_time == 0 ? "remove" : "update";
+    dns_name_print(new_host->name, new_host_name, sizeof new_host_name);
+
+    // Compute a checksum on the key, ignoring up to three bytes at the end.
+    for (i = 0; i < new_host->key->data.key.len; i += 4) {
+        key_id += ((new_host->key->data.key.key[i] << 24) | (new_host->key->data.key.key[i + 1] << 16) |
+                   (new_host->key->data.key.key[i + 2] << 8) | (new_host->key->data.key.key[i + 3]));
+    }
+
+    // Log the update info.
+    INFO("srp_update_start: host update for " PRI_S_SRP ", key id %" PRIx32, new_host_name, key_id);
+    for (addr = new_host->addrs; addr != NULL; addr = addr->next) {
+        if (addr->rr.type == dns_rrtype_a) {
+            IPv4_ADDR_GEN_SRP(&addr->rr.data.a.s_addr, addr_buf);
+            INFO("srp_update_start: host " PUB_S_SRP " for " PRI_S_SRP ", address " PRI_IPv4_ADDR_SRP, updatestr,
+                 new_host_name, IPv4_ADDR_PARAM_SRP(&addr->rr.data.a.s_addr, addr_buf));
+        } else {
+            SEGMENTED_IPv6_ADDR_GEN_SRP(addr->rr.data.aaaa.s6_addr, addr_buf);
+            INFO("srp_update_start: host " PUB_S_SRP " for " PRI_S_SRP ", address " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                 updatestr, new_host_name, SEGMENTED_IPv6_ADDR_PARAM_SRP(addr->rr.data.aaaa.s6_addr, addr_buf));
+        }
+    }
+    for (new_instance = instances; new_instance != NULL; new_instance = new_instance->next) {
+        extract_instance_name(instance_name, sizeof instance_name, service_type, sizeof service_type, new_instance);
+        INFO("srp_update_start: host " PUB_S_SRP " for " PRI_S_SRP ", instance name " PRI_S_SRP ", type " PRI_S_SRP
+             ", port %d", updatestr, new_host_name, instance_name, service_type,
+             new_instance->srv != NULL ? new_instance->srv->data.srv.port : -1);
+    }
+
+    // SRP doesn't currently support removal.   I think it needs to, but I'm going to mostly leave that
+    // out of this code for now.
+
+    // Look for matching service instance names.   A service instance name that matches, but has a different
+    // hostname, means that there is a conflict.   We have to look through all the entries; the presence of
+    // a matching hostname doesn't mean we are done UNLESS there's a matching service instance name pointing
+    // to that hostname.
+    for (host = hosts; host; host = host->next) {
+        // We need to look for matches both in the registered instances for this registration, and also in
+        // the list of new instances, in case we get a duplicate update while a previous update is in progress.
+        for (new_instance = instances; new_instance; new_instance = new_instance->next) {
+            extract_instance_name(instance_name, sizeof instance_name, service_type, sizeof service_type, new_instance);
+
+            // First check for a match or conflict in the host itself.
+            for (i = 0; i < host->instances->num; i++) {
+                outcome = compare_instance(host->instances->vec[i], new_host, host,
+                                           instance_name, service_type);
+                if (outcome != missed) {
+                    goto found_something;
+                }
+            }
+            // Then look for the same thing in any subsequent updates that have been baked.
+            for (update = host->updates; update; update = update->next) {
+                for (i = 0; i < update->add_instances->num; i++) {
+                    outcome = compare_instance(update->add_instances->vec[i], new_host, host,
+                                               instance_name, service_type);
+                    if (outcome != missed) {
+                        goto found_something;
+                    }
+                }
+            }
+            // Finally, look for it in any updates that _haven't_ been baked.
+            for (client_update = host->clients; client_update; client_update = client_update->next) {
+                for (client_instance = client_update->instances; client_instance;
+                     client_instance = client_instance->next) {
+                    if (dns_names_equal(client_instance->name, new_instance->name)) {
+                        if (!dns_names_equal_text(new_host->name, host->name)) {
+                            outcome = conflict;
+                        } else {
+                            outcome = match;
+                        }
+                        goto found_something;
+                    }
+                }
+            }
+        }
+    }
+found_something:
+    if (outcome == conflict) {
+        ERROR("srp_update_start: service instance name " PRI_S_SRP "/" PRI_S_SRP " already pointing to host "
+              PRI_S_SRP ", not host " PRI_S_SRP, instance_name, service_type, host->name, new_host_name);
+        advertise_finished(connection, raw_message, dns_rcode_yxdomain, NULL);
+        goto cleanup;
+    }
+
+    // If we fall off the end looking for a matching service instance, there isn't a matching
+    // service instance, but there may be a matching host, so look for that.
+    if (outcome == missed) {
+        for (p_hosts = &hosts; *p_hosts; p_hosts = &host->next) {
+            host = *p_hosts;
+            if (dns_names_equal_text(new_host->name, host->name)) {
+                if (key_id == host->key_id && dns_keys_rdata_equal(new_host->key, &host->key)) {
+                    outcome = match;
+                    break;
+                }
+                ERROR("srp_update_start: update for host " PRI_S_SRP " has key id %" PRIx32
+                      " which doesn't match host key id %" PRIx32 ".",
+                      host->name, key_id, host->key_id);
+                advertise_finished(connection, raw_message, dns_rcode_yxdomain, NULL);
+                goto cleanup;
+            }
+        }
+    } else {
+        if (key_id != host->key_id || !dns_keys_rdata_equal(new_host->key, &host->key)) {
+            ERROR("srp_update_start: new host with name " PRI_S_SRP " and key id %" PRIx32
+                  " conflicts with existing host %s with key id %" PRIx32,
+                  new_host_name, key_id, host->name, host->key_id);
+            advertise_finished(connection, raw_message, dns_rcode_yxdomain, NULL);
+            goto cleanup;
+        }
+    }
+
+    // If we didn't find a matching host, we can make a new one.   When we create it, it just has
+    // a name and no records.  The update that we then construct will have the missing records.
+    // We don't want to do this for a remove, obviously.
+    if (outcome == missed) {
+        if (remove) {
+            ERROR("Remove for host " PRI_S_SRP " which doesn't exist.", new_host_name);
+            advertise_finished(connection, raw_message, dns_rcode_nxdomain, NULL);
+            goto cleanup;
+        }
+
+        host = calloc(1, sizeof *host);
+        if (host == NULL) {
+            ERROR("srp_update_start: no memory for host data structure.");
+            advertise_finished(connection, raw_message, dns_rcode_servfail, NULL);
+            goto cleanup;
+        }
+        host->instances = adv_instance_vec_create(0);
+        if (host->instances == NULL) {
+            ERROR("srp_update_start: no memory for host instance vector.");
+            advertise_finished(connection, raw_message, dns_rcode_servfail, NULL);
+            host_finalize(host);
+            goto cleanup;
+        }
+
+        host->retry_wakeup = ioloop_wakeup_create();
+        if (host->retry_wakeup != NULL) {
+            host->lease_wakeup = ioloop_wakeup_create();
+        }
+        if (host->lease_wakeup == NULL) {
+            ERROR("srp_update_start: no memory for wake event on host");
+            advertise_finished(connection, raw_message, dns_rcode_servfail, NULL);
+            host_finalize(host);
+            goto cleanup;
+        }
+        dns_name_print(new_host->name, pres_name, sizeof pres_name);
+        host->name = strdup(pres_name);
+        if (host->name == NULL) {
+            host_finalize(host);
+            ERROR("srp_update_start: no memory for hostname.");
+            advertise_finished(connection, raw_message, dns_rcode_servfail, NULL);
+            goto cleanup;
+        }
+        host->key = *new_host->key;
+#ifndef __clang_analyzer__
+        // Normally this would be invalid, but we never use the name of the key record.
+        host->key.name = NULL;
+#endif
+        host->key_rdlen = new_host->key->data.key.len + 4;
+        host->key_rdata = malloc(host->key_rdlen);
+        if (host->key_rdata == NULL) {
+            host_finalize(host);
+            ERROR("srp_update_start: no memory for host key.");
+            advertise_finished(connection, raw_message, dns_rcode_servfail, NULL);
+            goto cleanup;
+        }
+        memcpy(host->key_rdata, &new_host->key->data.key.flags, 2);
+        host->key_rdata[2] = new_host->key->data.key.protocol;
+        host->key_rdata[3] = new_host->key->data.key.algorithm;
+        memcpy(&host->key_rdata[4], new_host->key->data.key.key, new_host->key->data.key.len);
+        host->key.data.key.key = &host->key_rdata[4];
+        host->key_id = key_id;
+
+        // Tack this on to the end of the list.  The if test is because the optimizer doesn't notice
+        // that p_hosts can never be null here--it will always be pointing to the end of the list of
+        // hosts if we get here.
+        if (p_hosts != NULL) {
+            *p_hosts = host;
+        }
+        p_hosts = NULL;
+    }
+
+    // See if this is a retransmission.
+    for (update = host->updates; update; update = update->next) {
+        if (update->client != NULL &&
+            update->client->message->wire.id == raw_message->wire.id) {
+        retransmission:
+            INFO("srp_update_start: dropping retransmission of in-progress update for host " PRI_S_SRP, host->name);
+        cleanup:
+            srp_update_free_parts(instances, NULL, services, new_host);
+            dns_message_free(parsed_message);
+            return true;
+        }
+    }
+    // Do the same for client updates.
+    for (p_client_update = &host->clients; *p_client_update; p_client_update = &client_update->next) {
+        client_update = *p_client_update;
+        if (client_update->message->wire.id == raw_message->wire.id) {
+            goto retransmission;
+        }
+    }
+
+    // If this is a remove, just remove the host.
+    if (remove) {
+        lease_callback(host);
+        advertise_finished(connection, raw_message, dns_rcode_noerror, NULL);
+        goto cleanup;
+    }
+
+    // At this point we have an update and a host to which to apply it.  We may already be doing an earlier
+    // update, or not.  Create a client update structure to hold the communication, so that when we are done,
+    // we can respond.
+    client_update = calloc(1, sizeof *client_update);
+    if (client_update == NULL) {
+        ERROR("srp_update_start: no memory for host data structure.");
+        advertise_finished(connection, raw_message, dns_rcode_servfail, NULL);
+        goto cleanup;
+    }
+
+    if (outcome == missed) {
+        INFO("srp_update_start: New host " PRI_S_SRP ", key id %" PRIx32 , host->name, host->key_id);
+    } else {
+        if (host->registered_name != host->name) {
+            INFO("srp_update_start: Renewing host " PRI_S_SRP ", alias %s, key id %" PRIx32,
+                 host->name, host->registered_name, host->key_id);
+        } else {
+            INFO("srp_update_start: Renewing host " PRI_S_SRP ", key id %" PRIx32, host->name, host->key_id);
+        }
+    }
+
+    if (host->registered_name == NULL) {
+        host->registered_name = host->name;
+    }
+
+    client_update->connection = connection;
+    ioloop_comm_retain(client_update->connection);
+    client_update->parsed_message = parsed_message;
+    client_update->message = raw_message;
+    ioloop_message_retain(client_update->message);
+    client_update->host = new_host;
+    client_update->instances = instances;
+    client_update->services = services;
+    client_update->update_zone = update_zone;
+    if (lease_time < max_lease_time) {
+        client_update->host_lease = lease_time;
+    } else {
+        client_update->host_lease = max_lease_time;
+    }
+    if (key_lease_time < max_lease_time * 7) {
+        client_update->key_lease = key_lease_time;
+    } else {
+        client_update->key_lease = max_lease_time * 7;
+    }
+    *p_client_update = client_update;
+
+    // If we aren't already applying an update to this host, apply this update now.
+    if (host->updates == NULL) {
+        INFO("srp_update_start: No ongoing host update: preparing this update to be applied.");
+        prepare_update(host);
+    } else {
+        INFO("srp_update_start: waiting for existing update to complete on host " PRI_S_SRP " before applying another.",
+             host->name);
+    }
+    return true;
+}
+
+#if defined(IOLOOP_MACOS)
+static bool
+adv_proxy_enable(xpc_object_t request, xpc_connection_t connection)
+{
+    srp_xpc_client_t *client, **p_client;
+    xpc_object_t response;
+    int err = kDNSSDAdvertisingProxyStatus_NoError;
+
+    response = xpc_dictionary_create_reply(request);
+    if (response == NULL) {
+        ERROR("adv_proxy_enable: Unable to create reply dictionary.");
+        return false;
+    }
+
+    client = calloc(1, sizeof(*client));
+    if (client == NULL) {
+        ERROR("adv_proxy_enable: unable to allocate client state structure.");
+        err = kDNSSDAdvertisingProxyStatus_NoMemory;
+        goto out;
+    }
+    if (srp_wanted == NULL) {
+        srp_wanted = calloc(1, sizeof(*srp_wanted));
+        if (srp_wanted == NULL) {
+            free(client);
+            ERROR("adv_proxy_enable: unable to allocate srp_wanted structure.");
+            err = kDNSSDAdvertisingProxyStatus_NoMemory;
+            goto out;
+        }
+        srp_wanted->transaction = os_transaction_create("com.apple.srp-mdns-proxy.ostransaction");
+        INFO("Wanted.");
+        thread_network_startup();
+    }
+    RETAIN_HERE(srp_wanted);
+
+    client->connection = connection;
+    xpc_retain(client->connection);
+    client->enabler = true;
+
+    INFO("adv_proxy_enable: connection from client: %p", client);
+
+    // Find the end of the list.
+    for (p_client = &srp_xpc_clients; *p_client != NULL; p_client = &(*p_client)->next) {
+    }
+
+    *p_client = client;
+out:
+    xpc_dictionary_set_uint64(response, kDNSAdvertisingProxyResponseStatus, err);
+    xpc_connection_send_message(connection, response);
+    xpc_release(response);
+    if (err == kDNSSDAdvertisingProxyStatus_NoError) {
+        return true;
+    }
+    return false;
+}
+
+static void
+srp_wanted_finalize(srp_wanted_state_t *__unused wanted)
+{
+    INFO("adv_proxy_enable: No longer wanted.");
+    os_release(srp_wanted->transaction);
+    free(srp_wanted);
+    srp_wanted = NULL;
+    thread_network_shutdown();
+}
+
+static void
+adv_xpc_connection_delete(xpc_connection_t connection)
+{
+    srp_xpc_client_t **p_client, *client;
+
+    for (p_client = &srp_xpc_clients; *p_client != NULL; ) {
+        client = *p_client;
+        if (client->connection == connection) {
+            xpc_release(client->connection);
+            if (client->enabler) {
+                RELEASE_HERE(srp_wanted, srp_wanted_finalize);
+            }
+            *p_client = client->next;
+            INFO("adv_xpc_connection_delete: deleting client: %p", client);
+            free(client);
+            return;
+        }
+        p_client = &(*p_client)->next;
+    }
+}
+
+void
+adv_xpc_disconnect(void)
+{
+    srp_xpc_client_t *client;
+
+    for (client = srp_xpc_clients; client != NULL; client = client->next) {
+        if (client->connection != NULL && !client->connection_canceled) {
+            INFO("adv_xpc_disconnect: disconnecting " PUB_S_SRP "client: %p", client->enabler ? "enabler " : "",
+                 client);
+            client->connection_canceled = true;
+            xpc_connection_cancel(client->connection);
+        }
+    }
+}
+
+static bool adv_xpc_message(xpc_connection_t NULLABLE connection, xpc_object_t NULLABLE request);
+
+static wakeup_t *adv_xpc_wakeup;
+
+static void
+adv_xpc_restart(void *__unused context)
+{
+    xpc_listener = ioloop_create_xpc_service(kDNSAdvertisingProxyService, adv_xpc_message);
+    if (xpc_listener == NULL) {
+        ioloop_add_wake_event(adv_xpc_wakeup, NULL, adv_xpc_restart, NULL, 10000);
+    }
+}
+
+static bool
+adv_xpc_list_services(xpc_connection_t request, xpc_object_t connection)
+{
+    xpc_object_t instances = xpc_array_create(NULL, 0);
+    adv_host_t *host;
+    xpc_object_t response;
+    int i;
+    char addrbuf[INET6_ADDRSTRLEN];
+    int64_t now = ioloop_timenow();
+    bool sent = false;
+    adv_instance_t *instance = NULL;
+
+    if (instances == NULL) {
+        ERROR("adv_xpc_list_services: Unable to create service array");
+        return false;
+    }
+
+    response = xpc_dictionary_create_reply(request);
+    if (response == NULL) {
+        ERROR("adv_xpc_list_services: Unable to create reply dictionary.");
+        return false;
+    }
+    xpc_dictionary_set_uint64(response, kDNSAdvertisingProxyResponseStatus, kDNSSDAdvertisingProxyStatus_NoError);
+
+    for (host = hosts; host != NULL; host = host->next) {
+        if (host->num_addresses > 0) {
+            inet_ntop(AF_INET6, host->addresses[0]->rdata, addrbuf, sizeof(addrbuf));
+        }
+        if (instances == NULL) {
+            ERROR("adv_xpc_list_services: failed to allocate instance array for " PRI_S_SRP, host->name);
+            goto fail;
+        }
+        sent = false;
+        xpc_object_t *dict;
+        for (i = 0; i < host->instances->num; i++) {
+            if (host->instances->vec[i] != NULL) {
+                instance = host->instances->vec[i];
+            send_host:
+                dict = xpc_dictionary_create(NULL, NULL, 0);
+                if (dict == NULL) {
+                    ERROR("adv_xpc_list_services: failed to allocate instance dictionary for " PRI_S_SRP, host->name);
+                    goto fail;
+                }
+                xpc_dictionary_set_string(dict, "hostname", host->name);
+                xpc_dictionary_set_string(dict, "regname", host->registered_name);
+                if (instance) {
+                    char portbuf[6];
+                    xpc_dictionary_set_string(dict, "name", instance->instance_name);
+                    xpc_dictionary_set_string(dict, "type", instance->service_type);
+                    snprintf(portbuf, sizeof(portbuf), "%u", instance->port);
+                    xpc_dictionary_set_string(dict, "port", portbuf);
+                    xpc_dictionary_set_data(dict, "txt", instance->txt_data, instance->txt_length);
+                }
+                if (host->num_addresses > 0) {
+                    xpc_dictionary_set_string(dict, "address", addrbuf);
+                }
+                xpc_dictionary_set_int64(dict, "lease", host->lease_expiry >= now ? host->lease_expiry - now : -1);
+                xpc_array_append_value(instances, dict);
+                xpc_release(dict);
+                sent = true;
+            }
+        }
+        if (!sent) {
+            instance = NULL;
+            goto send_host;
+        }
+    }
+
+    xpc_dictionary_set_value(response, "instances", instances);
+    xpc_release(instances);
+    xpc_connection_send_message(connection, response);
+    xpc_release(response);
+    return true;
+fail:
+    if (instances != NULL) {
+        xpc_release(instances);
+    }
+    if (response != NULL) {
+        xpc_release(response);
+    }
+    return false;
+}
+
+static bool
+adv_xpc_block_service(xpc_connection_t request, xpc_object_t connection, bool enable)
+{
+    xpc_object_t response;
+    int status = kDNSSDAdvertisingProxyStatus_NoError;
+    extern srp_proxy_listener_state_t *srp_listener;
+
+    response = xpc_dictionary_create_reply(request);
+    if (response == NULL) {
+        ERROR("adv_xpc_list_services: Unable to create reply dictionary.");
+        return false;
+    }
+
+    if (enable) {
+        if (srp_listener != NULL) {
+            srp_proxy_listener_cancel(srp_listener);
+            srp_listener = NULL;
+        } else {
+            status = kDNSSDAdvertisingProxyStatus_UnknownErr;
+        }
+    } else {
+        if (srp_listener == NULL) {
+            partition_start_srp_listener();
+        } else {
+            status = kDNSSDAdvertisingProxyStatus_UnknownErr;
+        }
+    }
+
+    xpc_dictionary_set_uint64(response, kDNSAdvertisingProxyResponseStatus, status);
+    xpc_connection_send_message(connection, response);
+    xpc_release(response);
+    return true;
+}
+
+static bool
+adv_xpc_regenerate_ula(xpc_connection_t request, xpc_object_t connection)
+{
+    xpc_object_t response;
+    int status = kDNSSDAdvertisingProxyStatus_NoError;
+
+    response = xpc_dictionary_create_reply(request);
+    if (response == NULL) {
+        ERROR("adv_xpc_list_services: Unable to create reply dictionary.");
+        return false;
+    }
+
+    partition_stop_advertising_pref_id();
+    thread_network_shutdown();
+    ula_generate();
+    thread_network_startup();
+
+    xpc_dictionary_set_uint64(response, kDNSAdvertisingProxyResponseStatus, status);
+    xpc_connection_send_message(connection, response);
+    xpc_release(response);
+
+    return true;
+}
+
+void
+srp_mdns_flush(void)
+{
+    adv_host_t *host, *host_next;
+
+    INFO("srp_mdns_flush: flushing all host entries.");
+    for (host = hosts; host; host = host_next) {
+        INFO("srp_mdns_flush: Flushing services and host entry for " PRI_S_SRP " (" PRI_S_SRP ")",
+             host->name, host->registered_name);
+        // Get rid of the updates before calling lease_callback, which will fail if update is not NULL.
+        if (host->updates != NULL) {
+            adv_update_t *update_next, *update = host->updates->next;
+            update_failed(host->updates, dns_rcode_refused, false);
+            while (update != NULL) {
+                update_next = update->next;
+                update->host = NULL;
+                update_finalize(update);
+                update = update_next;
+            }
+            host->updates = NULL;
+        }
+        // Get rid of clients for the same reason.
+        if (host->clients != NULL) {
+            client_update_t *client, *client_next;
+            for (client = host->clients; client; client = client_next) {
+                client_next = client->next;
+                client_finalize(client);
+            }
+            host->clients = NULL;
+        }
+        host_next = host->next;
+        host_finalize(host);
+    }
+    hosts = NULL;
+}
+
+static bool
+adv_xpc_message(xpc_connection_t connection, xpc_object_t request)
+{
+    int pid = -1;
+    int uid = -1;
+
+    // This means that the listener failed for some reason.   Try again in ten seconds.
+    if (connection == NULL && request == NULL) {
+        if (adv_xpc_wakeup == NULL) {
+            adv_xpc_wakeup = ioloop_wakeup_create();
+            if (adv_xpc_wakeup == NULL) {
+                INFO("adv_xpc_message: can't create a wakeup to try to recover.");
+                return false;
+            }
+        } else {
+            ioloop_cancel_wake_event(adv_xpc_wakeup);
+        }
+        ioloop_add_wake_event(adv_xpc_wakeup, NULL, adv_xpc_restart, NULL, 10000);
+        return false;
+    }
+
+    if (connection == NULL) {
+        INFO("adv_xpc_message: disconnected.");
+        return false;
+    }
+
+    pid = xpc_connection_get_pid(connection);
+    uid = xpc_connection_get_euid(connection);
+
+    if (request == NULL) {
+        INFO("adv_xpc_message: Client uid %d pid %d disconnected.", uid, pid);
+        adv_xpc_connection_delete(connection);
+        return false;
+    }
+
+    const char *message_type = xpc_dictionary_get_string(request, kDNSAdvertisingProxyCommand);
+
+    if (message_type == NULL) {
+        ERROR("Client uid %d pid %d sent a request with no message type.", uid, pid);
+        adv_xpc_connection_delete(connection);
+        // Close the connection
+        return false;
+    }
+
+    if (!strcmp(message_type, kDNSAdvertisingProxyEnable)) {
+        INFO("adv_xpc_message: Client uid %d pid %d sent a " PUB_S_SRP " request.", uid, pid, message_type);
+        return adv_proxy_enable(request, connection);
+    } else if (!strcmp(message_type, kDNSAdvertisingProxyListServiceTypes)) {
+        INFO("adv_xpc_message: Client uid %d pid %d sent a " PUB_S_SRP " request.", uid, pid, message_type);
+    } else if (!strcmp(message_type, kDNSAdvertisingProxyListServices)) {
+        INFO("adv_xpc_message: Client uid %d pid %d sent a " PUB_S_SRP " request.", uid, pid, message_type);
+        return adv_xpc_list_services(request, connection);
+    } else if (!strcmp(message_type, kDNSAdvertisingProxyListHosts)) {
+        INFO("adv_xpc_message: Client uid %d pid %d sent a " PUB_S_SRP " request.", uid, pid, message_type);
+    } else if (!strcmp(message_type, kDNSAdvertisingProxyGetHost)) {
+        INFO("adv_xpc_message: Client uid %d pid %d sent a " PUB_S_SRP " request.", uid, pid, message_type);
+    } else if (!strcmp(message_type, kDNSAdvertisingProxyFlushEntries)) {
+        INFO("adv_xpc_message: Client uid %d pid %d sent a %s request.", uid, pid, message_type);
+        srp_mdns_flush();
+    } else if (!strcmp(message_type, kDNSAdvertisingProxyBlockService)) {
+        INFO("adv_xpc_message: Client uid %d pid %d sent a " PUB_S_SRP " request.", uid, pid, message_type);
+        adv_xpc_block_service(request, connection, true);
+    } else if (!strcmp(message_type, kDNSAdvertisingProxyUnblockService)) {
+        INFO("adv_xpc_message: Client uid %d pid %d sent a " PUB_S_SRP " request.", uid, pid, message_type);
+        adv_xpc_block_service(request, connection, false);
+    } else if (!strcmp(message_type, kDNSAdvertisingProxyRegenerateULA)) {
+        INFO("adv_xpc_message: Client uid %d pid %d sent a " PUB_S_SRP " request.", uid, pid, message_type);
+        adv_xpc_regenerate_ula(request, connection);
+    } else {
+        ERROR("Client uid %d pid %d sent a request with unknown message type " PUB_S_SRP ".", uid, pid, message_type);
+        // Close the connection
+        adv_xpc_connection_delete(connection);
+        return false;
+    }
+
+    xpc_object_t response;
+    response = xpc_dictionary_create_reply(request);
+    if (response == NULL) {
+        ERROR("adv_xpc_message: Unable to create reply dictionary.");
+        return false;
+    }
+    xpc_dictionary_set_uint64(response, kDNSAdvertisingProxyResponseStatus, kDNSSDAdvertisingProxyStatus_NoError);
+    xpc_connection_send_message(connection, response);
+    xpc_release(response);
+    return false;
+}
+#endif
+
+static void
+usage(void)
+{
+    ERROR("srp-mdns-proxy [--max-lease-time <seconds>] [--log-stderr]");
+    exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+    int i;
+    char *end;
+    int log_stderr = false;
+
+    for (i = 1; i < argc; i++) {
+        if (!strcmp(argv[i], "--max-lease-time")) {
+            if (i + 1 == argc) {
+                usage();
+            }
+            max_lease_time = (uint32_t)strtoul(argv[i + 1], &end, 10);
+            if (end == argv[i + 1] || end[0] != 0) {
+                usage();
+            }
+            i++;
+        } else if (!strcmp(argv[i], "--log-stderr")) {
+            log_stderr = true;
+        } else {
+            usage();
+        }
+    }
+
+    OPENLOG(log_stderr);
+    INFO("--------------------------------srp-mdns-proxy starting--------------------------------");
+
+    if (!ioloop_init()) {
+        return 1;
+    }
+
+    if (!start_icmp_listener()) {
+        return 1;
+    }
+
+#ifdef IOLOOP_MACOS
+    // On MacOS, drop privileges once we have the ICMP listener.
+#ifdef DROP_PRIVILEGES
+    int ret;
+    ssize_t bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+    if (bufsize < 0) {
+        bufsize = 1024;
+    }
+    char *getpwnam_r_buf = malloc(bufsize);
+    struct passwd mdnsresponder_pwd;
+    int mdnsresponder_uid = 65; // This is what's in /etc/passwd now.
+    if (getpwnam_r_buf != NULL) {
+        struct passwd *result;
+        ret = getpwnam_r("_mdnsresponder", &mdnsresponder_pwd, getpwnam_r_buf, bufsize, &result);
+        if (ret < 0 || result == NULL) {
+            ERROR("getpwnam_r failed: " PUB_S_SRP, strerror(ret));
+        } else {
+            mdnsresponder_uid = mdnsresponder_pwd.pw_uid;
+        }
+        free(getpwnam_r_buf);
+        endpwent();
+    } else {
+        ERROR("Unable to allocate getpwnam_r buffer.");
+    }
+    ret = setuid(mdnsresponder_uid);
+    if (ret < 0) {
+        ERROR("setuid failed: " PUB_S_SRP, strerror(errno));
+    }
+#endif
+
+    // On MacOS, we can set up an XPC service to check on registrations and also to start the service
+    // from launchd.
+    xpc_listener = ioloop_create_xpc_service(kDNSAdvertisingProxyService, adv_xpc_message);
+    if (xpc_listener == NULL) {
+        return 1;
+    }
+#endif
+
+    // We require one open file per service and one per instance.
+    struct rlimit limits;
+    if (getrlimit(RLIMIT_NOFILE, &limits) < 0) {
+        ERROR("getrlimit failed: " PUB_S_SRP, strerror(errno));
+        return 1;
+    }
+
+    if (limits.rlim_cur < 1024) {
+        if (limits.rlim_max < 1024) {
+            INFO("getrlimit: file descriptor hard limit is %llu", limits.rlim_max);
+            if (limits.rlim_cur != limits.rlim_max) {
+                limits.rlim_cur = limits.rlim_max;
+            }
+        } else {
+            limits.rlim_cur = 1024;
+        }
+        if (setrlimit(RLIMIT_NOFILE, &limits) < 0) {
+            ERROR("setrlimit failed: " PUB_S_SRP, strerror(errno));
+        }
+    }
+
+    do {
+        int something = 0;
+        ioloop();
+        INFO("dispatched %d events.", something);
+    } while (1);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 120
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/srp-mdns-proxy.h b/ServiceRegistration/srp-mdns-proxy.h
new file mode 100644 (file)
index 0000000..c4a7d96
--- /dev/null
@@ -0,0 +1,171 @@
+/* srp-mdns-proxy.h
+ *
+ * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains structure definitions used by the SRP Advertising Proxy.
+ */
+
+typedef struct adv_instance adv_instance_t;
+typedef struct adv_address_registration adv_address_t;
+typedef struct adv_host adv_host_t;
+typedef struct adv_update adv_update_t;
+typedef struct client_update client_update_t;
+typedef struct adv_instance_vec adv_instance_vec_t;
+typedef struct adv_host_vec adv_host_vec_t;
+typedef struct adv_address_vec adv_address_vec_t;
+
+
+struct adv_instance {
+    int ref_count;
+    dnssd_txn_t *NULLABLE txn;     // Outstanding mDNSServiceRegister transaction, if any.
+    adv_host_t *NONNULL host;      // Host to which this service instance belongs
+    adv_update_t *NULLABLE update; // Ongoing update that currently owns this instance, if any.
+    char *NONNULL instance_name;   // Single label instance name (future: service instance FQDN)
+    char *NONNULL service_type;    // Two label service type (e.g., _ipps._tcp)
+    int port;                      // Port on which service can be found.
+    char *NULLABLE txt_data;       // Contents of txt record
+    uint16_t txt_length;           // length of txt record contents
+};
+
+// An address registration
+struct adv_address_registration {
+    int ref_count;
+    // dnssd_txn_t *txn;         // The registration
+    adv_host_t *NONNULL host;    // The host this record belongs to
+    uint16_t rrtype;             // A or AAAA
+    uint16_t rdlen;              // 4 or 16
+    uint8_t rdata[16];           // Room for either IPv4 or IPv6 address
+};
+
+struct adv_host {
+    int ref_count;
+    wakeup_t *NONNULL retry_wakeup;        // Wakeup for retry when we run into a temporary failure
+    wakeup_t *NONNULL lease_wakeup;        // Wakeup at least expiry time
+    dnssd_txn_t *NULLABLE txn;             // dnssd transaction for host RR
+    adv_host_t *NULLABLE next;             // Hosts are maintained in a linked list.
+    adv_update_t *NULLABLE updates;        // Updates to this host, if any
+    client_update_t *NULLABLE clients;     // Updates that clients have sent for which replies have not yet been sent.
+    char *NONNULL name;                    // Name of host (without domain)
+    char *NONNULL registered_name;         // The name that is registered, which may be different due to mDNS conflict
+    int name_serial;                       // The number we are using to disambiguate the name.
+    int num_addresses;                     // Number of addresses registered (we only actually support one)
+    adv_address_t *NULLABLE *NULLABLE addresses; // One or more addresses
+    adv_instance_vec_t *NONNULL instances; // Zero or more service instances.
+    DNSRecordRef NULLABLE rref;            // Record reference for key or address.
+    dns_rr_t key;                          // The key data represented as an RR; key->name is NULL.
+    uint32_t key_id;                       // A possibly-unique id that is computed across the key for brevity in
+                                           // debugging
+    int retry_interval;                    // Interval to wait before attempting to re-register after the daemon has
+                                           // died.
+    uint16_t key_rdlen;                    // Length of key
+    uint8_t *NONNULL key_rdata;            // Raw KEY rdata, suitable for DNSServiceRegisterRecord().
+    uint32_t lease_interval;               // Interval for address lease
+    uint32_t key_lease;                    // Interval for key lease
+    int64_t lease_expiry;                  // Time when lease expires, relative to ioloop_timenow().
+    bool have_registration;                // True if we've registered a key or address record for the host.
+
+    // True if we have a pending late conflict resolution. If we get a conflict after the update for the
+    // host registration has expired, and there happens to be another update in progress, then we want
+    // to defer the host registration.
+    bool hostname_update_pending;
+};
+
+struct adv_update {
+    adv_host_t *NONNULL host;              // Host being updated
+
+    // Ordinarily NULL, but may be non-NULL if we lost the server during an update and had
+    // to construct an update to re-add the host.
+    adv_update_t *NULLABLE next;
+
+    // Connection state, if applicable, of the client request that produced this update.
+    client_update_t *NULLABLE client;
+    int num_outstanding_updates;   // Total count updates that have been issued but not yet confirmed.
+
+    // Addresses to apply to the host.  At present only one address is ever advertised, but we remember all the
+    // addresses that were registered.
+    int num_remove_addresses;
+    adv_address_t *NULLABLE *NULLABLE remove_addresses;
+    int num_add_addresses;
+    adv_address_t *NULLABLE *NONNULL add_addresses;
+
+    // If non-null, this update is changing the advertised address of the host to the referenced address.
+    adv_address_t *NULLABLE selected_addr;
+
+    // The set of instances from the update that already exist but have changed.
+    // This array mirrors the array of instances configured on the host; entries to be updated
+    // are non-NULL, entries that don't need updated are NULL.
+    adv_instance_vec_t *NONNULL update_instances;
+
+    // The set of instances that exist and need to be removed.
+    adv_instance_vec_t *NONNULL remove_instances;
+
+    // The set of instances that need to be added.
+    adv_instance_vec_t *NONNULL add_instances;
+
+    // Outstanding instance updates
+    int num_instances_started;
+    int num_instances_completed;
+
+    // Lease intervals for host entry and key entry.
+    uint32_t host_lease, key_lease;
+
+    // If nonzero, this is an explicit expiry time for the lease, because the update is restoring
+    // a host after a server restart, or else renaming a host after a late name conflict. In this
+    // case, we do not want to extend the lease--just get the host registration right.
+    uint64_t lease_expiry;
+
+    // True if we are registering the key to hold the hostname.
+    bool registering_key;
+};
+
+struct adv_instance_vec {
+    int ref_count;
+    int num;
+    adv_instance_t * NULLABLE *NONNULL vec;
+};
+
+struct adv_host_vec {
+    int ref_count;
+    int num;
+    adv_host_t * NULLABLE *NONNULL vec;
+};
+
+struct adv_address_vec {
+    int ref_count;
+    int num;
+    adv_address_t * NULLABLE *NONNULL vec;
+};
+
+struct client_update {
+    client_update_t *NULLABLE next;
+    comm_t *NONNULL connection;               // Connection on which in-process update was received.
+    dns_message_t *NONNULL parsed_message;    // Message that triggered the update.
+    message_t *NONNULL message;               // Message that triggered the update.
+
+    dns_host_description_t *NONNULL host;     // Host data parsed from message
+    service_instance_t *NULLABLE instances;   // Service instances parsed from message
+    service_t *NONNULL services;              // Services parsed from message
+    dns_name_t *NONNULL update_zone;          // Zone being updated
+    uint32_t host_lease, key_lease;           // Lease intervals for host entry and key entry.
+};
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 120
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/srp-parse.c b/ServiceRegistration/srp-parse.c
new file mode 100644 (file)
index 0000000..c698a75
--- /dev/null
@@ -0,0 +1,799 @@
+/* srp-parse.c
+ *
+ * Copyright (c) 2018-2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains support routines for the DNSSD SRP update and mDNS proxies.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <dns_sd.h>
+
+#include "srp.h"
+#include "dns-msg.h"
+#include "srp-crypto.h"
+#include "ioloop.h"
+#include "dnssd-proxy.h"
+#include "srp-gw.h"
+#include "config-parse.h"
+#include "srp-proxy.h"
+
+static dns_name_t *service_update_zone; // The zone to update when we receive an update for default.service.arpa.
+
+// Free the data structures into which the SRP update was parsed.   The pointers to the various DNS objects that these
+// structures point to are owned by the parsed DNS message, and so these do not need to be freed here.
+void
+srp_update_free_parts(service_instance_t *service_instances, service_instance_t *added_instances,
+                      service_t *services, dns_host_description_t *host_description)
+{
+    service_instance_t *sip;
+    service_t *sp;
+
+    for (sip = service_instances; sip; ) {
+        service_instance_t *next = sip->next;
+        free(sip);
+        sip = next;
+    }
+    for (sip = added_instances; sip; ) {
+        service_instance_t *next = sip->next;
+        free(sip);
+        sip = next;
+    }
+    for (sp = services; sp; ) {
+        service_t *next = sp->next;
+        free(sp);
+        sp = next;
+    }
+    if (host_description != NULL) {
+        host_addr_t *host_addr, *next;
+        for (host_addr = host_description->addrs; host_addr; host_addr = next) {
+            next = host_addr->next;
+            free(host_addr);
+        }
+        free(host_description);
+    }
+}
+
+static bool
+add_host_addr(host_addr_t **dest, dns_rr_t *rr)
+{
+    host_addr_t *addr = calloc(1, sizeof *addr);
+    if (addr == NULL) {
+        ERROR("add_host_addr: no memory for record");
+        return false;
+    }
+
+    while (*dest) {
+        dest = &(*dest)->next;
+    }
+    *dest = addr;
+    addr->rr = *rr;
+    return true;
+}
+
+static bool
+replace_zone_name(dns_name_t **nzp_in, dns_name_t *uzp, dns_name_t *replacement_zone)
+{
+    dns_name_t **nzp = nzp_in;
+    while (*nzp != NULL && *nzp != uzp) {
+        nzp = &((*nzp)->next);
+    }
+    if (*nzp == NULL) {
+        ERROR("replace_zone: dns_name_subdomain_of returned bogus pointer.");
+        return false;
+    }
+
+    // Free the suffix we're replacing
+    dns_name_free(*nzp);
+
+    // Replace it.
+    *nzp = dns_name_copy(replacement_zone);
+    if (*nzp == NULL) {
+        ERROR("replace_zone_name: no memory for replacement zone");
+        return false;
+    }
+    return true;
+}
+
+// We call advertise_finished when a client request has finished, successfully or otherwise.
+static void
+send_fail_response(comm_t *connection, message_t *message, int rcode)
+{
+    struct iovec iov;
+    dns_wire_t response;
+
+    memset(&response, 0, DNS_HEADER_SIZE);
+    response.id = message->wire.id;
+    response.bitfield = message->wire.bitfield;
+    dns_rcode_set(&response, rcode);
+    dns_qr_set(&response, dns_qr_response);
+
+    iov.iov_base = &response;
+    iov.iov_len = DNS_HEADER_SIZE;
+
+    ioloop_send_message(connection, message, &iov, 1);
+}
+
+bool
+srp_evaluate(comm_t *connection, dns_message_t *message, message_t *raw_message)
+{
+    int i;
+    dns_host_description_t *host_description = NULL;
+    delete_t *deletes = NULL, *dp, **dpp = &deletes;
+    service_instance_t *service_instances = NULL, *sip, **sipp = &service_instances;
+    service_t *services = NULL, *sp, **spp = &services;
+    dns_rr_t *signature;
+    bool ret = false;
+    struct timeval now;
+    dns_name_t *update_zone, *replacement_zone;
+    dns_name_t *uzp;
+    dns_rr_t *key = NULL;
+    dns_rr_t **keys = NULL;
+    int num_keys = 0;
+    int max_keys = 1;
+    bool found_key = false;
+    uint32_t lease_time, key_lease_time;
+    dns_edns0_t *edns0;
+    int rcode = dns_rcode_servfail;
+    bool found_lease = false;
+
+    // Update requires a single SOA record as the question
+    if (message->qdcount != 1) {
+        ERROR("srp_evaluate: update received with qdcount > 1");
+        return false;
+    }
+
+    // Update should contain zero answers.
+    if (message->ancount != 0) {
+        ERROR("srp_evaluate: update received with ancount > 0");
+        return false;
+    }
+
+    if (message->questions[0].type != dns_rrtype_soa) {
+        ERROR("srp_evaluate: update received with rrtype %d instead of SOA in question section.",
+              message->questions[0].type);
+        return false;
+    }
+
+    update_zone = message->questions[0].name;
+    if (service_update_zone != NULL && dns_names_equal_text(update_zone, "default.service.arpa.")) {
+        replacement_zone = service_update_zone;
+    } else {
+        replacement_zone = NULL;
+    }
+
+    // Scan over the authority RRs; do the delete consistency check.  We can't do other consistency checks
+    // because we can't assume a particular order to the records other than that deletes have to come before
+    // adds.
+    for (i = 0; i < message->nscount; i++) {
+        dns_rr_t *rr = &message->authority[i];
+
+        // If this is a delete for all the RRs on a name, record it in the list of deletes.
+        if (rr->type == dns_rrtype_any && rr->qclass == dns_qclass_any && rr->ttl == 0) {
+            for (dp = deletes; dp; dp = dp->next) {
+                if (dns_names_equal(dp->name, rr->name)) {
+                    DNS_NAME_GEN_SRP(rr->name, name_buf);
+                    ERROR("srp_evaluate: two deletes for the same name: " PRI_DNS_NAME_SRP,
+                          DNS_NAME_PARAM_SRP(rr->name, name_buf));
+                    rcode = dns_rcode_formerr;
+                    goto out;
+                }
+            }
+            dp = calloc(1, sizeof *dp);
+            if (!dp) {
+                ERROR("srp_evaluate: no memory.");
+                goto out;
+            }
+            *dpp = dp;
+            dpp = &dp->next;
+
+            // Make sure the name is a subdomain of the zone being updated.
+            dp->zone = dns_name_subdomain_of(rr->name, update_zone);
+            if (dp->zone == NULL) {
+                DNS_NAME_GEN_SRP(update_zone, update_zone_buf);
+                DNS_NAME_GEN_SRP(rr->name, name_buf);
+                ERROR("srp_evaluate: delete for record not in update zone " PRI_DNS_NAME_SRP ": " PRI_DNS_NAME_SRP,
+                      DNS_NAME_PARAM_SRP(update_zone, update_zone_buf), DNS_NAME_PARAM_SRP(rr->name, name_buf));
+                rcode = dns_rcode_formerr;
+                goto out;
+            }
+            dp->name = rr->name;
+        }
+
+        // The update should really only contain one key, but it's allowed for keys to appear on
+        // service instance names as well, since that's what will be stored in the zone.   So if
+        // we get one key, we'll assume it's a host key until we're done scanning, and then check.
+        // If we get more than one, we allocate a buffer and store all the keys so that we can
+        // check them all later.
+        else if (rr->type == dns_rrtype_key) {
+            if (num_keys < 1) {
+                key = rr;
+                num_keys++;
+            } else {
+                if (num_keys == 1) {
+                    // We can't have more keys than there are authority records left, plus
+                    // one for the key we already have, so allocate a buffer that large.
+                    max_keys = message->nscount - i + 1;
+                    keys = calloc(max_keys, sizeof *keys);
+                    if (keys == NULL) {
+                        ERROR("srp_evaluate: no memory");
+                        goto out;
+                    }
+                    keys[0] = key;
+                }
+                if (num_keys >= max_keys) {
+                    ERROR("srp_evaluate: coding error in key allocation");
+                    goto out;
+                }
+                keys[num_keys++] = rr;
+            }
+        }
+
+        // Otherwise if it's an A or AAAA record, it's part of a hostname entry.
+        else if (rr->type == dns_rrtype_a || rr->type == dns_rrtype_aaaa) {
+            // Allocate the hostname record
+            if (!host_description) {
+                host_description = calloc(1, sizeof *host_description);
+                if (!host_description) {
+                    ERROR("srp_evaluate: no memory");
+                    goto out;
+                }
+            }
+
+            // Make sure it's preceded by a deletion of all the RRs on the name.
+            if (!host_description->delete) {
+                for (dp = deletes; dp; dp = dp->next) {
+                    if (dns_names_equal(dp->name, rr->name)) {
+                        break;
+                    }
+                }
+                if (dp == NULL) {
+                    DNS_NAME_GEN_SRP(rr->name, name_buf);
+                    ERROR("srp_evaluate: ADD for hostname " PRI_DNS_NAME_SRP " without a preceding delete.",
+                          DNS_NAME_PARAM_SRP(rr->name, name_buf));
+                    rcode = dns_rcode_formerr;
+                    goto out;
+                }
+                host_description->delete = dp;
+                host_description->name = dp->name;
+                dp->consumed = true; // This delete is accounted for.
+
+                // In principle, we should be checking this name to see that it's a subdomain of the update
+                // zone.  However, it turns out we don't need to, because the /delete/ has to be a subdomain
+                // of the update zone, and we won't find that delete if it's not present.
+            }
+
+            if (rr->type == dns_rrtype_a || rr->type == dns_rrtype_aaaa) {
+                if (!add_host_addr(&host_description->addrs, rr)) {
+                    goto out;
+                }
+            }
+        }
+
+        // Otherwise if it's an SRV entry, that should be a service instance name.
+        else if (rr->type == dns_rrtype_srv || rr->type == dns_rrtype_txt) {
+            // Should be a delete that precedes this service instance.
+            for (dp = deletes; dp; dp = dp->next) {
+                if (dns_names_equal(dp->name, rr->name)) {
+                    break;
+                }
+            }
+            if (dp == NULL) {
+                DNS_NAME_GEN_SRP(rr->name, name_buf);
+                ERROR("srp_evaluate: ADD for service instance not preceded by delete: " PRI_DNS_NAME_SRP,
+                      DNS_NAME_PARAM_SRP(rr->name, name_buf));
+                rcode = dns_rcode_formerr;
+                goto out;
+            }
+            for (sip = service_instances; sip; sip = sip->next) {
+                if (dns_names_equal(sip->name, rr->name)) {
+                    break;
+                }
+            }
+            if (!sip) {
+                sip = calloc(1, sizeof *sip);
+                if (sip == NULL) {
+                    ERROR("srp_evaluate: no memory");
+                    goto out;
+                }
+                sip->delete = dp;
+                dp->consumed = true;
+                sip->name = dp->name;
+                *sipp = sip;
+                sipp = &sip->next;
+            }
+            if (rr->type == dns_rrtype_srv) {
+                if (sip->srv != NULL) {
+                    DNS_NAME_GEN_SRP(rr->name, name_buf);
+                    ERROR("srp_evaluate: more than one SRV rr received for service instance: " PRI_DNS_NAME_SRP,
+                          DNS_NAME_PARAM_SRP(rr->name, name_buf));
+                    rcode = dns_rcode_formerr;
+                    goto out;
+                }
+                sip->srv = rr;
+            } else if (rr->type == dns_rrtype_txt) {
+                if (sip->txt != NULL) {
+                    DNS_NAME_GEN_SRP(rr->name, name_buf);
+                    ERROR("srp_evaluate: more than one TXT rr received for service instance: " PRI_DNS_NAME_SRP,
+                          DNS_NAME_PARAM_SRP(rr->name, name_buf));
+                    rcode = dns_rcode_formerr;
+                    goto out;
+                }
+                sip->txt = rr;
+            }
+        }
+
+        // Otherwise if it's a PTR entry, that should be a service name
+        else if (rr->type == dns_rrtype_ptr) {
+            sp = calloc(1, sizeof *sp);
+            if (sp == NULL) {
+                ERROR("srp_evaluate: no memory");
+                goto out;
+            }
+            *spp = sp;
+            spp = &sp->next;
+            sp->rr = rr;
+
+            // Make sure the service name is in the update zone.
+            sp->zone = dns_name_subdomain_of(sp->rr->name, update_zone);
+            if (sp->zone == NULL) {
+                DNS_NAME_GEN_SRP(rr->name, name_buf);
+                DNS_NAME_GEN_SRP(rr->data.ptr.name, data_name_buf);
+                ERROR("srp_evaluate: service name " PRI_DNS_NAME_SRP " for " PRI_DNS_NAME_SRP
+                      " is not in the update zone", DNS_NAME_PARAM_SRP(rr->name, name_buf),
+                      DNS_NAME_PARAM_SRP(rr->data.ptr.name, data_name_buf));
+                rcode = dns_rcode_formerr;
+                goto out;
+            }
+        }
+
+        // Otherwise it's not a valid update
+        else {
+            DNS_NAME_GEN_SRP(rr->name, name_buf);
+            ERROR("srp_evaluate: unexpected rrtype %d on " PRI_DNS_NAME_SRP " in update.", rr->type,
+                  DNS_NAME_PARAM_SRP(rr->name, name_buf));
+            rcode = dns_rcode_formerr;
+            goto out;
+        }
+    }
+
+    // Now that we've scanned the whole update, do the consistency checks for updates that might
+    // not have come in order.
+
+    // First, make sure there's a host description.
+    if (host_description == NULL) {
+        ERROR("srp_evaluate: SRP update does not include a host description.");
+        rcode = dns_rcode_formerr;
+        goto out;
+    }
+
+    // Make sure that each service add references a service instance that's in the same update.
+    for (sp = services; sp; sp = sp->next) {
+        for (sip = service_instances; sip; sip = sip->next) {
+            if (dns_names_equal(sip->name, sp->rr->data.ptr.name)) {
+                // Note that we have already verified that there is only one service instance
+                // with this name, so this could only ever happen once in this loop even without
+                // the break statement.
+                sip->service = sp;
+                sip->num_instances++;
+                break;
+            }
+        }
+        // If this service doesn't point to a service instance that's in the update, then the
+        // update fails validation.
+        if (sip == NULL) {
+            DNS_NAME_GEN_SRP(sp->rr->name, name_buf);
+            ERROR("srp_evaluate: service points to an instance that's not included: " PRI_DNS_NAME_SRP,
+                  DNS_NAME_PARAM_SRP(sp->rr->name, name_buf));
+            rcode = dns_rcode_formerr;
+            goto out;
+        }
+    }
+
+    for (sip = service_instances; sip; sip = sip->next) {
+        // For each service instance, make sure that at least one service references it
+        if (sip->num_instances == 0) {
+            DNS_NAME_GEN_SRP(sip->name, name_buf);
+            ERROR("srp_evaluate: service instance update for " PRI_DNS_NAME_SRP
+                  " is not referenced by a service update.", DNS_NAME_PARAM_SRP(sip->name, name_buf));
+            rcode = dns_rcode_formerr;
+            goto out;
+        }
+
+        // For each service instance, make sure that it references the host description
+        if (dns_names_equal(host_description->name, sip->srv->data.srv.name)) {
+            sip->host = host_description;
+            host_description->num_instances++;
+        }
+    }
+
+    // Make sure that at least one service instance references the host description
+    if (host_description->num_instances == 0) {
+        DNS_NAME_GEN_SRP(host_description->name, name_buf);
+        ERROR("srp_evaluate: host description " PRI_DNS_NAME_SRP " is not referenced by any service instances.",
+              DNS_NAME_PARAM_SRP(host_description->name, name_buf));
+        rcode = dns_rcode_formerr;
+        goto out;
+    }
+
+    // Make sure the host description has at least one address record.
+    if (host_description->addrs == NULL) {
+        DNS_NAME_GEN_SRP(host_description->name, name_buf);
+        ERROR("srp_evaluate: host description " PRI_DNS_NAME_SRP " doesn't contain any IP addresses.",
+              DNS_NAME_PARAM_SRP(host_description->name, name_buf));
+        rcode = dns_rcode_formerr;
+        goto out;
+    }
+
+    for (i = 0; i < num_keys; i++) {
+        // If this isn't the only key, make sure it's got the same contents as the other keys.
+        if (i > 0) {
+            if (!dns_keys_rdata_equal(key, keys[i])) {
+                ERROR("srp_evaluate: more than one key presented");
+                rcode = dns_rcode_formerr;
+                goto out;
+            }
+            // This is a hack so that if num_keys == 1, we don't have to allocate keys[].
+            // At the bottom of this if statement, key is always the key we are looking at.
+            key = keys[i];
+        }
+        // If there is a key, and the host description doesn't currently have a key, check
+        // there first since that's the default.
+        if (host_description->key == NULL && dns_names_equal(key->name, host_description->name)) {
+            host_description->key = key;
+            found_key = true;
+        } else {
+            for (sip = service_instances; sip != NULL; sip = sip->next) {
+                if (dns_names_equal(sip->name, key->name)) {
+                    found_key = true;
+                    break;
+                }
+            }
+        }
+        if (!found_key) {
+            DNS_NAME_GEN_SRP(key->name, key_name_buf);
+            ERROR("srp_evaluate: key present for name " PRI_DNS_NAME_SRP
+                  " which is neither a host nor an instance name.", DNS_NAME_PARAM_SRP(key->name, key_name_buf));
+            rcode = dns_rcode_formerr;
+            goto out;
+        }
+    }
+    if (keys != NULL) {
+        free(keys);
+        keys = NULL;
+    }
+
+    // And make sure it has a key record
+    if (host_description->key == NULL) {
+        DNS_NAME_GEN_SRP(host_description->name, host_name_buf);
+        ERROR("srp_evaluate: host description " PRI_DNS_NAME_SRP " doesn't contain a key.",
+              DNS_NAME_PARAM_SRP(host_description->name, host_name_buf));
+        rcode = dns_rcode_formerr;
+        goto out;
+    }
+
+    // Make sure that all the deletes are for things that are then added.
+    for (dp = deletes; dp; dp = dp->next) {
+        if (!dp->consumed) {
+            DNS_NAME_GEN_SRP(host_description->name, host_name_buf);
+            ERROR("srp_evaluate: delete for which there is no subsequent add: " PRI_DNS_NAME_SRP,
+                  DNS_NAME_PARAM_SRP(host_description->name, host_name_buf));
+            rcode = dns_rcode_formerr;
+            goto out;
+        }
+    }
+
+    // The signature should be the last thing in the additional section.   Even if the signature
+    // is valid, if it's not at the end we reject it.   Note that we are just checking for SIG(0)
+    // so if we don't find what we're looking for, we forward it to the DNS auth server which
+    // will either accept or reject it.
+    if (message->arcount < 1) {
+        ERROR("srp_evaluate: signature not present");
+        rcode = dns_rcode_formerr;
+        goto out;
+    }
+    signature = &message->additional[message->arcount -1];
+    if (signature->type != dns_rrtype_sig) {
+        ERROR("srp_evaluate: signature is not at the end or is not present");
+        rcode = dns_rcode_formerr;
+        goto out;
+    }
+
+    // Make sure that the signer name is the hostname.   If it's not, it could be a legitimate
+    // update with a different key, but it's not an SRP update, so we pass it on.
+    if (!dns_names_equal(signature->data.sig.signer, host_description->name)) {
+        DNS_NAME_GEN_SRP(signature->data.sig.signer, signer_name_buf);
+        DNS_NAME_GEN_SRP(host_description->name, host_name_buf);
+        ERROR("srp_evaluate: signer " PRI_DNS_NAME_SRP " doesn't match host " PRI_DNS_NAME_SRP,
+              DNS_NAME_PARAM_SRP(signature->data.sig.signer, signer_name_buf),
+              DNS_NAME_PARAM_SRP(host_description->name, host_name_buf));
+        rcode = dns_rcode_formerr;
+        goto out;
+    }
+
+    // Make sure we're in the time limit for the signature.   Zeroes for the inception and expiry times
+    // mean the host that send this doesn't have a working clock.   One being zero and the other not isn't
+    // valid unless it's 1970.
+    if (signature->data.sig.inception != 0 || signature->data.sig.expiry != 0) {
+        gettimeofday(&now, NULL);
+        // The sender does the bracketing, so we can just do a simple comparison.
+        if ((uint32_t)(now.tv_sec & UINT32_MAX) > signature->data.sig.expiry ||
+            (uint32_t)(now.tv_sec & UINT32_MAX) < signature->data.sig.inception) {
+            ERROR("signature is not timely: %lu < %lu < %lu does not hold",
+                  (unsigned long)signature->data.sig.inception, (unsigned long)now.tv_sec,
+                  (unsigned long)signature->data.sig.expiry);
+            goto badsig;
+        }
+    }
+
+    // Now that we have the key, we can validate the signature.   If the signature doesn't validate,
+    // there is no need to pass the message on.
+    if (!srp_sig0_verify(&raw_message->wire, host_description->key, signature)) {
+        ERROR("signature is not valid");
+        goto badsig;
+    }
+
+    // Now that we have validated the SRP message, go through and fix up all instances of
+    // *default.service.arpa to use the replacement zone, if this update is for
+    // default.services.arpa and there is a replacement zone.
+    if (replacement_zone != NULL) {
+        // All of the service instances and the host use the name from the delete, so if
+        // we update these, the names for those are taken care of.   We already found the
+        // zone for which the delete is a subdomain, so we can just replace it without
+        // finding it again.
+        for (dp = deletes; dp; dp = dp->next) {
+            replace_zone_name(&dp->name, dp->zone, replacement_zone);
+        }
+
+        // All services have PTR records, which point to names.   Both the service name and the
+        // PTR name have to be fixed up.
+        for (sp = services; sp; sp = sp->next) {
+            replace_zone_name(&sp->rr->name, sp->zone, replacement_zone);
+            uzp = dns_name_subdomain_of(sp->rr->data.ptr.name, update_zone);
+            // We already validated that the PTR record points to something in the zone, so this
+            // if condition should always be false.
+            if (uzp == NULL) {
+                ERROR("srp_evaluate: service PTR record zone match fail!!");
+                goto out;
+            }
+            replace_zone_name(&sp->rr->data.ptr.name, uzp, replacement_zone);
+        }
+
+        // All service instances have SRV records, which point to names.  The service instance
+        // name is already fixed up, because it's the same as the delete, but the name in the
+        // SRV record must also be fixed.
+        for (sip = service_instances; sip; sip = sip->next) {
+            uzp = dns_name_subdomain_of(sip->srv->data.srv.name, update_zone);
+            // We already validated that the SRV record points to something in the zone, so this
+            // if condition should always be false.
+            if (uzp == NULL) {
+                ERROR("srp_evaluate: service instance SRV record zone match fail!!");
+                goto out;
+            }
+            replace_zone_name(&sip->srv->data.srv.name, uzp, replacement_zone);
+        }
+
+        // We shouldn't need to replace the hostname zone because it's actually pointing to
+        // the name of a delete.
+    }
+
+    // Get the lease time.
+    lease_time = 3600;
+    key_lease_time = 604800;
+    for (edns0 = message->edns0; edns0; edns0 = edns0->next) {
+        if (edns0->type == dns_opt_update_lease) {
+            unsigned off = 0;
+            if (edns0->length != 4 && edns0->length != 8) {
+                ERROR("srp_evaluate: edns0 update-lease option length bogus: %d", edns0->length);
+                rcode = dns_rcode_formerr;
+                goto out;
+            }
+            dns_u32_parse(edns0->data, edns0->length, &off, &lease_time);
+            if (edns0->length == 8) {
+                dns_u32_parse(edns0->data, edns0->length, &off, &key_lease_time);
+            } else {
+                key_lease_time = 7 * lease_time;
+            }
+            found_lease = true;
+            break;
+        }
+    }
+
+    // Start the update.
+    DNS_NAME_GEN_SRP(host_description->name, host_description_name_buf);
+    INFO("srp_evaluate: update for " PRI_DNS_NAME_SRP " xid %x validates.",
+         DNS_NAME_PARAM_SRP(host_description->name, host_description_name_buf), raw_message->wire.id);
+    rcode = dns_rcode_noerror;
+    ret = srp_update_start(connection, message, raw_message, host_description, service_instances, services,
+                           replacement_zone == NULL ? update_zone : replacement_zone,
+                           lease_time, key_lease_time);
+    if (ret) {
+        goto success;
+    }
+    ERROR("update start failed");
+    goto out;
+
+badsig:
+    // True means it was intended for us, and shouldn't be forwarded.
+    ret = true;
+    // We're not actually going to return this; it simply indicates that we aren't sending a fail response.
+    rcode = dns_rcode_noerror;
+    // Because we're saying this is ours, we have to free the parsed message.
+    dns_message_free(message);
+
+out:
+    // free everything we allocated but (it turns out) aren't going to use
+    if (keys != NULL) {
+        free(keys);
+    }
+    srp_update_free_parts(service_instances, NULL, services, host_description);
+
+success:
+    // No matter how we get out of this, we free the delete structures, because they are not
+    // used to do the update.
+    for (dp = deletes; dp; ) {
+        delete_t *next = dp->next;
+        free(dp);
+        dp = next;
+    }
+
+    if (ret == true && rcode != dns_rcode_noerror) {
+        send_fail_response(connection, raw_message, rcode);
+    }
+    return ret;
+}
+
+static void
+dns_evaluate(comm_t *connection, message_t *message)
+{
+    dns_message_t *parsed_message;
+
+    // Drop incoming responses--we're a server, so we only accept queries.
+    if (dns_qr_get(&message->wire) == dns_qr_response) {
+        ERROR("dns_evaluate: received a message that was a DNS response: %d", dns_opcode_get(&message->wire));
+        return;
+    }
+
+    // Forward incoming messages that are queries but not updates.
+    // XXX do this later--for now we operate only as a translator, not a proxy.
+    if (dns_opcode_get(&message->wire) != dns_opcode_update) {
+        send_fail_response(connection, message, dns_rcode_refused);
+        ERROR("dns_evaluate: received a message that was not a DNS update: %d", dns_opcode_get(&message->wire));
+        return;
+    }
+
+    // Parse the UPDATE message.
+    if (!dns_wire_parse(&parsed_message, &message->wire, message->length)) {
+        send_fail_response(connection, message, dns_rcode_servfail);
+        ERROR("dns_wire_parse failed.");
+        return;
+    }
+
+    // We need the wire message to validate the signature...
+    if (!srp_evaluate(connection, parsed_message, message)) {
+        // The message wasn't invalid, but wasn't an SRP message.
+        dns_message_free(parsed_message);
+        // dns_forward(connection)
+        send_fail_response(connection, message, dns_rcode_refused);
+    }
+}
+
+void
+dns_input(comm_t *comm, message_t *message, void *context)
+{
+    (void)context;
+    dns_evaluate(comm, message);
+}
+
+struct srp_proxy_listener_state {
+    comm_t *NULLABLE tcp_listener;
+    comm_t *NULLABLE tls_listener;
+    comm_t *NULLABLE udp_listener;
+};
+
+void
+srp_proxy_listener_cancel(srp_proxy_listener_state_t *listener_state)
+{
+    if (listener_state->tcp_listener != NULL) {
+        ioloop_listener_cancel(listener_state->tcp_listener);
+        ioloop_listener_release(listener_state->tcp_listener);
+    }
+    if (listener_state->tls_listener != NULL) {
+        ioloop_listener_cancel(listener_state->tls_listener);
+        ioloop_listener_release(listener_state->tls_listener);
+    }
+    if (listener_state->udp_listener != NULL) {
+        ioloop_listener_cancel(listener_state->udp_listener);
+        ioloop_listener_release(listener_state->udp_listener);
+    }
+    free(listener_state);
+}
+
+srp_proxy_listener_state_t *
+srp_proxy_listen(const char *update_zone, uint16_t *avoid_ports, int num_avoid_ports, ready_callback_t ready)
+{
+#if SRP_STREAM_LISTENER_ENABLED
+    uint16_t tcp_listen_port;
+#ifndef EXCLUDE_TLS
+    uint16_t tls_listen_port;
+#endif
+#endif
+    srp_proxy_listener_state_t *listeners = calloc(1, sizeof *listeners);
+    if (listeners == NULL) {
+        ERROR("srp_proxy_listen: no memory for listeners structure.");
+        return NULL;
+    }
+    (void)avoid_ports;
+    (void)num_avoid_ports;
+
+#if SRP_STREAM_LISTENER_ENABLED
+    tcp_listen_port = 53;
+    tls_listen_port = 853;
+#endif
+
+    // Set up listeners
+    // XXX UDP listeners should bind to interface addresses, not INADDR_ANY.
+    listeners->udp_listener = ioloop_listener_create(false, false, avoid_ports,
+                                                     num_avoid_ports, NULL, NULL, "UDP listener", dns_input,
+                                                     NULL, NULL, ready, NULL, NULL);
+    if (listeners->udp_listener == NULL) {
+        srp_proxy_listener_cancel(listeners);
+        ERROR("UDP listener: fail.");
+        return 0;
+    }
+#ifdef SRP_STREAM_LISTENER_ENABLED
+    listeners->tcp_listener = ioloop_listener_create(true, false, NULL, 0, NULL, NULL,
+                                                     "TCP listener", dns_input, NULL, NULL, ready, NULL, NULL);
+    if (listeners->tcp_listener == NULL) {
+        srp_proxy_listener_cancel(listeners);
+        ERROR("TCP listener: fail.");
+        return 0;
+    }
+#ifndef EXCLUDE_TLS
+    listeners->tls_listener = ioloop_listener_create(true, true, NULL, 0, NULL, NULL,
+                                                     "TLS listener", dns_input, NULL, NULL, ready, NULL, NULL);
+    if (listeners->tls_listener == NULL) {
+        srp_proxy_listener_cancel(listeners);
+        ERROR("TLS listener: fail.");
+        return 0;
+    }
+#endif
+#endif
+
+    // For now, hardcoded, should be configurable
+    if (service_update_zone != NULL) {
+        dns_name_free(service_update_zone);
+    }
+    service_update_zone = dns_pres_name_parse(update_zone);
+
+    return listeners;
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/srp-proxy.h b/ServiceRegistration/srp-proxy.h
new file mode 100644 (file)
index 0000000..f6ddc2f
--- /dev/null
@@ -0,0 +1,55 @@
+/* srp-proxy.h
+ *
+ * Copyright (c) 2018-2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Service Registration Protocol common definitions
+ */
+
+#ifndef __SRP_PROXY_H
+#define __SRP_PROXY_H
+
+typedef struct srp_proxy_listener_state srp_proxy_listener_state_t;
+
+void srp_proxy_listener_cancel(srp_proxy_listener_state_t *NONNULL listener_state);
+srp_proxy_listener_state_t *NULLABLE srp_proxy_listen(const char *NONNULL update_zone, uint16_t *NULLABLE avoid_ports,
+                                                      int num_avoid_ports, ready_callback_t NULLABLE ready);
+bool srp_evaluate(comm_t *NONNULL comm, dns_message_t *NONNULL message, message_t *NONNULL raw_message);
+bool srp_update_start(comm_t *NONNULL connection, dns_message_t *NONNULL parsed_message, message_t *NONNULL raw_message,
+                      dns_host_description_t *NONNULL new_host, service_instance_t *NONNULL instances,
+                      service_t *NONNULL services, dns_name_t *NONNULL update_zone,
+                      uint32_t lease_time, uint32_t key_lease_time);
+void srp_update_free_parts(service_instance_t *NULLABLE service_instances, service_instance_t *NULLABLE added_instances,
+                           service_t *NULLABLE services, dns_host_description_t *NULLABLE host_description);
+void srp_update_free(update_t *NONNULL update);
+
+
+// Provided
+void dns_input(comm_t *NONNULL comm, message_t *NONNULL message, void *NULLABLE context);
+#if TARGET_OS_TV
+#ifndef OPEN_SOURCE
+void adv_xpc_disconnect(void);
+#endif
+void srp_mdns_flush(void);
+#endif
+#endif // __SRP_PROXY_H
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/srp-simple.c b/ServiceRegistration/srp-simple.c
deleted file mode 100644 (file)
index 3590dcf..0000000
+++ /dev/null
@@ -1,272 +0,0 @@
-/* srp-simple.c
- *
- * Copyright (c) 2018 Apple Computer, Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * Simple Service Registration Protocol Client
- *
- * This is intended for the constrained node solution for SRP.   It's intended to be flexible and
- * understandable while linking in the minimum possible support code to reduce code size.  It does
- * no mallocs, does not put anything big on the stack, and doesn't require an event loop.
- */
-
-#include <stdio.h>
-#include <arpa/inet.h>
-#include <string.h>
-#include <stdlib.h>
-
-#include "srp.h"
-#include "dns-msg.h"
-#include "srp-crypto.h"
-
-static void
-dns_response_callback(dns_transaction_t *txn)
-{
-}
-
-int
-main(int argc, char **argv)
-{
-    const char *host_name = "thread-demo";
-    const char *zone_name = "default.service.arpa";
-    const char *host_fqdn = "thread-demo.default.service.arpa";
-    const char *service_type = "_ipps._tcp";
-    const char *a_record = "127.0.0.1";
-    const char *aaaa_record = "::1";
-    const char *txt_record = "0";
-    const char *anycast_address = "127.0.0.1";
-//    const char *anycast_address = "73.186.137.119"; // cer.fugue.com
-    const char *keyfile_name = "srp-simple.key";
-    int port = 9992;
-    srp_key_t *key;
-    dns_wire_t message, response;
-    uint16_t key_tag;
-    static dns_transaction_t txn;
-    dns_towire_state_t *towire = &txn.towire;
-    dns_name_pointer_t p_host_name;
-    dns_name_pointer_t p_zone_name;
-    dns_name_pointer_t p_service_name;
-    dns_name_pointer_t p_service_instance_name;
-    int line;
-
-    key = srp_load_keypair(keyfile_name);
-    if (key == NULL) {
-        key = srp_generate_key();
-        if (key == NULL) {
-            printf("Unable to load or generate a key.");
-            exit(1);
-        }
-        if (!srp_write_key_to_file(keyfile_name, key)) {
-            printf("Unable to safe generated key.");
-            exit(1);
-        }
-    }
-
-#define CH if (towire->error) { line = __LINE__; goto fail; }
-
-    // Generate a random UUID.
-#ifdef NOTYET
-    message.id = srp_random16();
-#else
-    srandomdev();
-    message.id = (uint32_t)(random()) & 65535;
-#endif
-    message.bitfield = 0;
-    dns_qr_set(&message, dns_qr_query);
-    dns_opcode_set(&message, dns_opcode_update);
-
-    // Message data...
-    memset(&txn, 0, sizeof txn);
-    towire->p = &message.data[0];  // We start storing RR data here.
-    towire->lim = &message.data[DNS_DATA_SIZE]; // This is the limit to how much we can store.
-    towire->message = &message;
-    txn.response = &response;
-    txn.response_length = (int)(sizeof response);
-
-    message.qdcount = htons(1); // ZOCOUNT = 1
-    // Copy in Zone name (and save pointer)
-    // ZTYPE = SOA
-    // ZCLASS = IN
-    dns_full_name_to_wire(&p_zone_name, towire, zone_name); CH;
-    dns_u16_to_wire(towire, dns_rrtype_soa); CH;
-    dns_u16_to_wire(towire, dns_qclass_in); CH;
-
-    message.ancount = 0;
-    // PRCOUNT = 0
-
-    message.nscount = 0;
-    // UPCOUNT = ...
-
-    // Host Description:
-    //  * Delete all RRsets from <hostname>; remember the pointer to hostname
-    //      NAME = hostname label followed by pointer to SOA name.
-    //      TYPE = ANY
-    //      CLASS = ANY
-    //      TTL = 0
-    //      RDLENGTH = 0
-    dns_name_to_wire(&p_host_name, towire, host_name); CH;
-    dns_pointer_to_wire(&p_host_name, towire, &p_zone_name); CH;
-    dns_u16_to_wire(towire, dns_rrtype_any); CH;
-    dns_u16_to_wire(towire, dns_qclass_any); CH;
-    dns_ttl_to_wire(towire, 0); CH;
-    dns_u16_to_wire(towire, 0); CH;
-    message.nscount++;
-    //  * Add either or both of an A or AAAA RRset, each of which contains one
-    //    or more A or AAAA RRs.
-    //      NAME = pointer to hostname from Delete (above)
-    //      TYPE = A or AAAA
-    //      CLASS = IN
-    //      TTL = 3600 ?
-    //      RDLENGTH = number of RRs * RR length (4 or 16)
-    //      RDATA = <the data>
-    dns_pointer_to_wire(NULL, towire, &p_host_name); CH;
-    dns_u16_to_wire(towire, dns_rrtype_a); CH;
-    dns_u16_to_wire(towire, dns_qclass_in); CH;
-    dns_ttl_to_wire(towire, 3600); CH;
-    dns_rdlength_begin(towire); CH;
-    dns_rdata_a_to_wire(towire, a_record); CH;
-    dns_rdlength_end(towire); CH;
-    message.nscount++;
-    
-    dns_pointer_to_wire(NULL, towire, &p_host_name); CH;
-    dns_u16_to_wire(towire, dns_rrtype_aaaa); CH;
-    dns_u16_to_wire(towire, dns_qclass_in); CH;
-    dns_ttl_to_wire(towire, 3600); CH;
-    dns_rdlength_begin(towire); CH;
-    dns_rdata_aaaa_to_wire(towire, aaaa_record); CH;
-    dns_rdlength_end(towire); CH;
-    message.nscount++;
-    
-    //  * Exactly one KEY RR:
-    //      NAME = pointer to hostname from Delete (above)
-    //      TYPE = KEY
-    //      CLASS = IN
-    //      TTL = 3600
-    //      RDLENGTH = length of key + 4 (32 bits)
-    //      RDATA = <flags(16) = 0000 0010 0000 0001, protocol(8) = 3, algorithm(8) = 8?, public key(variable)>
-    dns_pointer_to_wire(NULL, towire, &p_host_name); CH;
-    dns_u16_to_wire(towire, dns_rrtype_key); CH;
-    dns_u16_to_wire(towire, dns_qclass_in); CH;
-    dns_ttl_to_wire(towire, 3600); CH;
-    dns_rdlength_begin(towire); CH;
-    key_tag = dns_rdata_key_to_wire(towire, 0, 2, 1, key); CH;
-    dns_rdlength_end(towire); CH;
-    message.nscount++;
-
-    // Service Discovery:
-    //   * Update PTR RR
-    //     NAME = service name (_a._b.service.arpa)
-    //     TYPE = PTR
-    //     CLASS = IN
-    //     TTL = 3600
-    //     RDLENGTH = 2
-    //     RDATA = service instance name
-    dns_name_to_wire(&p_service_name, towire, service_type); CH;
-    dns_pointer_to_wire(&p_service_name, towire, &p_zone_name); CH;
-    dns_u16_to_wire(towire, dns_rrtype_ptr); CH;
-    dns_u16_to_wire(towire, dns_qclass_in); CH;
-    dns_ttl_to_wire(towire, 3600); CH;
-    dns_rdlength_begin(towire); CH;
-    dns_name_to_wire(&p_service_instance_name, towire, host_name); CH;
-    dns_pointer_to_wire(&p_service_instance_name, towire, &p_service_name); CH;
-    dns_rdlength_end(towire); CH;
-    message.nscount++;
-
-    // Service Description:
-    //   * Delete all RRsets from service instance name
-    //      NAME = service instance name (save pointer to service name, which is the second label)
-    //      TYPE = ANY
-    //      CLASS = ANY
-    //      TTL = 0
-    //      RDLENGTH = 0
-    dns_pointer_to_wire(NULL, towire, &p_service_instance_name); CH;
-    dns_u16_to_wire(towire, dns_rrtype_any); CH;
-    dns_u16_to_wire(towire, dns_qclass_any); CH;
-    dns_ttl_to_wire(towire, 0); CH;
-    dns_u16_to_wire(towire, 0); CH;
-    message.nscount++;
-
-    //   * Add one SRV RRset pointing to Host Description
-    //      NAME = pointer to service instance name from above
-    //      TYPE = SRV
-    //      CLASS = IN
-    //      TTL = 3600
-    //      RDLENGTH = 8
-    //      RDATA = <priority(16) = 0, weight(16) = 0, port(16) = service port, target = pointer to hostname>
-    dns_pointer_to_wire(NULL, towire, &p_service_instance_name); CH;
-    dns_u16_to_wire(towire, dns_rrtype_srv); CH;
-    dns_u16_to_wire(towire, dns_qclass_in); CH;
-    dns_ttl_to_wire(towire, 3600); CH;
-    dns_rdlength_begin(towire); CH;
-    dns_u16_to_wire(towire, 0); // priority CH;
-    dns_u16_to_wire(towire, 0); // weight CH;
-    dns_u16_to_wire(towire, port); // port CH;
-    dns_pointer_to_wire(NULL, towire, &p_host_name); CH;
-    dns_rdlength_end(towire); CH;
-    message.nscount++;
-
-    //   * Add one or more TXT records
-    //      NAME = pointer to service instance name from above
-    //      TYPE = TXT
-    //      CLASS = IN
-    //      TTL = 3600
-    //      RDLENGTH = <length of text>
-    //      RDATA = <text>
-    dns_pointer_to_wire(NULL, towire, &p_service_instance_name); CH;
-    dns_u16_to_wire(towire, dns_rrtype_txt); CH;
-    dns_u16_to_wire(towire, dns_qclass_in); CH;
-    dns_ttl_to_wire(towire, 3600); CH;
-    dns_rdlength_begin(towire); CH;
-    dns_rdata_txt_to_wire(towire, txt_record); CH;
-    dns_rdlength_end(towire); CH;
-    message.nscount++;
-    message.nscount = htons(message.nscount);
-    
-    // What about services with more than one name?   Are these multiple service descriptions?
-
-    // ARCOUNT = 2
-    //   EDNS(0) options
-    //     ...
-    //   SIG(0)
-    
-    message.arcount = htons(1);
-    dns_edns0_header_to_wire(towire, DNS_MAX_UDP_PAYLOAD, 0, 0, 1); CH;   // XRCODE = 0; VERSION = 0; DO=1
-    dns_rdlength_begin(towire); CH;
-    dns_u16_to_wire(towire, dns_opt_update_lease); CH;  // OPTION-CODE
-    dns_edns0_option_begin(towire); CH;                 // OPTION-LENGTH
-    dns_u32_to_wire(towire, 3600); CH;                  // LEASE (1 hour)
-    dns_u32_to_wire(towire, 604800); CH;                // KEY-LEASE (7 days)
-    dns_edns0_option_end(towire); CH;                   // Now we know OPTION-LENGTH
-    dns_rdlength_end(towire); CH;
-
-    dns_sig0_signature_to_wire(towire, key, key_tag, &p_host_name, host_fqdn); CH;
-    // The signature is computed before counting the signature RR in the header counts.
-    message.arcount = htons(ntohs(message.arcount) + 1);
-
-    // Send the update
-    if (dns_send_to_server(&txn, anycast_address, 53, dns_response_callback) < 0) {
-        line = __LINE__;
-    fail:
-        printf("dns_send_to_server failed: %s at line %d\n", strerror(towire->error), line);
-    }
-}
-
-// Local Variables:
-// mode: C
-// tab-width: 4
-// c-file-style: "bsd"
-// c-basic-offset: 4
-// fill-column: 108
-// indent-tabs-mode: nil
-// End:
diff --git a/ServiceRegistration/srp-thread.c b/ServiceRegistration/srp-thread.c
new file mode 100644 (file)
index 0000000..33a98e9
--- /dev/null
@@ -0,0 +1,467 @@
+/* srp-thread.c
+ *
+ * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * srp host API implementation for Thread accessories using OpenThread.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <openthread/ip6.h>
+#include <openthread/instance.h>
+#include <openthread/thread.h>
+#include <openthread/joiner.h>
+#include <openthread/message.h>
+#include <openthread/udp.h>
+#include <openthread/platform/time.h>
+#include <openthread/platform/settings.h>
+
+
+#include "app_scheduler.h"
+#include "app_timer.h"
+#include "srp.h"
+#include "srp-thread.h"
+#include "srp-api.h"
+#include "dns_sd.h"
+#include "HAPPlatformRandomNumber.h"
+#include "dns-msg.h"
+#include "dns_sd.h"
+#define SRP_CRYPTO_MBEDTLS_INTERNAL 1
+#include "srp-crypto.h"
+
+APP_TIMER_DEF(m_srp_timer);
+#define HAPTIME_FREQUENCY 1000ULL
+
+const char *key_filename = "srp.key";
+
+#define SRP_IO_CONTEXT_MAGIC 0xFEEDFACEFADEBEEFULL  // BEES!   Everybody gets BEES!
+typedef struct io_context io_context_t;
+
+struct io_context {
+    uint64_t magic_cookie1;
+    io_context_t *next;
+    HAPTime wakeup_time;
+    void *NONNULL srp_context;
+    otSockAddr sockaddr;
+    otUdpSocket sock;
+    srp_wakeup_callback_t wakeup_callback;
+    srp_datagram_callback_t datagram_callback;
+    bool sock_active;
+    uint64_t magic_cookie2;
+} *io_contexts;
+
+static otInstance *otThreadInstance;
+
+static int
+validate_io_context(io_context_t **dest, void *src)
+{
+    io_context_t *context = src;
+    if (context->magic_cookie1 == SRP_IO_CONTEXT_MAGIC &&
+        context->magic_cookie2 == SRP_IO_CONTEXT_MAGIC)
+    {
+        *dest = context;
+        return kDNSServiceErr_NoError;
+    }
+    return kDNSServiceErr_BadState;
+}
+
+void
+datagram_callback(void *context, otMessage *message, const otMessageInfo *messageInfo)
+{
+    static uint8_t *buf;
+    const int buf_len = 1500;
+    int length;
+    io_context_t *io_context;
+    if (validate_io_context(&io_context, context) == kDNSServiceErr_NoError) {
+        if (buf == NULL) {
+            buf = malloc(buf_len);
+            if (buf == NULL) {
+                INFO("No memory for read buffer");
+                return;
+            }
+        }
+
+        DEBUG("%d bytes received", otMessageGetLength(message) - otMessageGetOffset(message));
+        length = otMessageRead(message, otMessageGetOffset(message), buf, buf_len - 1);
+        io_context->datagram_callback(io_context->srp_context, buf, length);
+    }
+}
+
+static void wakeup_callback(void *context);
+
+static void
+note_wakeup(const char *what, void *at, uint64_t when)
+{
+#ifdef VERBOSE_DEBUG_MESSAGES
+    int microseconds = (int)(when % HAPTIME_FREQUENCY);
+    HAPTime seconds = when / HAPTIME_FREQUENCY;
+    int minute = (int)((seconds / 60) % 60);
+    int hour = (int)((seconds / 3600) % (7 * 24));
+    int second = (int)(seconds % 60);
+
+    DEBUG(PUB_S_SRP " %p at %llu %d:%d:%d.%d", what, at, when, hour, minute, second, microseconds);
+#endif
+}
+
+static void
+compute_wakeup_time(HAPTime now)
+{
+    io_context_t *io_context;
+    HAPTime next = 0;
+    uint32_t err;
+
+    for (io_context = io_contexts; io_context; io_context = io_context->next) {
+        if (next == 0 || (io_context->wakeup_time != 0 && io_context->wakeup_time < next)) {
+            next = io_context->wakeup_time;
+        }
+    }
+
+    // If we don't have a wakeup to schedule, wake up anyway in ten seconds.
+    if (next == 0) {
+        next = now + 10 * HAPTIME_FREQUENCY;
+    }
+    note_wakeup("next wakeup", NULL, next);
+    if (next != 0) {
+        int milliseconds;
+        if (next <= now) {
+            milliseconds = 1;
+        } else {
+            milliseconds = (int)((next - now) / (HAPTIME_FREQUENCY / 1000));
+        }
+        err = app_timer_start(m_srp_timer, APP_TIMER_TICKS(milliseconds), NULL);
+        if (err != 0) {
+            ERROR("app_timer_start returned %lu", err);
+        }
+    }
+}
+
+static void
+wakeup_callback(void *context)
+{
+    io_context_t *io_context;
+    HAPTime now = HAPPlatformClockGetCurrent(), next = 0;
+    bool more;
+
+    note_wakeup("     wakeup", NULL, now);
+    do {
+        more = false;
+        for (io_context = io_contexts; io_context; io_context = io_context->next) {
+            if (io_context->wakeup_time != 0 && io_context->wakeup_time < now) {
+                more = true;
+                note_wakeup("io wakeup", io_context, io_context->wakeup_time);
+                io_context->wakeup_time = 0;
+                io_context->wakeup_callback(io_context->srp_context);
+                break;
+            }
+            note_wakeup("no wakeup", io_context, io_context->wakeup_time);
+            if (next == 0 || (io_context->wakeup_time != 0 && io_context->wakeup_time < next))
+            {
+                next = io_context->wakeup_time;
+            }
+        }
+    } while (more);
+    compute_wakeup_time(now);
+}
+
+int
+srp_deactivate_udp_context(void *host_context, void *in_context)
+{
+    io_context_t *io_context, **p_io_contexts;
+    int err;
+
+    err = validate_io_context(&io_context, in_context);
+    if (err == kDNSServiceErr_NoError) {
+        for (p_io_contexts = &io_contexts; *p_io_contexts; p_io_contexts = &(*p_io_contexts)->next) {
+            if (*p_io_contexts == io_context) {
+                break;
+            }
+        }
+        // If we don't find it on the list, something is wrong.
+        if (*p_io_contexts == NULL) {
+            return kDNSServiceErr_Invalid;
+        }
+        *p_io_contexts = io_context->next;
+        io_context->wakeup_time = 0;
+        if (io_context->sock_active) {
+            otUdpClose(&io_context->sock);
+        }
+        free(io_context);
+    }
+    return err;
+}
+
+int
+srp_connect_udp(void *context, const uint8_t *port, uint16_t address_type, const uint8_t *address, uint16_t addrlen)
+{
+    io_context_t *io_context;
+    int err, oterr;
+
+    err = validate_io_context(&io_context, context);
+
+    if (err == kDNSServiceErr_NoError) {
+        if (address_type != dns_rrtype_aaaa || addrlen != 16) {
+            ERROR("srp_make_udp_context: invalid address");
+            return kDNSServiceErr_Invalid;
+        }
+        memcpy(&io_context->sockaddr.mAddress, address, 16);
+        memcpy(&io_context->sockaddr.mPort, port, 2);
+#ifdef OT_NETIF_INTERFACE_ID_THREAD
+        io_context->sockaddr.mScopeId = OT_NETIF_INTERFACE_ID_THREAD;
+#endif
+
+        oterr = otUdpOpen(otThreadInstance, &io_context->sock, datagram_callback, io_context);
+        if (oterr != OT_ERROR_NONE) {
+            ERROR("srp_make_udp_context: otUdpOpen returned %d", oterr);
+            return kDNSServiceErr_Unknown;
+        }
+
+        oterr = otUdpConnect(&io_context->sock, &io_context->sockaddr);
+        if (oterr != OT_ERROR_NONE) {
+            otUdpClose(&io_context->sock);
+            ERROR("srp_make_udp_context: otUdpConnect returned %d", oterr);
+            return kDNSServiceErr_Unknown;
+        }
+        io_context->sock_active = true;
+        err = kDNSServiceErr_NoError;
+    }
+    return err;
+}
+
+int
+srp_disconnect_udp(void *context)
+{
+    io_context_t *io_context;
+    int err;
+
+    err = validate_io_context(&io_context, context);
+    if (err == kDNSServiceErr_NoError && io_context->sock_active) {
+        otUdpClose(&io_context->sock);
+        io_context->sock_active = false;
+    }
+    return err;
+}
+
+int
+srp_make_udp_context(void *host_context, void **p_context, srp_datagram_callback_t callback, void *context)
+{
+    io_context_t *io_context = calloc(1, sizeof *io_context);
+    if (io_context == NULL) {
+        ERROR("srp_make_udp_context: no memory");
+        return kDNSServiceErr_NoMemory;
+    }
+    io_context->magic_cookie1 = io_context->magic_cookie2 = SRP_IO_CONTEXT_MAGIC;
+    io_context->datagram_callback = callback;
+    io_context->srp_context = context;
+
+    *p_context = io_context;
+    io_context->next = io_contexts;
+    io_contexts = io_context;
+    return kDNSServiceErr_NoError;
+}
+
+int
+srp_set_wakeup(void *host_context, void *context, int milliseconds, srp_wakeup_callback_t callback)
+{
+    int err;
+    io_context_t *io_context;
+    HAPTime now;
+
+    err = validate_io_context(&io_context, context);
+    if (err == kDNSServiceErr_NoError) {
+        now = HAPPlatformClockGetCurrent();
+        io_context->wakeup_time = now + milliseconds * (HAPTIME_FREQUENCY / 1000);
+        io_context->wakeup_callback = callback;
+        INFO("srp_set_wakeup: %llu (%llu + %dms)", io_context->wakeup_time, now, milliseconds);
+        compute_wakeup_time(now);
+    }
+    return err;
+}
+
+int
+srp_cancel_wakeup(void *host_context, void *context)
+{
+    int err;
+    io_context_t *io_context;
+
+    err = validate_io_context(&io_context, context);
+    if (err == kDNSServiceErr_NoError) {
+        io_context->wakeup_time = 0;
+    }
+    return err;
+}
+
+int
+srp_send_datagram(void *host_context, void *context, void *payload, size_t message_length)
+{
+    int err;
+    io_context_t *io_context;
+    otError       error;
+    otMessageInfo messageInfo;
+    otMessage *   message = NULL;
+    uint8_t *ap;
+
+#ifdef VERBOSE_DEBUG_MESSAGES
+    int i, j;
+    char buf[80], *bufp;
+    char *hexdigits = "01234567689abcdef";
+    uint8_t *msg = payload;
+#endif // VERBOSE_DEBUG_MESSAGES
+
+    err = validate_io_context(&io_context, context);
+    if (err == kDNSServiceErr_NoError) {
+        memset(&messageInfo, 0, sizeof(messageInfo));
+#ifdef OT_NETIF_INTERFACE_ID_THREAD
+        messageInfo.mInterfaceId = OT_NETIF_INTERFACE_ID_THREAD;
+#endif
+        messageInfo.mPeerPort    = io_context->sockaddr.mPort;
+        messageInfo.mPeerAddr    = io_context->sockaddr.mAddress;
+        ap = (uint8_t *)&io_context->sockaddr.mAddress;
+        SEGMENTED_IPv6_ADDR_GEN_SRP(ap, ap_buf);
+        INFO("Sending to " PRI_SEGMENTED_IPv6_ADDR_SRP " port %d", SEGMENTED_IPv6_ADDR_PARAM_SRP(ap, ap_buf),
+             io_context->sockaddr.mPort);
+#ifdef VERBOSE_DEBUG_MESSAGES
+        for (i = 0; i < message_length; i += 32) {
+            bufp = buf;
+            for (j = 0; bufp < buf + sizeof buf && i + j < message_length; j++) {
+                *bufp++ = hexdigits[msg[i + j] >> 4];
+                if (bufp < buf + sizeof buf) {
+                    *bufp++ = hexdigits[msg[i + j] % 15];
+                }
+                if (bufp < buf + sizeof buf && (j & 1) == 1) {
+                    *bufp++ = ' ';
+                }
+            }
+            *bufp = 0;
+            DEBUG(PUB_S_SRP, buf);
+        }
+#endif
+
+        message = otUdpNewMessage(otThreadInstance, NULL);
+        if (message == NULL) {
+            ERROR("srp_send_datagram: otUdpNewMessage returned NULL");
+            return kDNSServiceErr_NoMemory;
+        }
+
+        error = otMessageAppend(message, payload, message_length);
+        if (error != OT_ERROR_NONE) {
+            ERROR("srp_send_datagram: otMessageAppend returned %d", error);
+            return kDNSServiceErr_NoMemory;
+        }
+
+        error = otUdpSend(&io_context->sock, message, &messageInfo);
+        if (error != OT_ERROR_NONE) {
+            ERROR("srp_send_datagram: otUdpSend returned %d", error);
+            return kDNSServiceErr_Unknown;
+        }
+    }
+    return err;
+}
+
+#define KEY_ID 1000
+int
+srp_load_key_data(void *host_context, const char *key_name,
+                  uint8_t *buffer, uint16_t *length, uint16_t buffer_size)
+{
+#ifndef DEBUG_CONFLICTS
+    otError err;
+    uint16_t rlength = buffer_size;
+    // Note that at present we ignore the key name: we are only going to have one host key on an
+    // accessory.
+    err = otPlatSettingsGet(otThreadInstance, KEY_ID, 0, buffer, &rlength);
+    if (err != OT_ERROR_NONE) {
+        *length = 0;
+        return kDNSServiceErr_NoSuchKey;
+    }
+    *length = rlength;
+    return kDNSServiceErr_NoError;
+#else
+        return kDNSServiceErr_NoSuchKey;
+#endif
+}
+
+int
+srp_store_key_data(void *host_context, const char *name, uint8_t *buffer, uint16_t length)
+{
+    otError err;
+    err = otPlatSettingsAdd(otThreadInstance, KEY_ID, buffer, length);
+    if (err != OT_ERROR_NONE) {
+        ERROR("Unable to store key (length %d): %d", length, err);
+        return kDNSServiceErr_Unknown;
+    }
+    return kDNSServiceErr_NoError;
+}
+
+int
+srp_reset_key(const char *name, void *host_context)
+{
+    otPlatSettingsDelete(otThreadInstance, KEY_ID);
+}
+
+void
+register_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
+                  const char *name, const char *regtype, const char *domain, void *context)
+{
+    INFO("Register Reply: %ld " PRI_S_SRP " " PRI_S_SRP " " PRI_S_SRP "\n", errorCode, name == NULL ? "<NULL>" : name,
+         regtype == NULL ? "<NULL>" : regtype, domain == NULL ? "<NULL>" : domain);
+}
+
+void
+conflict_callback(const char *hostname)
+{
+    ERROR("Host name conflict: %s", hostname);
+}
+
+int
+srp_thread_init(otInstance *instance)
+{
+    uint32_t app_err;
+    DEBUG("In srp_thread_init().");
+    otThreadInstance = instance;
+    srp_host_init(otThreadInstance);
+
+    app_err = app_timer_create(&m_srp_timer, APP_TIMER_MODE_SINGLE_SHOT, wakeup_callback);
+    if (app_err != 0) {
+        ERROR("app_timer_create returned %lu", app_err);
+        return kDNSServiceErr_Unknown;
+    }
+    return kDNSServiceErr_NoError;
+}
+
+int
+srp_thread_shutdown(otInstance *instance)
+{
+    INFO("In srp_thread_shutdown().");
+    uint32_t app_err;
+    app_err = app_timer_stop(m_srp_timer);
+    if (app_err != 0) {
+        ERROR("app_timer_stop returned %lu", app_err);
+        return kDNSServiceErr_Unknown;
+    }
+    return kDNSServiceErr_NoError;
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/srp-thread.h b/ServiceRegistration/srp-thread.h
new file mode 100644 (file)
index 0000000..10b8588
--- /dev/null
@@ -0,0 +1,32 @@
+/* srp-ioloop.c
+ *
+ * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * srp host API implementation for Posix using ioloop primitives.
+ */
+
+#include <openthread/ip6.h>
+
+int srp_thread_init(otInstance* instance);
+int srp_thread_shutdown(otInstance* instance);
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/srp-tls.h b/ServiceRegistration/srp-tls.h
new file mode 100644 (file)
index 0000000..c7cb902
--- /dev/null
@@ -0,0 +1,58 @@
+/* srp-tls.h
+ *
+ * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * TLS Shim definitions.   These entry points should in principle work for any TLS
+ * library, with the addition of a single shim file, for example tls-mbedtls.c.
+ */
+
+#ifndef __SRP_TLS_H
+#define __SRP_TLS_H
+// Anonymous key structure, depends on the target.
+typedef struct srp_key srp_key_t;
+
+#ifdef SRP_CRYPTO_MBEDTLS_INTERNAL
+#include <mbedtls/certs.h>
+#include <mbedtls/x509.h>
+#include <mbedtls/ssl.h>
+
+struct tls_context {
+    struct mbedtls_ssl_context context;
+    enum { handshake_in_progress, handshake_complete } state;
+};
+#endif // SRP_CRYPTO_MBEDTLS_INTERNAL
+
+// tls_*.c:
+bool srp_tls_init(void);
+bool srp_tls_client_init(void);
+bool srp_tls_server_init(const char *NULLABLE cacert_file,
+                        const char *NULLABLE srvcrt_file, const char *NULLABLE server_key_file);
+bool srp_tls_accept_setup(comm_t *NONNULL comm);
+bool srp_tls_listen_callback(comm_t *NONNULL comm);
+bool srp_tls_connect_callback(comm_t *NONNULL comm);
+ssize_t srp_tls_read(comm_t *NONNULL comm, unsigned char *NONNULL buf, size_t max);
+void srp_tls_context_free(comm_t *NONNULL comm);
+ssize_t srp_tls_write(comm_t *NONNULL comm, struct iovec *NONNULL iov, int iov_len);
+
+#endif // __SRP_TLS_H
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
index de89d82bf70c1846e316841df5adc8114f302882..7b4e2d06044784b57bbd690be03696737469d5ec 100644 (file)
 #include <stdbool.h>
 
 #ifdef __clang__
-#define NULLABLE _Nullable
-#define NONNULL _Nonnull
+    #define NULLABLE _Nullable
+    #define NONNULL _Nonnull
 #else
-#define NULLABLE
-#define NONNULL
+    #define NULLABLE
+    #define NONNULL
 #endif
 
-#define ERROR(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__)
-#define INFO(fmt, ...)  fprintf(stderr, fmt "\n", ##__VA_ARGS__)
-#define DEBUG(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__)
+#ifdef DEBUG
+    #undef DEBUG
+    // #define DEBUG_VERBOSE
+#endif
 
-typedef struct srp_key srp_key_t;
+// We always want this until we start shipping
+#define DEBUG_VERBOSE
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+#ifdef THREAD_DEVKIT_ADK
+
+    #include "nrf_delay.h"
+
+    #define NULLABLE
+    #define NONNULL
+
+    #if !(DARWIN)
+        struct in_addr {
+            uint8_t s_addr;
+        };
+        typedef struct in6_addr {
+            union {
+                __uint8_t __u6_addr8[16];
+                __uint16_t __u6_addr16[8];
+                __uint32_t __u6_addr32[4];
+            } __u6_addr; /* 128-bit IP6 address */
+        } in6_addr_t;
+
+        #define s6_addr __u6_addr.__u6_addr8
+
+        #define ntohs(x) ((((uint16_t)(x) & 0xff00) >> 8) | (((uint16_t)(x) & 0xff) << 8))
+        #define htons(x) ntohs(x)
+
+        struct iovec {
+            void *base;
+            size_t len;
+        };
+    #endif // !(DARWIN)
+
+    #define OPENLOG(consolep)
+    #define DELAY 250
+    #define ERROR(fmt, ...) do {                               \
+            nrf_delay_ms(DELAY);                               \
+            HAPLogError(&kHAPLog_Default, fmt, ##__VA_ARGS__); \
+            nrf_delay_ms(DELAY);                               \
+        } while (0)
+    #define INFO(fmt, ...) do {                                \
+            nrf_delay_ms(DELAY);                               \
+            HAPLogInfo(&kHAPLog_Default, fmt, ##__VA_ARGS__);  \
+            nrf_delay_ms(DELAY);                               \
+        } while (0)
+
+    #ifdef DEBUG_VERBOSE
+        #define DEBUG(fmt, ...) do {                               \
+                nrf_delay_ms(DELAY);                               \
+                HAPLogDebug(&kHAPLog_Default, fmt, ##__VA_ARGS__); \
+                nrf_delay_ms(DELAY);                               \
+            } while (0)
+    #else // ifdef DEBUG_VERBOSE
+        #define DEBUG(fmt, ...)
+    #endif // ifdef DEBUG_VERBOSE
+
+    #define NO_CLOCK
+#else // ifdef THREAD_DEVKIT_ADK
+
+    #ifdef LOG_FPRINTF_STDERR
+        #define OPENLOG(consolep)
+        #define ERROR(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__)
+        #define INFO(fmt, ...)  fprintf(stderr, fmt "\n", ##__VA_ARGS__)
+        #ifdef DEBUG_VERBOSE
+            #ifdef IOLOOP_MACOS
+                int get_num_fds(void);
+            #endif // ifdef IOLOOP_MACOS
+            #define DEBUG(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__)
+        #else // ifdef DEBUG_VERBOSE
+            #define DEBUG(fmt, ...)
+        #endif
+    #else // ifdef LOG_FPRINTF_STDERR
+        #include <syslog.h>
+
+        // Apple device always has OS_LOG support.
+        #ifdef __APPLE__
+            #define OS_LOG_ENABLED 1
+            #include <os/log.h>
+
+            // Define log level
+            #define LOG_TYPE_FAULT      OS_LOG_TYPE_FAULT
+            #define LOG_TYPE_ERROR      OS_LOG_TYPE_ERROR
+            #define LOG_TYPE_DEFAULT    OS_LOG_TYPE_DEFAULT
+            #define LOG_TYPE_DEBUG      OS_LOG_TYPE_DEBUG
+            // Define log macro
+            #define log_with_component_and_type(CATEGORY, LEVEL, FORMAT, ...) os_log_with_type((CATEGORY), (LEVEL), \
+                                                                                (FORMAT), ##__VA_ARGS__)
+
+            #define OPENLOG(consolep) do {} while(0)
+            #define FAULT(FORMAT, ...)  log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_FAULT, FORMAT, \
+                                            ##__VA_ARGS__)
+            #define ERROR(FORMAT, ...)  log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_ERROR, FORMAT, \
+                                            ##__VA_ARGS__)
+
+            #ifdef DEBUG_VERBOSE
+                #ifdef DEBUG_FD_LEAKS
+                    int get_num_fds(void);
+                    #define INFO(FORMAT, ...) \
+                        do { \
+                            int foo = get_num_fds(); \
+                            log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_DEFAULT, "%d " FORMAT, foo, \
+                                ##__VA_ARGS__); \
+                        } while(0)
+                #else // ifdef IOLOOP_MACOS
+                    #define INFO(FORMAT, ...)   log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_DEFAULT, FORMAT, \
+                                                    ##__VA_ARGS__)
+                #endif // ifdef IOLOOP_MACOS
+
+                #define DEBUG(FORMAT, ...)  log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_DEBUG, FORMAT, \
+                                                ##__VA_ARGS__)
+            #else // ifdef DEBUG_VERBOSE
+                #define INFO(FORMAT, ...)   log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_DEFAULT, FORMAT, \
+                                                ##__VA_ARGS__)
+                #define DEBUG(FORMAT, ...)  do {} while(0)
+            #endif // ifdef DEBUG_VERBOSE
+        #else // ifdef __APPLE__
+            #define OS_LOG_ENABLED 0
+
+            #define OPENLOG(consolep) openlog("srp-mdns-proxy", (consolep ? LOG_PERROR : 0) | LOG_PID, LOG_DAEMON)
+            #define FAULT(fmt, ...) syslog(LOG_CRIT, fmt, ##__VA_ARGS__)
+            #define ERROR(fmt, ...) syslog(LOG_ERR, fmt, ##__VA_ARGS__)
+
+            #ifdef DEBUG_VERBOSE
+                #ifdef DEBUG_FD_LEAKS
+                    int get_num_fds(void);
+                    #define INFO(fmt, ...) \
+                        do { \
+                            int foo = get_num_fds(); \
+                            syslog(LOG_INFO, "%d " fmt, foo, ##__VA_ARGS__); \
+                        } while (0)
+                #else // ifdef IOLOOP_MACOS
+                    #define INFO(fmt, ...)  syslog(LOG_INFO, fmt, ##__VA_ARGS__)
+                #endif // ifdef IOLOOP_MACOS
+                #define DEBUG(fmt, ...) syslog(LOG_DEBUG, fmt, ##__VA_ARGS__)
+            #else // ifdef DEBUG_VERBOSE
+                #define INFO(fmt, ...)  syslog(LOG_INFO, fmt, ##__VA_ARGS__)
+                #define DEBUG(fmt, ...) do {} while(0)
+            #endif // ifdef DEBUG_VERBOSE
+        #endif // ifdef __APPLE__
+    #endif // ifdef LOG_FPRINTF_STDERR
+#endif // ifdef THREAD_DEVKIT_ADK
+
+//======================================================================================================================
+// MARK: - Logging Macros
+/**
+ * With the logging routines defined above, the logging macros are defined to facilitate the log redaction enforced by
+ * os_log on Apple platforms. By using the specifier "%{mask.hash}", the "<private>" text in the logs of customer device
+ * would be shown as a hashing value, which could be used as a way to associate other SRP logs even if it's redacted.
+ *
+ * On Apple platforms, the current existing log routines will be defined as:
+ * #define log_with_component_and_type(CATEGORY, LEVEL, FORMAT, ...) os_log_with_type((CATEGORY), (LEVEL), (FORMAT), \
+ *                                                                      ##__VA_ARGS__)
+ * #define ERROR(FORMAT, ...)  log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_ERROR, FORMAT, ##__VA_ARGS__)
+ * #define INFO(FORMAT, ...)   log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_DEFAULT, FORMAT, ##__VA_ARGS__)
+ * #define DEBUG(FORMAT, ...)  log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_DEBUG, FORMAT, ##__VA_ARGS__)
+ * And to follow the same log level with os_log, FAULT() is defined.
+ * #define FAULT(FORMAT, ...)  log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_FAULT, FORMAT, ##__VA_ARGS__)
+ * Therefore, all the previous logs would be put under OS_LOG_DEFAULT category.
+ * FAULT level lof will be mapped to  LOG_TYPE_FAULT in os_log.
+ * ERROR level log will be mapped to LOG_TYPE_ERROR in os_log.
+ * INFO level log will be mapped to LOG_TYPE_DEFAULT in os_log.
+ * DEBUG level log woll be mapped to LOG_TYPE_DEBUG in os_log.
+ *
+ * On platforms other than Apple, syslog will be used to write logs.
+ * FAULT level lof will be mapped to  LOG_CRIT in syslog.
+ * ERROR level log will be mapped to LOG_ERR in syslog.
+ * INFO level log will be mapped to LOG_INFO in syslog.
+ * DEBUG level log woll be mapped to LOG_DEBUG in syslog.
+ *
+ * The defined specifiers are:
+ * String specifier:
+ *      PUB_S_SRP: Use this in the format string when trying to log string and do not want it to be redacted.
+ *      PRI_S_SRP: Use this when trying to log string and redact it to a hash string.
+ *      Usage:
+ *          INFO("Public string: " PUB_S_SRP, ", private string: " PRI_S_SRP, string_ptr, string_ptr);
+ *
+ * DNS name (with dns_label_t type) specifier:
+ *      DNS_NAME_GEN_SRP: Always call this before logging DNS name.
+ *      PUB_DNS_NAME_SRP: Use this in the format string when trying to log DNS name and do not want it to be redacted.
+ *      PRI_DNS_NAME_SRP: Use this in the format string when trying to log DNS name and redact it to a hash string.
+ *      DNS_NAME_PARAM_SRP: Always use DNS_NAME_PARAM_SRP in the paramter list.
+ *      Usage:
+ *          DNS_NAME_GEN_SRP(dns_name_ptr, dns_name_buf);
+ *          INFO("Public DNS name: " PUB_DNS_NAME_SRP, ", private DNS name: " PRI_DNS_NAME_SRP,
+ *              DNS_NAME_PARAM_SRP(dns_name_ptr, dns_name_buf), DNS_NAME_PARAM_SRP(dns_name_ptr, dns_name_buf));
+ *
+ * IPv4 address specifier (with in_addr * type or a pointer to uint8_t[4]):
+ *      IPv4_ADDR_GEN_SRP: Always call this before logging IPv4 address.
+ *      PUB_IPv4_ADDR_SRP: Use this in the format string when trying to log IPv4 address and do not want it to be
+ *                         redacted.
+ *      PRI_IPv4_ADDR_SRP: Use this in the format string when trying to log IPv4 address and redact it to a hash string.
+ *      IPv4_ADDR_PARAM_SRP: Always use IPv4_ADDR_PARAM_SRP in the paramter list.
+ *      Usage:
+ *          IPv4_ADDR_GEN_SRP(in_addr_ptr_1, in_addr_buf_1);
+ *          IPv4_ADDR_GEN_SRP(in_addr_ptr_2, in_addr_buf_2);
+ *          INFO("Public IPv4 address: " PUB_IPv4_ADDR_SRP, ", private IPv4 address: " PRI_IPv4_ADDR_SRP,
+ *              IPv4_ADDR_PARAM_SRP(in_addr_ptr_1, in_addr_buf_1), IPv4_ADDR_PARAM_SRP(in_addr_ptr_2, in_addr_buf_2));
+ *
+ * IPv6 address specifier (with in6_addr * type or a pointer to uint8_t[16]):
+ *      IPv6_ADDR_GEN_SRP: Always call this before logging IPv6 address.
+ *      PUB_IPv6_ADDR_SRP: Use this in the format string when trying to log IPv6 address and do not want it to be
+ *                         redacted.
+ *      PRI_IPv6_ADDR_SRP: Use this in the format string when trying to log IPv6 address and redact it to a hash string.
+ *      IPv6_ADDR_PARAM_SRP: Always use IPv6_ADDR_PARAM_SRP in the paramter list.
+ *      Usage:
+ *          IPv6_ADDR_GEN_SRP(in6_addr_ptr_1, in6_addr_buf_1);
+ *          IPv6_ADDR_GEN_SRP(in6_addr_ptr_2, in6_addr_buf_2);
+ *          INFO("Public IPv6 address: " PUB_IPv6_ADDR_SRP, ", private IPv6 address: " PRI_IPv6_ADDR_SRP,
+ *              IPv6_ADDR_PARAM_SRP(in6_addr_ptr_1, in6_addr_buf_1),
+ *              IPv6_ADDR_PARAM_SRP(in6_addr_ptr_2, in6_addr_buf_2));
+ *
+ * Segmented IPv6  address specifier (with in6_addr * type or a pointer to uint8_t[16]):
+ *      SEGMENTED_IPv6_ADDR_GEN_SRP: Always call this before logging segmented IPv6 address.
+ *      PUB_SEGMENTED_IPv6_ADDR_SRP: Use this in the format string when trying to log segmented IPv6 address and do not
+ *                                   want it to be redacted.
+ *      PRI_SEGMENTED_IPv6_ADDR_SRP: Use this in the format string when trying to log segmented IPv6 address and redact
+ *                                   it to a hash string.
+ *      SEGMENTED_IPv6_ADDR_PARAM_SRP: Always use SEGMENTED_IPv6_ADDR_PARAM_SRP in the paramter list.
+ *      Usage:
+ *          SEGMENTED_IPv6_ADDR_GEN_SRP(in6_addr_ptr_1, in6_addr_buf_1);
+ *          SEGMENTED_IPv6_ADDR_GEN_SRP(in6_addr_ptr_2, in6_addr_buf_2);
+ *          INFO("Public IPv6 address: " PUB_SEGMENTED_IPv6_ADDR_SRP, ", private IPv6 address: "
+ *               PRI_SEGMENTED_IPv6_ADDR_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(in6_addr_ptr_1, in6_addr_buf_1),
+ *               SEGMENTED_IPv6_ADDR_PARAM_SRP(in6_addr_ptr_2, in6_addr_buf_2));
+ *      Note:
+ *          Segmented IPv6 is prefered when logging IPv6 address in SRP, because the address is divided to: 48 bit
+ *          prefix, 16 bit subnet, 64 bit host, which makes it easier to match the prefix even when log redaction is
+ *          turned on and the address is hashed to a string.
+ *
+ * IPv6 prefix specifier (with a pointer to uint8_t[] array):
+ *      IPv6_PREFIX_GEN_SRP: Always call this before logging IPv6 prefix (which is also segmented).
+ *      PUB_IPv6_PREFIX_SRP: Use this in the format string when trying to log IPv6 prefix and do not want it to be
+ *                           redacted.
+ *      PRI_IPv6_PREFIX_SRP: Use this in the format string when trying to log IPv6 prefix and redact it to a hash
+ *                           string.
+ *      IPv6_PREFIX_PARAM_SRP: Always use IPv6_PREFIX_PARAM_SRP in the paramter list.
+ *      Usage:
+ *          IPv6_PREFIX_GEN_SRP(in6_prefix_ptr_1, sizeof(in6_prefix_1), in6_prefix_buf_1);
+ *          IPv6_PREFIX_GEN_SRP(in6_prefix_ptr_2, sizeof(in6_prefix_2), in6_prefix_buf_2);
+ *          INFO("Public IPv6 prefix: " PUB_IPv6_PREFIX_SRP, ", private IPv6 prefix: " PRI_IPv6_PREFIX_SRP,
+ *              IPv6_PREFIX_PARAM_SRP(in6_prefix_buf_1), IPv6_PREFIX_PARAM_SRP(in6_prefix_buf_2));
+ *
+ * Mac address specifier (with a pointer to uint8_t[6] array):
+ *      PUB_MAC_ADDR_SRP: Use this in the format string when trying to log Mac address and do not want it to be
+ *                        redacted.
+ *      PRI_MAC_ADDR_SRP: Use this in the format string when trying to log Mac address and redact it to a hash string.
+ *      MAC_ADDR_PARAM_SRP: Always use MAC_ADDR_PARAM_SRP in the paramter list.
+ *      Usage:
+ *          INFO("Public MAC address: " PUB_MAC_ADDR_SRP, ", private MAC address: " PRI_MAC_ADDR_SRP,
+ *              MAC_ADDR_PARAM_SRP(mac_addr), MAC_ADDR_PARAM_SRP(mac_addr));
+ */
+
+// Helper macro to display if the correspoding IPv6 is ULA (Unique Local Address), LUA (Link Local Address)
+// or GUA (Global Unicast Address).
+// ULA starts with FC00::/7.
+// LUA starts with fe80::/10.
+// GUA starts with 2000::/3.
+#define ADDRESS_RANGE_STR(ADDR) ( \
+                                    ((ADDR)[0] & 0xFE) == 0xFC ? \
+                                        " (ULA)" : \
+                                        ( \
+                                            ( ((ADDR)[0] == 0xFE) && ((uint8_t)(ADDR)[1] & 0xC0) == 0x80 ) ? \
+                                                " (LUA)" : \
+                                                ( ((ADDR)[0] & 0xE0) == 0x20 ? " (GUA)" : "" ) \
+                                        ) \
+                                )
+
+// Logging macros
+#if OS_LOG_ENABLED
+    // Define log specifier
+    // String
+    #define PUB_S_SRP "%{public}s"
+    #define PRI_S_SRP "%{private, mask.hash}s"
+    // DNS name, when the pointer to DNS name is NULL, <NULL> will be logged.
+    #define DNS_NAME_GEN_SRP(NAME, BUF_NAME) \
+        char BUF_NAME[DNS_MAX_NAME_SIZE_ESCAPED + 1]; \
+        if (NAME != NULL) { \
+            dns_name_print(NAME, BUF_NAME, sizeof(BUF_NAME)); \
+        } else { \
+            strncpy(BUF_NAME, "<null>", sizeof("<null>") < sizeof(BUF_NAME) ? sizeof("<null>") : sizeof(BUF_NAME)); \
+        }
+    #define PUB_DNS_NAME_SRP PUB_S_SRP
+    #define PRI_DNS_NAME_SRP PRI_S_SRP
+    #define DNS_NAME_PARAM_SRP(NAME, BUF) (BUF)
+    // IP address
+    // IPv4
+    #define IPv4_ADDR_GEN_SRP(ADDR, BUF_NAME) do {} while(0)
+    #define PUB_IPv4_ADDR_SRP "%{public, network:in_addr}.4P"
+    #define PRI_IPv4_ADDR_SRP "%{private, mask.hash, network:in_addr}.4P"
+    #define IPv4_ADDR_PARAM_SRP(ADDR, BUF) ((uint8_t *)ADDR)
+    // IPv6
+    #define IPv6_ADDR_GEN_SRP(ADDR, BUF_NAME) do {} while(0)
+    #define PUB_IPv6_ADDR_SRP "%{public, network:in6_addr}.16P%{public}s"
+    #define PRI_IPv6_ADDR_SRP "%{private, mask.hash, network:in6_addr}.16P%{public}s"
+    #define IPv6_ADDR_PARAM_SRP(ADDR, BUF) ((uint8_t *)ADDR), ADDRESS_RANGE_STR((uint8_t *)ADDR)
+    // Segmented IPv6
+    // Subnet part can always be public.
+    #define SEGMENTED_IPv6_ADDR_GEN_SRP(ADDR, BUF_NAME) do {} while(0)
+    #define PUB_SEGMENTED_IPv6_ADDR_SRP "{%{public, srp:in6_addr_segment}.6P%{public}s, " \
+                                            "%{public, srp:in6_addr_segment}.2P, " \
+                                            "%{public, srp:in6_addr_segment}.8P}"
+    #define PRI_SEGMENTED_IPv6_ADDR_SRP "{%{private, mask.hash, srp:in6_addr_segment}.6P%{public}s, " \
+                                            "%{public, mask.hash, srp:in6_addr_segment}.2P, " \
+                                            "%{private, mask.hash, srp:in6_addr_segment}.8P}"
+    #define SEGMENTED_IPv6_ADDR_PARAM_SRP(ADDR, BUF) ((uint8_t *)(ADDR)), ADDRESS_RANGE_STR((uint8_t *)ADDR), \
+                                                        ((uint8_t *)(ADDR) + 6), ((uint8_t *)(ADDR) + 8)
+    // MAC address
+    #define PUB_MAC_ADDR_SRP "%{public, srp:mac_addr}.6P"
+    #define PRI_MAC_ADDR_SRP "%{private, mask.hash, srp:mac_addr}.6P"
+    #define MAC_ADDR_PARAM_SRP(ADDR) ((uint8_t *)ADDR)
+
+#else // ifdef OS_LOG_ENABLED
+    // When os_log is not available, all logs would be public.
+    // Define log specifier
+    // String
+    #define PUB_S_SRP "%s"
+    #define PRI_S_SRP PUB_S_SRP
+    // DNS name, when the pointer to DNS name is NULL, <NULL> will be logged.
+    #define DNS_NAME_GEN_SRP(NAME, BUF_NAME) \
+        char BUF_NAME[DNS_MAX_NAME_SIZE_ESCAPED + 1]; \
+        if (NAME != NULL) { \
+            dns_name_print(NAME, BUF_NAME, sizeof(BUF_NAME)); \
+        } else { \
+            strncpy(BUF_NAME, "<null>", sizeof("<null>") < sizeof(BUF_NAME) ? sizeof("<null>") : sizeof(BUF_NAME)); \
+        }
+    #define PUB_DNS_NAME_SRP "%s"
+    #define PRI_DNS_NAME_SRP PUB_DNS_NAME_SRP
+    #define DNS_NAME_PARAM_SRP(NAME, BUF) (BUF)
+    // IP address
+    // IPv4
+    #define IPv4_ADDR_GEN_SRP(ADDR, BUF_NAME) char BUF_NAME[INET_ADDRSTRLEN]; \
+                                                    inet_ntop(AF_INET, ((uint8_t *)ADDR), BUF_NAME, sizeof(BUF_NAME))
+    #define PUB_IPv4_ADDR_SRP "%s"
+    #define PRI_IPv4_ADDR_SRP PUB_IPv4_ADDR_SRP
+    #define IPv4_ADDR_PARAM_SRP(ADDR, BUF) (BUF)
+    // IPv6
+    #define IPv6_ADDR_GEN_SRP(ADDR, BUF_NAME) char BUF_NAME[INET6_ADDRSTRLEN]; \
+                                                    inet_ntop(AF_INET6, ((uint8_t *)ADDR), BUF_NAME, sizeof(BUF_NAME))
+    #define PUB_IPv6_ADDR_SRP "%s%s"
+    #define PRI_IPv6_ADDR_SRP PUB_IPv6_ADDR_SRP
+    #define IPv6_ADDR_PARAM_SRP(ADDR, BUF) (BUF), ADDRESS_RANGE_STR((uint8_t *)ADDR)
+    // Segmented IPv6
+    #define SEGMENTED_IPv6_ADDR_GEN_SRP(ADDR, BUF_NAME) IPv6_ADDR_GEN_SRP(ADDR, BUF_NAME)
+
+    #define PUB_SEGMENTED_IPv6_ADDR_SRP PUB_IPv6_ADDR_SRP
+    #define PRI_SEGMENTED_IPv6_ADDR_SRP PRI_IPv6_ADDR_SRP
+    #define SEGMENTED_IPv6_ADDR_PARAM_SRP(ADDR, BUF) IPv6_ADDR_PARAM_SRP(ADDR, BUF)
+    // MAC address
+    #define PUB_MAC_ADDR_SRP "%02x:%02x:%02x:%02x:%02x:%02x"
+    #define PRI_MAC_ADDR_SRP PUB_MAC_ADDR_SRP
+    #define MAC_ADDR_PARAM_SRP(ADDR) ((uint8_t *)ADDR)[0], ((uint8_t *)ADDR)[1], ((uint8_t *)ADDR)[2], \
+                                        ((uint8_t *)ADDR)[3], ((uint8_t *)ADDR)[4], ((uint8_t *)ADDR)[5]
+
+#endif // ifdef OS_LOG_ENABLED
+
+// IPv6 ULA 48-bit prefix
+#define IPv6_PREFIX_GEN_SRP(PREFIX, PREFIX_LEN, BUF_NAME) \
+    struct in6_addr _in6_addr_##BUF_NAME##_full_addr = {0}; \
+    memcpy(_in6_addr_##BUF_NAME##_full_addr.s6_addr, (PREFIX), \
+        MIN(sizeof(_in6_addr_##BUF_NAME##_full_addr.s6_addr), (PREFIX_LEN))); \
+    SEGMENTED_IPv6_ADDR_GEN_SRP(_in6_addr_##BUF_NAME##_full_addr.s6_addr, BUF_NAME);
+#define PUB_IPv6_PREFIX_SRP PUB_SEGMENTED_IPv6_ADDR_SRP
+#define PRI_IPv6_PREFIX_SRP PRI_SEGMENTED_IPv6_ADDR_SRP
+#define IPv6_PREFIX_PARAM_SRP(BUF_NAME) SEGMENTED_IPv6_ADDR_PARAM_SRP(_in6_addr_##BUF_NAME##_full_addr.s6_addr, \
+                                            BUF_NAME)
+
+//======================================================================================================================
+
+#pragma clang diagnostic pop
+
+#ifdef DEBUG_VERBOSE
+#ifdef __clang_analyzer__
+#define RELEASE_BASE(x, finalize, file, line) \
+    finalize(x)
+#else
+#define RELEASE_BASE(x, finalize, file, line)                                 \
+    do {                                                                      \
+        INFO("ALLOC: release at %2.2d: %p (%10s): %s:%d",                     \
+             (x)->ref_count, (void *)(x), # x, strrchr(file, '/') + 1, line); \
+        --(x)->ref_count;                                                     \
+        if ((x)->ref_count == 0) {                                            \
+            INFO("ALLOC:      finalize: %p (%10s): %s:%d",                    \
+                 (void *)(x), # x, strrchr(file, '/') + 1, line);             \
+            finalize(x);                                                      \
+        }                                                                     \
+    } while (0)
+#endif // __clang_analyzer__
+#define RETAIN_BASE(x, file, line)                                            \
+    do {                                                                      \
+        INFO("ALLOC:  retain at %2.2d: %p (%10s): %s:%d",                     \
+             (x)->ref_count, (void *)(x), # x, strrchr(file, '/') + 1, line); \
+        ++(x)->ref_count;                                                     \
+    } while (0)
+#define RELEASE(x, finalize) RELEASE_BASE(x, finalize, file, line)
+#define RETAIN(x) RETAIN_BASE(x, file, line)
+#define RELEASE_HERE(x, finalize) RELEASE_BASE(x, finalize, __FILE__, __LINE__)
+#define RETAIN_HERE(x) RETAIN_BASE(x, __FILE__, __LINE__)
+#else
+#ifdef __clang_analyzer__
+#define RELEASE(x, finalize) finalize(x)
+#define RELEASE_HERE(x, finalize) finalize(x)
+#define RETAIN(x)
+#define RETAIN_HERE(x)
+#else
+#define RELEASE(x, finalize) do {    \
+        if (--(x)->ref_count == 0) { \
+            finalize(x);             \
+            (void)file; (void)line;  \
+        }                            \
+    } while (0)
+#define RETAIN(x) do {          \
+        (x)->ref_count++;       \
+        (void)file; (void)line; \
+    } while (0)
+#define RELEASE_HERE(x, finalize) do {    \
+        if (--(x)->ref_count == 0) {      \
+            finalize(x);                  \
+        }                                 \
+    } while (0)
+#define RETAIN_HERE(x) ((x)->ref_count++)
+#endif
+#endif // DEBUG_VERBOSE
+
+#define THREAD_ENTERPRISE_NUMBER ((uint64_t)44970)
+#define THREAD_SRP_SERVER_OPTION 0x5d
+#define THREAD_PREF_ID_OPTION    0x9d
+
+#define IS_SRP_SERVICE(service) \
+    ((cti_service)->enterprise_number == THREAD_ENTERPRISE_NUMBER &&    \
+     (cti_service)->service_type == THREAD_SRP_SERVER_OPTION &&         \
+     (cti_service)->service_version == 1 &&                             \
+     (cti_service)->server_length == 18)
+#define IS_PREF_ID_SERVICE(service) \
+    ((cti_service)->enterprise_number == THREAD_ENTERPRISE_NUMBER &&    \
+     (cti_service)->service_type == THREAD_PREF_ID_OPTION &&            \
+     (cti_service)->service_version == 1 &&                             \
+     (cti_service)->server_length == 9)
+
+#ifdef MALLOC_DEBUG_LOGGING
+void *debug_malloc(size_t len, const char *file, int line);
+void *debug_calloc(size_t count, size_t len, const char *file, int line);
+char *debug_strdup(const char *s, const char *file, int line);
+void debug_free(void *p, const char *file, int line);
+
+#define malloc(x) debug_malloc(x, __FILE__, __LINE__)
+#define calloc(c, y) debug_calloc(c, y, __FILE__, __LINE__)
+#define strdup(s) debug_strdup(s, __FILE__, __LINE__)
+#define free(p) debug_free(p, __FILE__, __LINE__)
 #endif
 
+typedef struct srp_key srp_key_t;
+#endif // __SRP_H
+
 // Local Variables:
 // mode: C
 // tab-width: 4
diff --git a/ServiceRegistration/tls-mbedtls.c b/ServiceRegistration/tls-mbedtls.c
new file mode 100644 (file)
index 0000000..aec1238
--- /dev/null
@@ -0,0 +1,332 @@
+/* tls-mbedtls.c
+ *
+ * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * DNS SIG(0) signature verification for DNSSD SRP using mbedtls.
+ *
+ * Provides functions for generating a public key validating context based on SIG(0) KEY RR data, and
+ * validating a signature using a context generated with that public key.  Currently only ECDSASHA256 is
+ * supported.
+ */
+
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "srp.h"
+#define SRP_CRYPTO_MBEDTLS_INTERNAL
+#include "dns-msg.h"
+#include "srp-crypto.h"
+#include "ioloop.h"
+#include "srp-tls.h"
+
+// Context that is shared amongs all TLS connections, regardless of which server cert/key is in use.
+mbedtls_entropy_context entropy;
+mbedtls_ctr_drbg_context ctr_drbg;
+
+// For now, assume that we are using just one key and one server cert, plus the ca cert.  Consequently, we
+// can treat this as global state.  If wanted later, we could make this its own structure.
+mbedtls_x509_crt cacert_struct, *cacert = NULL;
+mbedtls_x509_crt srvcert_struct, *srvcert = NULL;
+mbedtls_pk_context srvkey;
+mbedtls_ssl_config tls_server_config;
+mbedtls_ssl_config tls_client_config;
+
+bool
+srp_tls_init(void)
+{
+    int status;
+
+    // Initialize the shared data structures.
+    mbedtls_x509_crt_init(&srvcert_struct);
+    mbedtls_pk_init(&srvkey);
+    mbedtls_entropy_init(&entropy);
+    mbedtls_ctr_drbg_init(&ctr_drbg);
+
+    status = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0);
+    if (status != 0) {
+        ERROR("Unable to seed RNG: %x", -status);
+        return false;
+    }
+    return true;
+}
+
+static bool
+mbedtls_config_init(mbedtls_ssl_config *config, int flags)
+{
+    int status = mbedtls_ssl_config_defaults(config, flags,
+                                             MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
+    if (status != 0) {
+        ERROR("Unable to set up default TLS config state: %x", -status);
+        return false;
+    }
+
+    mbedtls_ssl_conf_rng(config, mbedtls_ctr_drbg_random, &ctr_drbg);
+    return true;
+}
+
+bool
+srp_tls_client_init(void)
+{
+    if (!mbedtls_config_init(&tls_client_config, MBEDTLS_SSL_IS_CLIENT)) {
+        return false;
+    }
+    return true;
+}
+
+bool
+srp_tls_server_init(const char *cacert_file, const char *srvcert_file, const char *server_key_file)
+{
+    int status;
+
+    // Load the public key and cert
+    if (cacert_file != NULL) {
+        status = mbedtls_x509_crt_parse_file(&cacert_struct, cacert_file);
+        if (status != 0) {
+            ERROR("Unable to parse ca cert file: %x", -status);
+            return false;
+        }
+        cacert = &cacert_struct;
+    }
+
+    if (srvcert_file != NULL) {
+        status = mbedtls_x509_crt_parse_file(&srvcert_struct, srvcert_file);
+        if (status != 0) {
+            ERROR("Unable to parse server cert file: %x", -status);
+            return false;
+        }
+        srvcert = &srvcert_struct;
+        if (srvcert_struct.next && cacert != NULL) {
+            cacert = srvcert_struct.next;
+        }
+    }
+
+    if (server_key_file != NULL) {
+        status = mbedtls_pk_parse_keyfile(&srvkey, server_key_file, NULL);
+        if (status != 0) {
+            ERROR("Unable to parse server cert file: %x", -status);
+            return false;
+        }
+    }
+
+    if (!mbedtls_config_init(&tls_server_config, MBEDTLS_SSL_IS_SERVER)) {
+        return false;
+    }
+
+    if (cacert != NULL) {
+        mbedtls_ssl_conf_ca_chain(&tls_server_config, cacert, NULL);
+    }
+
+    status = mbedtls_ssl_conf_own_cert(&tls_server_config, srvcert, &srvkey);
+    if (status != 0) {
+        ERROR("Unable to configure own cert: %x", -status);
+        return false;
+    }
+    return true;
+}
+
+static int
+srp_tls_io_send(void *ctx, const unsigned char *buf, size_t len)
+{
+    ssize_t ret;
+    comm_t *comm = ctx;
+    ret = write(comm->io.sock, buf, len);
+    if (ret < 0) {
+        if (errno == EAGAIN) {
+            return MBEDTLS_ERR_SSL_WANT_WRITE;
+        } else {
+            return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
+        }
+    } else {
+        return (int)ret;
+    }
+}
+
+static int
+srp_tls_io_recv(void *ctx, unsigned char *buf, size_t max)
+{
+    ssize_t ret;
+    comm_t *comm = ctx;
+    ret = read(comm->io.sock, buf, max);
+    if (ret < 0) {
+        if (errno == EWOULDBLOCK || errno == EAGAIN) {
+            return MBEDTLS_ERR_SSL_WANT_READ;
+        } else {
+            return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
+        }
+    } else if (ret == 0) {
+        return MBEDTLS_ERR_SSL_CONN_EOF;
+    } else {
+        return (int)ret;
+    }
+}
+
+bool
+srp_tls_listen_callback(comm_t *comm)
+{
+    int status;
+
+    // Allocate the TLS config and state structures.
+    comm->tls_context = calloc(1, sizeof *comm->tls_context);
+    if (comm->tls_context == NULL) {
+        return false;
+    }
+    status = mbedtls_ssl_setup(&comm->tls_context->context, &tls_server_config);
+    if (status != 0) {
+        ERROR("Unable to set up TLS listener state: %x", -status);
+        return false;
+    }
+
+    // Set up the I/O functions.
+    mbedtls_ssl_set_bio(&comm->tls_context->context, comm, srp_tls_io_send, srp_tls_io_recv, NULL);
+
+    // Start the TLS handshake.
+    status = mbedtls_ssl_handshake(&comm->tls_context->context);
+    if (status != 0 && status != MBEDTLS_ERR_SSL_WANT_READ && status != MBEDTLS_ERR_SSL_WANT_WRITE) {
+        ERROR("TLS handshake failed: %x", -status);
+        srp_tls_context_free(comm);
+        ioloop_close(&comm->io);
+    }
+    return true;
+}
+
+bool
+srp_tls_connect_callback(comm_t *comm)
+{
+    int status;
+
+    // Allocate the TLS config and state structures.
+    comm->tls_context = calloc(1, sizeof *comm->tls_context);
+    if (comm->tls_context == NULL) {
+        return false;
+    }
+    status = mbedtls_ssl_setup(&comm->tls_context->context, &tls_client_config);
+    if (status != 0) {
+        ERROR("Unable to set up TLS connect state: %x", -status);
+        return false;
+    }
+
+    // Set up the I/O functions.
+    mbedtls_ssl_set_bio(&comm->tls_context->context, comm, srp_tls_io_send, srp_tls_io_recv, NULL);
+
+    // Start the TLS handshake.
+    status = mbedtls_ssl_handshake(&comm->tls_context->context);
+    if (status != 0 && status != MBEDTLS_ERR_SSL_WANT_READ && status != MBEDTLS_ERR_SSL_WANT_WRITE) {
+        ERROR("TLS handshake failed: %x", -status);
+        srp_tls_context_free(comm);
+        ioloop_close(&comm->io);
+    }
+    return true;
+}
+
+ssize_t
+srp_tls_read(comm_t *comm, unsigned char *buf, size_t max)
+{
+    int ret = mbedtls_ssl_read(&comm->tls_context->context, buf, max);
+    if (ret < 0) {
+        switch (ret) {
+        case MBEDTLS_ERR_SSL_WANT_READ:
+            return 0;
+        case MBEDTLS_ERR_SSL_WANT_WRITE:
+            ERROR("Got SSL want write in TLS read!");
+            // This means we got EWOULDBLOCK on a write operation.
+            // Not implemented yet, but the right way to handle this is probably to
+            // deselect read events until the socket is ready to write, then write,
+            // and then re-enable read events.   What we don't want is to keep calling
+            // read, because that will spin.
+            return 0;
+        case MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS:
+            ERROR("Got async in progress in TLS read!");
+            // No idea how to handle this yet.
+            return 0;
+#ifdef MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS
+        case MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS:
+            ERROR("Got crypto in progress in TLS read!");
+            // No idea how to handle this.
+            return 0;
+#endif
+        default:
+            ERROR("Unexpected response from SSL read: %x", -ret);
+            return -1;
+        }
+    } else {
+        // mbedtls returns 0 for EOF, just like read(), but we need a different signal,
+        // so we treat 0 as an error (for now).   In principle, we should get a notification
+        // when the remote end is done writing, so a clean close should be different than
+        // an abrupt close.
+        if (ret == 0) {
+            return -1;
+        }
+        return ret;
+    }
+}
+
+void
+srp_tls_context_free(comm_t *comm)
+{
+    // Free any state that the TLS library allocated
+    mbedtls_ssl_free(&comm->tls_context->context);
+    // Free and forget the context data structure
+    free(comm->tls_context);
+    comm->tls_context = 0;
+}
+
+ssize_t
+srp_tls_write(comm_t *comm, struct iovec *iov, int iov_len)
+{
+    int ret;
+    int i;
+    int bytes_written = 0;
+    for (i = 0; i < iov_len; i++) {
+        ret = mbedtls_ssl_write(&comm->tls_context->context, iov[i].iov_base, iov[i].iov_len);
+        if (ret < 0) {
+            switch (ret) {
+            case MBEDTLS_ERR_SSL_WANT_READ:
+                return bytes_written;
+            case MBEDTLS_ERR_SSL_WANT_WRITE:
+                ERROR("Got SSL want write in TLS read!");
+                return bytes_written;
+            case MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS:
+                ERROR("Got async in progress in TLS read!");
+                return bytes_written;
+#ifdef MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS
+            case MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS:
+                ERROR("Got crypto in progress in TLS read!");
+                return bytes_written;
+#endif
+            default:
+                ERROR("Unexpected response from SSL read: %x", -ret);
+                return -1;
+            }
+        } else if (ret != iov[i].iov_len) {
+            return bytes_written + ret;
+        } else {
+            bytes_written += ret;
+        }
+    }
+    return bytes_written;
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
index 1d82ef2236036af5bf2f31b8a1c5c0a709914cce..d1cb1cbb2994ceb3613866536e2ec2191e858470 100644 (file)
@@ -1,6 +1,6 @@
-/* wire.c
+/* towire.c
  *
- * Copyright (c) 2018 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2018-2019 Apple Computer, Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,9 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *
- * DNS wire-format functions.
+ * DNS to-wire wire-format functions.
  *
- * These are really simple functions for constructing DNS messages wire format.
+ * These are really simple functions for constructing DNS messages in wire format.
  * The flow is that there is a transaction structure which contains pointers to both
  * a message output buffer and a response input buffer.   The structure is initialized,
  * and then the various wire format functions are called repeatedly to store data.
 #include <stdio.h>
 #include <unistd.h>
 #include <string.h>
-#include <sys/errno.h>
-#include <sys/socket.h>
+#include <errno.h>
+#ifndef THREAD_DEVKIT_ADK
 #include <arpa/inet.h>
+#endif
+#include <stdlib.h>
+
 #include "srp.h"
 #include "dns-msg.h"
 #include "srp-crypto.h"
 #include <sys/time.h>
 #endif
 
+static int
+dns_parse_label(const char *cur, const char *NONNULL *NONNULL nextp, uint8_t *NONNULL lenp, uint8_t *NONNULL buf,
+                ssize_t max)
+{
+    const char *end;
+    int tlen;
+    const char *s;
+    uint8_t *t;
+
+    end = strchr(cur, '.');
+    if (end == NULL) {
+        end = cur + strlen(cur);
+        if (end == cur) {
+            *lenp = 0;
+            *nextp = NULL;
+            return 0;
+        }
+        *nextp = NULL;
+    } else {
+        if (end == cur) {
+            return EINVAL;
+        }
+        *nextp = end + 1;
+    }
+
+    // Figure out the length of the label after escapes have been converted.
+    tlen = 0;
+    for (s = cur; s < end; s++) {
+        if (*s == '\\') {
+            if (s + 4 <= end) {
+                tlen++;
+                s += 3;
+            } else {
+                tlen++;
+            }
+        } else {
+            tlen++;
+        }
+    }
+
+    // Is there no space?
+
+    if (tlen >= max) {
+        return ENOBUFS;
+    }
+
+    // Is the label too long?
+    if (end - cur > DNS_MAX_LABEL_SIZE) {
+        return ENAMETOOLONG;
+    }
+
+    // Store the label length
+    *lenp = (uint8_t)(tlen);
+
+    // Store the label.
+    t = buf;
+    for (s = cur; s < end; s++) {
+        if (*s == '\\') {
+            if (s + 4 <= end) {
+                int v0 = s[1] - '0';
+                int v1 = s[2] - '0';
+                int v2 = s[3] - '0';
+                int val = v0 * 100 + v1 * 10 + v2;
+                if (val < 255) {
+                    *t++ = val;
+                    s += 3;
+                } else {
+                    return EINVAL;
+                }
+            } else {
+                return EINVAL;
+            }
+        } else {
+            *t++ = *s;
+        }
+    }
+    return 0;
+}
+
 // Convert a name to wire format.   Does not store the root label (0) at the end.   Does not support binary labels.
 void
-dns_name_to_wire(dns_name_pointer_t *NULLABLE r_pointer,
-                 dns_towire_state_t *NONNULL txn,
-                 const char *NONNULL name)
+dns_name_to_wire_(dns_name_pointer_t *NULLABLE r_pointer, dns_towire_state_t *NONNULL txn,
+                  const char *NONNULL name, int line)
 {
-    const char *next, *cur, *end;
+    const char *next, *cur;
+    int status;
     dns_name_pointer_t np;
+
     if (!txn->error) {
         memset(&np, 0, sizeof np);
-        np.message_start = (u_int8_t *)txn->message;
+        np.message_start = (uint8_t *)txn->message;
         np.name_start = txn->p;
 
         cur = name;
         do {
-            end = strchr(cur, '.');
-            if (end == NULL) {
-                end = cur + strlen(cur);
-                if (end == cur) {
-                    break;
+            // Note that nothing is stored through txn->p until dns_name_parse has verified that
+            // there is space in the buffer for the label as well as the length.
+            status = dns_parse_label(cur, &next, txn->p, txn->p + 1, txn->lim - txn->p - 1);
+            if (status) {
+                if (status == ENOBUFS) {
+                    txn->truncated = true;
                 }
-                next = NULL;
-            } else {
-                if (end == cur) {
-                    break;
-                }
-                next = end + 1;
-            }
-
-            // Is there no space?
-            if (txn->p + (1 + end - cur) >= txn->lim) {
-                txn->error = ENOBUFS;
+                txn->error = status;
+                txn->line = line;
                 return;
             }
 
-            // Is the label too long?
-            if (end - cur > DNS_MAX_LABEL_SIZE) {
-                txn->error = ENAMETOOLONG;
-                return;
+            // Don't use the root label if it was parsed.
+            if (*txn->p != 0) {
+                np.num_labels++;
+                np.length += 1 + *txn->p;
+                txn->p = txn->p + *txn->p + 1;
+                cur = next;
             }
-
-            // Store the label length
-            *txn->p++ = (uint8_t)(end - cur);
-
-            // Store the label.
-            memcpy(txn->p, cur, end - cur);
-            txn->p += (end - cur);
-            np.num_labels++;
-            np.length += 1 + (end - cur);
-
-            cur = next;
         } while (next != NULL);
 
         if (np.length > DNS_MAX_NAME_SIZE) {
             txn->error = ENAMETOOLONG;
+            txn->line = line;
             return;
         }
         if (r_pointer != NULL) {
@@ -104,9 +171,8 @@ dns_name_to_wire(dns_name_pointer_t *NULLABLE r_pointer,
 
 // Like dns_name_to_wire, but includes the root label at the end.
 void
-dns_full_name_to_wire(dns_name_pointer_t *NULLABLE r_pointer,
-                      dns_towire_state_t *NONNULL txn,
-                      const char *NONNULL name)
+dns_full_name_to_wire_(dns_name_pointer_t *NULLABLE r_pointer, dns_towire_state_t *NONNULL txn,
+                       const char *NONNULL name, int line)
 {
     dns_name_pointer_t np;
     if (!txn->error) {
@@ -115,6 +181,8 @@ dns_full_name_to_wire(dns_name_pointer_t *NULLABLE r_pointer,
         if (!txn->error) {
             if (txn->p + 1 >= txn->lim) {
                 txn->error = ENOBUFS;
+                txn->truncated = true;
+                txn->line = line;
                 return;
             }
             *txn->p++ = 0;
@@ -122,6 +190,7 @@ dns_full_name_to_wire(dns_name_pointer_t *NULLABLE r_pointer,
             np.length += 1;
             if (np.length > DNS_MAX_NAME_SIZE) {
                 txn->error = ENAMETOOLONG;
+                txn->line = line;
                 return;
             }
             if (r_pointer) {
@@ -133,18 +202,20 @@ dns_full_name_to_wire(dns_name_pointer_t *NULLABLE r_pointer,
 
 // Store a pointer to a name that's already in the message.
 void
-dns_pointer_to_wire(dns_name_pointer_t *NULLABLE r_pointer,
-                    dns_towire_state_t *NONNULL txn,
-                    dns_name_pointer_t *NONNULL pointer)
+dns_pointer_to_wire_(dns_name_pointer_t *NULLABLE r_pointer, dns_towire_state_t *NONNULL txn,
+                     dns_name_pointer_t *NONNULL pointer, int line)
 {
     if (!txn->error) {
-        u_int16_t offset = pointer->name_start - pointer->message_start;
+        uint16_t offset = pointer->name_start - pointer->message_start;
         if (offset > DNS_MAX_POINTER) {
             txn->error = ETOOMANYREFS;
+            txn->line = line;
             return;
         }
         if (txn->p + 2 >= txn->lim) {
             txn->error = ENOBUFS;
+            txn->truncated = true;
+            txn->line = line;
             return;
         }
         *txn->p++ = 0xc0 | (offset >> 8);
@@ -154,6 +225,7 @@ dns_pointer_to_wire(dns_name_pointer_t *NULLABLE r_pointer,
             r_pointer->length += pointer->length + 1;
             if (r_pointer->length > DNS_MAX_NAME_SIZE) {
                 txn->error = ENAMETOOLONG;
+                txn->line = line;
                 return;
             }
         }
@@ -161,12 +233,13 @@ dns_pointer_to_wire(dns_name_pointer_t *NULLABLE r_pointer,
 }
 
 void
-dns_u8_to_wire(dns_towire_state_t *NONNULL txn,
-                 uint8_t val)
+dns_u8_to_wire_(dns_towire_state_t *NONNULL txn, uint8_t val, int line)
 {
     if (!txn->error) {
         if (txn->p + 1 >= txn->lim) {
             txn->error = ENOBUFS;
+            txn->truncated = true;
+            txn->line = line;
             return;
         }
         *txn->p++ = val;
@@ -175,12 +248,13 @@ dns_u8_to_wire(dns_towire_state_t *NONNULL txn,
 
 // Store a 16-bit integer in network byte order
 void
-dns_u16_to_wire(dns_towire_state_t *NONNULL txn,
-                 uint16_t val)
+dns_u16_to_wire_(dns_towire_state_t *NONNULL txn, uint16_t val, int line)
 {
     if (!txn->error) {
         if (txn->p + 2 >= txn->lim) {
             txn->error = ENOBUFS;
+            txn->truncated = true;
+            txn->line = line;
             return;
         }
         *txn->p++ = val >> 8;
@@ -189,12 +263,13 @@ dns_u16_to_wire(dns_towire_state_t *NONNULL txn,
 }
 
 void
-dns_u32_to_wire(dns_towire_state_t *NONNULL txn,
-                 uint32_t val)
+dns_u32_to_wire_(dns_towire_state_t *NONNULL txn, uint32_t val, int line)
 {
     if (!txn->error) {
         if (txn->p + 4 >= txn->lim) {
             txn->error = ENOBUFS;
+            txn->truncated = true;
+            txn->line = line;
             return;
         }
         *txn->p++ = val >> 24;
@@ -205,28 +280,26 @@ dns_u32_to_wire(dns_towire_state_t *NONNULL txn,
 }
 
 void
-dns_ttl_to_wire(dns_towire_state_t *NONNULL txn,
-                int32_t val)
+dns_ttl_to_wire_(dns_towire_state_t *NONNULL txn, int32_t val, int line)
 {
     if (!txn->error) {
-        if (val < 0) {
-            txn->error = EINVAL;
-            return;
-        }
-        dns_u32_to_wire(txn, (uint32_t)val);
+        dns_u32_to_wire_(txn, (uint32_t)val, line);
     }
 }
 
 void
-dns_rdlength_begin(dns_towire_state_t *NONNULL txn)
+dns_rdlength_begin_(dns_towire_state_t *NONNULL txn, int line)
 {
     if (!txn->error) {
         if (txn->p + 2 >= txn->lim) {
             txn->error = ENOBUFS;
+            txn->truncated = true;
+            txn->line = line;
             return;
         }
         if (txn->p_rdlength != NULL) {
             txn->error = EINVAL;
+            txn->line = line;
             return;
         }
         txn->p_rdlength = txn->p;
@@ -235,12 +308,13 @@ dns_rdlength_begin(dns_towire_state_t *NONNULL txn)
 }
 
 void
-dns_rdlength_end(dns_towire_state_t *NONNULL txn)
+dns_rdlength_end_(dns_towire_state_t *NONNULL txn, int line)
 {
-    int rdlength;
+    ssize_t rdlength;
     if (!txn->error) {
         if (txn->p_rdlength == NULL) {
             txn->error = EINVAL;
+            txn->line = line;
             return;
         }
         rdlength = txn->p - txn->p_rdlength - 2;
@@ -250,64 +324,78 @@ dns_rdlength_end(dns_towire_state_t *NONNULL txn)
     }
 }
 
+#ifndef THREAD_DEVKIT_ADK
 void
-dns_rdata_a_to_wire(dns_towire_state_t *NONNULL txn,
-                    const char *NONNULL ip_address)
+dns_rdata_a_to_wire_(dns_towire_state_t *NONNULL txn, const char *NONNULL ip_address, int line)
 {
     if (!txn->error) {
         if (txn->p + 4 >= txn->lim) {
             txn->error = ENOBUFS;
+            txn->truncated = true;
+            txn->line = line;
             return;
         }
         if (!inet_pton(AF_INET, ip_address, txn->p)) {
             txn->error = EINVAL;
+            txn->line = line;
+            return;
         }
         txn->p += 4;
     }
 }
 
 void
-dns_rdata_aaaa_to_wire(dns_towire_state_t *NONNULL txn,
-                       const char *NONNULL ip_address)
+dns_rdata_aaaa_to_wire_(dns_towire_state_t *NONNULL txn, const char *NONNULL ip_address, int line)
 {
     if (!txn->error) {
         if (txn->p + 16 >= txn->lim) {
             txn->error = ENOBUFS;
+            txn->truncated = true;
+            txn->line = line;
             return;
         }
         if (!inet_pton(AF_INET6, ip_address, txn->p)) {
             txn->error = EINVAL;
+            txn->line = line;
+            return;
         }
         txn->p += 16;
     }
 }
+#endif
 
 uint16_t
-dns_rdata_key_to_wire(dns_towire_state_t *NONNULL txn,
-                      unsigned key_type,
-                      unsigned name_type,
-                      unsigned signatory,
-                      srp_key_t *key)
+dns_rdata_key_to_wire_(dns_towire_state_t *NONNULL txn, unsigned key_type, unsigned name_type,
+                       unsigned signatory, srp_key_t *key, int line)
 {
-    int key_len = srp_pubkey_length(key);
+    ssize_t key_len = srp_pubkey_length(key), copied_len;
     uint8_t *rdata = txn->p;
     uint32_t key_tag;
-    int i, rdlen;
-    
+    int i;
+    ssize_t rdlen;
+
     if (!txn->error) {
         if (key_type > 3 || name_type > 3 || signatory > 15) {
             txn->error = EINVAL;
+            txn->line = line;
             return 0;
         }
         if (txn->p + key_len + 4 >= txn->lim) {
             txn->error = ENOBUFS;
+            txn->truncated = true;
+            txn->line = line;
             return 0;
         }
         *txn->p++ = (key_type << 6) | name_type;
         *txn->p++ = signatory;
         *txn->p++ = 3; // protocol type is always 3
         *txn->p++ = srp_key_algorithm(key);
-        srp_pubkey_copy(txn->p, key_len, key);
+        copied_len = srp_pubkey_copy(txn->p, key_len, key);
+        if (copied_len == 0) {
+            txn->error = EINVAL;
+            txn->line = line;
+            return 0;
+        }
         txn->p += key_len;
     }
     rdlen = txn->p - rdata;
@@ -322,31 +410,35 @@ dns_rdata_key_to_wire(dns_towire_state_t *NONNULL txn,
 }
 
 void
-dns_rdata_txt_to_wire(dns_towire_state_t *NONNULL txn,
-                      const char *NONNULL txt_record)
+dns_rdata_txt_to_wire_(dns_towire_state_t *NONNULL txn, const char *NONNULL txt_record, int line)
 {
     if (!txn->error) {
-        unsigned len = strlen(txt_record);
+        ssize_t len = strlen(txt_record);
         if (txn->p + len + 1 >= txn->lim) {
             txn->error = ENOBUFS;
+            txn->truncated = true;
+            txn->line = line;
             return;
         }
         if (len > 255) {
             txn->error = ENAMETOOLONG;
+            txn->line = line;
             return;
         }
-        *txn->p++ = (u_int8_t)len;
+        *txn->p++ = (uint8_t)len;
         memcpy(txn->p, txt_record, len);
         txn->p += len;
     }
 }
 
 void
-dns_rdata_raw_data_to_wire(dns_towire_state_t *NONNULL txn, const void *NONNULL raw_data, size_t length)
+dns_rdata_raw_data_to_wire_(dns_towire_state_t *NONNULL txn, const void *NONNULL raw_data, size_t length, int line)
 {
     if (!txn->error) {
         if (txn->p + length >= txn->lim) {
             txn->error = ENOBUFS;
+            txn->truncated = true;
+            txn->line = line;
             return;
         }
         memcpy(txn->p, raw_data, length);
@@ -355,15 +447,13 @@ dns_rdata_raw_data_to_wire(dns_towire_state_t *NONNULL txn, const void *NONNULL
 }
 
 void
-dns_edns0_header_to_wire(dns_towire_state_t *NONNULL txn,
-                         int mtu,
-                         int xrcode,
-                         int version,
-                         int DO)
+dns_edns0_header_to_wire_(dns_towire_state_t *NONNULL txn, int mtu, int xrcode, int version, int DO, int line)
 {
     if (!txn->error) {
         if (txn->p + 9 >= txn->lim) {
             txn->error = ENOBUFS;
+            txn->truncated = true;
+            txn->line = line;
             return;
         }
         *txn->p++ = 0; // root label
@@ -371,21 +461,24 @@ dns_edns0_header_to_wire(dns_towire_state_t *NONNULL txn,
         dns_u16_to_wire(txn, mtu);
         *txn->p++ = xrcode;
         *txn->p++ = version;
-        *txn->p++ = DO << 7;   // flags (usb)
-        *txn->p++ = 0;                 // flags (lsb, mbz)
+        *txn->p++ = DO << 7; // flags (usb)
+        *txn->p++ = 0;       // flags (lsb, mbz)
     }
 }
 
 void
-dns_edns0_option_begin(dns_towire_state_t *NONNULL txn)
+dns_edns0_option_begin_(dns_towire_state_t *NONNULL txn, int line)
 {
     if (!txn->error) {
         if (txn->p + 2 >= txn->lim) {
             txn->error = ENOBUFS;
+            txn->truncated = true;
+            txn->line = line;
             return;
         }
         if (txn->p_opt != NULL) {
             txn->error = EINVAL;
+            txn->line = line;
             return;
         }
         txn->p_opt = txn->p;
@@ -394,12 +487,13 @@ dns_edns0_option_begin(dns_towire_state_t *NONNULL txn)
 }
 
 void
-dns_edns0_option_end(dns_towire_state_t *NONNULL txn)
+dns_edns0_option_end_(dns_towire_state_t *NONNULL txn, int line)
 {
-    int opt_length;
+    ssize_t opt_length;
     if (!txn->error) {
         if (txn->p_opt == NULL) {
             txn->error = EINVAL;
+            txn->line = line;
             return;
         }
         opt_length = txn->p - txn->p_opt - 2;
@@ -410,13 +504,11 @@ dns_edns0_option_end(dns_towire_state_t *NONNULL txn)
 }
 
 void
-dns_sig0_signature_to_wire(dns_towire_state_t *NONNULL txn,
-                           srp_key_t *key,
-                           uint16_t key_tag,
-                           dns_name_pointer_t *NONNULL signer,
-                           const char *NONNULL signer_fqdn)
+dns_sig0_signature_to_wire_(dns_towire_state_t *NONNULL txn, srp_key_t *key, uint16_t key_tag,
+                            dns_name_pointer_t *NONNULL signer, const char *NONNULL signer_hostname,
+                            const char *NONNULL signer_domain, int line)
 {
-    int siglen = srp_signature_length(key);
+    ssize_t siglen = srp_signature_length(key);
     uint8_t *start, *p_signer, *p_signature, *rrstart = txn->p;
 #ifndef NO_CLOCK
     struct timeval now;
@@ -431,7 +523,7 @@ dns_sig0_signature_to_wire(dns_towire_state_t *NONNULL txn,
     // 29 bytes so far
     // signature data (depends on algorithm, e.g. 64 for ECDSASHA256)
     // so e.g. 93 bytes total
-    
+
     if (!txn->error) {
         dns_u8_to_wire(txn, 0);        // root label
         dns_u16_to_wire(txn, dns_rrtype_sig);
@@ -443,117 +535,92 @@ dns_sig0_signature_to_wire(dns_towire_state_t *NONNULL txn,
         dns_u8_to_wire(txn, srp_key_algorithm(key));
         dns_u8_to_wire(txn, 0); // labels field doesn't apply for transaction signature
         dns_ttl_to_wire(txn, 0); // original ttl doesn't apply
-#ifdef NO_CLOCK
-        dns_u32_to_wire(txn, 0); // Indicate that we have no clock: set expiry and inception times to zero
-        dns_u32_to_wire(txn, 0);
-#else        
+#ifndef NO_CLOCK
         gettimeofday(&now, NULL);
-        dns_u32_to_wire(txn, now.tv_sec + 300); // signature expiration time is five minutes from now
-        dns_u32_to_wire(txn, now.tv_sec - 300); // signature inception time, five minutes in the past
+        uint32_t sec = (uint32_t)now.tv_sec;
+        // In te extraordinarily unlikely event that time_t has rolled over
+        if (sec < 300) {
+#endif
+            dns_u32_to_wire(txn, 0); // Indicate that we have no clock: set expiry and inception times to zero
+            dns_u32_to_wire(txn, 0);
+#ifndef NO_CLOCK
+        } else {
+            dns_u32_to_wire(txn, sec + 300); // signature expiration time is five minutes from now
+            dns_u32_to_wire(txn, sec - 300); // signature inception time, five minutes in the past
+        }
 #endif
         dns_u16_to_wire(txn, key_tag);
+
         p_signer = txn->p;
         // We store the name in uncompressed form because that's what we have to sign
-        dns_full_name_to_wire(NULL, txn, signer_fqdn);
+        if (signer_hostname != NULL) {
+            dns_name_to_wire(NULL, txn, signer_hostname);
+        }
+        dns_full_name_to_wire(NULL, txn, signer_domain);
         // And that means we're going to have to copy the signature back earlier in the packet.
         p_signature = txn->p;
 
         // Sign the message, signature RRDATA (less signature) first.
-        srp_sign(txn->p, siglen, (uint8_t *)txn->message, rrstart - (uint8_t *)txn->message,
-                 start, txn->p - start, key);
-
-        // Now that it's signed, back up and store the pointer to the name, because we're trying
-        // to be as compact as possible.
-        txn->p = p_signer;
-        dns_pointer_to_wire(NULL, txn, signer); // Pointer to the owner name the key is attached to
-        // And move the signature earlier in the packet.
-        memmove(txn->p, p_signature, siglen);
-
-        txn->p += siglen;
-        dns_rdlength_end(txn);
-    }
-}
-
-int
-dns_send_to_server(dns_transaction_t *NONNULL txn,
-                   const char *NONNULL anycast_address, uint16_t port,
-                   dns_response_callback_t NONNULL callback)
-{
-    union {
-        struct sockaddr_storage s;
-        struct sockaddr sa;
-        struct sockaddr_in sin;
-        struct sockaddr_in6 sin6;
-    } addr, from;
-    socklen_t len, fromlen;
-    ssize_t rv, datasize;
-
-    if (!txn->towire.error) {
-        memset(&addr, 0, sizeof addr);
-
-        // Try IPv4 first because IPv6 addresses are never valid IPv4 addresses
-        if (inet_pton(AF_INET, anycast_address, &addr.sin.sin_addr)) {
-            addr.sin.sin_family = AF_INET;
-            addr.sin.sin_port = htons(port);
-            len = sizeof addr.sin;
-        } else if (inet_pton(AF_INET6, anycast_address, &addr.sin6.sin6_addr)) {
-            addr.sin6.sin6_family = AF_INET6;
-            addr.sin6.sin6_port = htons(port);
-            len = sizeof addr.sin6;
+        if (!srp_sign(txn->p, siglen, (uint8_t *)txn->message, rrstart - (uint8_t *)txn->message,
+                      start, txn->p - start, key)) {
+            txn->error = true;
+            txn->line = __LINE__;
         } else {
-            txn->towire.error = EPROTONOSUPPORT;
-            return -1;
-        }
-//#ifdef HAVE_SA_LEN
-        addr.sa.sa_len = len;
-//#endif
+            // Now that it's signed, back up and store the pointer to the name, because we're trying
+            // to be as compact as possible.
+            txn->p = p_signer;
+            dns_pointer_to_wire(NULL, txn, signer); // Pointer to the owner name the key is attached to
+            // And move the signature earlier in the packet.
+            memmove(txn->p, p_signature, siglen);
 
-        txn->sock = socket(addr.sa.sa_family, SOCK_DGRAM, IPPROTO_UDP);
-        if (txn->sock < 0) {
-            txn->towire.error = errno;
-            return -1;
+            txn->p += siglen;
+            dns_rdlength_end(txn);
         }
 
-#if 0
-        memset(&myaddr, 0, sizeof myaddr);
-        myaddr.sin.sin_port = htons(9999);
-        myaddr.sa.sa_len = len;
-        myaddr.sa.sa_family = addr.sa.sa_family;
-        rv = bind(txn->sock, &myaddr.sa, len);
-        if (rv < 0) {
-            txn->towire.error = errno;
-            return -1;
+        if (txn->error) {
+            txn->outer_line = line;
         }
-#endif
-
-        datasize = txn->towire.p - ((u_int8_t *)txn->towire.message);
-        rv = sendto(txn->sock, txn->towire.message, datasize, 0, &addr.sa, len);
-        if (rv < 0) {
-            txn->towire.error = errno;
-            goto out;
-        }
-        if (rv != datasize) {
-            txn->towire.error = EMSGSIZE;
-            goto out;
-        }
-        fromlen = sizeof from;
-        rv = recvfrom(txn->sock, txn->response, sizeof *txn->response, 0, &from.sa, &fromlen);
-        if (rv < 0) {
-            txn->towire.error = errno;
-            goto out;
-        }
-        txn->response_length = rv;
     }
-out:
-    close(txn->sock);
-    txn->sock = 0;
+}
 
-    if (txn->towire.error) {
-        return -1;
-    }
-    return 0;
+#ifdef MALLOC_DEBUG_LOGGING
+#undef malloc
+#undef calloc
+#undef strdup
+#undef free
+
+void *
+debug_malloc(size_t len, const char *file, int line)
+{
+    void *ret = malloc(len);
+    INFO("%p: malloc(%zu) at " PUB_S_SRP ":%d", ret, len, file, line);
+    return ret;
 }
 
+void *
+debug_calloc(size_t count, size_t len, const char *file, int line)
+{
+    void *ret = calloc(count, len);
+    INFO("%p: calloc(%zu, %zu) at " PUB_S_SRP ":%d", ret, count, len, file, line);
+    return ret;
+}
+
+char *
+debug_strdup(const char *s, const char *file, int line)
+{
+    char *ret = strdup(s);
+    INFO("%p: strdup(%p) at " PUB_S_SRP ":%d", ret, s, file, line);
+    return ret;
+}
+
+void
+debug_free(void *p, const char *file, int line)
+{
+    INFO("%p: free() at " PUB_S_SRP ":%d", p, file, line);
+    free(p);
+}
+#endif
+
 // Local Variables:
 // mode: C
 // tab-width: 4
diff --git a/ServiceRegistration/verify-macos.c b/ServiceRegistration/verify-macos.c
new file mode 100644 (file)
index 0000000..1b0fac6
--- /dev/null
@@ -0,0 +1,295 @@
+/* verify_macos.c
+ *
+ * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * DNS SIG(0) signature verification for DNSSD SRP using MacOS Security Framework.
+ *
+ * Provides functions for generating a public key validating context based on SIG(0) KEY RR data, and
+ * validating a signature using a context generated with that public key.  Currently only ECDSASHA256 is
+ * supported.
+ */
+
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdlib.h>
+#include <CommonCrypto/CommonDigestSPI.h>
+#include <AssertMacros.h>
+
+#include "srp.h"
+#define SRP_CRYPTO_MACOS_INTERNAL
+#include "dns-msg.h"
+#include "srp-crypto.h"
+
+//======================================================================================================================
+
+static SecKeyRef
+create_public_sec_key(const dns_rr_t *const key_record);
+
+static CFDataRef
+create_data_to_verify(dns_wire_t *const message, const dns_rr_t *const signature);
+
+bool
+srp_sig0_verify(dns_wire_t *message, dns_rr_t *key, dns_rr_t *signature)
+{
+    bool valid = false;
+    CFErrorRef cf_error = NULL;
+    SecKeyRef public_key = NULL;
+    CFDataRef data_to_verify_cfdata = NULL;
+    CFDataRef sig_to_match_cfdata = NULL;
+
+    // The algorithm in KEY and SIG(0) has to match.
+    require_action_quiet(key->data.key.algorithm == signature->data.sig.algorithm, exit,
+                         ERROR("KEY algorithm does not match the SIG(0) algorithm - "
+                               "KEY algorithm: %u, SIG(0) algorithm: %u",
+                               key->data.key.algorithm, signature->data.sig.algorithm));
+
+    // The only supported algorithm now is ECDSA Curve P-256 with SHA-256.
+    require_action_quiet(signature->data.sig.algorithm == dnssec_keytype_ecdsa, exit,
+                         ERROR("Unsupported KEY algorithm - KEY algorithm: %u", signature->data.sig.algorithm));
+
+    // The key size should always be ECDSA_KEY_SIZE since only ECDSA Curve P-256 with SHA-256 is used now.
+    require_action_quiet(key->data.key.len == ECDSA_KEY_SIZE, exit,
+                         ERROR("Invalid KEY length - KEY len: %d", key->data.key.len));
+
+    // The signature size should always be ECDSA_SHA256_SIG_SIZE, since only ECDSA Curve P-256 with SHA-256 is used now.
+    require_action_quiet(signature->data.sig.len == ECDSA_SHA256_SIG_SIZE, exit,
+                         ERROR("Invalid SIG(0) length - SIG(0) length: %d", signature->data.sig.len));
+
+    // Get SecKeyRef given the KEY data.
+    public_key = create_public_sec_key(key);
+    require_action_quiet(public_key != NULL, exit, ERROR("Failed to create public_key"));
+
+    // Create signature to check.
+    sig_to_match_cfdata = CFDataCreate(kCFAllocatorDefault, signature->data.sig.signature, signature->data.sig.len);
+    require_action_quiet(sig_to_match_cfdata != NULL, exit,
+                         ERROR("CFDataCreate failed when creating sig_to_match_cfdata"));
+
+    // Reconstruct the data (the digest of the raw data) that is signed by SIG(0).
+    data_to_verify_cfdata = create_data_to_verify(message, signature);
+    require_action_quiet(data_to_verify_cfdata != NULL, exit, ERROR("Failed to data_to_verify_cfdata"));
+
+    // Set the corresponding SecKeyAlgorithm for SecKeyVerifySignature.
+    SecKeyAlgorithm verify_algorithm;
+    switch (key->data.key.algorithm) {
+        case dnssec_keytype_ecdsa:
+            verify_algorithm = kSecKeyAlgorithmECDSASignatureRFC4754;
+            break;
+
+        default:
+            FAULT("Unsupported KEY algorithm - KEY algorithm: %u", key->data.key.algorithm);
+            goto exit;
+    }
+
+    // Validate the signature.
+    valid = SecKeyVerifySignature(public_key, verify_algorithm, data_to_verify_cfdata, sig_to_match_cfdata, &cf_error);
+    if (!valid) {
+        CFStringRef error_cfstring = CFErrorCopyDescription(cf_error);
+        ERROR("SecKeyVerifySignature failed to validate - Error Description: %@", error_cfstring);
+        CFRelease(error_cfstring);
+        CFRelease(cf_error);
+        cf_error = NULL;
+    }
+
+exit:
+    if (data_to_verify_cfdata != NULL) {
+        CFRelease(data_to_verify_cfdata);
+    }
+    if (sig_to_match_cfdata != NULL) {
+        CFRelease(sig_to_match_cfdata);
+    }
+    if (public_key != NULL) {
+        CFRelease(public_key);
+    }
+
+    return valid;
+}
+
+static SecKeyRef
+create_public_sec_key(const dns_rr_t *const key_record)
+{
+    SecKeyRef key_ref = NULL;
+    CFErrorRef cf_error = NULL;
+    if (key_record->data.key.algorithm == dnssec_keytype_ecdsa){
+        uint8_t four = 4;
+        const void *public_key_options_keys[] = {kSecAttrKeyType, kSecAttrKeyClass};
+        const void *public_key_options_values[] = {kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyClassPublic};
+        CFMutableDataRef public_key_cfdata = NULL;
+        CFDictionaryRef public_key_options = NULL;
+
+        // The format of the public key data is 04 | X | Y
+        public_key_cfdata = CFDataCreateMutable(kCFAllocatorDefault, 1 + key_record->data.key.len);
+        require_action_quiet(public_key_cfdata != NULL, ecdsa_exit,
+                             ERROR("CFDataCreateMutable failed when creating public key CFMutableDataRef"));
+        CFDataAppendBytes(public_key_cfdata, &four, sizeof(four));
+        CFDataAppendBytes(public_key_cfdata, key_record->data.key.key, key_record->data.key.len);
+
+        public_key_options = CFDictionaryCreate(kCFAllocatorDefault, public_key_options_keys, public_key_options_values,
+                                                sizeof(public_key_options_keys) / sizeof(void *),
+                                                &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+        require_action_quiet(public_key_options != NULL, ecdsa_exit,
+                             ERROR("CFDictionaryCreate failed when creating public key options CFDictionaryRef"));
+
+        key_ref = SecKeyCreateWithData(public_key_cfdata, public_key_options, &cf_error);
+        require_action_quiet(key_ref != NULL, ecdsa_exit,
+                             ERROR("SecKeyCreateWithData failed when creating public key SecKeyRef"));
+
+    ecdsa_exit:
+        if (public_key_cfdata != NULL) {
+            CFRelease(public_key_cfdata);
+        }
+        if (public_key_options != NULL) {
+            CFRelease(public_key_options);
+        }
+    } else {
+        key_ref = NULL;
+    }
+
+    if (cf_error != NULL) {
+        CFRelease(cf_error);
+        cf_error = NULL;
+    }
+    return key_ref;
+}
+
+static CFDataRef
+create_data_to_verify(dns_wire_t *const message, const dns_rr_t *const signature)
+{
+    bool encounter_error;
+    CCDigestAlgorithm cc_digest_algorithm;
+    CFDataRef data_to_verify_cfdata = NULL;
+    uint8_t *canonical_signer_name = NULL;
+#define MAX_HASH_SIZE ECDSA_SHA256_HASH_SIZE
+    uint8_t digest[MAX_HASH_SIZE];
+    size_t digest_length;
+
+    switch (signature->data.sig.algorithm) {
+        case dnssec_keytype_ecdsa:
+            cc_digest_algorithm = kCCDigestSHA256;
+            digest_length = ECDSA_SHA256_HASH_SIZE;
+            break;
+
+        default:
+            encounter_error = true;
+            FAULT("Unsupported SIG(0) algorithm - SIG(0) algorithm: %u", signature->data.sig.algorithm);
+            goto exit;
+    }
+    CCDigestCtx cc_digest_context;
+    CCDigestInit(cc_digest_algorithm, &cc_digest_context);
+
+    // data to be hashed = (SIG(0) RDATA without signature field) + (request - SIG(0)).
+
+    // (SIG(0) RDATA without signature field) = SIG(0) fields without signer name + canonical signer name.
+    // Copy SIG(0) fields without signer name.
+    CCDigestUpdate(&cc_digest_context, &message->data[signature->data.sig.start + SIG_HEADERLEN], SIG_STATIC_RDLEN);
+
+    // Construct and copy canonical signer name.
+    size_t canonical_signer_name_length = dns_name_wire_length(signature->data.sig.signer);
+    canonical_signer_name = malloc(canonical_signer_name_length);
+    require_action_quiet(canonical_signer_name != NULL, exit, encounter_error = true;
+                         ERROR("malloc failed when allocating memory - for canonical_signer_name, len: %lu",
+                               canonical_signer_name_length));
+
+    bool convert_fail = !dns_name_to_wire_canonical(canonical_signer_name, canonical_signer_name_length,
+                                                    signature->data.sig.signer);
+    require_action_quiet(!convert_fail, exit, encounter_error = true;
+                         ERROR("Failed to write canonical name - canonical_signer_name_length: %lu",
+                               canonical_signer_name_length));
+
+    CCDigestUpdate(&cc_digest_context, canonical_signer_name, canonical_signer_name_length);
+
+    // Copy (request - SIG(0)).
+    // The authority response count is before the counts have been adjusted for the inclusion of the SIG(0).
+    message->arcount = htons(ntohs(message->arcount) - 1);
+    CCDigestUpdate(&cc_digest_context, (uint8_t *)message, offsetof(dns_wire_t, data) + signature->data.sig.start);
+    // Recover the count after copying.
+    message->arcount = htons(ntohs(message->arcount) + 1);
+
+    // Generate the final digest.
+    CCDigestFinal(&cc_digest_context, digest);
+
+    // Create CFDataRef.
+    data_to_verify_cfdata = CFDataCreate(kCFAllocatorDefault, digest, digest_length);
+    require_action_quiet(data_to_verify_cfdata != NULL, exit, encounter_error = true;
+                         ERROR("CFDataCreate failed when creating data_to_verify_cfdata"));
+
+    encounter_error = false;
+exit:
+    if (canonical_signer_name != NULL) {
+        free(canonical_signer_name);
+    }
+    if (encounter_error) {
+        if (data_to_verify_cfdata != NULL) {
+            CFRelease(data_to_verify_cfdata);
+        }
+    }
+    return data_to_verify_cfdata;
+}
+
+//======================================================================================================================
+
+#if SECTRANSFORM_AVAILABLE
+// Function to copy out the public key as binary data
+void
+srp_print_key(srp_key_t *key)
+{
+    SecTransformRef b64encoder;
+    CFDataRef key_data, public_key = NULL;
+    CFDataRef encoded;
+    const uint8_t *data;
+    CFErrorRef error = NULL;
+
+    key_data = SecKeyCopyExternalRepresentation(key->public, &error);
+    if (error == NULL) {
+        data = CFDataGetBytePtr(key_data);
+        public_key = CFDataCreateWithBytesNoCopy(NULL, data + 1,
+                                                 CFDataGetLength(key_data) - 1, kCFAllocatorNull);
+        if (public_key != NULL) {
+            b64encoder = SecEncodeTransformCreate(public_key, &error);
+            if (error == NULL) {
+                SecTransformSetAttribute(b64encoder, kSecTransformInputAttributeName, key, &error);
+                if (error == NULL) {
+                    encoded = SecTransformExecute(b64encoder, &error);
+                    if (error == NULL) {
+                        data = CFDataGetBytePtr(encoded);
+                        fputs("thread-demo.default.service.arpa. IN KEY 513 3 13 ", stdout);
+                        fwrite(data, CFDataGetLength(encoded), 1, stdout);
+                        putc('\n', stdout);
+                    }
+                    if (encoded != NULL) {
+                        CFRelease(encoded);
+                    }
+                }
+            }
+            if (b64encoder != NULL) {
+                CFRelease(b64encoder);
+            }
+            CFRelease(public_key);
+        }
+    }
+    if (key_data != NULL) {
+        CFRelease(key_data);
+    }
+}
+#endif // SECTRANSFORM_AVAILABLE
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
index 58205d97f711c16f0176a980544749fedc66d7ea..ed0634fb688a36946e3698d6c433de7bb54deadb 100644 (file)
@@ -75,11 +75,11 @@ srp_sig0_verify(dns_wire_t *message, dns_rr_t *key, dns_rr_t *signature)
     mbedtls_mpi_init(&s);
     mbedtls_sha256_init(&sha);
     memset(hash, 0, sizeof hash);
-    
+
     if ((status = mbedtls_mpi_read_binary(&pubkey.X, key->data.key.key, ECDSA_KEY_PART_SIZE)) != 0 ||
         (status = mbedtls_mpi_read_binary(&pubkey.Y, key->data.key.key + ECDSA_KEY_PART_SIZE, ECDSA_KEY_PART_SIZE)) != 0) {
         mbedtls_strerror(status, errbuf, sizeof errbuf);
-        ERROR("mbedtls_mpi_read_binary: reading key: %s", errbuf);
+        ERROR("mbedtls_mpi_read_binary: reading key: " PUB_S_SRP, errbuf);
     }
     mbedtls_mpi_lset(&pubkey.Z, 1);
 
@@ -87,9 +87,9 @@ srp_sig0_verify(dns_wire_t *message, dns_rr_t *key, dns_rr_t *signature)
         (status = mbedtls_mpi_read_binary(&s, signature->data.sig.signature + ECDSA_SHA256_SIG_PART_SIZE,
                                           ECDSA_SHA256_SIG_PART_SIZE)) != 0) {
         mbedtls_strerror(status, errbuf, sizeof errbuf);
-        ERROR("mbedtls_mpi_read_binary: reading signature: %s", errbuf);
+        ERROR("mbedtls_mpi_read_binary: reading signature: " PUB_S_SRP, errbuf);
     }
-    
+
     // The hash is across the message _before_ the SIG RR is added, so we have to decrement arcount before
        // computing it.
     message->arcount = htons(ntohs(message->arcount) - 1);
@@ -112,25 +112,25 @@ srp_sig0_verify(dns_wire_t *message, dns_rr_t *key, dns_rr_t *signature)
 
     // First compute the hash across the SIG RR, then hash the message up to the SIG RR
     if ((status = mbedtls_sha256_starts_ret(&sha, 0)) != 0 ||
-        (status = srp_mbedtls_sha256_update_ret(&sha, rdata, rdlen)) != 0 ||
-        (status = srp_mbedtls_sha256_update_ret(&sha, (uint8_t *)message,
+        (status = srp_mbedtls_sha256_update_ret("rdata", &sha, rdata, rdlen)) != 0 ||
+        (status = srp_mbedtls_sha256_update_ret("message", &sha, (uint8_t *)message,
                                                 signature->data.sig.start +
                                                 (sizeof *message) - DNS_DATA_SIZE)) != 0 ||
         (status = srp_mbedtls_sha256_finish_ret(&sha, hash)) != 0) {
         // Put it back
         message->arcount = htons(ntohs(message->arcount) + 1);
         mbedtls_strerror(status, errbuf, sizeof errbuf);
-        ERROR("mbedtls_sha_256 hash failed: %s", errbuf);
+        ERROR("mbedtls_sha_256 hash failed: " PUB_S_SRP, errbuf);
         return 0;
     }
     message->arcount = htons(ntohs(message->arcount) + 1);
     free(rdata);
-    
+
     // Now check the signature against the hash
     status = mbedtls_ecdsa_verify(&group, hash, sizeof hash, &pubkey, &r, &s);
     if (status != 0) {
         mbedtls_strerror(status, errbuf, sizeof errbuf);
-        ERROR("mbedtls_ecdsa_verify failed: %s", errbuf);
+        ERROR("mbedtls_ecdsa_verify failed: " PUB_S_SRP, errbuf);
         return 0;
     }
     return 1;
@@ -151,14 +151,14 @@ srp_print_key(srp_key_t *key)
     if ((status = mbedtls_mpi_write_binary(&ecp->Q.X, buf, ECDSA_KEY_PART_SIZE)) != 0 ||
         (status = mbedtls_mpi_write_binary(&ecp->Q.Y, buf + ECDSA_KEY_PART_SIZE, ECDSA_KEY_PART_SIZE)) != 0) {
         mbedtls_strerror(status, errbuf, sizeof errbuf);
-        ERROR("mbedtls_mpi_write_binary: %s", errbuf);
+        ERROR("mbedtls_mpi_write_binary: " PUB_S_SRP, errbuf);
         return;
     }
 
     status = mbedtls_base64_encode(b64buf, sizeof b64buf, &b64len, buf, ECDSA_KEY_SIZE);
     if (status != 0) {
         mbedtls_strerror(status, errbuf, sizeof errbuf);
-        ERROR("mbedtls_mpi_write_binary: %s", errbuf);
+        ERROR("mbedtls_mpi_write_binary: " PUB_S_SRP, errbuf);
         return;
     }
     fputs("thread-demo.default.service.arpa. IN KEY 513 3 13 ", stdout);
diff --git a/ServiceRegistration/wireutils.c b/ServiceRegistration/wireutils.c
new file mode 100644 (file)
index 0000000..8a3e54b
--- /dev/null
@@ -0,0 +1,528 @@
+/* wireutils.c
+ *
+ * Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * DNS wire-format utility functions.
+ *
+ * Functions that are neither necessary for very simple DNS packet generation, nor required for parsing
+ * a message, e.g. compression, name printing, etc.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include "srp.h"
+#include "dns-msg.h"
+
+#include "mDNSEmbeddedAPI.h"
+#include "DNSCommon.h"
+
+#undef LogMsg
+#define LogMsg(...)
+
+// We need the compression routines from DNSCommon.c, but we can't link to it because that
+// pulls in a _lot_ of stuff we don't want.   The solution?   Define STANDALONE (this is done
+// in the Makefile, and include DNSCommon.c.
+//
+// The only functions that aren't excluded by STANDALONE are FindCompressionPointer and
+// putDomainNameAsLabels.
+
+#define STANDALONE
+#include "../mDNSCore/DNSCommon.c"
+
+void dns_name_free(dns_label_t *name)
+{
+    dns_label_t *next;
+    if (name == NULL) {
+        return;
+    }
+    next = name->next;
+    free(name);
+    if (next != NULL) {
+        return dns_name_free(next);
+    }
+}
+
+dns_name_t *
+dns_name_copy(dns_name_t *original)
+{
+    dns_name_t *ret = NULL, **cur = &ret;
+    dns_name_t *next;
+
+    for (next = original; next; next = next->next) {
+        *cur = calloc(1, 1 + next->len + (sizeof (dns_name_t)) - DNS_MAX_LABEL_SIZE);
+        if (*cur == NULL) {
+            if (ret != NULL) {
+                dns_name_free(ret);
+            }
+            return NULL;
+        }
+        if (next->len) {
+            memcpy((*cur)->data, next->data, next->len + 1);
+        }
+        (*cur)->len = next->len;
+        cur = &((*cur)->next);
+    }
+    return ret;
+}
+
+// Needed for TSIG (RFC2845).
+void
+dns_u48_to_wire_(dns_towire_state_t *NONNULL txn,
+                 uint64_t val, int line)
+{
+    if (!txn->error) {
+        if (txn->p + 6 >= txn->lim) {
+            txn->error = ENOBUFS;
+            txn->truncated = true;
+            txn->line = line;
+            return;
+        }
+        *txn->p++ = (val >> 40) & 0xff;
+        *txn->p++ = (val >> 32) & 0xff;
+        *txn->p++ = (val >> 24) & 0xff;
+        *txn->p++ = (val >> 16) & 0xff;
+        *txn->p++ = (val >> 8) & 0xff;
+        *txn->p++ = val & 0xff;
+    }
+}
+
+void
+dns_concatenate_name_to_wire_(dns_towire_state_t *towire, dns_name_t *labels_prefix, const char *prefix,
+                              const char *suffix, int line)
+{
+    dns_wire_t namebuf;
+    dns_towire_state_t namewire;
+    mDNSu8 *ret;
+    namebuf.data[0] = 0;
+
+    // Don't do all this work if we're already past an error.
+    if (towire->error) {
+        return;
+    }
+    memset(&namewire, 0, sizeof namewire);
+    namewire.message = &namebuf;
+    namewire.lim = &namebuf.data[DNS_DATA_SIZE];
+    namewire.p = namebuf.data;
+    if (prefix != NULL) {
+        dns_name_to_wire(NULL, &namewire, prefix);
+    } else if (labels_prefix != NULL) {
+        size_t bytes_written;
+
+        if (!namewire.error) {
+            bytes_written = namewire.lim - namewire.p;
+            if (bytes_written > INT16_MAX) {
+                towire->error = true;
+                towire->line = __LINE__;
+                return;
+            }
+            bytes_written = dns_name_to_wire_canonical(namewire.p,  (int)bytes_written, labels_prefix);
+            // This can never occur with a valid name.
+            if (bytes_written == 0) {
+                namewire.truncated = true;
+            } else {
+                namewire.p += bytes_written;
+            }
+        }
+    }
+    if (suffix != NULL) {
+        dns_full_name_to_wire(NULL, &namewire, suffix);
+    }
+    if (namewire.error) {
+        towire->truncated = namewire.truncated;
+        towire->error = namewire.error;
+        towire->line = line;
+    }
+
+    ret = putDomainNameAsLabels((DNSMessage *)towire->message, towire->p, towire->lim, (domainname *)namebuf.data);
+    if (ret == NULL) {
+        towire->error = ENOBUFS;
+        towire->truncated = true;
+        towire->line = line;
+        return;
+    }
+
+    // Shouldn't happen
+    if (ret > towire->lim) {
+        towire->error = ENOBUFS;
+        towire->truncated = true;
+        towire->line = line;
+    } else {
+        towire->p = ret;
+    }
+}
+
+// Convert a dns_name_t to presentation format.   Stop conversion at the specified limit.
+// A trailing dot is only written if a null label is present.
+const char *NONNULL
+dns_name_print_to_limit(dns_name_t *NONNULL name, dns_name_t *NULLABLE limit, char *buf, int bufmax)
+{
+    dns_label_t *lp;
+    int ix = 0;
+    int i;
+
+    // Copy the labels in one at a time, putting a dot between each one; if there isn't room
+    // in the buffer (shouldn't be the case), copy as much as will fit, leaving room for a NUL
+    // termination.
+    for (lp = name; lp != limit && lp != NULL; lp = lp->next) {
+        if (ix != 0) {
+            if (ix + 2 >= bufmax) {
+                break;
+            }
+            buf[ix++] = '.';
+        }
+        for (i = 0; i < lp->len; i++) {
+            if (isascii(lp->data[i]) && (lp->data[i] == ' ' || isprint(lp->data[i]))) {
+                if (ix + 2 >= bufmax) {
+                    break;
+                }
+                buf[ix++] = lp->data[i];
+            } else {
+                if (ix + 5 >= bufmax) {
+                    break;
+                }
+                buf[ix++] = '\\';
+                buf[ix++] = '0' + (lp->data[i] / 100);
+                buf[ix++] = '0' + (lp->data[i] /  10) % 10;
+                buf[ix++] = '0' + lp->data[i]         % 10;
+            }
+        }
+        if (i != lp->len) {
+            break;
+        }
+    }
+    buf[ix++] = 0;
+    return buf;
+}
+
+const char *NONNULL
+dns_name_print(dns_name_t *NONNULL name, char *buf, int bufmax)
+{
+    return dns_name_print_to_limit(name, NULL, buf, bufmax);
+}
+
+bool
+dns_labels_equal(const char *label1, const char *label2, size_t len)
+{
+    unsigned i;
+    for (i = 0; i < len; i++) {
+        if (isascii(label1[i]) && isascii(label2[i])) {
+            if (tolower(label1[i]) != tolower(label2[i])) {
+                return false;
+            }
+        }
+        else {
+            if (label1[i] != label2[i]) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+bool
+dns_names_equal(dns_label_t *NONNULL name1, dns_label_t *NONNULL name2)
+{
+    if (name1->len != name2->len) {
+        return false;
+    }
+    if (name1->len != 0 && !dns_labels_equal(name1->data, name2->data, name1->len) != 0) {
+        return false;
+    }
+    if (name1->next != NULL && name2->next != NULL) {
+        return dns_names_equal(name1->next, name2->next);
+    }
+    if (name1->next == NULL && name2->next == NULL) {
+        return true;
+    }
+    return false;
+}
+
+// Note that "foo.arpa" is not the same as "foo.arpa."
+bool
+dns_names_equal_text(dns_label_t *NONNULL name1, const char *NONNULL name2)
+{
+    const char *ndot;
+    const char *s, *t;
+    int tlen = 0;
+    ndot = strchr(name2, '.');
+    if (ndot == NULL) {
+        ndot = name2 + strlen(name2);
+    }
+    for (s = name2; s < ndot; s++) {
+        if (*s == '\\') {
+            if (s + 4 <= ndot) {
+                tlen++;
+                s += 3;
+            } else {
+                return false;  // An invalid name can't be equal to anything.
+            }
+        } else {
+            tlen++;
+        }
+    }
+    if (name1->len != tlen) {
+        return false;
+    }
+    if (name1->len != 0) {
+        t = name1->data;
+        for (s = name2; s < ndot; s++, t++) {
+            if (*s == '\\') { // already bounds checked
+                int v0 = s[1] - '0';
+                int v1 = s[2] - '0';
+                int v2 = s[3] - '0';
+                int val = v0 * 100 + v1 * 10 + v2;
+                if (val > 255) {
+                    return false;
+                } else if (isascii(*s) && isascii(*t)) {
+                    if (tolower(*s) != tolower(*t)) {
+                        return false;
+                    }
+                } else if (val != *t) {
+                    return false;
+                }
+                s += 3;
+            } else {
+                if (*s != *t) {
+                    return false;
+                }
+            }
+        }
+    }
+    if (name1->next != NULL && *ndot == '.') {
+        return dns_names_equal_text(name1->next, ndot + 1);
+    }
+    if (name1->next == NULL && *ndot == 0) {
+        return true;
+    }
+    return false;
+}
+
+// Find the length of a name in uncompressed wire format.
+static size_t
+dns_name_wire_length_in(dns_label_t *NONNULL name, size_t ret)
+{
+    // Root label.
+    if (name == NULL)
+        return ret;
+    return dns_name_wire_length_in(name->next, ret + name->len + 1);
+}
+
+size_t
+dns_name_wire_length(dns_label_t *NONNULL name)
+{
+    return dns_name_wire_length_in(name, 0);
+}
+
+// Copy a name we've parsed from a message out in canonical wire format so that we can
+// use it to verify a signature.   As above, not actually needed for copying to a message
+// we're going to send, since in that case we want to try to compress.
+static size_t
+dns_name_to_wire_canonical_in(uint8_t *NONNULL buf, size_t max, size_t ret, dns_label_t *NONNULL name)
+{
+    if (name == NULL) {
+        return ret;
+    }
+    if (max < name->len + 1) {
+        return 0;
+    }
+    *buf = name->len;
+    memcpy(buf + 1, name->data, name->len);
+    return dns_name_to_wire_canonical_in(buf + name->len + 1,
+                                         max - name->len - 1, ret + name->len + 1, name->next);
+}
+
+size_t
+dns_name_to_wire_canonical(uint8_t *NONNULL buf, size_t max, dns_label_t *NONNULL name)
+{
+    return dns_name_to_wire_canonical_in(buf, max, 0, name);
+}
+
+// Parse a NUL-terminated text string into a sequence of labels.
+dns_name_t *
+dns_pres_name_parse(const char *pname)
+{
+    const char *dot, *s, *label;
+    dns_label_t *next, *ret, **prev = &ret;
+    size_t len;
+    char *t;
+    char buf[DNS_MAX_LABEL_SIZE];
+    ret = NULL;
+
+    label = pname;
+    dot = strchr(label, '.');
+    while (true) {
+        if (dot == NULL) {
+            dot = label + strlen(label);
+        }
+        len = dot - label;
+        if (len > 0) {
+            t = buf;
+            for (s = label; s < dot; s++) {
+                if (*s == '\\') { // already bounds checked
+                    int v0 = s[1] - '0';
+                    int v1 = s[2] - '0';
+                    int v2 = s[3] - '0';
+                    int val = v0 * 100 + v1 * 10 + v2;
+                    if (val > 255) {
+                        goto fail;
+                    }
+                    s += 3;
+                    *t++ = val;
+                } else {
+                    *t++ = *s;
+                }
+            }
+            len = t - buf;
+        }
+        next = calloc(1, len + 1 + (sizeof *next) - DNS_MAX_LABEL_SIZE);
+        if (next == NULL) {
+            goto fail;
+        }
+        *prev = next;
+        prev = &next->next;
+        next->len = len;
+        if (next->len > 0) {
+            memcpy(next->data, buf, next->len);
+        }
+        next->data[next->len] = 0;
+        if (dot[0] == '.' && len > 0) {
+            dot = dot + 1;
+        }
+        if (*dot == '\0') {
+            if (len > 0) {
+                label = dot;
+            } else {
+                break;
+            }
+        } else {
+            label = dot;
+            dot = strchr(label, '.');
+        }
+    }
+    return ret;
+
+fail:
+    if (ret) {
+        dns_name_free(ret);
+    }
+    return NULL;
+}
+
+// See if name is a subdomain of domain.   If so, return a pointer to the label in name
+// where the match to domain begins.
+dns_name_t *
+dns_name_subdomain_of(dns_name_t *name, dns_name_t *domain)
+{
+    int dnum = 0, nnum = 0;
+    dns_name_t *np, *dp;
+
+    for (dp = domain; dp; dp = dp->next) {
+        dnum++;
+    }
+    for (np = name; np; np = np->next) {
+        nnum++;
+    }
+    if (nnum < dnum) {
+        return NULL;
+    }
+    for (np = name; np; np = np->next) {
+        if (nnum-- == dnum) {
+            break;
+        }
+    }
+    if (np != NULL && dns_names_equal(np, domain)) {
+        return np;
+    }
+    return NULL;
+}
+
+const char *
+dns_rcode_name(int rcode)
+{
+    switch(rcode) {
+    case dns_rcode_noerror:
+        return "No Error";
+    case dns_rcode_formerr:
+        return "Format Error";
+    case dns_rcode_servfail:
+        return "Server Failure";
+    case dns_rcode_nxdomain:
+        return "Non-Existent Domain";
+    case dns_rcode_notimp:
+        return "Not Implemented";
+    case dns_rcode_refused:
+        return "Query Refused";
+    case dns_rcode_yxdomain:
+        return "RFC6672] Name Exists when it should not";
+    case dns_rcode_yxrrset:
+        return "RR Set Exists when it should not";
+    case dns_rcode_nxrrset:
+        return "RR Set that should exist does not";
+    case dns_rcode_notauth:
+        return "Not Authorized";
+    case dns_rcode_notzone:
+        return "Name not contained in zone";
+    case dns_rcode_dsotypeni:
+        return "DSO-Type Not Implemented";
+    case dns_rcode_badvers:
+        return "TSIG Signature Failure";
+    case dns_rcode_badkey:
+        return "Key not recognized";
+    case dns_rcode_badtime:
+        return "Signature out of time window";
+    case dns_rcode_badmode:
+        return "Bad TKEY Mode";
+    case dns_rcode_badname:
+        return "Duplicate key name";
+    case dns_rcode_badalg:
+        return "Algorithm not supported";
+    case dns_rcode_badtrunc:
+        return "Bad Truncation";
+    case dns_rcode_badcookie:
+        return "Bad/missing Server Cookie";
+    default:
+        return "Unknown rcode.";
+    }
+}
+
+bool
+dns_keys_rdata_equal(dns_rr_t *key1, dns_rr_t *key2)
+{
+    if ((key1->type == dns_rrtype_key && key2->type == dns_rrtype_key) &&
+        key1->data.key.flags == key2->data.key.flags &&
+        key1->data.key.protocol == key2->data.key.protocol &&
+        key1->data.key.algorithm == key2->data.key.algorithm &&
+        key1->data.key.len == key2->data.key.len &&
+        !memcmp(key1->data.key.key, key2->data.key.key, key1->data.key.len))
+    {
+        return true;
+    }
+    return false;
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/mDNSCore/CryptoAlg.c b/mDNSCore/CryptoAlg.c
deleted file mode 100644 (file)
index aeca551..0000000
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright (c) 2011-2019 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// ***************************************************************************
-// CryptoAlg.c:
-// Interface to DNSSEC cryptographic algorithms. The crypto support itself is
-// provided by the platform and the functions in this file just provide an
-// interface to access them in a more generic way.
-// ***************************************************************************
-
-#include "mDNSEmbeddedAPI.h"
-#include "CryptoAlg.h"
-
-AlgFuncs *DigestAlgFuncs[DIGEST_TYPE_MAX];
-AlgFuncs *CryptoAlgFuncs[CRYPTO_ALG_MAX];
-AlgFuncs *EncAlgFuncs[ENC_ALG_MAX];
-
-mDNSexport mStatus DigestAlgInit(mDNSu8 digestType, AlgFuncs *func)
-{
-    if (digestType >= DIGEST_TYPE_MAX)
-    {
-        LogMsg("DigestAlgInit: digestType %d exceeds bounds", digestType);
-        return mStatus_BadParamErr;
-    }
-    // As digestTypes may not be consecutive, check for specific digest types
-    // that we support
-    if (digestType != SHA1_DIGEST_TYPE &&
-        digestType != SHA256_DIGEST_TYPE)
-    {
-        LogMsg("DigestAlgInit: digestType %d not supported", digestType);
-        return mStatus_BadParamErr;
-    }
-    DigestAlgFuncs[digestType] = func;
-    return mStatus_NoError;
-}
-
-mDNSexport mStatus CryptoAlgInit(mDNSu8 alg, AlgFuncs *func)
-{
-    if (alg >= CRYPTO_ALG_MAX)
-    {
-        LogMsg("CryptoAlgInit: alg %d exceeds bounds", alg);
-        return mStatus_BadParamErr;
-    }
-    // As algs may not be consecutive, check for specific algorithms
-    // that we support
-    if (alg != CRYPTO_RSA_SHA1 && alg != CRYPTO_RSA_SHA256 && alg != CRYPTO_RSA_SHA512 &&
-        alg != CRYPTO_DSA_NSEC3_SHA1 && alg != CRYPTO_RSA_NSEC3_SHA1)
-    {
-        LogMsg("CryptoAlgInit: alg %d not supported", alg);
-        return mStatus_BadParamErr;
-    }
-
-    CryptoAlgFuncs[alg] = func;
-    return mStatus_NoError;
-}
-
-mDNSexport mStatus EncAlgInit(mDNSu8 alg, AlgFuncs *func)
-{
-    if (alg >= ENC_ALG_MAX)
-    {
-        LogMsg("EncAlgInit: alg %d exceeds bounds", alg);
-        return mStatus_BadParamErr;
-    }
-
-    // As algs may not be consecutive, check for specific algorithms
-    // that we support
-    if (alg != ENC_BASE32 && alg != ENC_BASE64)
-    {
-        LogMsg("EncAlgInit: alg %d not supported", alg);
-        return mStatus_BadParamErr;
-    }
-
-    EncAlgFuncs[alg] = func;
-    return mStatus_NoError;
-}
-
-mDNSexport AlgContext *AlgCreate(AlgType type, mDNSu8 alg)
-{
-    AlgFuncs *func = mDNSNULL;
-    AlgContext *ctx;
-
-    if (type == CRYPTO_ALG)
-    {
-        if (alg >= CRYPTO_ALG_MAX) return mDNSNULL;
-        func = CryptoAlgFuncs[alg];
-    }
-    else if (type == DIGEST_ALG)
-    {
-        if (alg >= DIGEST_TYPE_MAX) return mDNSNULL;
-        func = DigestAlgFuncs[alg];
-    }
-    else if (type == ENC_ALG)
-    {
-        if (alg >= ENC_ALG_MAX) return mDNSNULL;
-        func = EncAlgFuncs[alg];
-    }
-
-    if (!func)
-    {
-        // If there is no support from the platform, this case can happen.
-        LogInfo("AlgCreate: func is NULL");
-        return mDNSNULL;
-    }
-
-    if (func->Create)
-    {
-        mStatus err;
-        ctx = (AlgContext *) mDNSPlatformMemAllocateClear(sizeof(*ctx));
-        if (!ctx) return mDNSNULL;
-        // Create expects ctx->alg to be initialized
-        ctx->alg = alg;
-        err = func->Create(ctx);
-        if (err == mStatus_NoError)
-        {
-            ctx->type = type;
-            return ctx;
-        }
-        mDNSPlatformMemFree(ctx);
-    }
-    return mDNSNULL;
-}
-
-mDNSexport mStatus AlgDestroy(AlgContext *ctx)
-{
-    AlgFuncs *func = mDNSNULL;
-
-    if (ctx->type == CRYPTO_ALG)
-        func = CryptoAlgFuncs[ctx->alg];
-    else if (ctx->type == DIGEST_ALG)
-        func = DigestAlgFuncs[ctx->alg];
-    else if (ctx->type == ENC_ALG)
-        func = EncAlgFuncs[ctx->alg];
-
-    if (!func)
-    {
-        LogMsg("AlgDestroy: ERROR!! func is NULL");
-        mDNSPlatformMemFree(ctx);
-        return mStatus_BadParamErr;
-    }
-
-    if (func->Destroy)
-        func->Destroy(ctx);
-
-    mDNSPlatformMemFree(ctx);
-    return mStatus_NoError;
-}
-
-mDNSexport mDNSu32 AlgLength(AlgContext *ctx)
-{
-    AlgFuncs *func = mDNSNULL;
-
-    if (ctx->type == CRYPTO_ALG)
-        func = CryptoAlgFuncs[ctx->alg];
-    else if (ctx->type == DIGEST_ALG)
-        func = DigestAlgFuncs[ctx->alg];
-    else if (ctx->type == ENC_ALG)
-        func = EncAlgFuncs[ctx->alg];
-
-    // This should never happen as AlgCreate would have failed
-    if (!func)
-    {
-        LogMsg("AlgLength: ERROR!! func is NULL");
-        return 0;
-    }
-
-    if (func->Length)
-        return (func->Length(ctx));
-    else
-        return 0;
-}
-
-mDNSexport mStatus AlgAdd(AlgContext *ctx, const void *data, mDNSu32 len)
-{
-    AlgFuncs *func = mDNSNULL;
-
-    if (ctx->type == CRYPTO_ALG)
-        func = CryptoAlgFuncs[ctx->alg];
-    else if (ctx->type == DIGEST_ALG)
-        func = DigestAlgFuncs[ctx->alg];
-    else if (ctx->type == ENC_ALG)
-        func = EncAlgFuncs[ctx->alg];
-
-    // This should never happen as AlgCreate would have failed
-    if (!func)
-    {
-        LogMsg("AlgAdd: ERROR!! func is NULL");
-        return mStatus_BadParamErr;
-    }
-
-    if (func->Add)
-        return (func->Add(ctx, data, len));
-    else
-        return mStatus_BadParamErr;
-}
-
-mDNSexport mStatus AlgVerify(AlgContext *ctx, mDNSu8 *key, mDNSu32 keylen, mDNSu8 *signature, mDNSu32 siglen)
-{
-    AlgFuncs *func = mDNSNULL;
-
-    if (ctx->type == CRYPTO_ALG)
-        func = CryptoAlgFuncs[ctx->alg];
-    else if (ctx->type == DIGEST_ALG)
-        func = DigestAlgFuncs[ctx->alg];
-    else if (ctx->type == ENC_ALG)
-        func = EncAlgFuncs[ctx->alg];
-
-    // This should never happen as AlgCreate would have failed
-    if (!func)
-    {
-        LogMsg("AlgVerify: ERROR!! func is NULL");
-        return mStatus_BadParamErr;
-    }
-
-    if (func->Verify)
-        return (func->Verify(ctx, key, keylen, signature, siglen));
-    else
-        return mStatus_BadParamErr;
-}
-
-mDNSexport mDNSu8* AlgEncode(AlgContext *ctx)
-{
-    AlgFuncs *func = mDNSNULL;
-
-    if (ctx->type == CRYPTO_ALG)
-        func = CryptoAlgFuncs[ctx->alg];
-    else if (ctx->type == DIGEST_ALG)
-        func = DigestAlgFuncs[ctx->alg];
-    else if (ctx->type == ENC_ALG)
-        func = EncAlgFuncs[ctx->alg];
-
-    // This should never happen as AlgCreate would have failed
-    if (!func)
-    {
-        LogMsg("AlgEncode: ERROR!! func is NULL");
-        return mDNSNULL;
-    }
-
-    if (func->Encode)
-        return (func->Encode(ctx));
-    else
-        return mDNSNULL;
-}
-
-mDNSexport mStatus AlgFinal(AlgContext *ctx, void *data, mDNSu32 len)
-{
-    AlgFuncs *func = mDNSNULL;
-
-    if (ctx->type == CRYPTO_ALG)
-        func = CryptoAlgFuncs[ctx->alg];
-    else if (ctx->type == DIGEST_ALG)
-        func = DigestAlgFuncs[ctx->alg];
-    else if (ctx->type == ENC_ALG)
-        func = EncAlgFuncs[ctx->alg];
-
-    // This should never happen as AlgCreate would have failed
-    if (!func)
-    {
-        LogMsg("AlgEncode: ERROR!! func is NULL");
-        return mDNSNULL;
-    }
-
-    if (func->Final)
-        return (func->Final(ctx, data, len));
-    else
-        return mStatus_BadParamErr;
-}
diff --git a/mDNSCore/CryptoAlg.h b/mDNSCore/CryptoAlg.h
deleted file mode 100644 (file)
index c21507e..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/* -*- Mode: C; tab-width: 4 -*-
- *
- * Copyright (c) 2011-2012 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __CRYPTO_ALG_H
-#define __CRYPTO_ALG_H
-
-typedef enum
-{
-    CRYPTO_ALG,
-    DIGEST_ALG,
-    ENC_ALG,
-} AlgType;
-
-typedef struct
-{
-    void *context;
-    AlgType type;
-    mDNSu8 alg;
-} AlgContext;
-
-typedef struct
-{
-    mStatus (*Create)(AlgContext *ctx);
-    mStatus (*Destroy)(AlgContext *ctx);
-    mDNSu32 (*Length)(AlgContext *ctx);
-    mStatus (*Add)(AlgContext *ctx, const void *data, mDNSu32 len);
-    // Verify the ctx using the key and compare it against signature/siglen
-    mStatus (*Verify)(AlgContext *ctx, mDNSu8 *key, mDNSu32 keylen, mDNSu8 *signature, mDNSu32 siglen);
-    // Encode the data and return the encoded data
-    mDNSu8* (*Encode)(AlgContext *ctx);
-    // Return the finalized data in data whose length is len (used by hash algorithms)
-    mStatus (*Final)(AlgContext *ctx, void *data, mDNSu32 len);
-} AlgFuncs;
-
-mDNSexport mStatus DigestAlgInit(mDNSu8 digestType, AlgFuncs *func);
-mDNSexport mStatus CryptoAlgInit(mDNSu8 algType, AlgFuncs *func);
-mDNSexport mStatus EncAlgInit(mDNSu8 algType, AlgFuncs *func);
-
-
-extern AlgContext *AlgCreate(AlgType type, mDNSu8 alg);
-extern mStatus AlgDestroy(AlgContext *ctx);
-extern mDNSu32 AlgLength(AlgContext *ctx);
-extern mStatus AlgAdd(AlgContext *ctx, const void *data, mDNSu32 len);
-extern mStatus AlgVerify(AlgContext *ctx, mDNSu8 *key, mDNSu32 keylen, mDNSu8 *signature, mDNSu32 siglen);
-extern mDNSu8* AlgEncode(AlgContext *ctx);
-extern mStatus AlgFinal(AlgContext *ctx, void *data, mDNSu32 len);
-
-#endif // __CRYPTO_ALG_H
index 1e7088383098f3f81cc56fecc58aab54f05e83d1..f24495e6e86ae83b7c366d7e2718687abaab5f4a 100644 (file)
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4; c-file-style: "bsd"; c-basic-offset: 4; fill-column: 108; indent-tabs-mode: nil; -*-
  *
- * Copyright (c) 2002-2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * limitations under the License.
  */
 
+#ifndef STANDALONE
 // Set mDNS_InstantiateInlines to tell mDNSEmbeddedAPI.h to instantiate inline functions, if necessary
 #define mDNS_InstantiateInlines 1
 #include "DNSCommon.h"
-#include "CryptoAlg.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include "dnssec_v2.h"
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
 
 // Disable certain benign warnings with Microsoft compilers
 #if (defined(_MSC_VER))
@@ -102,7 +105,6 @@ mDNSexport const mDNSOpaque16 zeroID          = { { 0, 0 } };
 mDNSexport const mDNSOpaque16 onesID          = { { 255, 255 } };
 mDNSexport const mDNSOpaque16 QueryFlags      = { { kDNSFlag0_QR_Query    | kDNSFlag0_OP_StdQuery,                0 } };
 mDNSexport const mDNSOpaque16 uQueryFlags     = { { kDNSFlag0_QR_Query    | kDNSFlag0_OP_StdQuery | kDNSFlag0_RD, 0 } };
-mDNSexport const mDNSOpaque16 DNSSecQFlags    = { { kDNSFlag0_QR_Query    | kDNSFlag0_OP_StdQuery | kDNSFlag0_RD, kDNSFlag1_CD } };
 mDNSexport const mDNSOpaque16 ResponseFlags   = { { kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery | kDNSFlag0_AA, 0 } };
 mDNSexport const mDNSOpaque16 UpdateReqFlags  = { { kDNSFlag0_QR_Query    | kDNSFlag0_OP_Update,                  0 } };
 mDNSexport const mDNSOpaque16 UpdateRespFlags = { { kDNSFlag0_QR_Response | kDNSFlag0_OP_Update,                  0 } };
@@ -209,6 +211,8 @@ mDNSexport char *DNSTypeName(mDNSu16 rrtype)
     case kDNSType_RRSIG: return("RRSIG");
     case kDNSType_DNSKEY: return("DNSKEY");
     case kDNSType_DS: return("DS");
+    case kDNSType_SVCB: return("SVCB");
+    case kDNSType_HTTPS: return("HTTPS");
     case kDNSQType_ANY: return("ANY");
     default:            {
         static char buffer[16];
@@ -218,36 +222,23 @@ mDNSexport char *DNSTypeName(mDNSu16 rrtype)
     }
 }
 
-mDNSlocal char *DNSSECAlgName(mDNSu8 alg)
+mDNSexport const char *mStatusDescription(mStatus error)
 {
-    switch (alg)
-    {
-    case CRYPTO_RSA_SHA1: return "RSA_SHA1";
-    case CRYPTO_DSA_NSEC3_SHA1: return "DSA_NSEC3_SHA1";
-    case CRYPTO_RSA_NSEC3_SHA1: return "RSA_NSEC3_SHA1";
-    case CRYPTO_RSA_SHA256: return "RSA_SHA256";
-    case CRYPTO_RSA_SHA512: return "RSA_SHA512";
-    default: {
-        static char algbuffer[16];
-        mDNS_snprintf(algbuffer, sizeof(algbuffer), "ALG%d", alg);
-        return(algbuffer);
-    }
-    }
-}
+    const char *error_description;
+    switch (error) {
+        case mStatus_NoError:
+            error_description = "mStatus_NoError";
+            break;
+        case mStatus_BadParamErr:
+            error_description = "mStatus_BadParamErr";
+            break;
 
-mDNSlocal char *DNSSECDigestName(mDNSu8 digest)
-{
-    switch (digest)
-    {
-    case SHA1_DIGEST_TYPE: return "SHA1";
-    case SHA256_DIGEST_TYPE: return "SHA256";
-    default:
-        {
-        static char digbuffer[16];
-        mDNS_snprintf(digbuffer, sizeof(digbuffer), "DIG%d", digest);
-        return(digbuffer);
-        }
+        default:
+            error_description = "mStatus_UnknownDescription";
+            break;
     }
+
+    return error_description;
 }
 
 mDNSexport mDNSu32 swap32(mDNSu32 x)
@@ -262,53 +253,6 @@ mDNSexport mDNSu16 swap16(mDNSu16 x)
     return (mDNSu16)((mDNSu16)ptr[0] << 8 | ptr[1]);
 }
 
-// RFC 4034 Appendix B: Get the keyid of a DNS KEY. It is not transmitted
-// explicitly on the wire.
-//
-// Note: This just helps narrow down the list of keys to look at. It is possible
-// for two DNS keys to have the same ID i.e., key ID is not a unqiue tag. We ignore
-// MD5 keys.
-//
-// 1st argument - the RDATA part of the DNSKEY RR
-// 2nd argument - the RDLENGTH
-//
-mDNSlocal mDNSu32 keytag(mDNSu8 *key, mDNSu32 keysize)
-{
-    unsigned long ac;
-    unsigned int i;
-
-    for (ac = 0, i = 0; i < keysize; ++i)
-        ac += (i & 1) ? key[i] : key[i] << 8;
-    ac += (ac >> 16) & 0xFFFF;
-    return ac & 0xFFFF;
-}
-
-mDNSexport int baseEncode(char *buffer, int blen, const mDNSu8 *data, int len, int encAlg)
-{
-    AlgContext *ctx;
-    mDNSu8 *outputBuffer;
-    int length;
-
-    ctx = AlgCreate(ENC_ALG, encAlg);
-    if (!ctx)
-    {
-        LogMsg("baseEncode: AlgCreate failed\n");
-        return 0;
-    }
-    AlgAdd(ctx, data, len);
-    outputBuffer = AlgEncode(ctx);
-    length = 0;
-    if (outputBuffer)
-    {
-        // Note: don't include any spaces in the format string below. This
-        // is also used by NSEC3 code for proving non-existence where it
-        // needs the base32 encoding without any spaces etc.
-        length = mDNS_snprintf(buffer, blen, "%s", outputBuffer);
-    }
-    AlgDestroy(ctx);
-    return length;
-}
-
 mDNSlocal void PrintTypeBitmap(const mDNSu8 *bmap, int bitmaplen, char *const buffer, mDNSu32 length)
 {
     int win, wlen, type;
@@ -347,36 +291,6 @@ mDNSlocal void PrintTypeBitmap(const mDNSu8 *bmap, int bitmaplen, char *const bu
     }
 }
 
-// Parse the fields beyond the base header. NSEC3 should have been validated.
-mDNSexport void NSEC3Parse(const ResourceRecord *const rr, mDNSu8 **salt, int *hashLength, mDNSu8 **nxtName, int *bitmaplen, mDNSu8 **bitmap)
-{
-       const RDataBody2 *const rdb = (RDataBody2 *)rr->rdata->u.data;
-       rdataNSEC3 *nsec3 = (rdataNSEC3 *)rdb->data;
-    mDNSu8 *p = (mDNSu8 *)&nsec3->salt;
-    int hlen;
-
-    if (salt)
-    {
-        if (nsec3->saltLength)
-            *salt = p;
-        else
-            *salt = mDNSNULL;
-    }
-    p += nsec3->saltLength;
-    // p is pointing at hashLength
-    hlen = (int)*p;
-    if (hashLength)
-        *hashLength = hlen;
-    p++;
-    if (nxtName)
-        *nxtName = p;
-    p += hlen;
-    if (bitmaplen)
-        *bitmaplen = rr->rdlength - (int)(p - rdb->data);
-    if (bitmap)
-        *bitmap = p;
-}
-
 // Note slight bug: this code uses the rdlength from the ResourceRecord object, to display
 // the rdata from the RDataBody object. Sometimes this could be the wrong length -- but as
 // long as this routine is only used for debugging messages, it probably isn't a big problem.
@@ -405,12 +319,27 @@ mDNSexport char *GetRRDisplayString_rdb(const ResourceRecord *const rr, const RD
     case kDNSType_HINFO:    // Display this the same as TXT (show all constituent strings)
     case kDNSType_TXT:  {
         const mDNSu8 *t = rd->txt.c;
-        while (t < rd->txt.c + rr->rdlength)
+        const mDNSu8 *const rdLimit = rd->data + rr->rdlength;
+        const char *separator = "";
+
+        while (t < rdLimit)
         {
-            length += mDNS_snprintf(buffer+length, RemSpc, "%s%#s", t > rd->txt.c ? "¦" : "", t);
-            t += 1 + t[0];
+            mDNSu32 characterStrLength = *t;
+            if (characterStrLength + 1 > (mDNSu32)(rdLimit - t)) // Character string goes out of boundary.
+            {
+                const mDNSu8 *const remainderStart = t + 1;
+                const mDNSu32 remainderLength = (mDNSu32)(rdLimit - remainderStart);
+                length += mDNS_snprintf(buffer + length, RemSpc, "%s%.*s<<OUT OF BOUNDARY CHARACTER STRING>>", separator,
+                    remainderLength, remainderStart);
+                (void)length; // Acknowledge "dead store" analyzer warning.
+                break;
+            }
+            length += mDNS_snprintf(buffer+length, RemSpc, "%s%.*s", separator, characterStrLength, t + 1);
+            separator = "¦";
+            t += 1 + characterStrLength;
         }
-    } break;
+    }
+        break;
 
     case kDNSType_AAAA: mDNS_snprintf(buffer+length, RemSpc, "%.16a", &rd->ipv6);       break;
     case kDNSType_SRV:  mDNS_snprintf(buffer+length, RemSpc, "%u %u %u %##s",
@@ -474,95 +403,13 @@ mDNSexport char *GetRRDisplayString_rdb(const ResourceRecord *const rr, const RD
 
     }
     break;
-    case kDNSType_NSEC3: {
-        rdataNSEC3 *nsec3 = (rdataNSEC3 *)rd->data;
-        const mDNSu8 *p = (mDNSu8 *)&nsec3->salt;
-        int hashLength, bitmaplen, i;
-
-        length += mDNS_snprintf(buffer+length, RemSpc, "\t%s  %d  %d ", 
-                                DNSSECDigestName(nsec3->alg), nsec3->flags, swap16(nsec3->iterations));
-        
-        if (!nsec3->saltLength)
-        {
-            length += mDNS_snprintf(buffer+length, RemSpc, "-");
-        }
-        else
-        {
-            for (i = 0; i < nsec3->saltLength; i++)
-            {
-                length += mDNS_snprintf(buffer+length, RemSpc, "%x", p[i]);
-            }
-        }
-
-        // put a space at the end
-        length += mDNS_snprintf(buffer+length, RemSpc, " ");
-
-        p += nsec3->saltLength;
-        // p is pointing at hashLength
-        hashLength = (int)*p++;
-        
-        length += baseEncode(buffer + length, RemSpc, p, hashLength, ENC_BASE32);
-
-        // put a space at the end
-        length += mDNS_snprintf(buffer+length, RemSpc, " ");
-
-        p += hashLength;
-        bitmaplen = rr->rdlength - (int)(p - rd->data);
-        PrintTypeBitmap(p, bitmaplen, buffer, length);
-    }
-    break;
-    case kDNSType_RRSIG:    {
-        rdataRRSig *rrsig = (rdataRRSig *)rd->data;
-        mDNSu8 expTimeBuf[64];
-        mDNSu8 inceptTimeBuf[64];
-        unsigned long inceptClock;
-        unsigned long expClock;
-        int len;
-
-        expClock = (unsigned long)swap32(rrsig->sigExpireTime);
-        mDNSPlatformFormatTime(expClock, expTimeBuf, sizeof(expTimeBuf));
-
-        inceptClock = (unsigned long)swap32(rrsig->sigInceptTime);
-        mDNSPlatformFormatTime(inceptClock, inceptTimeBuf, sizeof(inceptTimeBuf));
-
-        length += mDNS_snprintf(buffer+length, RemSpc, "\t%s  %s  %d  %d  %s  %s  %d  %##s ",
-                                DNSTypeName(swap16(rrsig->typeCovered)), DNSSECAlgName(rrsig->alg), rrsig->labels, swap32(rrsig->origTTL),
-                                expTimeBuf, inceptTimeBuf, swap16(rrsig->keyTag), rrsig->signerName);
-
-        len = DomainNameLength((domainname *)&rrsig->signerName);
-        baseEncode(buffer + length, RemSpc, (const mDNSu8 *)(rd->data + len + RRSIG_FIXED_SIZE),
-                               rr->rdlength - (len + RRSIG_FIXED_SIZE), ENC_BASE64);
-    }
-    break;
-    case kDNSType_DNSKEY:   {
-        rdataDNSKey *rrkey = (rdataDNSKey *)rd->data;
-        length += mDNS_snprintf(buffer+length, RemSpc, "\t%d  %d  %s  %u ", swap16(rrkey->flags), rrkey->proto,
-                                DNSSECAlgName(rrkey->alg), (unsigned int)keytag((mDNSu8 *)rrkey, rr->rdlength));
-        baseEncode(buffer + length, RemSpc, (const mDNSu8 *)(rd->data + DNSKEY_FIXED_SIZE),
-                               rr->rdlength - DNSKEY_FIXED_SIZE, ENC_BASE64);
-    }
-    break;
-    case kDNSType_DS:       {
-        mDNSu8 *p;
-        int i;
-        rdataDS *rrds = (rdataDS *)rd->data;
-
-        length += mDNS_snprintf(buffer+length, RemSpc, "\t%s\t%d\t%s ", DNSSECAlgName(rrds->alg), swap16(rrds->keyTag),
-                                DNSSECDigestName(rrds->digestType));
-
-        p = (mDNSu8 *)(rd->data + DS_FIXED_SIZE);
-        for (i = 0; i < (rr->rdlength - DS_FIXED_SIZE); i++)
-        {
-            length += mDNS_snprintf(buffer+length, RemSpc, "%x", p[i]);
-        }
-    }
-    break;
 
     default:            mDNS_snprintf(buffer+length, RemSpc, "RDLen %d: %.*s", rr->rdlength, rr->rdlength, rd->data);
         // Really should scan buffer to check if text is valid UTF-8 and only replace with dots if not
         for (ptr = buffer; *ptr; ptr++) if (*ptr < ' ') *ptr = '.';
         break;
     }
+
     return(buffer);
 }
 
@@ -1349,7 +1196,11 @@ mDNSexport void mDNS_SetupResourceRecord(AuthRecord *rr, RData *RDataStorage, mD
     rr->resrec.rrtype            = rrtype;
     rr->resrec.rrclass           = kDNSClass_IN;
     rr->resrec.rroriginalttl     = ttl;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    rr->resrec.dnsservice        = NULL;
+#else
     rr->resrec.rDNSServer        = mDNSNULL;
+#endif
 //     rr->resrec.rdlength          = MUST set by client and/or in mDNS_Register_internal
 //     rr->resrec.rdestimate        = set in mDNS_Register_internal
 //     rr->resrec.rdata             = MUST be set by client
@@ -1425,8 +1276,6 @@ mDNSexport void mDNS_SetupQuestion(DNSQuestion *const q, const mDNSInterfaceID I
     q->TimeoutQuestion     = 0;
     q->WakeOnResolve       = 0;
     q->UseBackgroundTraffic = mDNSfalse;
-    q->ValidationRequired  = 0;
-    q->ValidatingResponse  = 0;
     q->ProxyQuestion       = 0;
     q->pid                 = mDNSPlatformGetPID();
     q->euid                = 0;
@@ -1651,70 +1500,6 @@ mDNSexport mDNSBool RRAssertsNonexistence(const ResourceRecord *const rr, mDNSu1
     return !RRAssertsExistence(rr, type);
 }
 
-// Checks whether the RRSIG or NSEC record answers the question "q".
-mDNSlocal mDNSBool DNSSECRecordAnswersQuestion(const ResourceRecord *const rr, const DNSQuestion *const q, mDNSBool *checkType)
-{
-    *checkType = mDNStrue;
-
-    // This function is called for all questions and as long as the type matches,
-    // return true. For the types (RRSIG and NSEC) that are specifically checked in
-    // this function, returning true still holds good.
-    if (q->qtype == rr->rrtype)
-        return mDNStrue;
-
-    // For DS and DNSKEY questions, the types should match i.e., don't answer using CNAME
-    // records as it answers any question type.
-    //
-    // - DS record comes from the parent zone where CNAME record cannot coexist and hence
-    //  cannot possibly answer it.
-    //
-    // - For DNSKEY, one could potentially follow CNAME but there could be a DNSKEY at
-    //   the "qname" itself. To keep it simple, we don't follow CNAME.
-
-    if ((q->qtype == kDNSType_DS || q->qtype == kDNSType_DNSKEY) && (q->qtype != rr->rrtype))
-    {
-        debugf("DNSSECRecordAnswersQuestion: %d type resource record matched question %##s (%s), ignoring", rr->rrtype,
-            q->qname.c, DNSTypeName(q->qtype));
-        return mDNSfalse;
-    }
-
-    // If we are validating a response using DNSSEC, we might already have the records
-    // for the "q->qtype" in the cache but we issued a query with DO bit set
-    // to get the RRSIGs e.g., if you have two questions one of which does not require
-    // DNSSEC validation. When the RRSIG is added to the cache, we need to deliver
-    // the response to the question. The RRSIG type won't match the q->qtype and hence
-    // we need to bypass the check in that case.
-    if (rr->rrtype == kDNSType_RRSIG && q->ValidatingResponse)
-    {
-        const RDataBody2 *const rdb = (RDataBody2 *)rr->rdata->u.data;
-        rdataRRSig *rrsig = (rdataRRSig *)rdb->data;
-        mDNSu16 typeCovered = swap16(rrsig->typeCovered);
-        debugf("DNSSECRecordAnswersQuestion: Matching RRSIG typeCovered %s", DNSTypeName(typeCovered));
-        if (typeCovered != kDNSType_CNAME && typeCovered != q->qtype)
-        {
-            debugf("DNSSECRecordAnswersQuestion: RRSIG did not match question %##s (%s)", q->qname.c,
-                    DNSTypeName(q->qtype));
-            return mDNSfalse;
-        }
-        LogInfo("DNSSECRecordAnswersQuestion: RRSIG matched question %##s (%s)", q->qname.c,
-                DNSTypeName(q->qtype));
-        *checkType = mDNSfalse;
-        return mDNStrue;
-    }
-    // If the NSEC record asserts the non-existence of a name looked up by the question, we would
-    // typically answer that e.g., the bitmap asserts that q->qtype does not exist. If we have
-    // to prove the non-existence as required by ValidatingResponse and ValidationRequired question,
-    // then we should not answer that as it may not be the right one always. We may need more than
-    // one NSEC to prove the non-existence.
-    if (rr->rrtype == kDNSType_NSEC && DNSSECQuestion(q))
-    {
-        debugf("DNSSECRecordAnswersQuestion: Question %##s (%s) matched record %##s (NSEC)", q->qname.c,
-                DNSTypeName(q->qtype), rr->name->c);
-        return mDNSfalse;
-    }
-    return mDNStrue;
-}
-
 // ResourceRecordAnswersQuestion returns mDNStrue if the given resource record is a valid answer to the given question.
 // SameNameRecordAnswersQuestion is the same, except it skips the expensive SameDomainName() call.
 // SameDomainName() is generally cheap when the names don't match, but expensive when they do match,
@@ -1744,10 +1529,13 @@ mDNSlocal mDNSBool SameNameRecordAnswersQuestion(const ResourceRecord *const rr,
     if (!isAuthRecord && !rr->InterfaceID)
     {
         if (mDNSOpaque16IsZero(q->TargetQID)) return(mDNSfalse);
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        if (rr->dnsservice != q->dnsservice) return(mDNSfalse);
+#else
         const mDNSu32 idr = rr->rDNSServer ? rr->rDNSServer->resGroupID : 0;
         const mDNSu32 idq = q->qDNSServer ? q->qDNSServer->resGroupID : 0;
         if (idr != idq) return(mDNSfalse);
-        if (!DNSSECRecordAnswersQuestion(rr, q, &checkType)) return mDNSfalse;
+#endif
     }
 
     // If ResourceRecord received via multicast, but question was unicast, then shouldn't use record to answer this question
@@ -1756,7 +1544,11 @@ mDNSlocal mDNSBool SameNameRecordAnswersQuestion(const ResourceRecord *const rr,
     // CNAME answers question of any type and a negative cache record should not prevent us from querying other
     // valid types at the same name.
     if (rr->rrtype == kDNSType_CNAME && rr->RecordType == kDNSRecordTypePacketNegative && rr->rrtype != q->qtype)
-        return mDNSfalse;
+         return mDNSfalse;
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    if (enables_dnssec_validation(q) && record_type_answers_dnssec_question(rr, q->qtype)) checkType = mDNSfalse;
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
 
     // RR type CNAME matches any query type. QTYPE ANY matches any RR type. QCLASS ANY matches any RR class.
     if (checkType && !RRTypeAnswersQuestionType(rr,q->qtype)) return(mDNSfalse);
@@ -1892,9 +1684,13 @@ mDNSexport mDNSBool AnyTypeRecordAnswersQuestion(const AuthRecord *const ar, con
     // both the DNSServers are assumed to be NULL in that case
     if (!rr->InterfaceID)
     {
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        if (rr->dnsservice != q->dnsservice) return(mDNSfalse);
+#else
         const mDNSu32 idr = rr->rDNSServer ? rr->rDNSServer->resGroupID : 0;
         const mDNSu32 idq = q->qDNSServer ? q->qDNSServer->resGroupID : 0;
         if (idr != idq) return(mDNSfalse);
+#endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, RANDOM_AWDL_HOSTNAME)
         if (!mDNSPlatformValidRecordForInterface(ar, q->InterfaceID)) return(mDNSfalse);
 #endif
@@ -1927,7 +1723,9 @@ mDNSexport mDNSBool ResourceRecordAnswersUnicastResponse(const ResourceRecord *c
     // If ResourceRecord received via multicast, but question was unicast, then shouldn't use record to answer this question.
     if (rr->InterfaceID && !mDNSOpaque16IsZero(q->TargetQID)) return(mDNSfalse);
 
-    if (!DNSSECRecordAnswersQuestion(rr, q, &checkType)) return mDNSfalse;
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    if (enables_dnssec_validation(q) && record_type_answers_dnssec_question(rr, q->qtype)) checkType = mDNSfalse;
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
 
     // RR type CNAME matches any query type. QTYPE ANY matches any RR type. QCLASS ANY matches any RR class.
     if (checkType && !RRTypeAnswersQuestionType(rr,q->qtype)) return(mDNSfalse);
@@ -1970,6 +1768,7 @@ mDNSexport mDNSu16 GetRDLength(const ResourceRecord *const rr, mDNSBool estimate
         case kDNSType_RT:
         case kDNSType_KX:   return (mDNSu16)(2 + CompressedDomainNameLength(&rd->mx.exchange, name));
 
+        case kDNSType_MINFO:
         case kDNSType_RP:   return (mDNSu16)(CompressedDomainNameLength(&rd->rp.mbox, name) +
                                              CompressedDomainNameLength(&rd->rp.txt, name));
 
@@ -2064,6 +1863,8 @@ mDNSexport void InitializeDNSMessage(DNSMessageHeader *h, mDNSOpaque16 id, mDNSO
     h->numAdditionals = 0;
 }
 
+#endif // !STANDALONE
+
 mDNSexport const mDNSu8 *FindCompressionPointer(const mDNSu8 *const base, const mDNSu8 *const end, const mDNSu8 *const domname)
 {
     const mDNSu8 *result = end - *domname - 1;
@@ -2170,6 +1971,8 @@ mDNSexport mDNSu8 *putDomainNameAsLabels(const DNSMessage *const msg,
     return(ptr);
 }
 
+#ifndef STANDALONE
+
 mDNSlocal mDNSu8 *putVal16(mDNSu8 *ptr, mDNSu16 val)
 {
     ptr[0] = (mDNSu8)((val >> 8 ) & 0xFF);
@@ -2401,7 +2204,8 @@ mDNSexport mDNSu8 *putRData(const DNSMessage *const msg, mDNSu8 *ptr, const mDNS
 
 #define IsUnicastUpdate(X) (!mDNSOpaque16IsZero((X)->h.id) && ((X)->h.flags.b[0] & kDNSFlag0_OP_Mask) == kDNSFlag0_OP_Update)
 
-mDNSexport mDNSu8 *PutResourceRecordTTLWithLimit(DNSMessage *const msg, mDNSu8 *ptr, mDNSu16 *count, ResourceRecord *rr, mDNSu32 ttl, const mDNSu8 *limit)
+mDNSexport mDNSu8 *PutResourceRecordTTLWithLimit(DNSMessage *const msg, mDNSu8 *ptr, mDNSu16 *count,
+    const ResourceRecord *rr, mDNSu32 ttl, const mDNSu8 *limit)
 {
     mDNSu8 *endofrdata;
     mDNSu16 actualLength;
@@ -2410,13 +2214,17 @@ mDNSexport mDNSu8 *PutResourceRecordTTLWithLimit(DNSMessage *const msg, mDNSu8 *
 
     if (rr->RecordType == kDNSRecordTypeUnregistered)
     {
-        LogMsg("PutResourceRecordTTLWithLimit ERROR! Attempt to put kDNSRecordTypeUnregistered %##s (%s)", rr->name->c, DNSTypeName(rr->rrtype));
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+            "Attempt to put kDNSRecordTypeUnregistered " PRI_DM_NAME " (" PUB_S ")",
+            DM_NAME_PARAM(rr->name), DNSTypeName(rr->rrtype));
         return(ptr);
     }
 
     if (!ptr)
     {
-        LogMsg("PutResourceRecordTTLWithLimit ptr is null %##s (%s)", rr->name->c, DNSTypeName(rr->rrtype));
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+            "Pointer to message is NULL while filling resource record " PRI_DM_NAME " (" PUB_S ")",
+            DM_NAME_PARAM(rr->name), DNSTypeName(rr->rrtype));
         return(mDNSNULL);
     }
 
@@ -2424,8 +2232,10 @@ mDNSexport mDNSu8 *PutResourceRecordTTLWithLimit(DNSMessage *const msg, mDNSu8 *
     // If we're out-of-space, return mDNSNULL
     if (!ptr || ptr + 10 >= limit)
     {
-        LogInfo("PutResourceRecordTTLWithLimit: can't put name, out of space %##s (%s), ptr %p, limit %p", rr->name->c,
-            DNSTypeName(rr->rrtype), ptr, limit);
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
+            "Can't put more names into current message, will possibly put it into the next message - "
+            "name: " PRI_DM_NAME " (" PUB_S "), remaining space: %ld",
+            DM_NAME_PARAM(rr->name), DNSTypeName(rr->rrtype), (long)(limit - ptr));
         return(mDNSNULL);
     }
     ptr[0] = (mDNSu8)(rr->rrtype  >> 8);
@@ -2441,8 +2251,10 @@ mDNSexport mDNSu8 *PutResourceRecordTTLWithLimit(DNSMessage *const msg, mDNSu8 *
     endofrdata = putRData(rdatacompressionbase, ptr+10, limit, rr);
     if (!endofrdata)
     {
-        LogInfo("PutResourceRecordTTLWithLimit: Ran out of space in PutResourceRecord for %##s (%s), ptr %p, limit %p", rr->name->c,
-            DNSTypeName(rr->rrtype), ptr+10, limit);
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
+            "Can't put more rdata into current message, will possibly put it into the next message - "
+            "name: " PRI_DM_NAME " (" PUB_S "), remaining space: %ld",
+            DM_NAME_PARAM(rr->name), DNSTypeName(rr->rrtype), (long)(limit - ptr - 10));
         return(mDNSNULL);
     }
 
@@ -2452,8 +2264,16 @@ mDNSexport mDNSu8 *PutResourceRecordTTLWithLimit(DNSMessage *const msg, mDNSu8 *
     ptr[8] = (mDNSu8)(actualLength >> 8);
     ptr[9] = (mDNSu8)(actualLength &  0xFF);
 
-    if (count) (*count)++;
-    else LogMsg("PutResourceRecordTTL: ERROR: No target count to update for %##s (%s)", rr->name->c, DNSTypeName(rr->rrtype));
+    if (count)
+    {
+        (*count)++;
+    }
+    else
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+            "No target count to update for " PRI_DM_NAME " (" PUB_S ")",
+            DM_NAME_PARAM(rr->name), DNSTypeName(rr->rrtype));
+    }
     return(endofrdata);
 }
 
@@ -2596,24 +2416,6 @@ mDNSexport mDNSu8 *putUpdateLeaseWithLimit(DNSMessage *msg, mDNSu8 *ptr, mDNSu32
     return ptr;
 }
 
-mDNSexport mDNSu8 *putDNSSECOption(DNSMessage *msg, mDNSu8 *end, mDNSu8 *limit)
-{
-    AuthRecord rr;
-    mDNSu32 ttl = 0;
-
-    mDNS_SetupResourceRecord(&rr, mDNSNULL, mDNSInterface_Any, kDNSType_OPT, kStandardTTL, kDNSRecordTypeKnownUnique, AuthRecordAny, mDNSNULL, mDNSNULL);
-    // It is still not clear what the right size is. We will have to fine tune this once we do
-    // a lot of testing with DNSSEC.
-    rr.resrec.rrclass    = 4096;
-    rr.resrec.rdlength   = 0;
-    rr.resrec.rdestimate = 0;
-    // set the DO bit
-    ttl |= 0x8000;
-    end = PutResourceRecordTTLWithLimit(msg, end, &msg->h.numAdditionals, &rr.resrec, ttl, limit);
-    if (!end) { LogMsg("ERROR: putDNSSECOption - PutResourceRecordTTLWithLimit"); return mDNSNULL; }
-    return end;
-}
-
 // ***************************************************************************
 #if COMPILER_LIKES_PRAGMA_MARK
 #pragma mark -
@@ -2779,20 +2581,34 @@ mDNSlocal mDNSu8 *SanityCheckBitMap(const mDNSu8 *bmap, const mDNSu8 *end, int l
     return (mDNSu8 *)bmap;
 }
 
+mDNSlocal mDNSBool AssignDomainNameWithLimit(domainname *const dst, const domainname *src, const mDNSu8 *const end)
+{
+    const mDNSu32 len = DomainNameLengthLimit(src, end);
+    if ((len >= 1) && (len <= MAX_DOMAIN_NAME))
+    {
+        mDNSPlatformMemCopy(dst->c, src->c, len);
+        return mDNStrue;
+    }
+    else
+    {
+        dst->c[0] = 0;
+        return mDNSfalse;
+    }
+}
+
 // This function is called with "msg" when we receive a DNS message and needs to parse a single resource record
 // pointed to by "ptr". Some resource records like SOA, SRV are converted to host order and also expanded
 // (domainnames are expanded to 256 bytes) when stored in memory.
 //
 // This function can also be called with "NULL" msg to parse a single resource record pointed to by ptr.
 // The caller can do this only if the names in the resource records are not compressed and validity of the
-// resource record has already been done before. DNSSEC currently uses it this way.
-mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, const mDNSu8 *end,
-    LargeCacheRecord *const largecr, mDNSu16 rdlength)
+// resource record has already been done before.
+mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, const mDNSu8 *end, ResourceRecord *const rr,
+    const mDNSu16 rdlength)
 {
-    CacheRecord *const rr = &largecr->r;
-    RDataBody2 *const rdb = (RDataBody2 *)rr->smallrdatastorage.data;
+    RDataBody2 *const rdb = (RDataBody2 *)&rr->rdata->u;
 
-    switch (rr->resrec.rrtype)
+    switch (rr->rrtype)
     {
     case kDNSType_A:
         if (rdlength != sizeof(mDNSv4Addr))
@@ -2819,7 +2635,10 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
         }
         else
         {
-            AssignDomainName(&rdb->name, (domainname *)ptr);
+            if (!AssignDomainNameWithLimit(&rdb->name, (domainname *)ptr, end))
+            {
+                goto fail;
+            }
             ptr += DomainNameLength(&rdb->name);
         }
         if (ptr != end)
@@ -2836,7 +2655,10 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
         }
         else
         {
-            AssignDomainName(&rdb->soa.mname, (domainname *)ptr);
+            if (!AssignDomainNameWithLimit(&rdb->soa.mname, (domainname *)ptr, end))
+            {
+                goto fail;
+            }
             ptr += DomainNameLength(&rdb->soa.mname);
         }
         if (!ptr)
@@ -2850,7 +2672,10 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
         }
         else
         {
-            AssignDomainName(&rdb->soa.rname, (domainname *)ptr);
+            if (!AssignDomainNameWithLimit(&rdb->soa.rname, (domainname *)ptr, end))
+            {
+                goto fail;
+            }
             ptr += DomainNameLength(&rdb->soa.rname);
         }
         if (!ptr)
@@ -2870,14 +2695,51 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
         rdb->soa.min     = (mDNSu32) ((mDNSu32)ptr[0x10] << 24 | (mDNSu32)ptr[0x11] << 16 | (mDNSu32)ptr[0x12] << 8 | ptr[0x13]);
         break;
 
-    case kDNSType_NULL:
     case kDNSType_HINFO:
+    // See https://tools.ietf.org/html/rfc1035#section-3.3.2 for HINFO RDATA format.
+    {
+        // HINFO should contain RDATA.
+        if (end <= ptr || rdlength != (mDNSu32)(end - ptr))
+        {
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
+                "SetRData: Malformed HINFO RDATA - invalid RDATA length: %u", rdlength);
+            goto fail;
+        }
+
+        const mDNSu8 *currentPtr = ptr;
+        // CPU character string length should be less than the RDATA length.
+        mDNSu32 cpuCharacterStrLength = currentPtr[0];
+        if (1 + cpuCharacterStrLength >= (mDNSu32)(end - currentPtr))
+        {
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
+                "SetRData: Malformed HINFO RDATA - CPU character string goes out of boundary");
+            goto fail;
+        }
+        currentPtr += 1 + cpuCharacterStrLength;
+
+        // OS character string should end at the RDATA ending.
+        mDNSu32 osCharacterStrLength = currentPtr[0];
+        if (1 + osCharacterStrLength != (mDNSu32)(end - currentPtr))
+        {
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
+                "SetRData: Malformed HINFO RDATA - OS character string does not end at the RDATA ending");
+            goto fail;
+        }
+
+        // Copy the validated RDATA.
+        rr->rdlength = rdlength;
+        mDNSPlatformMemCopy(rdb->data, ptr, rdlength);
+        break;
+    }
+    case kDNSType_NULL:
     case kDNSType_TXT:
     case kDNSType_X25:
     case kDNSType_ISDN:
     case kDNSType_LOC:
     case kDNSType_DHCID:
-        rr->resrec.rdlength = rdlength;
+       case kDNSType_SVCB:
+       case kDNSType_HTTPS:
+        rr->rdlength = rdlength;
         mDNSPlatformMemCopy(rdb->data, ptr, rdlength);
         break;
 
@@ -2896,7 +2758,10 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
         }
         else
         {
-            AssignDomainName(&rdb->mx.exchange, (domainname *)ptr);
+            if (!AssignDomainNameWithLimit(&rdb->mx.exchange, (domainname *)ptr, end))
+            {
+                goto fail;
+            }
             ptr += DomainNameLength(&rdb->mx.exchange);
         }
         if (ptr != end)
@@ -2915,7 +2780,10 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
         }
         else
         {
-            AssignDomainName(&rdb->rp.mbox, (domainname *)ptr);
+            if (!AssignDomainNameWithLimit(&rdb->rp.mbox, (domainname *)ptr, end))
+            {
+                goto fail;
+            }
             ptr += DomainNameLength(&rdb->rp.mbox);
         }
         if (!ptr)
@@ -2929,7 +2797,10 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
         }
         else
         {
-            AssignDomainName(&rdb->rp.txt, (domainname *)ptr);
+            if (!AssignDomainNameWithLimit(&rdb->rp.txt, (domainname *)ptr, end))
+            {
+                goto fail;
+            }
             ptr += DomainNameLength(&rdb->rp.txt);
         }
         if (ptr != end)
@@ -2951,7 +2822,10 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
         }
         else
         {
-            AssignDomainName(&rdb->px.map822, (domainname *)ptr);
+            if (!AssignDomainNameWithLimit(&rdb->px.map822, (domainname *)ptr, end))
+            {
+                goto fail;
+            }
             ptr += DomainNameLength(&rdb->px.map822);
         }
         if (!ptr)
@@ -2965,7 +2839,10 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
         }
         else
         {
-            AssignDomainName(&rdb->px.mapx400, (domainname *)ptr);
+            if (!AssignDomainNameWithLimit(&rdb->px.mapx400, (domainname *)ptr, end))
+            {
+                goto fail;
+            }
             ptr += DomainNameLength(&rdb->px.mapx400);
         }
         if (ptr != end)
@@ -2996,7 +2873,10 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
         }
         else
         {
-            AssignDomainName(&rdb->srv.target, (domainname *)ptr);
+            if (!AssignDomainNameWithLimit(&rdb->srv.target, (domainname *)ptr, end))
+            {
+                goto fail;
+            }
             ptr += DomainNameLength(&rdb->srv.target);
         }
         if (ptr != end)
@@ -3012,8 +2892,7 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
         domainname name;
         const mDNSu8 *orig = ptr;
 
-        // Make sure the data is parseable and within the limits. DNSSEC code looks at
-        // the domain name in the end for a valid domainname.
+        // Make sure the data is parseable and within the limits.
         //
         // Fixed length: Order, preference (4 bytes)
         // Variable length: flags, service, regexp, domainname
@@ -3050,7 +2929,7 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
             goto fail;
         }
 
-        savelen = ptr - orig;
+        savelen = (int)(ptr - orig);
 
         // RFC 2915 states that name compression is not allowed for this field. But RFC 3597
         // states that for NAPTR we should decompress. We make sure that we store the full
@@ -3061,7 +2940,10 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
         }
         else
         {
-            AssignDomainName(&name, (domainname *)ptr);
+            if (!AssignDomainNameWithLimit(&name, (domainname *)ptr, end))
+            {
+                goto fail;
+            }
             ptr += DomainNameLength(&name);
         }
         if (ptr != end)
@@ -3070,12 +2952,12 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
             goto fail;
         }
 
-        rr->resrec.rdlength = savelen + DomainNameLength(&name);
+        rr->rdlength = savelen + DomainNameLength(&name);
         // The uncompressed size should not exceed the limits
-        if (rr->resrec.rdlength > MaximumRDSize)
+        if (rr->rdlength > MaximumRDSize)
         {
-            LogInfo("SetRData: Malformed NAPTR rdlength %d, rr->resrec.rdlength %d, "
-                    "bmaplen %d, name %##s", rdlength, rr->resrec.rdlength, name.c);
+            LogInfo("SetRData: Malformed NAPTR rdlength %d, rr->rdlength %d, "
+                    "bmaplen %d, name %##s", rdlength, rr->rdlength, name.c);
             goto fail;
         }
         mDNSPlatformMemCopy(rdb->data, orig, savelen);
@@ -3083,10 +2965,10 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
         break;
     }
     case kDNSType_OPT:  {
-        mDNSu8 *dataend     = rr->resrec.rdata->u.data;
-        rdataOPT *opt = rr->resrec.rdata->u.opt;
-        rr->resrec.rdlength = 0;
-        while (ptr < end && (mDNSu8 *)(opt+1) < &dataend[MaximumRDSize])
+        const mDNSu8 * const dataend = &rr->rdata->u.data[rr->rdata->MaxRDLength];
+        rdataOPT *opt = rr->rdata->u.opt;
+        rr->rdlength = 0;
+        while ((ptr < end) && ((dataend - ((const mDNSu8 *)opt)) >= ((mDNSs32)sizeof(*opt))))
         {
             const rdataOPT *const currentopt = opt;
             if (ptr + 4 > end) { LogInfo("SetRData: OPT RDATA ptr + 4 > end"); goto fail; }
@@ -3154,7 +3036,7 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
             }
             ptr += currentopt->optlen;
         }
-        rr->resrec.rdlength = (mDNSu16)((mDNSu8*)opt - rr->resrec.rdata->u.data);
+        rr->rdlength = (mDNSu16)((mDNSu8*)opt - rr->rdata->u.data);
         if (ptr != end) { LogInfo("SetRData: Malformed OptRdata"); goto fail; }
         break;
     }
@@ -3172,7 +3054,10 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
         }
         else
         {
-            AssignDomainName(&name, (domainname *)ptr);
+            if (!AssignDomainNameWithLimit(&name, (domainname *)ptr, end))
+            {
+                goto fail;
+            }
             ptr += DomainNameLength(&name);
         }
         if (!ptr)
@@ -3201,69 +3086,19 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
 
         // Initialize the right length here. When we call SetNewRData below which in turn calls
         // GetRDLength and for NSEC case, it assumes that rdlength is intitialized
-        rr->resrec.rdlength = DomainNameLength(&name) + bmaplen;
+        rr->rdlength = DomainNameLength(&name) + bmaplen;
 
         // Do we have space after the name expansion ?
-        if (rr->resrec.rdlength > MaximumRDSize)
+        if (rr->rdlength > MaximumRDSize)
         {
-            LogInfo("SetRData: Malformed NSEC rdlength %d, rr->resrec.rdlength %d, "
-                    "bmaplen %d, name %##s", rdlength, rr->resrec.rdlength, name.c);
+            LogInfo("SetRData: Malformed NSEC rdlength %d, rr->rdlength %d, "
+                    "bmaplen %d, name %##s", rdlength, rr->rdlength, name.c);
             goto fail;
         }
         AssignDomainName((domainname *)rdb->data, &name);
         mDNSPlatformMemCopy(rdb->data + dlen, bmap, bmaplen);
         break;
     }
-    case kDNSType_NSEC3:
-    {
-        rdataNSEC3 *nsec3 = (rdataNSEC3 *)ptr;
-        mDNSu8 *p = (mDNSu8 *)&nsec3->salt;
-        int hashLength, bitmaplen;
-
-        if (rdlength < NSEC3_FIXED_SIZE + 1)
-        {
-            LogInfo("SetRData: NSEC3 too small length %d", rdlength);
-            goto fail;
-        }
-        if (nsec3->alg != SHA1_DIGEST_TYPE)
-        {
-            LogInfo("SetRData: nsec3 alg %d not supported", nsec3->alg);
-            goto fail;
-        }
-        if (swap16(nsec3->iterations) > NSEC3_MAX_ITERATIONS)
-        {
-            LogInfo("SetRData: nsec3 iteration count %d too big", swap16(nsec3->iterations));
-            goto fail;
-        } 
-        p += nsec3->saltLength;
-        // There should at least be one byte beyond saltLength
-        if (p >= end)
-        {
-            LogInfo("SetRData: nsec3 too small, at saltlength %d, p %p, end %p", nsec3->saltLength, p, end);
-            goto fail;
-        }
-        // p is pointing at hashLength
-        hashLength = (int)*p++;
-        if (!hashLength)
-        {
-            LogInfo("SetRData: hashLength zero");
-            goto fail;
-        }
-        p += hashLength;
-        if (p > end)
-        {
-            LogInfo("SetRData: nsec3 too small, at hashLength %d, p %p, end %p", hashLength, p, end);
-            goto fail;
-        }
-
-        bitmaplen = rdlength - (int)(p - ptr);
-        p = SanityCheckBitMap(p, end, bitmaplen);
-        if (!p)
-            goto fail;
-        rr->resrec.rdlength = rdlength;
-        mDNSPlatformMemCopy(rdb->data, ptr, rdlength);
-        break;
-    }
     case kDNSType_TKEY:
     case kDNSType_TSIG:
     {
@@ -3278,98 +3113,39 @@ mDNSexport mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, con
         }
         else
         {
-            AssignDomainName(&name, (domainname *)ptr);
+            if (!AssignDomainNameWithLimit(&name, (domainname *)ptr, end))
+            {
+                goto fail;
+            }
             ptr += DomainNameLength(&name);
         }
         if (!ptr || ptr >= end)
         {
-            LogInfo("SetRData: Malformed name for TSIG/TKEY type %d", rr->resrec.rrtype);
+            LogInfo("SetRData: Malformed name for TSIG/TKEY type %d", rr->rrtype);
             goto fail;
         }
         dlen = DomainNameLength(&name);
-        rlen = end - ptr;
-        rr->resrec.rdlength = dlen + rlen;
-        if (rr->resrec.rdlength > MaximumRDSize)
+        rlen = (int)(end - ptr);
+        rr->rdlength = dlen + rlen;
+        if (rr->rdlength > MaximumRDSize)
         {
-            LogInfo("SetRData: Malformed TSIG/TKEY rdlength %d, rr->resrec.rdlength %d, "
-                    "bmaplen %d, name %##s", rdlength, rr->resrec.rdlength, name.c);
+            LogInfo("SetRData: Malformed TSIG/TKEY rdlength %d, rr->rdlength %d, "
+                    "bmaplen %d, name %##s", rdlength, rr->rdlength, name.c);
             goto fail;
         }
         AssignDomainName((domainname *)rdb->data, &name);
         mDNSPlatformMemCopy(rdb->data + dlen, ptr, rlen);
         break;
     }
-    case kDNSType_RRSIG:
-    {
-        const mDNSu8 *sig = ptr + RRSIG_FIXED_SIZE;
-        const mDNSu8 *orig = sig;
-        domainname name;
-        if (rdlength < RRSIG_FIXED_SIZE + 1)
-        {
-            LogInfo("SetRData: RRSIG too small length %d", rdlength);
-            goto fail;
-        }
-        if (msg)
-        {
-            sig = getDomainName(msg, sig, end, &name);
-        }
-        else
-        {
-            AssignDomainName(&name, (domainname *)sig);
-            sig += DomainNameLength(&name);
-        }
-        if (!sig)
-        {
-            LogInfo("SetRData: Malformed RRSIG record");
-            goto fail;
-        }
-
-        if ((sig - orig) != DomainNameLength(&name))
-        {
-            LogInfo("SetRData: Malformed RRSIG record, signer name compression");
-            goto fail;
-        }
-        // Just ensure that we have at least one byte of the signature
-        if (sig + 1 >= end)
-        {
-            LogInfo("SetRData: Not enough bytes for signature type %d", rr->resrec.rrtype);
-            goto fail;
-        }
-        rr->resrec.rdlength = rdlength;
-        mDNSPlatformMemCopy(rdb->data, ptr, rdlength);
-        break;
-    }
-    case kDNSType_DNSKEY:
-    {
-        if (rdlength < DNSKEY_FIXED_SIZE + 1)
-        {
-            LogInfo("SetRData: DNSKEY too small length %d", rdlength);
-            goto fail;
-        }
-        rr->resrec.rdlength = rdlength;
-        mDNSPlatformMemCopy(rdb->data, ptr, rdlength);
-        break;
-    }
-    case kDNSType_DS:
-    {
-        if (rdlength < DS_FIXED_SIZE + 1)
-        {
-            LogInfo("SetRData: DS too small length %d", rdlength);
-            goto fail;
-        }
-        rr->resrec.rdlength = rdlength;
-        mDNSPlatformMemCopy(rdb->data, ptr, rdlength);
-        break;
-    }
     default:
         debugf("SetRData: Warning! Reading resource type %d (%s) as opaque data",
-               rr->resrec.rrtype, DNSTypeName(rr->resrec.rrtype));
+               rr->rrtype, DNSTypeName(rr->rrtype));
         // Note: Just because we don't understand the record type, that doesn't
         // mean we fail. The DNS protocol specifies rdlength, so we can
         // safely skip over unknown records and ignore them.
         // We also grab a binary copy of the rdata anyway, since the caller
         // might know how to interpret it even if we don't.
-        rr->resrec.rdlength = rdlength;
+        rr->rdlength = rdlength;
         mDNSPlatformMemCopy(rdb->data, ptr, rdlength);
         break;
     }
@@ -3395,13 +3171,20 @@ mDNSexport const mDNSu8 *GetLargeResourceRecord(mDNS *const m, const DNSMessage
     rr->TimeRcvd          = m ? m->timenow : 0;
     rr->DelayDelivery     = 0;
     rr->NextRequiredQuery = m ? m->timenow : 0;     // Will be updated to the real value when we call SetNextCacheCheckTimeForRecord()
+#if MDNSRESPONDER_SUPPORTS(APPLE, CACHE_ANALYTICS)
+    rr->LastCachedAnswerTime = 0;
+#endif
     rr->CRActiveQuestion  = mDNSNULL;
     rr->UnansweredQueries = 0;
     rr->LastUnansweredTime= 0;
     rr->NextInCFList      = mDNSNULL;
 
     rr->resrec.InterfaceID       = InterfaceID;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mdns_forget(&rr->resrec.dnsservice);
+#else
     rr->resrec.rDNSServer = mDNSNULL;
+#endif
 
     ptr = getDomainName(msg, ptr, end, &largecr->namestorage);      // Will bail out correctly if ptr is NULL
     if (!ptr) { debugf("GetLargeResourceRecord: Malformed RR name"); return(mDNSNULL); }
@@ -3445,8 +3228,13 @@ mDNSexport const mDNSu8 *GetLargeResourceRecord(mDNS *const m, const DNSMessage
     // two domainnames are different when semantically they are the same name and it's only the unused bytes that differ.
     if (rr->resrec.rrclass == kDNSQClass_ANY && pktrdlength == 0)   // Used in update packets to mean "Delete An RRset" (RFC 2136)
         rr->resrec.rdlength = 0;
-    else if (!SetRData(msg, ptr, end, largecr, pktrdlength))
+    else if (!SetRData(msg, ptr, end, &rr->resrec, pktrdlength))
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+            "GetLargeResourceRecord: SetRData failed for " PRI_DM_NAME " (" PUB_S ")",
+            DM_NAME_PARAM(rr->resrec.name), DNSTypeName(rr->resrec.rrtype));
         goto fail;
+    }
 
     SetNewRData(&rr->resrec, mDNSNULL, 0);      // Sets rdlength, rdestimate, rdatahash for us
 
@@ -3776,19 +3564,20 @@ mDNSexport void DumpPacket(mStatus status, mDNSBool sent, const char *transport,
 {
     const mDNSAddr zeroIPv4Addr = { mDNSAddrType_IPv4, {{{ 0 }}} };
     char action[32];
+    const char* interfaceName = "interface";
+
     if (!status) mDNS_snprintf(action, sizeof(action), sent ? "Sent" : "Received");
     else         mDNS_snprintf(action, sizeof(action), "ERROR %d %sing", status, sent ? "Send" : "Receiv");
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
+    interfaceName = InterfaceNameForID(&mDNSStorage, interfaceID);
+#endif
+
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
         "[Q%u] " PUB_S " " PUB_S " DNS Message %lu bytes from " PRI_IP_ADDR ":%d to " PRI_IP_ADDR ":%d via " PUB_S " (%p)",
         mDNSVal16(msg->h.id), action, transport, (unsigned long)(end - (const mDNSu8 *)msg),
         srcaddr ? srcaddr : &zeroIPv4Addr, mDNSVal16(srcport), dstaddr ? dstaddr : &zeroIPv4Addr, mDNSVal16(dstport),
-#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
-        InterfaceNameForID(&mDNSStorage, interfaceID),
-#else
-        "interface",
-#endif
-        interfaceID);
+        interfaceName, interfaceID);
     DNSMessageDumpToLog(msg, end);
 }
 
@@ -4500,10 +4289,52 @@ mDNSexport mDNSu32 mDNS_snprintf(char *sbuffer, mDNSu32 buflen, const char *fmt,
     return(length);
 }
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 mDNSexport mDNSu32 mDNS_GetNextResolverGroupID(void)
 {
     static mDNSu32 lastID = 0;
     if (++lastID == 0) lastID = 1; // Valid resolver group IDs are non-zero.
     return(lastID);
 }
+#endif
+
+#define kReverseIPv6Domain  ((const domainname *) "\x3" "ip6" "\x4" "arpa")
+
+mDNSexport mDNSBool GetReverseIPv6Addr(const domainname *name, mDNSu8 outIPv6[16])
+{
+    const mDNSu8 *      ptr;
+    int                 i;
+    mDNSu8              ipv6[16];
+
+    // If the name is of the form "x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.ip6.arpa.", where each x
+    // is a hex digit, then the sequence of 32 hex digit labels represents the nibbles of an IPv6 address in reverse order.
+    // See <https://tools.ietf.org/html/rfc3596#section-2.5>.
+
+    ptr = name->c;
+    for (i = 0; i < 32; i++)
+    {
+        unsigned int c, nibble;
+        const int j = 15 - (i / 2);
+        if (*ptr++ != 1) return (mDNSfalse);                    // If this label's length is not 1, then fail.
+        c = *ptr++;                                             // Get label byte.
+        if (     (c >= '0') && (c <= '9')) nibble =  c - '0';   // If it's a hex digit, get its numeric value.
+        else if ((c >= 'a') && (c <= 'f')) nibble = (c - 'a') + 10;
+        else if ((c >= 'A') && (c <= 'F')) nibble = (c - 'A') + 10;
+        else                               return (mDNSfalse);  // Otherwise, fail.
+        if ((i % 2) == 0)
+        {
+            ipv6[j] = (mDNSu8)nibble;
+        }
+        else
+        {
+            ipv6[j] |= (mDNSu8)(nibble << 4);
+        }
+    }
 
+    // The rest of the name needs to be "ip6.arpa.". If it isn't, fail.
+
+    if (!SameDomainName((const domainname *)ptr, kReverseIPv6Domain)) return (mDNSfalse);
+    if (outIPv6) mDNSPlatformMemCopy(outIPv6, ipv6, 16);
+    return (mDNStrue);
+}
+#endif // !STANDALONE
index 1e422d771e8bf81d12586caa2554d34a91c57cdd..48de85f60956981e54df7d57e6cf638fb2743347 100644 (file)
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2002-2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -98,7 +98,9 @@ extern mDNSInterfaceID GetNextActiveInterfaceID(const NetworkInterfaceInfo *intf
 
 extern mDNSu32 mDNSRandom(mDNSu32 max);     // Returns pseudo-random result from zero to max inclusive
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 extern mDNSu32 mDNS_GetNextResolverGroupID(void);
+#endif
 
 // ***************************************************************************
 #if COMPILER_LIKES_PRAGMA_MARK
@@ -211,7 +213,8 @@ extern mDNSu8 *putRData(const DNSMessage *const msg, mDNSu8 *ptr, const mDNSu8 *
 
 #define AllowedRRSpace(msg) (((msg)->h.numAnswers || (msg)->h.numAuthorities || (msg)->h.numAdditionals) ? NormalMaxDNSMessageData : AbsoluteMaxDNSMessageData)
 
-extern mDNSu8 *PutResourceRecordTTLWithLimit(DNSMessage *const msg, mDNSu8 *ptr, mDNSu16 *count, ResourceRecord *rr, mDNSu32 ttl, const mDNSu8 *limit);
+extern mDNSu8 *PutResourceRecordTTLWithLimit(DNSMessage *const msg, mDNSu8 *ptr, mDNSu16 *count, const ResourceRecord *rr,
+    mDNSu32 ttl, const mDNSu8 *limit);
 
 #define PutResourceRecordTTL(msg, ptr, count, rr, ttl) \
     PutResourceRecordTTLWithLimit((msg), (ptr), (count), (rr), (ttl), (msg)->data + AllowedRRSpace(msg))
@@ -238,7 +241,6 @@ extern mDNSu8 *putDeleteAllRRSets(DNSMessage *msg, mDNSu8 *ptr, const domainname
 extern mDNSu8 *putUpdateLease(DNSMessage *msg, mDNSu8 *ptr, mDNSu32 lease);
 extern mDNSu8 *putUpdateLeaseWithLimit(DNSMessage *msg, mDNSu8 *ptr, mDNSu32 lease, mDNSu8 *limit);
 
-extern mDNSu8 *putDNSSECOption(DNSMessage *msg, mDNSu8 *end, mDNSu8 *limit);
 extern int baseEncode(char *buffer, int blen, const mDNSu8 *data, int len, int encAlg);
 extern void NSEC3Parse(const ResourceRecord *const rr, mDNSu8 **salt, int *hashLength, mDNSu8 **nxtName, int *bitmaplen, mDNSu8 **bitmap);
 
@@ -257,8 +259,8 @@ extern const mDNSu8 *getDomainName(const DNSMessage *const msg, const mDNSu8 *pt
 extern const mDNSu8 *skipResourceRecord(const DNSMessage *msg, const mDNSu8 *ptr, const mDNSu8 *end);
 extern const mDNSu8 *GetLargeResourceRecord(mDNS *const m, const DNSMessage * const msg, const mDNSu8 *ptr,
                                             const mDNSu8 * end, const mDNSInterfaceID InterfaceID, mDNSu8 RecordType, LargeCacheRecord *const largecr);
-extern mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, const mDNSu8 *end,
-    LargeCacheRecord *const largecr, mDNSu16 rdlength);
+extern mDNSBool SetRData(const DNSMessage *const msg, const mDNSu8 *ptr, const mDNSu8 *end, ResourceRecord *rr,
+    mDNSu16 rdlength);
 extern const mDNSu8 *skipQuestion(const DNSMessage *msg, const mDNSu8 *ptr, const mDNSu8 *end);
 extern const mDNSu8 *getQuestion(const DNSMessage *msg, const mDNSu8 *ptr, const mDNSu8 *end, const mDNSInterfaceID InterfaceID,
                                  DNSQuestion *question);
@@ -278,6 +280,8 @@ extern mDNSBool BitmapTypeCheck(mDNSu8 *bmap, int bitmaplen, mDNSu16 type);
 extern mDNSu16 swap16(mDNSu16 x);
 extern mDNSu32 swap32(mDNSu32 x);
 
+extern mDNSBool GetReverseIPv6Addr(const domainname *inQName, mDNSu8 outIPv6[16]);
+
 // ***************************************************************************
 #if COMPILER_LIKES_PRAGMA_MARK
 #pragma mark -
index 120d2fc4cb09b54cf4b6758c731e4d5bb78e9069..58dafed0f89beb1fbdd4ce1e52e72bc51b7fdde3 100644 (file)
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2011-2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2011-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include "dnsproxy.h"
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+#include <nw/private.h>
+#endif
+
 #ifndef UNICAST_DISABLED
 
 extern mDNS mDNSStorage;
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+static mDNSBool gDNS64Enabled = mDNSfalse;
+static mDNSBool gDNS64ForceAAAASynthesis = mDNSfalse;
+static nw_nat64_prefix_t gDNS64Prefix;
+#endif
 
 // Implementation Notes
 //
@@ -54,6 +63,18 @@ extern mDNS mDNSStorage;
 
 typedef struct DNSProxyClient_struct DNSProxyClient;
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+typedef enum
+{
+    kDNSProxyDNS64State_Initial                 = 0,    // Initial state.
+    kDNSProxyDNS64State_AAAASynthesis           = 1,    // Querying for A record for AAAA record synthesis.
+    kDNSProxyDNS64State_PTRSynthesisTrying      = 2,    // Querying for in-addr.arpa PTR record to map from ip6.arpa PTR.
+    kDNSProxyDNS64State_PTRSynthesisSuccess     = 3,    // in-addr.arpa PTR query got non-negative non-CNAME answer.
+    kDNSProxyDNS64State_PTRSynthesisNXDomain    = 4     // in-addr.arpa PTR query produced no useful result.
+
+}   DNSProxyDNS64State;
+#endif
+
 struct DNSProxyClient_struct {
 
     DNSProxyClient *next; 
@@ -67,10 +88,13 @@ struct DNSProxyClient_struct {
     mDNSu8 *optRR;                  // EDNS0 option
     mDNSu16 optLen;                 // Total Length of the EDNS0 option 
     mDNSu16 rcvBufSize;             // How much can the client receive ?
-    mDNSBool DNSSECOK;              // DNSSEC OK ?
     void *context;                  // Platform context to be disposed if non-NULL
     domainname qname;               // q->qname can't be used for duplicate check
     DNSQuestion q;                  // as it can change underneath us for CNAMEs
+    mDNSu16 qtype;
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+    DNSProxyDNS64State dns64state;
+#endif
 };
 
 #define MIN_DNS_MESSAGE_SIZE    512
@@ -106,7 +130,6 @@ mDNSlocal mDNSBool ParseEDNS0(DNSProxyClient *pc, const mDNSu8 *ptr, int length,
     debugf("rrtype is %s, length is %d, rcode %d, version %d, flag 0x%x", DNSTypeName(rrtype), rrclass, rcode, version, flag);
 #endif
     pc->rcvBufSize = rrclass;
-    pc->DNSSECOK = ptr[6] & 0x80;
     
     return mDNStrue;
 }
@@ -193,9 +216,8 @@ mDNSlocal mDNSu8 *AddResourceRecords(DNSProxyClient *pc, mDNSu8 **prevptr, mStat
     mDNSu8 *ptr = mDNSNULL;
     mDNSs32 now;
     mDNSs32 ttl;
-    CacheRecord *nsec = mDNSNULL;
-    CacheRecord *soa = mDNSNULL;
-    CacheRecord *cname = mDNSNULL;
+    const CacheRecord *soa = mDNSNULL;
+    const CacheRecord *cname = mDNSNULL;
     mDNSu8 *limit;
     domainname tempQName;
     mDNSu32 tempQNameHash;
@@ -226,11 +248,24 @@ mDNSlocal mDNSu8 *AddResourceRecords(DNSProxyClient *pc, mDNSu8 **prevptr, mStat
     }
     LogInfo("AddResourceRecords: Limit is %d", limit - m->omsg.data);
 
-    AssignDomainName(&tempQName, &pc->qname);
-    tempQNameHash = DomainNameHashValue(&tempQName);
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+    if (pc->dns64state == kDNSProxyDNS64State_PTRSynthesisSuccess)
+    {
+        // We're going to synthesize a CNAME record to map the originally requested ip6.arpa domain name to the
+        // in-addr.arpa domain name, so we use pc->q.qname, which contains the in-addr.arpa domain name, to get the
+        // in-addr.arpa PTR record.
+        AssignDomainName(&tempQName, &pc->q.qname);
+        tempQNameHash = DomainNameHashValue(&tempQName);
+    }
+    else
+#endif
+    {
+        AssignDomainName(&tempQName, &pc->qname);
+        tempQNameHash = DomainNameHashValue(&tempQName);
+    }
 
 again:
-    nsec = soa = cname = mDNSNULL;
+    soa = cname = mDNSNULL;
 
     cg = CacheGroupForName(m, tempQNameHash, &tempQName);
     if (!cg)
@@ -239,10 +274,6 @@ again:
         *error = mStatus_NoSuchRecord;
         return mDNSNULL;
     }
-    // Set ValidatingResponse so that you can get RRSIGs also matching
-    // the question
-    if (pc->DNSSECOK)
-        pc->q.ValidatingResponse = 1;
     for (cr = cg->members; cr; cr = cr->next)
     {
         if (SameNameCacheRecordAnswersQuestion(cr, &pc->q))
@@ -254,13 +285,37 @@ again:
                 // cache record
                 mDNSOpaque16 responseFlags = SetResponseFlags(pc, cr->responseFlags);
                 InitializeDNSMessage(&m->omsg.h, pc->msgid, responseFlags);
-                ptr = putQuestion(&m->omsg, m->omsg.data, m->omsg.data + AbsoluteMaxDNSMessageData, &pc->qname, pc->q.qtype, pc->q.qclass);
+                ptr = putQuestion(&m->omsg, m->omsg.data, m->omsg.data + AbsoluteMaxDNSMessageData, &pc->qname, pc->qtype, pc->q.qclass);
                 if (!ptr)
                 {
-                    LogInfo("AddResourceRecords: putQuestion NULL for %##s (%s)", &pc->qname.c, DNSTypeName(pc->q.qtype));
+                    LogInfo("AddResourceRecords: putQuestion NULL for %##s (%s)", &pc->qname.c, DNSTypeName(pc->qtype));
                     return mDNSNULL;
                 }
                 first = mDNSfalse;
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+                if (pc->dns64state == kDNSProxyDNS64State_PTRSynthesisSuccess)
+                {
+                    // For the first answer record, synthesize a CNAME record to map the originally requested ip6.arpa
+                    // domain name to the in-addr.arpa domain name.
+                    // See <https://tools.ietf.org/html/rfc6147#section-5.3.1>.
+                    RData rdata;
+                    ResourceRecord newRR;
+                    mDNSPlatformMemZero(&newRR, (mDNSu32)sizeof(newRR));
+                    newRR.RecordType    = kDNSRecordTypePacketAns;
+                    newRR.rrtype        = kDNSType_CNAME;
+                    newRR.rrclass       = kDNSClass_IN;
+                    newRR.name          = &pc->qname;
+                    AssignDomainName(&rdata.u.name, &pc->q.qname);
+                    rdata.MaxRDLength   = (mDNSu32)sizeof(rdata.u);
+                    newRR.rdata         = &rdata;
+                    ptr = PutResourceRecordTTLWithLimit(&m->omsg, ptr, &m->omsg.h.numAnswers, &newRR, 0, limit);
+                    if (!ptr)
+                    {
+                        *prevptr = orig;
+                        return mDNSNULL;
+                    }
+                }
+#endif
             }
             // - For NegativeAnswers there is nothing to add
             // - If DNSSECOK is set, we also automatically lookup the RRSIGs which
@@ -269,9 +324,40 @@ again:
             //   DNSSECOK bit only influences whether we add the RRSIG or not.
             if (cr->resrec.RecordType != kDNSRecordTypePacketNegative)
             {
-                LogInfo("AddResourceRecords: Answering question with %s", CRDisplayString(m, cr));
+                const ResourceRecord *rr;
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+                RData rdata;
+                ResourceRecord newRR;
+                if ((pc->dns64state == kDNSProxyDNS64State_AAAASynthesis) && (cr->resrec.rrtype == kDNSType_A))
+                {
+                    struct in_addr  addrV4;
+                    struct in6_addr addrV6;
+
+                    newRR               = cr->resrec;
+                    newRR.rrtype        = kDNSType_AAAA;
+                    newRR.rdlength      = 16;
+                    rdata.MaxRDLength   = newRR.rdlength;
+                    newRR.rdata         = &rdata;
+
+                    memcpy(&addrV4.s_addr, cr->resrec.rdata->u.ipv4.b, 4);
+                    if (nw_nat64_synthesize_v6(&gDNS64Prefix, &addrV4, &addrV6))
+                    {
+                        memcpy(rdata.u.ipv6.b, addrV6.s6_addr, 16);
+                        rr = &newRR;
+                    }
+                    else
+                    {
+                        continue;
+                    }
+                }
+                else
+#endif
+                {
+                    rr = &cr->resrec;
+                }
+                LogInfo("AddResourceRecords: Answering question with %s", RRDisplayString(m, rr));
                 ttl = cr->resrec.rroriginalttl - (now - cr->TimeRcvd) / mDNSPlatformOneSecond;
-                ptr = PutResourceRecordTTLWithLimit(&m->omsg, ptr, &m->omsg.h.numAnswers, &cr->resrec, ttl, limit);
+                ptr = PutResourceRecordTTLWithLimit(&m->omsg, ptr, &m->omsg.h.numAnswers, rr, ttl, limit);
                 if (!ptr)
                 {
                     *prevptr = orig;
@@ -280,13 +366,6 @@ again:
                 len += (ptr - orig); 
                 orig = ptr;
             }
-            // If we have nsecs (wildcard expanded answer or negative response), add them
-            // in the additional section below if the DNSSECOK bit is set
-            if (pc->DNSSECOK && cr->nsec)
-            {
-                LogInfo("AddResourceRecords: nsec set for %s", CRDisplayString(m ,cr));
-                nsec = cr->nsec;
-            }
             if (cr->soa)
             {
                 LogInfo("AddResourceRecords: soa set for %s", CRDisplayString(m ,cr));
@@ -315,21 +394,6 @@ again:
     // - if we issue a non-DNSSEC question followed by DNSSEC question for the same name,
     //   the "core" flushes the cache entry and re-issue the question with EDNS0/DOK bit and
     //   in this case we return all the DNSSEC records we have.
-    for (; nsec; nsec = nsec->next)
-    {
-        if (!pc->DNSSECOK && DNSSECRecordType(nsec->resrec.rrtype))
-            continue;
-        LogInfo("AddResourceRecords:NSEC Answering question with %s", CRDisplayString(m, nsec));
-        ttl = nsec->resrec.rroriginalttl - (now - nsec->TimeRcvd) / mDNSPlatformOneSecond;
-        ptr = PutResourceRecordTTLWithLimit(&m->omsg, ptr, &m->omsg.h.numAuthorities, &nsec->resrec, ttl, limit);
-        if (!ptr)
-        {
-            *prevptr = orig;
-            return mDNSNULL;
-        }
-        len += (ptr - orig); 
-        orig = ptr;
-    }
     if (soa)
     {
         LogInfo("AddResourceRecords: SOA Answering question with %s", CRDisplayString(m, soa));
@@ -382,93 +446,99 @@ mDNSlocal void ProxyClientCallback(mDNS *const m, DNSQuestion *question, const R
 
     LogInfo("ProxyClientCallback: ResourceRecord %s", RRDisplayString(m, answer));
 
-    // We asked for validation and not timed out yet, then wait for the DNSSEC result.
-    // We have to set the AD bit in the response if it is secure which can't be done
-    // till we get the DNSSEC result back (indicated by QC_dnssec).
-    if (question->ValidationRequired)
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+    if (gDNS64Enabled)
     {
-        mDNSs32 now;
-
-        mDNS_Lock(m);
-        now = m->timenow;
-        mDNS_Unlock(m);
-        if (((now - question->StopTime) < 0) && AddRecord != QC_dnssec)
+        if (pc->dns64state == kDNSProxyDNS64State_Initial)
         {
-            LogInfo("ProxyClientCallback: No DNSSEC answer yet for Question %##s (%s), AddRecord %d, answer %s", question->qname.c,
-                DNSTypeName(question->qtype), AddRecord, RRDisplayString(m, answer));
-            return;
-        }
-    }
-
-    if (answer->RecordType != kDNSRecordTypePacketNegative)
-    {
-        if (answer->rrtype != question->qtype)
-        {
-            // Wait till we get called for the real response
-            LogInfo("ProxyClientCallback: Received %s, not answering yet", RRDisplayString(m, answer));
-            return;
+            // If we get a negative AAAA answer, then retry the query as an A record query.
+            // See <https://tools.ietf.org/html/rfc6147#section-5.1.6>.
+            if ((answer->RecordType == kDNSRecordTypePacketNegative) && (question->qtype == kDNSType_AAAA) &&
+                (answer->rrtype == kDNSType_AAAA) && (answer->rrclass == kDNSClass_IN))
+            {
+                mDNS_StopQuery(m, question);
+                pc->dns64state = kDNSProxyDNS64State_AAAASynthesis;
+                question->qtype = kDNSType_A;
+                mDNS_StartQuery(m, question);
+                return;
+            }
         }
-    }
-    ptr = AddResourceRecords(pc, &prevptr, &error);
-    if (!ptr)
-    {
-        LogInfo("ProxyClientCallback: AddResourceRecords NULL for %##s (%s)", &pc->qname.c, DNSTypeName(pc->q.qtype));
-        if (error == mStatus_NoError && prevptr)
+        else if (pc->dns64state == kDNSProxyDNS64State_PTRSynthesisTrying)
         {
-            // No space to add the record. Set the Truncate bit for UDP.
-            //
-            // TBD: For TCP, we need to send the rest of the data. But finding out what is left
-            // is harder. We should allocate enough buffer in the first place to send all
-            // of the data.
-            if (!pc->tcp)
+            // If we get a non-negative non-CNAME answer, then this is the answer we give to the client.
+            // Otherwise, just respond with NXDOMAIN.
+            // See <https://tools.ietf.org/html/rfc6147#section-5.3.1>.
+            if ((answer->RecordType != kDNSRecordTypePacketNegative) && (question->qtype == kDNSType_PTR) &&
+                (answer->rrtype == kDNSType_PTR) && (answer->rrclass == kDNSClass_IN))
             {
-                m->omsg.h.flags.b[0] |= kDNSFlag0_TC;
-                ptr = prevptr;
+                pc->dns64state = kDNSProxyDNS64State_PTRSynthesisSuccess;
             }
             else
             {
-                LogInfo("ProxyClientCallback: ERROR!! Not enough space to return in TCP for %##s (%s)", &pc->qname.c, DNSTypeName(pc->q.qtype));
-                ptr = prevptr;
+                pc->dns64state = kDNSProxyDNS64State_PTRSynthesisNXDomain;
             }
         }
-        else
+    }
+    if (pc->dns64state == kDNSProxyDNS64State_PTRSynthesisNXDomain)
+    {
+        const mDNSOpaque16 flags = { { kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery, kDNSFlag1_RC_NXDomain } };
+        InitializeDNSMessage(&m->omsg.h, pc->msgid, flags);
+        ptr = putQuestion(&m->omsg, m->omsg.data, m->omsg.data + AbsoluteMaxDNSMessageData, &pc->qname, pc->qtype,
+            pc->q.qclass);
+        if (!ptr)
         {
-            mDNSOpaque16 flags   = { { kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery, kDNSFlag1_RC_ServFail } };
-            // We could not find the record for some reason. Return a response, so that the client
-            // is not waiting forever.
-            LogInfo("ProxyClientCallback: No response");
-            if (!mDNSOpaque16IsZero(pc->q.responseFlags))
-                flags = pc->q.responseFlags;
-            InitializeDNSMessage(&m->omsg.h, pc->msgid, flags);
-            ptr = putQuestion(&m->omsg, m->omsg.data, m->omsg.data + AbsoluteMaxDNSMessageData, &pc->qname, pc->q.qtype, pc->q.qclass);
-            if (!ptr)
-            {
-                LogInfo("ProxyClientCallback: putQuestion NULL for %##s (%s)", &pc->qname.c, DNSTypeName(pc->q.qtype));
-                goto done;
-            }
+            LogInfo("ProxyClientCallback: putQuestion NULL for %##s (%s)", &pc->qname.c, DNSTypeName(pc->qtype));
         }
     }
-    if (question->ValidationRequired)
+    else
+#endif
     {
-        if (question->ValidationState == DNSSECValDone && question->ValidationStatus == DNSSEC_Secure)
+        if ((answer->RecordType != kDNSRecordTypePacketNegative) && (answer->rrtype != question->qtype))
         {
-            LogInfo("ProxyClientCallback: Setting AD bit for Question %##s (%s)", question->qname.c, DNSTypeName(question->qtype));
-            m->omsg.h.flags.b[1] |= kDNSFlag1_AD;
+            // Wait till we get called for the real response
+            LogInfo("ProxyClientCallback: Received %s, not answering yet", RRDisplayString(m, answer));
+            return;
         }
-        else
+        ptr = AddResourceRecords(pc, &prevptr, &error);
+        if (!ptr)
         {
-            // If some external resolver sets the AD bit and we did not validate the response securely, don't set
-            // the AD bit. It is possible that we did not see all the records that the upstream resolver saw or
-            // a buggy implementation somewhere.
-            if (m->omsg.h.flags.b[1] & kDNSFlag1_AD)
+            LogInfo("ProxyClientCallback: AddResourceRecords NULL for %##s (%s)", &pc->qname.c, DNSTypeName(pc->qtype));
+            if (error == mStatus_NoError && prevptr)
             {
-                LogInfo("ProxyClientCallback: AD bit set in the response for response that was not validated locally %##s (%s)",
-                    question->qname.c, DNSTypeName(question->qtype));
-                m->omsg.h.flags.b[1] &= ~kDNSFlag1_AD;
+                // No space to add the record. Set the Truncate bit for UDP.
+                //
+                // TBD: For TCP, we need to send the rest of the data. But finding out what is left
+                // is harder. We should allocate enough buffer in the first place to send all
+                // of the data.
+                if (!pc->tcp)
+                {
+                    m->omsg.h.flags.b[0] |= kDNSFlag0_TC;
+                    ptr = prevptr;
+                }
+                else
+                {
+                    LogInfo("ProxyClientCallback: ERROR!! Not enough space to return in TCP for %##s (%s)", &pc->qname.c, DNSTypeName(pc->qtype));
+                    ptr = prevptr;
+                }
+            }
+            else
+            {
+                mDNSOpaque16 flags   = { { kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery, kDNSFlag1_RC_ServFail } };
+                // We could not find the record for some reason. Return a response, so that the client
+                // is not waiting forever.
+                LogInfo("ProxyClientCallback: No response");
+                if (!mDNSOpaque16IsZero(pc->q.responseFlags))
+                    flags = pc->q.responseFlags;
+                InitializeDNSMessage(&m->omsg.h, pc->msgid, flags);
+                ptr = putQuestion(&m->omsg, m->omsg.data, m->omsg.data + AbsoluteMaxDNSMessageData, &pc->qname, pc->qtype, pc->q.qclass);
+                if (!ptr)
+                {
+                    LogInfo("ProxyClientCallback: putQuestion NULL for %##s (%s)", &pc->qname.c, DNSTypeName(pc->qtype));
+                    goto done;
+                }
             }
         }
     }
-    
     debugf("ProxyClientCallback: InterfaceID is %p for response to client", pc->interfaceID);
 
     if (!pc->tcp)
@@ -529,12 +599,12 @@ mDNSlocal DNSQuestion *IsDuplicateClient(const mDNSAddr *const addr, const mDNSI
 {
     DNSProxyClient *pc;
 
-       for (pc = DNSProxyClients; pc; pc = pc->next)
+    for (pc = DNSProxyClients; pc; pc = pc->next)
     {
         if (mDNSSameAddress(&pc->addr, addr)   &&
             mDNSSameIPPort(pc->port, port)  &&
             mDNSSameOpaque16(pc->msgid, id) &&
-            pc->q.qtype == question->qtype  &&
+            pc->qtype == question->qtype  &&
             pc->q.qclass  == question->qclass &&
             SameDomainName(&pc->qname, &question->qname))
         {
@@ -645,7 +715,7 @@ mDNSlocal void ProxyCallbackCommon(void *socket, DNSMessage *const msg, const mD
         }
         else
         {
-            optLen = ptr - optRR;
+            optLen = (int)(ptr - optRR);
             LogInfo("ProxyCallbackCommon: EDNS0 opt length %d present in Question %##s (%s)", optLen, q.qname.c, DNSTypeName(q.qtype));
         }
     }
@@ -701,27 +771,32 @@ mDNSlocal void ProxyCallbackCommon(void *socket, DNSMessage *const msg, const mD
     // Set ReturnIntermed so that we get the negative responses
     pc->q.ReturnIntermed  = mDNStrue;
     pc->q.ProxyQuestion   = mDNStrue;
-    pc->q.ProxyDNSSECOK   = pc->DNSSECOK;
     pc->q.responseFlags   = zeroID;
-    if (pc->DNSSECOK)
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+    pc->qtype = pc->q.qtype;
+    if (gDNS64Enabled)
     {
-        if (!(msg->h.flags.b[1] & kDNSFlag1_CD) && pc->q.qtype != kDNSType_RRSIG && pc->q.qtype != kDNSQType_ANY)
+        if (pc->qtype == kDNSType_PTR)
         {
-            LogInfo("ProxyCallbackCommon: Setting Validation required bit for %#a:%d, validating %##s (%s)", srcaddr, mDNSVal16(srcport),
-                q.qname.c, DNSTypeName(q.qtype));
-            pc->q.ValidationRequired = DNSSEC_VALIDATION_SECURE;
+            struct in6_addr v6Addr;
+            struct in_addr v4Addr;
+            if (GetReverseIPv6Addr(&pc->qname, v6Addr.s6_addr) && nw_nat64_extract_v4(&gDNS64Prefix, &v6Addr, &v4Addr))
+            {
+                const mDNSu8 *const a = (const mDNSu8 *)&v4Addr.s_addr;
+                char qnameStr[MAX_REVERSE_MAPPING_NAME_V4];
+                mDNS_snprintf(qnameStr, (mDNSu32)sizeof(qnameStr), "%u.%u.%u.%u.in-addr.arpa.", a[3], a[2], a[1], a[0]);
+                MakeDomainNameFromDNSNameString(&pc->q.qname, qnameStr);
+                pc->q.qnamehash = DomainNameHashValue(&pc->q.qname);
+                pc->dns64state = kDNSProxyDNS64State_PTRSynthesisTrying;
+            }
         }
-        else
+        else if ((pc->qtype == kDNSType_AAAA) && gDNS64ForceAAAASynthesis)
         {
-            LogInfo("ProxyCallbackCommon: CD bit not set OR not a valid type for %#a:%d, not validating %##s (%s)", srcaddr, mDNSVal16(srcport),
-                q.qname.c, DNSTypeName(q.qtype));
+            pc->dns64state = kDNSProxyDNS64State_AAAASynthesis;
+            pc->q.qtype    = kDNSType_A;
         }
     }
-    else
-    {
-        LogInfo("ProxyCallbackCommon: DNSSEC OK bit not set for %#a:%d, not validating %##s (%s)", srcaddr, mDNSVal16(srcport),
-                q.qname.c, DNSTypeName(q.qtype));
-    }
+#endif
 
     while (*ppc)
         ppc = &((*ppc)->next);
@@ -770,7 +845,12 @@ mDNSexport void ProxyTCPCallback(void *socket, DNSMessage *const msg, const mDNS
     ProxyCallbackCommon(socket, msg, end, srcaddr, srcport, dstaddr, dstport, InterfaceID, mDNStrue, context);
 }
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+mDNSexport void DNSProxyInit(mDNSu32 IpIfArr[MaxIp], mDNSu32 OpIf, const mDNSu8 IPv6Prefix[16], int IPv6PrefixBitLen,
+                             mDNSBool forceAAAASynthesis)
+#else
 mDNSexport void DNSProxyInit(mDNSu32 IpIfArr[MaxIp], mDNSu32 OpIf)
+#endif
 {
     mDNS *const m = &mDNSStorage;
     int i;
@@ -782,6 +862,65 @@ mDNSexport void DNSProxyInit(mDNSu32 IpIfArr[MaxIp], mDNSu32 OpIf)
 
     LogInfo("DNSProxyInit Storing interface list: Input [%d, %d, %d, %d, %d] Output [%d]", m->dp_ipintf[0],
             m->dp_ipintf[1], m->dp_ipintf[2], m->dp_ipintf[3], m->dp_ipintf[4], m->dp_opintf);
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+    if (IPv6Prefix)
+    {
+        mDNSu32 copyLen;
+        mDNSPlatformMemZero(&gDNS64Prefix, (mDNSu32)sizeof(gDNS64Prefix));
+        switch (IPv6PrefixBitLen)
+        {
+            case 32:
+                gDNS64Prefix.length = nw_nat64_prefix_length_32;
+                copyLen = 4;
+                break;
+
+            case 40:
+                gDNS64Prefix.length = nw_nat64_prefix_length_40;
+                copyLen = 5;
+                break;
+
+            case 48:
+                gDNS64Prefix.length = nw_nat64_prefix_length_48;
+                copyLen = 6;
+                break;
+
+            case 56:
+                gDNS64Prefix.length = nw_nat64_prefix_length_56;
+                copyLen = 7;
+                break;
+
+            case 64:
+                gDNS64Prefix.length = nw_nat64_prefix_length_64;
+                copyLen = 8;
+                break;
+
+            case 96:
+                gDNS64Prefix.length = nw_nat64_prefix_length_96;
+                copyLen = 12;
+                break;
+
+            default:
+                copyLen = 0;
+                break;
+        }
+        if (copyLen > 0)
+        {
+            mDNSPlatformMemCopy(gDNS64Prefix.data, IPv6Prefix, copyLen);
+            gDNS64ForceAAAASynthesis = forceAAAASynthesis;
+            gDNS64Enabled = mDNStrue;
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+                "DNSProxy using DNS64 IPv6 prefix: " PRI_IPv6_ADDR "/%d" PUB_S,
+                IPv6Prefix, IPv6PrefixBitLen, gDNS64ForceAAAASynthesis ? "" : " (force AAAA synthesis)");
+        }
+        else
+        {
+            gDNS64Enabled = mDNSfalse;
+            gDNS64ForceAAAASynthesis = mDNSfalse;
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+                "DNSProxy not using invalid DNS64 IPv6 prefix: " PRI_IPv6_ADDR "/%d", IPv6Prefix, IPv6PrefixBitLen);
+        }
+    }
+#endif
 }
 
 mDNSexport void DNSProxyTerminate(void)
@@ -796,6 +935,9 @@ mDNSexport void DNSProxyTerminate(void)
     
     LogInfo("DNSProxyTerminate Cleared interface list: Input [%d, %d, %d, %d, %d] Output [%d]", m->dp_ipintf[0],
             m->dp_ipintf[1], m->dp_ipintf[2], m->dp_ipintf[3], m->dp_ipintf[4], m->dp_opintf);
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+    gDNS64Enabled = mDNSfalse;
+#endif
 }
 #else // UNICAST_DISABLED
 
index 6889e73a2b1ff8fd6e9f04e8df10199ae03b1507..dcb7d55faf442c920aba353050c2f3d41bd6d3ab 100644 (file)
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2011-2013 Apple Inc. All rights reserved.
+ * Copyright (c) 2011-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -25,7 +25,12 @@ extern void ProxyUDPCallback(void *socket, DNSMessage *const msg, const mDNSu8 *
                              const mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID, void *context);
 extern void ProxyTCPCallback(void *socket, DNSMessage *const msg, const mDNSu8 *const end, const mDNSAddr *const srcaddr,
                              const mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID, void *context);                          
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+extern void DNSProxyInit(mDNSu32 IpIfArr[MaxIp], mDNSu32 OpIf, const mDNSu8 IPv6Prefix[16], int IPv6PrefixLen,
+                         mDNSBool alwaysSynthesize);
+#else
 extern void DNSProxyInit(mDNSu32 IpIfArr[MaxIp], mDNSu32 OpIf);
+#endif
 extern void DNSProxyTerminate(void);
 
 #endif // __DNS_PROXY_H
diff --git a/mDNSCore/dnssec.c b/mDNSCore/dnssec.c
deleted file mode 100644 (file)
index 0766582..0000000
+++ /dev/null
@@ -1,4087 +0,0 @@
-/* -*- Mode: C; tab-width: 4 -*-
- *
- * Copyright (c) 2011-2019 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "mDNSEmbeddedAPI.h"
-#include "DNSSECSupport.h"
-#include "DNSCommon.h"
-#include "dnssec.h"
-#include "CryptoAlg.h"
-#include "nsec.h"
-#include "nsec3.h"
-
-// Define DNSSEC_DISABLED to remove all the DNSSEC functionality
-// and use the stub functions implemented later in this file.
-
-#ifndef DNSSEC_DISABLED
-
-//#define DNSSEC_DEBUG
-
-#ifdef DNSSEC_DEBUG
-#define debugdnssec LogMsg
-#else
-#define debugdnssec debug_noop
-#endif
-//
-// Implementation Notes
-//
-// The entry point to DNSSEC Verification is VerifySignature. This function is called from the "core" when
-// the answer delivered to the application needs DNSSEC validation. If a question needs DNSSEC
-// validation, "ValidationRequired" would be set. As we need to issue more queries to validate the
-// original question, we create another question as part of the verification process (question is part of
-// DNSSECVerifier). This question sets "ValidatingResponse" to distinguish itself from the original
-// question. Without this, it will be a duplicate and never sent out. The "core" almost treats both the
-// types identically (like adding EDNS0 option with DO bit etc.) except for a few differences. When RRSIGs
-// are added to the cache, "ValidatingResponse" question gets called back as long as the typeCovered matches
-// the question's qtype. See the comment in DNSSECRecordAnswersQuestion for the details. The other big
-// difference is that "ValidationRequired" question kicks off the verification process by calling into
-// "VerifySignature" whereas ValidationResponse don't do that as it gets callback for its questions.
-//
-// VerifySignature does not retain the original question that started the verification process. It just
-// remembers the name and the type. It takes a snapshot of the cache at that instance which will be
-// verified using DNSSEC. If the cache changes subsequently e.g., network change etc., it will be detected
-// when the validation is completed. If there is a change, it will be revalidated.
-//
-// The verification flow looks like this:
-//
-// VerifySignature -> StartDNSSECVerification - GetAllRRSetsForVerification -> FinishDNSSECVerification -> VerifySignature
-//
-// Verification is a recursive process. It stops when we find a trust anchor or if we have recursed too deep.
-//
-// If the original question resulted in NODATA/NXDOMAIN error, there should have been NSECs as part of the response.
-// These nsecs are cached along with the negative cache record. These are validated using ValidateWithNSECS called
-// from Verifysignature.
-//
-// The flow in this case looks like this:
-//
-// VerifySignature -> ValidateWithNSECS -> {NoDataProof, NameErrorProof} -> VerifyNSECS -> StartDNSSECVerification
-//
-// Once the DNSSEC verification is started, it is similar to the previous flow described above. When the verification
-// is done, DNSSECPositiveValidationCB or DNSSECNegativeValidationCB will be called which will then deliver the
-// validation results to the original question that started the validation.
-//
-// Insecure proofs are done when the verification ends up bogus. The flow would look like this
-//
-// VerifySignature -> StartDNSSECVerification - GetAllRRSetsForVerification -> FinishDNSSECVerification -> DNSSECValidationCB
-// {DNSSECPositiveValidationCB, DNSSECNegativeValidationCB} -> ProveInsecure -> VerifySignaure ->
-//
-// ProveInsecure finds the break in trust in a top-down fashion.
-//
-// Forward declaration
-mDNSlocal void VerifySigCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord);
-mDNSlocal mStatus TrustedKey(mDNS *const m, DNSSECVerifier *dv);
-mDNSlocal mDNSBool TrustedKeyPresent(mDNS *const m, DNSSECVerifier *dv);
-mDNSlocal mStatus ValidateDS(DNSSECVerifier *dv);
-mDNSlocal void DNSSECNegativeValidationCB(mDNS *const m, DNSSECVerifier *dv, CacheGroup *cg, ResourceRecord *answer, DNSSECStatus status);
-mDNSlocal RRVerifier* CopyRRVerifier(RRVerifier *from);
-mDNSlocal void FreeDNSSECAuthChainInfo(AuthChain *ac);
-
-// Currently we use this to convert a RRVerifier to resource record so that we can
-// use the standard DNS utility functions
-LargeCacheRecord largerec;
-
-// Verification is a recursive process. We arbitrarily limit to 10 just to be cautious which should be
-// removed in the future.
-#define MAX_RECURSE_COUNT   10
-
-// TTL (in seconds) when the DNSSEC status is Bogus
-#define RR_BOGUS_TTL        60
-
-// RFC 4034 Appendix B: Get the keyid of a DNS KEY. It is not transmitted
-// explicitly on the wire.
-//
-// Note: This just helps narrow down the list of keys to look at. It is possible
-// for two DNS keys to have the same ID i.e., key ID is not a unqiue tag
-//
-// 1st argument - the RDATA part of the DNSKEY RR
-// 2nd argument - the RDLENGTH
-//
-mDNSlocal mDNSu32 keytag(mDNSu8 *key, mDNSu32 keysize)
-{
-    unsigned long ac;
-    unsigned int i;
-
-    // DST_ALG_RSAMD5 will be rejected automatically as the keytag
-    // is calculated wrongly
-
-    for (ac = 0, i = 0; i < keysize; ++i)
-        ac += (i & 1) ? key[i] : key[i] << 8;
-    ac += (ac >> 16) & 0xFFFF;
-    return ac & 0xFFFF;
-}
-
-mDNSexport int DNSMemCmp(const mDNSu8 *const m1, const mDNSu8 *const m2, int len)
-{
-    int res;
-
-    res = mDNSPlatformMemCmp(m1, m2, len);
-    if (res != 0)
-        return (res < 0 ? -1 : 1);
-    return 0;
-}
-
-// RFC 4034:
-//
-// Section 6.1:
-//
-// For the purposes of DNS security, owner names are ordered by treating
-// individual labels as unsigned left-justified octet strings.  The
-// absence of a octet sorts before a zero value octet, and uppercase
-// US-ASCII letters are treated as if they were lowercase US-ASCII
-// letters.
-//
-// To compute the canonical ordering of a set of DNS names, start by
-// sorting the names according to their most significant (rightmost)
-// labels.  For names in which the most significant label is identical,
-// continue sorting according to their next most significant label, and
-// so forth.
-//
-// Returns 0 if the names are same
-// Returns -1 if d1 < d2
-// Returns  1 if d1 > d2
-//
-// subdomain is set if there is at least one label match (starting from the end)
-// and d1 has more labels than d2 e.g., a.b.com is a subdomain of b.com
-//
-mDNSexport int DNSSECCanonicalOrder(const domainname *const d1, const domainname *const d2, int *subdomain)
-{
-    int count, c1, c2;
-    int i, skip1, skip2;
-
-    c1 = CountLabels(d1);
-    skip1 = c1 - 1;
-    c2 = CountLabels(d2);
-    skip2 = c2 - 1;
-
-    if (subdomain) *subdomain = 0;
-
-    // Compare as many labels as possible starting from the rightmost
-    count = c1 < c2 ? c1 : c2;
-    for (i = count; i > 0; i--)
-    {
-        mDNSu8 *a, *b;
-        int j, len, lena, lenb;
-
-        a = (mDNSu8 *)SkipLeadingLabels(d1, skip1);
-        b = (mDNSu8 *)SkipLeadingLabels(d2, skip2);
-        lena = *a;
-        lenb = *b;
-        // Compare label by label. Note that "z" > "yak" because z > y, but z < za
-        // (lena - lenb check below) because 'za' has two characters. Hence compare the
-        // letters first and then compare the length of the label at the end.
-        len = lena < lenb ? lena : lenb;
-        a++; b++;
-        for (j = 0; j < len; j++)
-        {
-            mDNSu8 ac = *a++;
-            mDNSu8 bc = *b++;
-            if (mDNSIsUpperCase(ac)) ac += 'a' - 'A';
-            if (mDNSIsUpperCase(bc)) bc += 'a' - 'A';
-            if (ac != bc)
-            {
-                verbosedebugf("DNSSECCanonicalOrder: returning ac %c, bc %c", ac, bc);
-                return ((ac < bc) ? -1 : 1);
-            }
-        }
-        if ((lena - lenb) != 0)
-        {
-            verbosedebugf("DNSSECCanonicalOrder: returning lena %d lenb %d", lena, lenb);
-            return ((lena < lenb) ? -1 : 1);
-        }
-
-        // Continue with the next label
-        skip1--;
-        skip2--;
-    }
-    // We have compared label by label. Both of them are same if we are here.
-    //
-    // Two possibilities.
-    //
-    // 1) Both names have same number of labels. In that case, return zero.
-    // 2) The number of labels is not same. As zero label sorts before, names
-    //    with more number of labels is greater.
-
-    // a.b.com is a subdomain of b.com
-    if ((c1 > c2) && subdomain)
-        *subdomain = 1;
-
-    verbosedebugf("DNSSECCanonicalOrder: returning c1 %d c2 %d\n", c1, c2);
-    if (c1 != c2)
-        return ((c1 < c2) ? -1 : 1);
-    else
-        return 0;
-}
-
-// Initialize the question enough so that it can be answered from the cache using SameNameCacheRecordAnswersQuestion or
-// ResourceRecordAnswersQuestion.
-mDNSexport void InitializeQuestion(mDNS *const m, DNSQuestion *question, mDNSInterfaceID InterfaceID, const domainname *qname,
-                                   mDNSu16 qtype, mDNSQuestionCallback *callback, void *context)
-{
-    debugf("InitializeQuestion: Called for %##s (%s)", qname->c, DNSTypeName(qtype));
-
-    if (question->ThisQInterval != -1) mDNS_StopQuery(m, question);
-
-    mDNS_SetupQuestion(question, InterfaceID, qname, qtype, callback, context);
-    question->qnamehash  = DomainNameHashValue(qname);
-    question->ValidatingResponse = mDNStrue;
-
-    // Need to hold the lock, as GetServerForQuestion (its callers) references m->timenow.
-    mDNS_Lock(m);
-    // We need to set the DNS server appropriately to match the question against the cache record.
-    // Though not all callers of this function need it, we always do it to keep it simple.
-    SetValidDNSServers(m, question);
-    question->qDNSServer = GetServerForQuestion(m, question);
-    mDNS_Unlock(m);
-
-    // Make it look like unicast
-    question->TargetQID = onesID;
-    question->TimeoutQuestion = 1;
-    question->ReturnIntermed = 1;
-    // SetupQuestion sets LongLived if qtype == PTR
-    question->LongLived = 0;
-}
-
-mDNSexport DNSSECVerifier *AllocateDNSSECVerifier(mDNS *const m, const domainname *name, mDNSu16 rrtype, mDNSInterfaceID InterfaceID,
-    mDNSu8 ValidationRequired, DNSSECVerifierCallback dvcallback, mDNSQuestionCallback qcallback)
-{
-    DNSSECVerifier *dv;
-
-    dv = (DNSSECVerifier *) mDNSPlatformMemAllocateClear(sizeof(*dv));
-    if (!dv) { LogMsg("AllocateDNSSECVerifier: ERROR!! memory alloc failed"); return mDNSNULL; }
-
-    LogDNSSEC("AllocateDNSSECVerifier called %p", dv);
-
-    // Remember the question's name and type so that when we are done processing all
-    // the verifications, we can trace the original question back
-    AssignDomainName(&dv->origName, name);
-    dv->origType = rrtype;
-    dv->InterfaceID = InterfaceID;
-    dv->DVCallback = dvcallback;
-    dv->q.ThisQInterval = -1;
-    ResetAuthChain(dv);
-    // These two are used for Insecure proof if we end up doing it.
-    // -Value of ValidationRequired so that we know whether this is a secure or insecure validation
-    // -InsecureProofDone tells us whether the proof has been done or not
-    dv->ValidationRequired = ValidationRequired;
-    dv->InsecureProofDone = 0;
-    dv->NumPackets = 0;
-    mDNS_Lock(m);
-    dv->StartTime = m->timenow;
-    mDNS_Unlock(m);
-    // The verifier's question has to be initialized as some of the callers assume it
-    InitializeQuestion(m, &dv->q, InterfaceID, name, rrtype, qcallback, dv);
-    return dv;
-}
-
-mDNSlocal AuthChain *AuthChainCopy(AuthChain *ae)
-{
-    RRVerifier *rvfrom, **rvto;
-    AuthChain **prev = mDNSNULL;
-    AuthChain *retac = mDNSNULL;
-    AuthChain *ac;
-
-
-    while (ae)
-    {
-        ac = (AuthChain *) mDNSPlatformMemAllocateClear(sizeof(*ac));
-        if (!ac)
-        {
-            LogMsg("AuthChainCopy: AuthChain alloc failure");
-            if (retac)
-                FreeDNSSECAuthChainInfo(retac);
-            return mDNSNULL;
-        }
-
-        ac->next  = mDNSNULL;
-
-        if (!retac)
-            retac = ac;
-
-        rvfrom = ae->rrset;
-        rvto = &ac->rrset;
-        while (rvfrom && rvto)
-        {
-            *rvto = CopyRRVerifier(rvfrom);
-            rvfrom = rvfrom->next;
-            rvto = &((*rvto)->next);
-        }
-
-        rvfrom = ae->rrsig;
-        rvto = &ac->rrsig;
-        while (rvfrom && rvto)
-        {
-            *rvto = CopyRRVerifier(rvfrom);
-            rvfrom = rvfrom->next;
-            rvto = &((*rvto)->next);
-        }
-
-        rvfrom = ae->key;
-        rvto = &ac->key;
-        while (rvfrom && rvto)
-        {
-            *rvto = CopyRRVerifier(rvfrom);
-            rvfrom = rvfrom->next;
-            rvto = &((*rvto)->next);
-        }
-
-        if (prev)
-        {
-            *prev = ac;
-        }
-        prev = &(ac->next);
-        ae = ae->next;
-    }
-    return retac;
-}
-
-mDNSlocal void FreeDNSSECAuthChainInfo(AuthChain *ac)
-{
-    RRVerifier *rrset;
-    RRVerifier *next;
-    AuthChain *acnext;
-
-    LogDNSSEC("FreeDNSSECAuthChainInfo: called");
-
-    while (ac)
-    {
-        acnext = ac->next;
-        rrset = ac->rrset;
-        while (rrset)
-        {
-            next = rrset->next;
-            mDNSPlatformMemFree(rrset);
-            rrset = next;
-        }
-        ac->rrset = mDNSNULL;
-
-        rrset = ac->rrsig;
-        while (rrset)
-        {
-            next = rrset->next;
-            mDNSPlatformMemFree(rrset);
-            rrset = next;
-        }
-        ac->rrsig = mDNSNULL;
-
-        rrset = ac->key;
-        while (rrset)
-        {
-            next = rrset->next;
-            mDNSPlatformMemFree(rrset);
-            rrset = next;
-        }
-        ac->key = mDNSNULL;
-
-        mDNSPlatformMemFree(ac);
-        ac = acnext;
-    }
-}
-
-mDNSlocal void FreeDNSSECAuthChain(DNSSECVerifier *dv)
-{
-    if (dv->ac)
-    {
-        FreeDNSSECAuthChainInfo(dv->ac);
-        // if someone reuses the "dv", it will be initialized properly
-        ResetAuthChain(dv);
-    }
-    if (dv->saveac)
-    {
-        FreeDNSSECAuthChainInfo(dv->saveac);
-        dv->saveac = mDNSNULL;
-    }
-}
-
-mDNSlocal void FreeAuthChain(mDNS *const m, void *context)
-{
-    AuthChain *ac = (AuthChain *)context;
-    (void) m; // unused
-
-    FreeDNSSECAuthChainInfo(ac);
-}
-
-mDNSlocal void FreeDNSSECVerifierRRSets(DNSSECVerifier *dv)
-{
-    RRVerifier *rrset;
-    RRVerifier *next;
-
-    //debugdnssec("FreeDNSSECVerifierRRSets called %p", dv);
-    rrset = dv->rrset;
-    while (rrset)
-    {
-        next = rrset->next;
-        mDNSPlatformMemFree(rrset);
-        rrset = next;
-    }
-    dv->rrset = mDNSNULL;
-
-    rrset = dv->rrsig;
-    while (rrset)
-    {
-        next = rrset->next;
-        mDNSPlatformMemFree(rrset);
-        rrset = next;
-    }
-    dv->rrsig = mDNSNULL;
-
-    rrset = dv->key;
-    while (rrset)
-    {
-        next = rrset->next;
-        mDNSPlatformMemFree(rrset);
-        rrset = next;
-    }
-    dv->key = mDNSNULL;
-
-    rrset = dv->rrsigKey;
-    while (rrset)
-    {
-        next = rrset->next;
-        mDNSPlatformMemFree(rrset);
-        rrset = next;
-    }
-    dv->rrsigKey = mDNSNULL;
-
-    rrset = dv->ds;
-    while (rrset)
-    {
-        next = rrset->next;
-        mDNSPlatformMemFree(rrset);
-        rrset = next;
-    }
-    dv->ds = mDNSNULL;
-    rrset = dv->pendingNSEC;
-    while (rrset)
-    {
-        next = rrset->next;
-        mDNSPlatformMemFree(rrset);
-        rrset = next;
-    }
-    dv->pendingNSEC = mDNSNULL;
-}
-
-mDNSexport void FreeDNSSECVerifier(mDNS *const m, DNSSECVerifier *dv)
-{
-    LogDNSSEC("FreeDNSSECVerifier called %p", dv);
-    if (dv->q.ThisQInterval != -1)
-        mDNS_StopQuery(m, &dv->q);
-    FreeDNSSECVerifierRRSets(dv);
-    if (dv->ctx)
-        AlgDestroy(dv->ctx);
-    if (dv->ac || dv->saveac)
-        FreeDNSSECAuthChain(dv);
-    if (dv->parent)
-    {
-        LogDNSSEC("FreeDNSSECVerifier freeing parent %p", dv->parent);
-        FreeDNSSECVerifier(m, dv->parent);
-    }
-    mDNSPlatformMemFree(dv);
-}
-
-mDNSlocal RRVerifier* CopyRRVerifier(RRVerifier *from)
-{
-    RRVerifier *r;
-
-    r = (RRVerifier *) mDNSPlatformMemAllocate(sizeof(*r) + from->rdlength);
-    if (!r)
-    {
-        LogMsg("CopyRRVerifier: memory failure");
-        return mDNSNULL;
-    }
-    mDNSPlatformMemCopy(r, from, sizeof(RRVerifier));
-    r->next = mDNSNULL;
-    r->rdata = (mDNSu8*) ((mDNSu8 *)r + sizeof(RRVerifier));
-    mDNSPlatformMemCopy(r->rdata, from->rdata, r->rdlength);
-    return r;
-}
-
-mDNSexport RRVerifier* AllocateRRVerifier(const ResourceRecord *const rr, mStatus *status)
-{
-    RRVerifier *r;
-
-    r = (RRVerifier *) mDNSPlatformMemAllocateClear(sizeof(*r) + rr->rdlength);
-    if (!r)
-    {
-        LogMsg("AllocateRRVerifier: memory failure");
-        *status = mStatus_NoMemoryErr;
-        return mDNSNULL;
-    }
-    r->next = mDNSNULL;
-    r->rrtype = rr->rrtype;
-    r->rrclass = rr->rrclass;
-    r->rroriginalttl = rr->rroriginalttl;
-    r->rdlength = rr->rdlength;
-    r->namehash = rr->namehash;
-    r->rdatahash = rr->rdatahash;
-    AssignDomainName(&r->name, rr->name);
-    r->rdata = (mDNSu8*) ((mDNSu8 *)r + sizeof(RRVerifier));
-
-    // When we parsed the DNS response in GeLargeResourceRecord, for some records, we parse them into
-    // host order so that the rest of the code does not have to bother with converting from network order
-    // to host order. For signature verification, we need them back in network order. For DNSSEC records
-    // like DNSKEY and DS, we just copy over the data both in GetLargeResourceRecord and putRData.
-
-    if (!putRData(mDNSNULL, r->rdata, r->rdata + rr->rdlength, rr))
-    {
-        LogMsg("AllocateRRVerifier: putRData failed");
-        *status = mStatus_BadParamErr;
-        return mDNSNULL;
-    }
-    *status = mStatus_NoError;
-    return r;
-}
-
-mDNSexport mStatus AddRRSetToVerifier(DNSSECVerifier *dv, const ResourceRecord *const rr, RRVerifier *rv, RRVerifierSet set)
-{
-    RRVerifier *r;
-    RRVerifier **v;
-    mStatus status;
-
-    if (!rv)
-    {
-        r = AllocateRRVerifier(rr, &status);
-        if (!r) return status;
-    }
-    else
-        r = rv;
-
-    switch (set)
-    {
-    case RRVS_rr:
-        v = &dv->rrset;
-        break;
-    case RRVS_rrsig:
-        v = &dv->rrsig;
-        break;
-    case RRVS_key:
-        v = &dv->key;
-        break;
-    case RRVS_rrsig_key:
-        v = &dv->rrsigKey;
-        break;
-    case RRVS_ds:
-        v = &dv->ds;
-        break;
-    default:
-        LogMsg("AddRRSetToVerifier: ERROR!! default case %d", set);
-        return mStatus_BadParamErr;
-    }
-    while (*v)
-        v = &(*v)->next;
-    *v = r;
-    return mStatus_NoError;
-}
-
-// Validate the RRSIG. "type" tells which RRSIG that we are supposed to validate. We fetch RRSIG for
-// the rrset (type is RRVS_rrsig) and RRSIG for the key (type is RRVS_rrsig_key).
-mDNSexport void ValidateRRSIG(DNSSECVerifier *dv, RRVerifierSet type, const ResourceRecord *const rr)
-{
-    RRVerifier *rv;
-    mDNSu32 currentTime;
-    rdataRRSig *rrsigRData = (rdataRRSig *)((mDNSu8 *)rr->rdata + sizeofRDataHeader);
-
-    if (type == RRVS_rrsig)
-    {
-        rv = dv->rrset;
-    }
-    else if (type == RRVS_rrsig_key)
-    {
-        rv = dv->key;
-    }
-    else
-    {
-        LogMsg("ValidateRRSIG: ERROR!! type not valid %d", type);
-        return;
-    }
-
-    // RFC 4035:
-    // For each authoritative RRset in a signed zone, there MUST be at least
-    // one RRSIG record that meets the following requirements:
-    //
-    // RRSet is defined by same name, class and type
-    //
-    // 1. The RRSIG RR and the RRset MUST have the same owner name and the same class.
-    if (!SameDomainName(&rv->name, rr->name) || (rr->rrclass != rv->rrclass))
-    {
-        debugdnssec("ValidateRRSIG: name mismatch or class mismatch");
-        return;
-    }
-
-    // 2. The RRSIG RR's Type Covered field MUST equal the RRset's type.
-    if ((swap16(rrsigRData->typeCovered)) != rv->rrtype)
-    {
-        debugdnssec("ValidateRRSIG: typeCovered mismatch rrsig %d, rr type %d", swap16(rrsigRData->typeCovered), rv->rrtype);
-        return;
-    }
-
-    // 3. The number of labels in the RRset owner name MUST be greater than or equal
-    //    to the value in the RRSIG RR's Labels field.
-    if (rrsigRData->labels > CountLabels(&rv->name))
-    {
-        debugdnssec("ValidateRRSIG: labels count problem rrsig %d, rr %d", rrsigRData->labels, CountLabels(&rv->name));
-        return;
-    }
-
-    // 4. The RRSIG RR's Signer's Name field MUST be the name of the zone that contains
-    //    the RRset. For a stub resolver, this can't be done in a secure way. Hence we
-    //    do it this way (discussed in dnsext mailing list)
-    switch (rv->rrtype)
-    {
-    case kDNSType_NS:
-    case kDNSType_SOA:
-    case kDNSType_DNSKEY:
-        //Signed by the owner
-        if (!SameDomainName(&rv->name, (domainname *)&rrsigRData->signerName))
-        {
-            debugdnssec("ValidateRRSIG: Signer Name does not match the record name for %s", DNSTypeName(rv->rrtype));
-            return;
-        }
-        break;
-    case kDNSType_DS:
-        // Should be signed by the parent
-        if (SameDomainName(&rv->name, (domainname *)&rrsigRData->signerName))
-        {
-            debugdnssec("ValidateRRSIG: Signer Name matches the record name for %s", DNSTypeName(rv->rrtype));
-            return;
-        }
-    // FALLTHROUGH
-    default:
-    {
-        int c1 = CountLabels(&rv->name);
-        int c2 = CountLabels((domainname *)&rrsigRData->signerName);
-        if (c1 < c2)
-        {
-            debugdnssec("ValidateRRSIG: Signer Name not a subdomain label count %d < %d ", c1, c2);
-            return;
-        }
-        domainname *d = (domainname *)SkipLeadingLabels(&rv->name, c1 - c2);
-        if (!SameDomainName(d, (domainname *)&rrsigRData->signerName))
-        {
-            debugdnssec("ValidateRRSIG: Signer Name not a subdomain");
-            return;
-        }
-        break;
-    }
-    }
-
-    // 5. The validator's notion of the current time MUST be less than or equal to the
-    //    time listed in the RRSIG RR's Expiration field.
-    //
-    // 6. The validator's notion of the current time MUST be greater than or equal to the
-    //    time listed in the RRSIG RR's Inception field.
-    currentTime = mDNSPlatformUTC();
-
-    if (DNS_SERIAL_LT(swap32(rrsigRData->sigExpireTime), currentTime))
-    {
-        LogDNSSEC("ValidateRRSIG: Expired: currentTime %d, ExpireTime %d", (int)currentTime,
-                  swap32((int)rrsigRData->sigExpireTime));
-        return;
-    }
-    if (DNS_SERIAL_LT(currentTime, swap32(rrsigRData->sigInceptTime)))
-    {
-        LogDNSSEC("ValidateRRSIG: Future: currentTime %d, InceptTime %d", (int)currentTime,
-                  swap32((int)rrsigRData->sigInceptTime));
-        return;
-    }
-
-    if (AddRRSetToVerifier(dv, rr, mDNSNULL, type) != mStatus_NoError)
-    {
-        LogMsg("ValidateRRSIG: ERROR!! cannot allocate RRSet");
-        return;
-    }
-}
-
-mDNSlocal mStatus CheckRRSIGForRRSet(mDNS *const m, DNSSECVerifier *dv, CacheRecord **negcr)
-{
-    CacheGroup *cg;
-    CacheRecord *cr;
-    RRVerifier *rv;
-    mDNSBool expectRRSIG = mDNSfalse;
-
-    *negcr = mDNSNULL;
-    if (!dv->rrset)
-    {
-        LogMsg("CheckRRSIGForRRSet: ERROR!! rrset NULL for origName %##s (%s)", dv->origName.c,
-               DNSTypeName(dv->origType));
-        return mStatus_BadParamErr;
-    }
-
-    rv = dv->rrset;
-    cg = CacheGroupForName(m, rv->namehash, &rv->name);
-    if (!cg)
-    {
-        debugdnssec("CheckRRSIGForRRSet: cg null");
-        return mStatus_NoSuchRecord;
-    }
-
-    for (cr=cg->members; cr; cr=cr->next)
-    {
-        debugdnssec("CheckRRSIGForRRSet: checking the validity of rrsig");
-        if (cr->resrec.rrtype != kDNSType_RRSIG)
-        {
-            // Check to see if we should expect RRSIGs for the type that we are looking for.
-            // We would expect RRSIGs, if we had previously issued the question with the
-            // EDNS0/DOK bit set.
-            if (cr->resrec.rrtype == dv->rrset->rrtype)
-            {
-                expectRRSIG = cr->CRDNSSECQuestion;
-                LogDNSSEC("CheckRRSIGForRRSet: %s RRSIG for %s", (expectRRSIG ? "Expecting" : "Not Expecting"), CRDisplayString(m, cr));
-            }
-            continue;
-        }
-        if (cr->resrec.RecordType == kDNSRecordTypePacketNegative)
-        {
-            if (!(*negcr))
-            {
-                LogDNSSEC("CheckRRSIGForRRSet: Negative cache record %s encountered for %##s (%s)", CRDisplayString(m, cr),
-                          rv->name.c, DNSTypeName(rv->rrtype));
-                *negcr = cr;
-            }
-            else
-            {
-                LogMsg("CheckRRSIGForRRSet: ERROR!! Negative cache record %s already set for %##s (%s)", CRDisplayString(m, cr),
-                       rv->name.c, DNSTypeName(rv->rrtype));
-            }
-            continue;
-        }
-        ValidateRRSIG(dv, RRVS_rrsig, &cr->resrec);
-    }
-    if (*negcr && dv->rrsig)
-    {
-        // Encountered both RRSIG and negative CR
-        LogMsg("CheckRRSIGForRRSet: ERROR!! Encountered negative cache record %s and RRSIG for %##s (%s)",
-               CRDisplayString(m, *negcr), rv->name.c, DNSTypeName(rv->rrtype));
-        return mStatus_BadParamErr;
-    }
-    // If we can't find RRSIGs, but we find a negative response then we need to validate that
-    // which the caller will do it. Otherwise, if we should be expecting RRSIGs to be in the
-    // cache already, then return error.
-    if (dv->rrsig || *negcr)
-        return mStatus_NoError;
-    else if (expectRRSIG)
-        return mStatus_BadParamErr;
-    else
-        return mStatus_NoSuchRecord;
-}
-
-mDNSlocal void CheckOneKeyForRRSIG(DNSSECVerifier *dv, const ResourceRecord *const rr)
-{
-    rdataRRSig *rrsig;
-
-    if (!dv->rrsig)
-    {
-        LogMsg("CheckOneKeyForRRSIG: ERROR!! rrsig NULL");
-        return;
-    }
-    rrsig = (rdataRRSig *)dv->rrsig->rdata;
-    if (!SameDomainName((domainname *)&rrsig->signerName, rr->name))
-    {
-        debugdnssec("CheckOneKeyForRRSIG: name mismatch");
-        return;
-    }
-
-    // We store all the keys including the ZSK and KSK and use them appropriately
-    // later
-    if (AddRRSetToVerifier(dv, rr, mDNSNULL, RRVS_key) != mStatus_NoError)
-    {
-        LogMsg("CheckOneKeyForRRSIG: ERROR!! cannot allocate RRSet");
-        return;
-    }
-}
-
-mDNSlocal mStatus CheckKeyForRRSIG(mDNS *const m, DNSSECVerifier *dv, CacheRecord **negcr)
-{
-    mDNSu32 namehash;
-    CacheGroup *cg;
-    CacheRecord *cr;
-    rdataRRSig *rrsig;
-    domainname *name;
-
-    *negcr = mDNSNULL;
-    if (!dv->rrsig)
-    {
-        LogMsg("CheckKeyForRRSIG: ERROR!! rrsig NULL");
-        return mStatus_BadParamErr;
-    }
-
-    // Signer name should be the same on all rrsig ??
-    rrsig = (rdataRRSig *)dv->rrsig->rdata;
-    name = (domainname *)&rrsig->signerName;
-
-    namehash = DomainNameHashValue(name);
-    cg = CacheGroupForName(m, namehash, name);
-    if (!cg)
-    {
-        debugdnssec("CheckKeyForRRSIG: cg null for %##s", name->c);
-        return mStatus_NoSuchRecord;
-    }
-
-    for (cr=cg->members; cr; cr=cr->next)
-    {
-        if (cr->resrec.rrtype != kDNSType_DNSKEY) continue;
-        if (cr->resrec.RecordType == kDNSRecordTypePacketNegative)
-        {
-            if (!(*negcr))
-            {
-                LogDNSSEC("CheckKeyForRRSIG: Negative cache record %s encountered for %##s (DNSKEY)", CRDisplayString(m, cr),
-                          name->c);
-                *negcr = cr;
-            }
-            else
-            {
-                LogMsg("CheckKeyForRRSIG: ERROR!! Negative cache record %s already set for %##s (DNSKEY)", CRDisplayString(m, cr),
-                       name->c);
-            }
-            continue;
-        }
-        debugdnssec("CheckKeyForRRSIG: checking the validity of key record");
-        CheckOneKeyForRRSIG(dv, &cr->resrec);
-    }
-    if (*negcr && dv->key)
-    {
-        // Encountered both RRSIG and negative CR
-        LogMsg("CheckKeyForRRSIG: ERROR!! Encountered negative cache record %s and DNSKEY for %##s",
-               CRDisplayString(m, *negcr), name->c);
-        return mStatus_BadParamErr;
-    }
-    if (dv->key || *negcr)
-        return mStatus_NoError;
-    else
-        return mStatus_NoSuchRecord;
-}
-
-mDNSlocal void CheckOneRRSIGForKey(DNSSECVerifier *dv, const ResourceRecord *const rr)
-{
-    rdataRRSig *rrsig;
-    if (!dv->rrsig)
-    {
-        LogMsg("CheckOneRRSIGForKey: ERROR!! rrsig NULL");
-        return;
-    }
-    rrsig = (rdataRRSig *)dv->rrsig->rdata;
-    if (!SameDomainName((domainname *)&rrsig->signerName, rr->name))
-    {
-        debugdnssec("CheckOneRRSIGForKey: name mismatch");
-        return;
-    }
-    ValidateRRSIG(dv, RRVS_rrsig_key, rr);
-}
-
-mDNSlocal mStatus CheckRRSIGForKey(mDNS *const m, DNSSECVerifier *dv, CacheRecord **negcr)
-{
-    mDNSu32 namehash;
-    CacheGroup *cg;
-    CacheRecord *cr;
-    rdataRRSig *rrsig;
-    domainname *name;
-    mDNSBool expectRRSIG = mDNSfalse;
-
-    *negcr = mDNSNULL;
-    if (!dv->rrsig)
-    {
-        LogMsg("CheckRRSIGForKey: ERROR!! rrsig NULL");
-        return mStatus_BadParamErr;
-    }
-    if (!dv->key)
-    {
-        LogMsg("CheckRRSIGForKey:  ERROR!! key NULL");
-        return mStatus_BadParamErr;
-    }
-    rrsig = (rdataRRSig *)dv->rrsig->rdata;
-    name = (domainname *)&rrsig->signerName;
-
-    namehash = DomainNameHashValue(name);
-    cg = CacheGroupForName(m, namehash, name);
-    if (!cg)
-    {
-        debugdnssec("CheckRRSIGForKey: cg null %##s", name->c);
-        return mStatus_NoSuchRecord;
-    }
-    for (cr=cg->members; cr; cr=cr->next)
-    {
-        if (cr->resrec.rrtype != kDNSType_RRSIG)
-        {
-            // Check to see if we should expect RRSIGs for the DNSKEY record that we are
-            // looking for. We would expect RRSIGs, if we had previously issued the question
-            // with the EDNS0/DOK bit set.
-            if (cr->resrec.rrtype == kDNSType_DNSKEY)
-            {
-                expectRRSIG = cr->CRDNSSECQuestion;
-                LogDNSSEC("CheckRRSIGForKey: %s RRSIG for %s", (expectRRSIG ? "Expecting" : "Not Expecting"), CRDisplayString(m, cr));
-            }
-            continue;
-        }
-        if (cr->resrec.RecordType == kDNSRecordTypePacketNegative)
-        {
-            if (!(*negcr))
-            {
-                LogDNSSEC("CheckRRSIGForKey: Negative cache record %s encountered for %##s (RRSIG)", CRDisplayString(m, cr),
-                          name->c);
-                *negcr = cr;
-            }
-            else
-            {
-                LogMsg("CheckRRSIGForKey: ERROR!! Negative cache record %s already set for %##s (RRSIG)", CRDisplayString(m, cr),
-                       name->c);
-            }
-            continue;
-        }
-        debugdnssec("CheckRRSIGForKey: checking the validity of rrsig");
-        CheckOneRRSIGForKey(dv, &cr->resrec);
-    }
-    if (*negcr && dv->rrsigKey)
-    {
-        // Encountered both RRSIG and negative CR
-        LogMsg("CheckRRSIGForKey: ERROR!! Encountered negative cache record %s and DNSKEY for %##s",
-               CRDisplayString(m, *negcr), name->c);
-        return mStatus_BadParamErr;
-    }
-    // If we can't find RRSIGs, but we find a negative response then we need to validate that
-    // which the caller will do it. Finally, make sure that we are not expecting RRSIGS.
-    if (dv->rrsigKey || *negcr)
-        return mStatus_NoError;
-    else if (expectRRSIG)
-        return mStatus_BadParamErr;
-    else
-        return mStatus_NoSuchRecord;
-}
-
-mDNSlocal void CheckOneDSForKey(DNSSECVerifier *dv, const ResourceRecord *const rr)
-{
-    mDNSu16 tag;
-    rdataDS *DS;
-    RRVerifier *keyv;
-    rdataDNSKey *key;
-    rdataRRSig *rrsig;
-
-    if (!dv->rrsig)
-    {
-        LogMsg("CheckOneDSForKey: ERROR!! rrsig NULL");
-        return;
-    }
-    rrsig = (rdataRRSig *)dv->rrsig->rdata;
-    DS = (rdataDS *)((mDNSu8 *)rr->rdata + sizeofRDataHeader);
-
-    if (!SameDomainName((domainname *)&rrsig->signerName, rr->name))
-    {
-        debugdnssec("CheckOneDSForKey: name mismatch");
-        return;
-    }
-    for (keyv = dv->key; keyv; keyv = keyv->next)
-    {
-        key = (rdataDNSKey *)keyv->rdata;
-        tag = (mDNSu16)keytag((mDNSu8 *)key, keyv->rdlength);
-        if (tag != swap16(DS->keyTag))
-        {
-            debugdnssec("CheckOneDSForKey: keyTag mismatch keyTag %d, DStag %d", tag, swap16(DS->keyTag));
-            continue;
-        }
-        if (key->alg != DS->alg)
-        {
-            debugdnssec("CheckOneDSForKey: alg mismatch key alg%d, DS alg %d", key->alg, swap16(DS->alg));
-            continue;
-        }
-        if (AddRRSetToVerifier(dv, rr, mDNSNULL, RRVS_ds) != mStatus_NoError)
-        {
-            debugdnssec("CheckOneDSForKey: cannot allocate RRSet");
-        }
-    }
-}
-
-mDNSlocal mStatus CheckDSForKey(mDNS *const m, DNSSECVerifier *dv, CacheRecord **negcr)
-{
-    mDNSu32 namehash;
-    CacheGroup *cg;
-    CacheRecord *cr;
-    rdataRRSig *rrsig;
-    domainname *name;
-
-    *negcr = mDNSNULL;
-    if (!dv->rrsig)
-    {
-        LogMsg("CheckDSForKey: ERROR!! rrsig NULL");
-        return mStatus_BadParamErr;
-    }
-    if (!dv->key)
-    {
-        LogMsg("CheckDSForKey: ERROR!! key NULL");
-        return mStatus_BadParamErr;
-    }
-    rrsig = (rdataRRSig *)dv->rrsig->rdata;
-    name = (domainname *)&rrsig->signerName;
-    namehash = DomainNameHashValue(name);
-    cg = CacheGroupForName(m, namehash, name);
-    if (!cg)
-    {
-        debugdnssec("CheckDSForKey: cg null for %s", name->c);
-        return mStatus_NoSuchRecord;
-    }
-    for (cr=cg->members; cr; cr=cr->next)
-    {
-        if (cr->resrec.rrtype != kDNSType_DS) continue;
-        if (cr->resrec.RecordType == kDNSRecordTypePacketNegative)
-        {
-            if (!(*negcr))
-            {
-                LogDNSSEC("CheckDSForKey: Negative cache record %s encountered for %##s (DS)", CRDisplayString(m, cr),
-                          name->c);
-                *negcr = cr;
-            }
-            else
-            {
-                LogMsg("CheckDSForKey: ERROR!! Negative cache record %s already set for %##s (DS)", CRDisplayString(m, cr),
-                       name->c);
-            }
-            continue;
-        }
-        CheckOneDSForKey(dv, &cr->resrec);
-    }
-    if (*negcr && dv->ds)
-    {
-        // Encountered both RRSIG and negative CR
-        LogMsg("CheckDSForKey: ERROR!! Encountered negative cache record %s and DS for %##s",
-               CRDisplayString(m, *negcr), name->c);
-        return mStatus_BadParamErr;
-    }
-    if (dv->ds || *negcr)
-        return mStatus_NoError;
-    else
-        return mStatus_NoSuchRecord;
-}
-
-// It returns mDNStrue if we have all the rrsets for verification and mDNSfalse otherwise.
-mDNSlocal mDNSBool GetAllRRSetsForVerification(mDNS *const m, DNSSECVerifier *dv)
-{
-    mStatus err;
-    CacheRecord *negcr;
-    rdataRRSig *rrsig;
-
-    if (!dv->rrset)
-    {
-        LogMsg("GetAllRRSetsForVerification: ERROR!! rrset NULL");
-        dv->DVCallback(m, dv, DNSSEC_Bogus);
-        return mDNSfalse;
-    }
-
-    if (dv->next == RRVS_done) return mDNStrue;
-
-    debugdnssec("GetAllRRSetsForVerification: next %d", dv->next);
-    switch (dv->next)
-    {
-    case RRVS_rrsig:
-        // If we can't find the RRSIG for the rrset, re-issue the query.
-        //
-        // NOTE: It is possible that the cache might answer partially e.g., RRSIGs match qtype but the
-        // whole set is not there. In that case the validation will fail. Ideally we should flush the
-        // cache and reissue the query (TBD).
-        err = CheckRRSIGForRRSet(m, dv, &negcr);
-        if (err != mStatus_NoSuchRecord && err != mStatus_NoError)
-        {
-            dv->DVCallback(m, dv, DNSSEC_Bogus);
-            return mDNSfalse;
-        }
-        // Need to initialize the question as if we end up in ValidateWithNSECS below, the nsec proofs
-        // looks in "dv->q" for the proof. Note that we have to use currQtype as the response could be
-        // a CNAME and dv->rrset->rrtype would be set to CNAME and not the original question type that
-        // resulted in CNAME.
-        InitializeQuestion(m, &dv->q, dv->InterfaceID, &dv->rrset->name, dv->currQtype, VerifySigCallback, dv);
-        // We may not have the NSECS if the previous query was a non-DNSSEC query
-        if (negcr && negcr->nsec)
-        {
-            ValidateWithNSECS(m, dv, negcr);
-            return mDNSfalse;
-        }
-
-        dv->next = RRVS_key;
-        if (!dv->rrsig)
-        {
-            // We already found the rrset to verify. Ideally we should just issue the query for the RRSIG. Unfortunately,
-            // that does not work well as the response may not contain the RRSIG whose typeCovered matches the
-            // rrset->rrtype (recursive server returns what is in its cache). Hence, we send the original query with the
-            // DO bit set again to get the RRSIG. Normally this would happen if there was question which did not require
-            // DNSSEC validation (ValidationRequied = 0) populated the cache and later when the ValidationRequired question
-            // comes along, we need to get the RRSIGs. If we started off with ValidationRequired question we would have
-            // already set the DO bit and not able to get RRSIGs e.g., bad CPE device, we would reissue the query here
-            // again once more.
-            //
-            // Also, if it is a wildcard expanded answer, we need to issue the query with the original type for it to
-            // elicit the right NSEC records. Just querying for RRSIG alone is not sufficient.
-            //
-            // Note: For this to work, the core needs to deliver RRSIGs when they are added to the cache even if the
-            // "qtype" is not RRSIG.
-            debugdnssec("GetAllRRSetsForVerification: Fetching RRSIGS for RRSET");
-            dv->NumPackets++;
-            mDNS_StartQuery(m, &dv->q);
-            return mDNSfalse;
-        }
-        // if we found the RRSIG, then fall through to find the DNSKEY
-    case RRVS_key:
-        err = CheckKeyForRRSIG(m, dv, &negcr);
-        if (err != mStatus_NoSuchRecord && err != mStatus_NoError)
-        {
-            dv->DVCallback(m, dv, DNSSEC_Bogus);
-            return mDNSfalse;
-        }
-        // Need to initialize the question as if we end up in ValidateWithNSECS below, the nsec proofs
-        // looks in "dv->q" for the proof.
-        rrsig = (rdataRRSig *)dv->rrsig->rdata;
-        InitializeQuestion(m, &dv->q, dv->InterfaceID, (domainname *)&rrsig->signerName, kDNSType_DNSKEY, VerifySigCallback, dv);
-        // We may not have the NSECS if the previous query was a non-DNSSEC query
-        if (negcr && negcr->nsec)
-        {
-            ValidateWithNSECS(m, dv, negcr);
-            return mDNSfalse;
-        }
-
-        dv->next = RRVS_rrsig_key;
-        if (!dv->key)
-        {
-            debugdnssec("GetAllRRSetsForVerification: Fetching DNSKEY for RRSET");
-            dv->NumPackets++;
-            mDNS_StartQuery(m, &dv->q);
-            return mDNSfalse;
-        }
-    // if we found the DNSKEY, then fall through to find the RRSIG for the DNSKEY
-    case RRVS_rrsig_key:
-        err = CheckRRSIGForKey(m, dv, &negcr);
-        // if we are falling through, then it is okay if we don't find the record
-        if (err != mStatus_NoSuchRecord && err != mStatus_NoError)
-        {
-            dv->DVCallback(m, dv, DNSSEC_Bogus);
-            return mDNSfalse;
-        }
-        // Need to initialize the question as if we end up in ValidateWithNSECS below, the nsec proofs
-        // looks in "dv->q" for the proof.
-        rrsig = (rdataRRSig *)dv->rrsig->rdata;
-        InitializeQuestion(m, &dv->q, dv->InterfaceID, (domainname *)&rrsig->signerName, kDNSType_DNSKEY, VerifySigCallback, dv);
-        // We may not have the NSECS if the previous query was a non-DNSSEC query
-        if (negcr && negcr->nsec)
-        {
-            ValidateWithNSECS(m, dv, negcr);
-            return mDNSfalse;
-        }
-        dv->next = RRVS_ds;
-        debugdnssec("GetAllRRSetsForVerification: RRVS_rrsig_key %p", dv->rrsigKey);
-        if (!dv->rrsigKey)
-        {
-            debugdnssec("GetAllRRSetsForVerification: Fetching RRSIGS for DNSKEY");
-            dv->NumPackets++;
-            mDNS_StartQuery(m, &dv->q);
-            return mDNSfalse;
-        }
-    // if we found RRSIG for the DNSKEY, then fall through to find the DS
-    case RRVS_ds:
-    {
-        domainname *qname;
-        rrsig = (rdataRRSig *)dv->rrsig->rdata;
-        qname = (domainname *)&rrsig->signerName;
-
-        err = CheckDSForKey(m, dv, &negcr);
-        if (err != mStatus_NoSuchRecord && err != mStatus_NoError)
-        {
-            dv->DVCallback(m, dv, DNSSEC_Bogus);
-            return mDNSfalse;
-        }
-        // Need to initialize the question as if we end up in ValidateWithNSECS below, the nsec proofs
-        // looks in "dv->q" for the proof.
-        InitializeQuestion(m, &dv->q, dv->InterfaceID, qname, kDNSType_DS, VerifySigCallback, dv);
-        // We may not have the NSECS if the previous query was a non-DNSSEC query
-        if (negcr && negcr->nsec)
-        {
-            ValidateWithNSECS(m, dv, negcr);
-            return mDNSfalse;
-        }
-        dv->next = RRVS_done;
-        // If we have a trust anchor, then don't bother looking up the DS record
-        if (!dv->ds && !TrustedKeyPresent(m, dv))
-        {
-            // There is no DS for the root. Hence, if we don't have the trust
-            // anchor for root, just fail.
-            if (SameDomainName(qname, (const domainname *)"\000"))
-            {
-                LogDNSSEC("GetAllRRSetsForVerification: Reached root");
-                dv->DVCallback(m, dv, DNSSEC_Bogus);
-                return mDNSfalse;
-            }
-            debugdnssec("GetAllRRSetsForVerification: Fetching DS");
-            dv->NumPackets++;
-            mDNS_StartQuery(m, &dv->q);
-            return mDNSfalse;
-        }
-        else
-        {
-            debugdnssec("GetAllRRSetsForVerification: Skipped fetching the DS");
-            return mDNStrue;
-        }
-    }
-    default:
-        LogMsg("GetAllRRSetsForVerification: ERROR!! unknown next %d", dv->next);
-        dv->DVCallback(m, dv, DNSSEC_Bogus);
-        return mDNSfalse;
-    }
-}
-
-#ifdef DNSSEC_DEBUG
-mDNSlocal void PrintFixedSignInfo(rdataRRSig *rrsig, domainname *signerName, int sigNameLen, mDNSu8 *fixedPart, int fixedPartLen)
-{
-    int j;
-    char buf[RRSIG_FIXED_SIZE *3 + 1]; // 3 bytes count for %2x + 1 and the one byte for null at the end
-    char sig[sigNameLen * 3 + 1];
-    char fp[fixedPartLen * 3 + 1];
-    int length;
-
-    length = 0;
-    for (j = 0; j < RRSIG_FIXED_SIZE; j++)
-        length += mDNS_snprintf(buf+length, sizeof(buf) - length - 1, "%2x ", ((mDNSu8 *)rrsig)[j]);
-    LogMsg("RRSIG(%d) %s", RRSIG_FIXED_SIZE, buf);
-
-
-    length = 0;
-    for (j = 0; j < sigNameLen; j++)
-        length += mDNS_snprintf(sig+length, sizeof(sig) - length - 1, "%2x ", signerName->c[j]);
-    LogMsg("SIGNAME(%d) %s", sigNameLen, sig);
-
-    length = 0;
-    for (j = 0; j < fixedPartLen; j++)
-        length += mDNS_snprintf(fp+length, sizeof(fp) - length - 1, "%2x ", fixedPart[j]);
-    LogMsg("fixedPart(%d) %s", fixedPartLen, fp);
-}
-
-mDNSlocal void PrintVarSignInfo(mDNSu16 rdlen, mDNSu8 *rdata)
-{
-    unsigned int j;
-    mDNSu8 *r;
-    unsigned int blen = swap16(rdlen);
-    char buf[blen * 3 + 1]; // 3 bytes count for %2x + 1 and the one byte for null at the end
-    int length;
-
-    length = 0;
-
-    r = (mDNSu8 *)&rdlen;
-    for (j = 0; j < sizeof(mDNSu16); j++)
-        length += mDNS_snprintf(buf+length, sizeof(buf) - length - 1, "%2x ", r[j]);
-    LogMsg("RDLENGTH(%d) %s", sizeof(mDNSu16), buf);
-
-    length = 0;
-    for (j = 0; j < blen; j++)
-        length += mDNS_snprintf(buf+length, sizeof(buf) - length - 1, "%2x ", rdata[j]);
-    LogMsg("RDATA(%d) %s", blen, buf);
-}
-#else
-mDNSlocal void PrintVarSignInfo(mDNSu16 rdlen, mDNSu8 *rdata)
-{
-    (void)rdlen;
-    (void)rdata;
-}
-mDNSlocal void PrintFixedSignInfo(rdataRRSig *rrsig, domainname *signerName, int sigNameLen, mDNSu8 *fixedPart, int fixedPartLen)
-{
-    (void)rrsig;
-    (void)signerName;
-    (void)sigNameLen;
-    (void)fixedPart;
-    (void)fixedPartLen;
-}
-#endif
-
-// Used for RDATA comparison
-typedef struct
-{
-    mDNSu16 rdlength;
-    mDNSu16 rrtype;
-    mDNSu8 *rdata;
-} rdataComp;
-
-mDNSlocal int rdata_compare(mDNSu8 *const rdata1, mDNSu8 *const rdata2, int rdlen1, int rdlen2)
-{
-    int len;
-    int ret;
-
-    len = (rdlen1 < rdlen2) ? rdlen1 : rdlen2;
-
-    ret = DNSMemCmp(rdata1, rdata2, len);
-    if (ret != 0) return ret;
-
-    // RDATA is same at this stage. Consider them equal if they are of same length. Otherwise
-    // decide based on their lengths.
-    return ((rdlen1 == rdlen2) ? 0 : (rdlen1 < rdlen2) ? -1 : 1);
-}
-
-mDNSlocal int name_compare(mDNSu8 *const rdata1, mDNSu8 *const rdata2, int rdlen1, int rdlen2)
-{
-    domainname *n1 = (domainname *)rdata1;
-    domainname *n2 = (domainname *)rdata2;
-    mDNSu8 *a = n1->c;
-    mDNSu8 *b = n2->c;
-    int count, c1, c2;
-    int i, j, len;
-
-    c1 = CountLabels(n1);
-    c2 = CountLabels(n2);
-
-    count = c1 < c2 ? c1 : c2;
-
-    // We can't use SameDomainName as we need to know exactly which is greater/smaller
-    // for sorting purposes. Hence, we need to compare label by label
-    for (i = 0; i < count; i++)
-    {
-        // Are the lengths same ?
-        if (*a != *b)
-        {
-            debugdnssec("compare_name: returning c1 %d, c2 %d", *a, *b);
-            return ((*a < *b) ? -1 : 1);
-        }
-        len = *a;
-        rdlen1 -= (len + 1);
-        rdlen2 -= (len + 1);
-        if (rdlen1 < 0 || rdlen2 < 0)
-        {
-            LogMsg("name_compare: ERROR!! not enough data rdlen1 %d, rdlen2 %d", rdlen1, rdlen2);
-            return -1;
-        }
-        a++; b++;
-        for (j = 0; j < len; j++)
-        {
-            mDNSu8 ac = *a++;
-            mDNSu8 bc = *b++;
-            if (mDNSIsUpperCase(ac)) ac += 'a' - 'A';
-            if (mDNSIsUpperCase(bc)) bc += 'a' - 'A';
-            if (ac != bc)
-            {
-                debugdnssec("compare_name: returning ac %c, bc %c", ac, bc);
-                return ((ac < bc) ? -1 : 1);
-            }
-        }
-    }
-
-    return 0;
-}
-
-mDNSlocal int srv_compare(rdataComp *const r1, rdataComp *const r2)
-{
-    int res;
-    int length1, length2;
-
-    length1 = r1->rdlength;
-    length2 = r2->rdlength;
-    // We should have at least priority, weight, port plus 1 byte
-    if (length1 < 7 || length2 < 7)
-    {
-        LogMsg("srv_compare: ERROR!! Length smaller than 7 bytes");
-        return -1;
-    }
-    // Compare priority, weight and port
-    res = DNSMemCmp(r1->rdata, r2->rdata, 6);
-    if (res != 0) return res;
-    length1 -= 6;
-    length2 -= 6;
-    return (name_compare(r1->rdata + 6, r2->rdata + 6, length1, length2));
-}
-
-mDNSlocal int tsig_compare(rdataComp *const r1, rdataComp *const r2)
-{
-    int offset1, offset2;
-    int length1, length2;
-    int res, dlen;
-
-    offset1 = offset2 = 0;
-    length1 = r1->rdlength;
-    length2 = r2->rdlength;
-
-    // we should have at least one byte to start with
-    if (length1 < 1 || length2 < 1)
-    {
-        LogMsg("sig_compare: Length smaller than 18 bytes");
-        return -1;
-    }
-
-    res = name_compare(r1->rdata, r2->rdata, length1, length2);
-    if (res != 0) return res;
-
-    dlen = DomainNameLength((domainname *)r1->rdata);
-    offset1 += dlen;
-    offset2 += dlen;
-    length1 -= dlen;
-    length2 -= dlen;
-
-    if (length1 <= 1 || length2 <= 1)
-    {
-        LogMsg("tsig_compare: data too small to compare length1 %d, length2 %d", length1, length2);
-        return -1;
-    }
-
-    return (rdata_compare(r1->rdata + offset1, r2->rdata + offset2, length1, length2));
-}
-
-// Compares types that conform to : <length><Value>
-mDNSlocal int lenval_compare(mDNSu8 *d1, mDNSu8 *d2, int *len1, int *len2, int rem1, int rem2)
-{
-    int len;
-    int res;
-
-    if (rem1 <= 1 || rem2 <= 1)
-    {
-        LogMsg("lenval_compare: data too small to compare length1 %d, length2 %d", rem1, rem2);
-        return -1;
-    }
-    *len1 = (int)d1[0];
-    *len2 = (int)d2[0];
-    len = (*len1 < *len2 ? *len1 : *len2);
-    res = DNSMemCmp(d1, d2, len + 1);
-    return res;
-}
-
-// RFC 2915: Order (2) Preference(2) and variable length: Flags Service Regexp Replacement
-mDNSlocal int naptr_compare(rdataComp *const r1, rdataComp *const r2)
-{
-    mDNSu8 *d1 = r1->rdata;
-    mDNSu8 *d2 = r2->rdata;
-    int len1, len2, res;
-    int length1, length2;
-
-    length1 = r1->rdlength;
-    length2 = r2->rdlength;
-
-    // Order, Preference plus at least 1 byte
-    if (length1 < 5 || length2 < 5)
-    {
-        LogMsg("naptr_compare: Length smaller than 18 bytes");
-        return -1;
-    }
-    // Compare order and preference
-    res = DNSMemCmp(d1, d2, 4);
-    if (res != 0) return res;
-
-    d1 += 4;
-    d2 += 4;
-    length1 -= 4;
-    length2 -= 4;
-
-    // Compare Flags (including the length byte)
-    res = lenval_compare(d1, d2, &len1, &len2, length1, length2);
-    if (res != 0) return res;
-    d1 += (len1 + 1);
-    d2 += (len2 + 1);
-    length1 -= (len1 + 1);
-    length2 -= (len2 + 1);
-
-    // Compare Service (including the length byte)
-    res = lenval_compare(d1, d2, &len1, &len2, length1, length2);
-    if (res != 0) return res;
-    d1 += (len1 + 1);
-    d2 += (len2 + 1);
-    length1 -= (len1 + 1);
-    length2 -= (len2 + 1);
-
-    // Compare regexp (including the length byte)
-    res = lenval_compare(d1, d2, &len1, &len2, length1, length2);
-    if (res != 0) return res;
-    d1 += (len1 + 1);
-    d2 += (len2 + 1);
-    length1 -= (len1 + 1);
-    length2 -= (len2 + 1);
-
-    // Compare Replacement
-    return name_compare(d1, d2, length1, length2);
-}
-
-// RFC 1035: MINFO: Two domain names
-// RFC 1183: RP: Two domain names
-mDNSlocal int dom2_compare(mDNSu8 *d1, mDNSu8 *d2, int length1, int length2)
-{
-    int res, dlen;
-
-    // We need at least one byte to start with
-    if (length1 < 1 || length2 < 1)
-    {
-        LogMsg("dom2_compare:1: data too small length1 %d, length2 %d", length1, length2);
-        return -1;
-    }
-    res = name_compare(d1, d2, length1, length2);
-    if (res != 0) return res;
-    dlen = DomainNameLength((domainname *)d1);
-
-    length1 -= dlen;
-    length2 -= dlen;
-    // We need at least one byte to start with
-    if (length1 < 1 || length2 < 1)
-    {
-        LogMsg("dom2_compare:2: data too small length1 %d, length2 %d", length1, length2);
-        return -1;
-    }
-
-    d1 += dlen;
-    d2 += dlen;
-
-    return name_compare(d1, d2, length1, length2);
-}
-
-// MX : preference (2 bytes), domainname
-mDNSlocal int mx_compare(rdataComp *const r1, rdataComp *const r2)
-{
-    int res;
-    int length1, length2;
-
-    length1 = r1->rdlength;
-    length2 = r2->rdlength;
-
-    // We need at least two bytes + 1 extra byte for the domainname to start with
-    if (length1 < 3 || length2 < 3)
-    {
-        LogMsg("mx_compare: data too small length1 %d, length2 %d", length1, length2);
-        return -1;
-    }
-
-    res = DNSMemCmp(r1->rdata, r2->rdata, 2);
-    if (res != 0) return res;
-    length1 -= 2;
-    length2 -= 2;
-    return name_compare(r1->rdata + 2, r2->rdata + 2, length1, length2);
-}
-
-// RFC 2163 (PX) : preference (2 bytes), map822. mapx400 (domainnames)
-mDNSlocal int px_compare(rdataComp *const r1, rdataComp *const r2)
-{
-    int res;
-
-    // We need at least two bytes + 1 extra byte for the domainname to start with
-    if (r1->rdlength < 3 || r2->rdlength < 3)
-    {
-        LogMsg("px_compare: data too small length1 %d, length2 %d", r1->rdlength, r2->rdlength);
-        return -1;
-    }
-
-    res = DNSMemCmp(r1->rdata, r2->rdata, 2);
-    if (res != 0) return res;
-
-    return dom2_compare(r1->rdata + 2, r2->rdata + 2, r1->rdlength - 2, r2->rdlength - 2);
-}
-
-mDNSlocal int soa_compare(rdataComp *r1, rdataComp *r2)
-{
-    int res, dlen;
-    int offset1, offset2;
-    int length1, length2;
-
-    length1 = r1->rdlength;
-    length2 = r2->rdlength;
-    offset1 = offset2 = 0;
-
-    // We need at least 20 bytes plus 1 byte for each domainname
-    if (length1 < 22 || length2 < 22)
-    {
-        LogMsg("soa_compare:1: data too small length1 %d, length2 %d", length1, length2);
-        return -1;
-    }
-
-    // There are two domainnames followed by 20 bytes of serial, refresh, retry, expire and min
-    // Compare the names and then the rest of the bytes
-
-    res = name_compare(r1->rdata, r2->rdata, length1, length2);
-    if (res != 0) return res;
-
-    dlen = DomainNameLength((domainname *)r1->rdata);
-
-    length1 -= dlen;
-    length2 -= dlen;
-    if (length1 < 1 || length2 < 1)
-    {
-        LogMsg("soa_compare:2: data too small length1 %d, length2 %d", length1, length2);
-        return -1;
-    }
-    offset1 += dlen;
-    offset2 += dlen;
-
-    res = name_compare(r1->rdata + offset1, r2->rdata + offset2, length1, length2);
-    if (res != 0) return res;
-
-    dlen = DomainNameLength((domainname *)r1->rdata);
-    length1 -= dlen;
-    length2 -= dlen;
-    if (length1 < 20 || length2 < 20)
-    {
-        LogMsg("soa_compare:3: data too small length1 %d, length2 %d", length1, length2);
-        return -1;
-    }
-    offset1 += dlen;
-    offset2 += dlen;
-
-    return (rdata_compare(r1->rdata + offset1, r2->rdata + offset2, length1, length2));
-}
-
-// RFC 4034 Section 6.0 states that:
-//
-// A canonical RR form and ordering within an RRset are required in order to
-// construct and verify RRSIG RRs.
-//
-// This function is called to order within an RRset. We can't just do a memcmp as
-// as stated in 6.3. This function is responsible for the third bullet in 6.2, where
-// the RDATA has to be converted to lower case if it has domain names.
-mDNSlocal int RDATACompare(const void *rdata1, const void *rdata2)
-{
-    rdataComp *r1 = (rdataComp *)rdata1;
-    rdataComp *r2 = (rdataComp *)rdata2;
-
-    if (r1->rrtype != r2->rrtype)
-    {
-        LogMsg("RDATACompare: ERROR!! comparing rdata of wrong types type1: %d, type2: %d", r1->rrtype, r2->rrtype);
-        return -1;
-    }
-    switch (r1->rrtype)
-    {
-    case kDNSType_A:                // 1. Address Record
-    case kDNSType_NULL:             // 10 NULL RR
-    case kDNSType_WKS:              // 11 Well-known-service
-    case kDNSType_HINFO:            // 13 Host information
-    case kDNSType_TXT:              // 16 Arbitrary text string
-    case kDNSType_X25:              // 19 X_25 calling address
-    case kDNSType_ISDN:             // 20 ISDN calling address
-    case kDNSType_NSAP:             // 22 NSAP address
-    case kDNSType_KEY:              // 25 Security key
-    case kDNSType_GPOS:             // 27 Geographical position (withdrawn)
-    case kDNSType_AAAA:             // 28 IPv6 Address
-    case kDNSType_LOC:              // 29 Location Information
-    case kDNSType_EID:              // 31 Endpoint identifier
-    case kDNSType_NIMLOC:           // 32 Nimrod Locator
-    case kDNSType_ATMA:             // 34 ATM Address
-    case kDNSType_CERT:             // 37 Certification record
-    case kDNSType_A6:               // 38 IPv6 Address (deprecated)
-    case kDNSType_SINK:             // 40 Kitchen sink (experimental)
-    case kDNSType_OPT:              // 41 EDNS0 option (meta-RR)
-    case kDNSType_APL:              // 42 Address Prefix List
-    case kDNSType_DS:               // 43 Delegation Signer
-    case kDNSType_SSHFP:            // 44 SSH Key Fingerprint
-    case kDNSType_IPSECKEY:         // 45 IPSECKEY
-    case kDNSType_RRSIG:            // 46 RRSIG
-    case kDNSType_NSEC:             // 47 Denial of Existence
-    case kDNSType_DNSKEY:           // 48 DNSKEY
-    case kDNSType_DHCID:            // 49 DHCP Client Identifier
-    case kDNSType_NSEC3:            // 50 Hashed Authenticated Denial of Existence
-    case kDNSType_NSEC3PARAM:       // 51 Hashed Authenticated Denial of Existence
-    case kDNSType_HIP:              // 55 Host Identity Protocol
-    case kDNSType_SPF:              // 99 Sender Policy Framework for E-Mail
-    default:
-        return rdata_compare(r1->rdata, r2->rdata, r1->rdlength, r2->rdlength);
-    case kDNSType_NS:               //  2 Name Server
-    case kDNSType_MD:               //  3 Mail Destination
-    case kDNSType_MF:               //  4 Mail Forwarder
-    case kDNSType_CNAME:            //  5 Canonical Name
-    case kDNSType_MB:               //  7 Mailbox
-    case kDNSType_MG:               //  8 Mail Group
-    case kDNSType_MR:               //  9 Mail Rename
-    case kDNSType_PTR:              // 12 Domain name pointer
-    case kDNSType_NSAP_PTR:         // 23 Reverse NSAP lookup (deprecated)
-    case kDNSType_DNAME:            // 39 Non-terminal DNAME (for IPv6)
-        return name_compare(r1->rdata, r2->rdata, r1->rdlength, r2->rdlength);
-    case kDNSType_SRV:              // 33 Service record
-        return srv_compare(r1, r2);
-    case kDNSType_SOA:              //  6 Start of Authority
-        return soa_compare(r1, r2);
-
-    case kDNSType_RP:               // 17 Responsible person
-    case kDNSType_MINFO:            // 14 Mailbox information
-        return dom2_compare(r1->rdata, r2->rdata, r1->rdlength, r2->rdlength);
-    case kDNSType_MX:               // 15 Mail Exchanger
-    case kDNSType_AFSDB:            // 18 AFS cell database
-    case kDNSType_RT:               // 21 Router
-    case kDNSType_KX:               // 36 Key Exchange
-        return mx_compare(r1, r2);
-    case kDNSType_PX:               // 26 X.400 mail mapping
-        return px_compare(r1, r2);
-    case kDNSType_NAPTR:            // 35 Naming Authority PoinTeR
-        return naptr_compare(r1, r2);
-    case kDNSType_TKEY:             // 249 Transaction key
-    case kDNSType_TSIG:             // 250 Transaction signature
-        // TSIG and TKEY have a domainname followed by data
-        return tsig_compare(r1, r2);
-    // TBD: We are comparing them as opaque types, perhaps not right
-    case kDNSType_SIG:              // 24 Security signature
-    case kDNSType_NXT:              // 30 Next domain (security)
-        LogMsg("RDATACompare: WARNING!! explicit support has not been added, using default");
-        return rdata_compare(r1->rdata, r2->rdata, r1->rdlength, r2->rdlength);
-    }
-}
-
-
-
-// RFC 4034 section 6.2 requirement for verifying signature.
-//
-// 3. if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
-// HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
-// SRV, DNAME, A6, RRSIG, or NSEC, all uppercase US-ASCII letters in
-// the DNS names contained within the RDATA are replaced by the
-// corresponding lowercase US-ASCII letters;
-//
-// NSEC and HINFO is not needed as per dnssec-bis update. RRSIG is done elsewhere
-// as part of signature verification
-mDNSlocal void ConvertRDATAToCanonical(mDNSu16 rrtype, mDNSu16 rdlength, mDNSu8 *rdata)
-{
-    domainname name;
-    int len;
-    mDNSu8 *origRdata = rdata;
-
-    // Ensure that we have at least one byte of data to examine and modify.
-
-    if (!rdlength) { LogMsg("ConvertRDATAToCanonical: rdlength zero for rrtype %s", DNSTypeName(rrtype)); return; }
-
-    switch (rrtype)
-    {
-    // Not adding suppot for A6 as it is deprecated
-    case kDNSType_A6:               // 38 IPv6 Address (deprecated)
-    default:
-        debugdnssec("ConvertRDATAToCanonical: returning from default %s", DNSTypeName(rrtype));
-        return;
-    case kDNSType_NS:               //  2 Name Server
-    case kDNSType_MD:               //  3 Mail Destination
-    case kDNSType_MF:               //  4 Mail Forwarder
-    case kDNSType_CNAME:            //  5 Canonical Name
-    case kDNSType_MB:               //  7 Mailbox
-    case kDNSType_MG:               //  8 Mail Group
-    case kDNSType_MR:               //  9 Mail Rename
-    case kDNSType_PTR:              // 12 Domain name pointer
-    case kDNSType_DNAME:            // 39 Non-terminal DNAME (for IPv6)
-    case kDNSType_NXT:              // 30 Next domain (security)
-
-    // TSIG and TKEY are not mentioned in RFC 4034, but we just leave it here
-    case kDNSType_TSIG:             // 250 Transaction signature
-    case kDNSType_TKEY:             // 249 Transaction key
-
-        if (DNSNameToLowerCase((domainname *)rdata, &name) != mStatus_NoError)
-        {
-            LogMsg("ConvertRDATAToCanonical: ERROR!! DNSNameToLowerCase failed");
-            return;
-        }
-        AssignDomainName((domainname *)rdata, &name);
-        return;
-    case kDNSType_MX:               // 15 Mail Exchanger
-    case kDNSType_AFSDB:            // 18 AFS cell database
-    case kDNSType_RT:               // 21 Router
-    case kDNSType_KX:               // 36 Key Exchange
-
-        // format: preference - 2 bytes, followed by name
-        // Ensure that we have at least 3 bytes (preference + 1 byte for the domain name)
-        if (rdlength <= 3)
-        {
-            LogMsg("ConvertRDATAToCanonical:MX: rdlength %d for rrtype %s too small", rdlength, DNSTypeName(rrtype));
-            return;
-        }
-        if (DNSNameToLowerCase((domainname *)(rdata + 2), &name) != mStatus_NoError)
-        {
-            LogMsg("ConvertRDATAToCanonical: MX: ERROR!! DNSNameToLowerCase failed");
-            return;
-        }
-        AssignDomainName((domainname *)(rdata + 2), &name);
-        return;
-    case kDNSType_SRV:              // 33 Service record
-        // format : priority, weight and port - 6 bytes, followed by name
-        if (rdlength <= 7)
-        {
-            LogMsg("ConvertRDATAToCanonical:SRV: rdlength %d for rrtype %s too small", rdlength, DNSTypeName(rrtype));
-            return;
-        }
-        if (DNSNameToLowerCase((domainname *)(rdata + 6), &name) != mStatus_NoError)
-        {
-            LogMsg("ConvertRDATAToCanonical: SRV: ERROR!! DNSNameToLowerCase failed");
-            return;
-        }
-        AssignDomainName((domainname *)(rdata + 6), &name);
-        return;
-    case kDNSType_PX:               // 26 X.400 mail mapping
-        if (rdlength <= 3)
-        {
-            LogMsg("ConvertRDATAToCanonical:PX: rdlength %d for rrtype %s too small", rdlength, DNSTypeName(rrtype));
-            return;
-        }
-        // Preference followed by two domain names
-        rdata += 2;
-    /* FALLTHROUGH */
-    case kDNSType_RP:               // 17 Responsible person
-    case kDNSType_SOA:              //  6 Start of Authority
-    case kDNSType_MINFO:            // 14 Mailbox information
-        if (DNSNameToLowerCase((domainname *)rdata, &name) != mStatus_NoError)
-        {
-            LogMsg("ConvertRDATAToCanonical: SOA1: ERROR!! DNSNameToLowerCase failed");
-            return;
-        }
-
-        AssignDomainName((domainname *)rdata, &name);
-        len = DomainNameLength((domainname *)rdata);
-        if (rdlength <= len + 1)
-        {
-            LogMsg("ConvertRDATAToCanonical:RP: rdlength %d for rrtype %s too small", rdlength, DNSTypeName(rrtype));
-            return;
-        }
-        rdata += len;
-
-        if (DNSNameToLowerCase((domainname *)rdata, &name) != mStatus_NoError)
-        {
-            LogMsg("ConvertRDATAToCanonical: SOA2: ERROR!! DNSNameToLowerCase failed");
-            return;
-        }
-        AssignDomainName((domainname *)rdata, &name);
-        return;
-    case kDNSType_NAPTR:            // 35 Naming Authority Pointer
-        // order and preference
-        rdata += 4;
-        // Flags (including the length byte)
-        rdata += (((int) rdata[0]) + 1);
-        // Service (including the length byte)
-        rdata += (((int) rdata[0]) + 1);
-        // regexp (including the length byte)
-        rdata += (((int) rdata[0]) + 1);
-
-        // Replacement field is a domainname. If we have at least one more byte, then we are okay.
-        if ((origRdata + rdlength) < rdata + 1)
-        {
-            LogMsg("ConvertRDATAToCanonical:NAPTR: origRdata %p, rdlength %d, rdata %p for rrtype %s too small", origRdata, rdlength, rdata, DNSTypeName(rrtype));
-            return;
-        }
-        if (DNSNameToLowerCase((domainname *)rdata, &name) != mStatus_NoError)
-        {
-            LogMsg("ConvertRDATAToCanonical: NAPTR2: ERROR!! DNSNameToLowerCase failed");
-            return;
-        }
-        AssignDomainName((domainname *)rdata, &name);
-    case kDNSType_SIG:              // 24 Security signature
-        // format: <18 bytes> <domainname> <data>
-        if (rdlength <= 19)
-        {
-            LogMsg("ConvertRDATAToCanonical:SIG: rdlength %d for rrtype %s too small", rdlength, DNSTypeName(rrtype));
-            return;
-        }
-        // Preference followed by two domain names
-        rdata += 18;
-        if (DNSNameToLowerCase((domainname *)rdata, &name) != mStatus_NoError)
-        {
-            LogMsg("ConvertRDATAToCanonical: SIG: ERROR!! DNSNameToLowerCase failed");
-            return;
-        }
-        AssignDomainName((domainname *)rdata, &name);
-        return;
-    }
-}
-
-mDNSlocal mDNSBool ValidateSignatureWithKey(DNSSECVerifier *dv, RRVerifier *rrset, RRVerifier *keyv, RRVerifier *sig)
-{
-    domainname name;
-    domainname signerName;
-    int labels;
-    mDNSu8 fixedPart[MAX_DOMAIN_NAME + 8];  // domainname + type + class + ttl
-    int fixedPartLen;
-    RRVerifier *tmp;
-    int nrrsets;
-    rdataComp *ptr, *start, *p;
-    rdataRRSig *rrsig;
-    rdataDNSKey *key;
-    int i;
-    int sigNameLen;
-    mDNSu16 temp;
-    mStatus algRet;
-
-
-    key = (rdataDNSKey *)keyv->rdata;
-    rrsig = (rdataRRSig *)sig->rdata;
-
-    LogDNSSEC("ValidateSignatureWithKey: Validating signature with key with tag %d", (mDNSu16)keytag((mDNSu8 *)key, keyv->rdlength));
-
-    if (DNSNameToLowerCase((domainname *)&rrsig->signerName, &signerName) != mStatus_NoError)
-    {
-        LogMsg("ValidateSignatureWithKey: ERROR!! cannot convert signer name to lower case");
-        return mDNSfalse;
-    }
-
-    if (DNSNameToLowerCase((domainname *)&rrset->name, &name) != mStatus_NoError)
-    {
-        LogMsg("ValidateSignatureWithKey: ERROR!! cannot convert rrset name to lower case");
-        return mDNSfalse;
-    }
-
-    sigNameLen = DomainNameLength(&signerName);
-    labels = CountLabels(&name);
-    // RFC 4034: RRSIG validation
-    //
-    // signature = sign(RRSIG_RDATA | RR(1) | RR(2)... )
-    //
-    // where RRSIG_RDATA excludes the signature and signer name in canonical form
-
-    if (dv->ctx) AlgDestroy(dv->ctx);
-    dv->ctx = AlgCreate(CRYPTO_ALG, rrsig->alg);
-    if (!dv->ctx)
-    {
-        LogDNSSEC("ValidateSignatureWithKey: ERROR!! No algorithm support for %d", rrsig->alg);
-        return mDNSfalse;
-    }
-    AlgAdd(dv->ctx, (const mDNSu8 *)rrsig, RRSIG_FIXED_SIZE);
-    AlgAdd(dv->ctx, signerName.c, sigNameLen);
-
-    if (labels - rrsig->labels > 0)
-    {
-        domainname *d;
-        LogDNSSEC("ValidateSignatureWithKey: ====splitting labels %d, rrsig->labels %d====", labels,rrsig->labels);
-        d = (domainname *)SkipLeadingLabels(&name, labels - rrsig->labels);
-        fixedPart[0] = 1;
-        fixedPart[1] = '*';
-        AssignDomainName((domainname *)(fixedPart + 2), d);
-        fixedPartLen = DomainNameLength(d) + 2;
-        // See RFC 4034 section 3.1.3. If you are looking up *.example.com,
-        // the labels count in the RRSIG is 2, but this is not considered as
-        // a wildcard answer
-        if (name.c[0] != 1 || name.c[1] != '*')
-        {
-            LogDNSSEC("ValidateSignatureWithKey: Wildcard exapnded answer for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType));
-            dv->flags |= WILDCARD_PROVES_ANSWER_EXPANDED;
-            dv->wildcardName = (domainname *)SkipLeadingLabels(&dv->origName, labels - rrsig->labels);
-            if (!dv->wildcardName) return mDNSfalse;
-        }
-    }
-    else
-    {
-        debugdnssec("ValidateSignatureWithKey: assigning domainname");
-        AssignDomainName((domainname *)fixedPart, &name);
-        fixedPartLen = DomainNameLength(&name);
-    }
-    temp = swap16(rrset->rrtype);
-    mDNSPlatformMemCopy(fixedPart + fixedPartLen, (mDNSu8 *)&temp, sizeof(rrset->rrtype));
-    fixedPartLen += sizeof(rrset->rrtype);
-    temp = swap16(rrset->rrclass);
-    mDNSPlatformMemCopy(fixedPart + fixedPartLen, (mDNSu8 *)&temp, sizeof(rrset->rrclass));
-    fixedPartLen += sizeof(rrset->rrclass);
-    mDNSPlatformMemCopy(fixedPart + fixedPartLen, (mDNSu8 *)&rrsig->origTTL, sizeof(rrsig->origTTL));
-    fixedPartLen += sizeof(rrsig->origTTL);
-
-
-    for (tmp = rrset, nrrsets = 0; tmp; tmp = tmp->next)
-        nrrsets++;
-
-    tmp = rrset;
-    start = ptr = (rdataComp *) mDNSPlatformMemAllocateClear(nrrsets * sizeof(rdataComp));
-    debugdnssec("ValidateSignatureWithKey: start %p, nrrsets %d", start, nrrsets);
-    if (ptr)
-    {
-        while (tmp)
-        {
-            ptr->rdlength = tmp->rdlength;
-            ptr->rrtype = tmp->rrtype;
-            if (ptr->rdlength)
-            {
-                ptr->rdata = (mDNSu8 *) mDNSPlatformMemAllocate(ptr->rdlength);
-                if (ptr->rdata)
-                {
-                    mDNSPlatformMemCopy(ptr->rdata, tmp->rdata, tmp->rdlength);
-                }
-                else
-                {
-                    for (i = 0; i < nrrsets; i++)
-                        if (start[i].rdata) mDNSPlatformMemFree(start[i].rdata);
-                    mDNSPlatformMemFree(start);
-                    LogMsg("ValidateSignatureWithKey:1: ERROR!! RDATA memory alloation failure");
-                    return mDNSfalse;
-                }
-            }
-            ptr++;
-            tmp = tmp->next;
-        }
-    }
-    else
-    {
-        LogMsg("ValidateSignatureWithKey:2: ERROR!! RDATA memory alloation failure");
-        return mDNSfalse;
-    }
-
-    PrintFixedSignInfo(rrsig, &signerName, sigNameLen, fixedPart, fixedPartLen);
-
-    mDNSPlatformQsort(start, nrrsets, sizeof(rdataComp), RDATACompare);
-    for (p = start, i = 0; i < nrrsets; p++, i++)
-    {
-        int rdlen;
-
-        // The array is sorted and hence checking adjacent entries for duplicate is sufficient
-        if (i > 0)
-        {
-            rdataComp *q = p - 1;
-            if (!RDATACompare((void *)p, (void *)q)) continue;
-        }
-
-        // Add the fixed part
-        AlgAdd(dv->ctx, (const mDNSu8 *)fixedPart, fixedPartLen);
-
-        // Add the rdlength
-        rdlen = swap16(p->rdlength);
-        AlgAdd(dv->ctx, (const mDNSu8 *)&rdlen, sizeof(mDNSu16));
-
-        ConvertRDATAToCanonical(p->rrtype, p->rdlength, p->rdata);
-
-        PrintVarSignInfo(rdlen, p->rdata);
-        AlgAdd(dv->ctx, (const mDNSu8 *)p->rdata, p->rdlength);
-    }
-    // free the memory as we don't need it anymore
-    for (i = 0; i < nrrsets; i++)
-        if (start[i].rdata) mDNSPlatformMemFree(start[i].rdata);
-    mDNSPlatformMemFree(start);
-
-    algRet = AlgVerify(dv->ctx, (mDNSu8 *)&key->data, keyv->rdlength - DNSKEY_FIXED_SIZE, (mDNSu8 *)(sig->rdata + sigNameLen + RRSIG_FIXED_SIZE), sig->rdlength - RRSIG_FIXED_SIZE - sigNameLen);
-    AlgDestroy(dv->ctx);
-    dv->ctx = mDNSNULL;
-    if (algRet != mStatus_NoError)
-    {
-        LogDNSSEC("ValidateSignatureWithKey: AlgVerify failed for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType));
-        // Reset the state if we set any above.
-        if (dv->flags & WILDCARD_PROVES_ANSWER_EXPANDED)
-        {
-            dv->flags &= ~WILDCARD_PROVES_ANSWER_EXPANDED;
-            dv->wildcardName = mDNSNULL;
-        }
-        return mDNSfalse;
-    }
-    return mDNStrue;
-}
-
-// Walk all the keys and for each key walk all the RRSIGS that signs the original rrset
-mDNSlocal mStatus ValidateSignature(DNSSECVerifier *dv, RRVerifier **resultKey, RRVerifier **resultRRSIG)
-{
-    RRVerifier *rrset;
-    RRVerifier *keyv;
-    RRVerifier *rrsigv;
-    RRVerifier *sig;
-    rdataDNSKey *key;
-    rdataRRSig *rrsig;
-    mDNSu16 tag;
-
-    rrset = dv->rrset;
-    sig = dv->rrsig;
-
-    for (keyv = dv->key; keyv; keyv = keyv->next)
-    {
-        key = (rdataDNSKey *)keyv->rdata;
-        tag = (mDNSu16)keytag((mDNSu8 *)key, keyv->rdlength);
-        for (rrsigv = sig; rrsigv; rrsigv = rrsigv->next)
-        {
-            rrsig = (rdataRRSig *)rrsigv->rdata;
-            // 7. The RRSIG RR's Signer's Name, Algorithm, and Key Tag fields MUST match the owner
-            //    name, algorithm, and key tag for some DNSKEY RR in the zone's apex DNSKEY RRset.
-            if (!SameDomainName((domainname *)&rrsig->signerName, &keyv->name))
-            {
-                debugdnssec("ValidateSignature: name mismatch");
-                continue;
-            }
-            if (key->alg != rrsig->alg)
-            {
-                debugdnssec("ValidateSignature: alg mismatch");
-                continue;
-            }
-            if (tag != swap16(rrsig->keyTag))
-            {
-                debugdnssec("ValidateSignature: keyTag mismatch rrsig tag %d(0x%x), keyTag %d(0x%x)", swap16(rrsig->keyTag),
-                            swap16(rrsig->keyTag), tag, tag);
-                continue;
-            }
-            // 8. The matching DNSKEY RR MUST be present in the zone's apex DNSKEY RRset, and MUST
-            //    have the Zone Flag bit (DNSKEY RDATA Flag bit 7) set.
-            if (!((swap16(key->flags)) & DNSKEY_ZONE_SIGN_KEY))
-            {
-                debugdnssec("ValidateSignature: ZONE flag bit not set");
-                continue;
-            }
-            debugdnssec("ValidateSignature:Found a key and RRSIG tag: %d", tag);
-            if (ValidateSignatureWithKey(dv, rrset, keyv, rrsigv))
-            {
-                LogDNSSEC("ValidateSignature: Validated successfully with key tag %d", tag);
-                *resultKey = keyv;
-                *resultRRSIG = rrsigv;
-                return mStatus_NoError;
-            }
-        }
-    }
-    *resultKey = mDNSNULL;
-    *resultRRSIG = mDNSNULL;
-    return mStatus_NoSuchRecord;
-}
-
-mDNSlocal mDNSBool ValidateSignatureWithKeyForAllRRSigs(DNSSECVerifier *dv, RRVerifier *rrset, RRVerifier *keyv, RRVerifier *sig)
-{
-    rdataRRSig *rrsig;
-    mDNSu16 tag;
-
-    while (sig)
-    {
-        rrsig = (rdataRRSig *)sig->rdata;
-        tag = (mDNSu16)keytag(keyv->rdata, keyv->rdlength);
-        if (tag == swap16(rrsig->keyTag))
-        {
-            if (ValidateSignatureWithKey(dv, rrset, keyv, sig))
-            {
-                LogDNSSEC("ValidateSignatureWithKeyForAllRRSigs: Validated");
-                return mDNStrue;
-            }
-        }
-        sig = sig->next;
-    }
-    return mDNSfalse;
-}
-
-mDNSlocal mStatus ValidateDS(DNSSECVerifier *dv)
-{
-    mDNSu8 *digest;
-    int digestLen;
-    domainname name;
-    rdataRRSig *rrsig;
-    rdataDS *ds;
-    rdataDNSKey *key;
-    RRVerifier *keyv;
-    RRVerifier *dsv;
-    mStatus algRet;
-
-    rrsig = (rdataRRSig *)dv->rrsig->rdata;
-
-    // Walk all the DS Records to see if we have a matching DNS KEY record that verifies
-    // the hash. If we find one, verify that this key was used to sign the KEY rrsets in
-    // this zone. Loop till we find one.
-    for (dsv = dv->ds; dsv; dsv = dsv->next)
-    {
-        ds = (rdataDS *)dsv->rdata;
-        if ((ds->digestType != SHA1_DIGEST_TYPE) && (ds->digestType != SHA256_DIGEST_TYPE))
-        {
-            LogDNSSEC("ValidateDS: Unsupported digest %d", ds->digestType);
-            return mStatus_BadParamErr;
-        }
-        else debugdnssec("ValidateDS: digest type %d", ds->digestType);
-        for (keyv = dv->key; keyv; keyv = keyv->next)
-        {
-            key = (rdataDNSKey *)keyv->rdata;
-            mDNSu16 tag = (mDNSu16)keytag((mDNSu8 *)key, keyv->rdlength);
-            if (tag != swap16(ds->keyTag))
-            {
-                debugdnssec("ValidateDS:Not a valid keytag %d", tag);
-                continue;
-            }
-
-            if (DNSNameToLowerCase((domainname *)&rrsig->signerName, &name) != mStatus_NoError)
-            {
-                LogMsg("ValidateDS: ERROR!! cannot convert to lower case");
-                continue;
-            }
-
-            if (dv->ctx) AlgDestroy(dv->ctx);
-            dv->ctx = AlgCreate(DIGEST_ALG, ds->digestType);
-            if (!dv->ctx)
-            {
-                LogMsg("ValidateDS: ERROR!! Cannot allocate context");
-                continue;
-            }
-            digest = (mDNSu8 *)&ds->digest;
-            digestLen = dsv->rdlength - DS_FIXED_SIZE;
-
-            AlgAdd(dv->ctx, name.c, DomainNameLength(&name));
-            AlgAdd(dv->ctx, (const mDNSu8 *)key, keyv->rdlength);
-
-            algRet = AlgVerify(dv->ctx, mDNSNULL, 0, digest, digestLen);
-            AlgDestroy(dv->ctx);
-            dv->ctx = mDNSNULL;
-            if (algRet == mStatus_NoError)
-            {
-                LogDNSSEC("ValidateDS: DS Validated Successfully, need to verify the key %d", tag);
-                // We found the DNS KEY that is authenticated by the DS in our parent zone. Check to see if this key
-                // was used to sign the DNS KEY RRSET. If so, then the keys in our DNS KEY RRSET are valid
-                if (ValidateSignatureWithKeyForAllRRSigs(dv, dv->key, keyv, dv->rrsigKey))
-                {
-                    LogDNSSEC("ValidateDS: DS Validated Successfully %d", tag);
-                    return mStatus_NoError;
-                }
-            }
-        }
-    }
-    return mStatus_NoSuchRecord;
-}
-
-mDNSlocal mDNSBool UnlinkRRVerifier(DNSSECVerifier *dv, RRVerifier *elem, RRVerifierSet set)
-{
-    RRVerifier **v;
-
-    switch (set)
-    {
-    case RRVS_rr:
-        v = &dv->rrset;
-        break;
-    case RRVS_rrsig:
-        v = &dv->rrsig;
-        break;
-    case RRVS_key:
-        v = &dv->key;
-        break;
-    case RRVS_rrsig_key:
-        v = &dv->rrsigKey;
-        break;
-    case RRVS_ds:
-        v = &dv->ds;
-        break;
-    default:
-        LogMsg("UnlinkRRVerifier: ERROR!! default case %d", set);
-        return mDNSfalse;
-    }
-    while (*v && *v != elem)
-        v = &(*v)->next;
-    if (!(*v))
-    {
-        LogMsg("UnlinkRRVerifier: ERROR!! cannot find element in set %d", set);
-        return mDNSfalse;
-    }
-    *v = elem->next;                  // Cut this record from the list
-    elem->next = mDNSNULL;
-    return mDNStrue;
-}
-
-// This can link a single AuthChain element or a list of AuthChain elements to
-// DNSSECVerifier. The latter happens when we have multiple NSEC proofs and
-// we gather up all the proofs in one place.
-mDNSexport void AuthChainLink(DNSSECVerifier *dv, AuthChain *ae)
-{
-    AuthChain *head;
-
-    LogDNSSEC("AuthChainLink: called");
-
-    head = ae;
-    // Get to the last element
-    while (ae->next)
-        ae = ae->next;
-    *(dv->actail) = head;                // Append this record to tail of auth chain
-    dv->actail = &(ae->next);          // Advance tail pointer
-}
-
-mDNSlocal mDNSBool AuthChainAdd(DNSSECVerifier *dv, RRVerifier *resultKey, RRVerifier *resultRRSig)
-{
-    AuthChain *ae;
-    rdataDNSKey *key;
-    mDNSu16 tag;
-
-    if (!dv->rrset || !resultKey || !resultRRSig)
-    {
-        LogMsg("AuthChainAdd: ERROR!! input argument NULL");
-        return mDNSfalse;
-    }
-
-    // Unlink resultKey and resultRRSig and store as part of AuthChain
-    if (!UnlinkRRVerifier(dv, resultKey, RRVS_key))
-    {
-        LogMsg("AuthChainAdd: ERROR!! cannot unlink key");
-        return mDNSfalse;
-    }
-    if (!UnlinkRRVerifier(dv, resultRRSig, RRVS_rrsig))
-    {
-        LogMsg("AuthChainAdd: ERROR!! cannot unlink rrsig");
-        return mDNSfalse;
-    }
-
-    ae = (AuthChain *) mDNSPlatformMemAllocateClear(sizeof(*ae));
-    if (!ae)
-    {
-        LogMsg("AuthChainAdd: AuthChain alloc failure");
-        return mDNSfalse;
-    }
-
-    ae->next  = mDNSNULL;
-    ae->rrset = dv->rrset;
-    dv->rrset = mDNSNULL;
-
-    ae->rrsig = resultRRSig;
-    ae->key   = resultKey;
-
-    key = (rdataDNSKey *)resultKey->rdata;
-    tag = (mDNSu16)keytag((mDNSu8 *)key, resultKey->rdlength);
-    LogDNSSEC("AuthChainAdd: inserting AuthChain element with rrset %##s (%s), DNSKEY tag %d", ae->rrset->name.c, DNSTypeName(ae->rrset->rrtype), tag);
-
-    AuthChainLink(dv, ae);
-    return mDNStrue;
-}
-       
-// RFC 4035: Section 5.3.3
-//
-// If the resolver accepts the RRset as authentic, the validator MUST set the TTL of
-// the RRSIG RR and each RR in the authenticated RRset to a value no greater than the
-// minimum of:
-//
-//   o  the RRset's TTL as received in the response;
-//
-//   o  the RRSIG RR's TTL as received in the response;
-//
-//   o  the value in the RRSIG RR's Original TTL field; and
-//
-//   o  the difference of the RRSIG RR's Signature Expiration time and the
-//      current time.
-mDNSlocal void SetTTLRRSet(mDNS *const m, DNSSECVerifier *dv, DNSSECStatus status)
-{
-    DNSQuestion question;
-    CacheRecord *rr;
-    RRVerifier *rrsigv;
-    rdataRRSig *rrsig;
-    CacheGroup *cg;
-    mDNSu32 rrTTL, rrsigTTL, rrsigOrigTTL, rrsigTimeTTL;
-    domainname *qname;
-    mDNSu16 qtype;
-    CacheRecord *rrsigRR;
-    mDNSs32 now;
-
-    debugdnssec("SetTTLRRSet called");
-
-    if (status == DNSSEC_Insecure || status == DNSSEC_Indeterminate)
-    {
-        LogDNSSEC("SetTTLRRSET: not setting ttl for status %s", DNSSECStatusName(status));
-        return;
-    }
-
-    mDNS_Lock(m);
-    now = m->timenow;
-    mDNS_Unlock(m);
-
-    mDNSPlatformMemZero(&question, sizeof(DNSQuestion));
-    rrTTL = rrsigTTL = rrsigOrigTTL = rrsigTimeTTL = 0;
-
-    // 1. Locate the rrset name and get its TTL (take the first one as a representative
-    // of the rrset). Ideally, we should set the TTL on the first validation. Instead,
-    // we do it whenever we validate which happens whenever a ValidationRequired question
-    // finishes validation. 
-    qname = &dv->origName;
-    qtype = dv->origType;
-
-    question.ThisQInterval = -1;
-    InitializeQuestion(m, &question, dv->InterfaceID, qname, qtype, mDNSNULL, mDNSNULL);
-    cg = CacheGroupForName(m, question.qnamehash, &question.qname);
-
-    if (!cg)
-    {
-        LogMsg("SetTTLRRSet cg NULL for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType));
-        return;
-    }
-
-    for (rr = cg->members; rr; rr = rr->next)
-        if (SameNameCacheRecordAnswersQuestion(rr, &question))
-        {
-            // originalttl is never touched. The actual TTL is derived based on when it was
-            // received.
-            rrTTL = rr->resrec.rroriginalttl - (now - rr->TimeRcvd)/mDNSPlatformOneSecond;
-            break;
-        }
-
-    // Should we check to see if it matches the record in dv->ac->rrset ?
-    if (!rr)
-    {
-        LogMsg("SetTTLRRSet: ERROR!! cannot locate main rrset for %##s (%s)", qname->c, DNSTypeName(qtype));
-        return;
-    }
-
-
-    // 2. Get the RRSIG ttl. For NSEC records we need to get the NSEC record's TTL as
-    // the negative cache record that we created may not be right.
-
-    if (dv->ac && dv->ac->rrsig)
-    {
-        rrsigv = dv->ac->rrsig;
-        rrsig = (rdataRRSig *)rrsigv->rdata;
-    }
-    else
-    {
-        rrsigv = mDNSNULL;
-        rrsig = mDNSNULL;
-    }
-
-    rrsigRR = mDNSNULL;
-    if (rr->resrec.RecordType == kDNSRecordTypePacketNegative && status == DNSSEC_Secure)
-    {
-        CacheRecord *ncr;
-        rrTTL = 0;
-        for (ncr = rr->nsec; ncr; ncr = ncr->next)
-        {
-            if (ncr->resrec.rrtype == kDNSType_NSEC || ncr->resrec.rrtype == kDNSType_NSEC3)
-            {
-                rrTTL = ncr->resrec.rroriginalttl - (now - ncr->TimeRcvd)/mDNSPlatformOneSecond;
-                debugdnssec("SetTTLRRSet: NSEC TTL %u", rrTTL);
-            }
-            // Note: we can't use dv->origName here as the NSEC record's RRSIG may not match
-            // the original name
-            if (rrsigv && ncr->resrec.rrtype == kDNSType_RRSIG && SameDomainName(ncr->resrec.name, &rrsigv->name))
-            {
-                RDataBody2 *rdb = (RDataBody2 *)ncr->resrec.rdata->u.data;
-                rdataRRSig *sig = (rdataRRSig *)rdb->data;
-                if (rrsigv->rdlength != ncr->resrec.rdlength)
-                {
-                    debugdnssec("SetTTLRRSet length mismatch");
-                    continue;
-                }
-                if (mDNSPlatformMemSame(sig, rrsig, rrsigv->rdlength))
-                {
-                    mDNSu32 remain = (now - ncr->TimeRcvd)/mDNSPlatformOneSecond;
-                    rrsigTTL = ncr->resrec.rroriginalttl - remain;
-                    rrsigOrigTTL = swap32(rrsig->origTTL) - remain;
-                    rrsigTimeTTL = swap32(rrsig->sigExpireTime) - swap32(rrsig->sigInceptTime);
-                }
-            }
-            if (rrTTL && (!rrsigv || rrsigTTL)) break;
-        }
-    }
-    else if (rrsigv)
-    {
-        // Look for the matching RRSIG so that we can get its TTL
-        for (rr = cg ? cg->members : mDNSNULL; rr; rr=rr->next)
-            if (rr->resrec.rrtype == kDNSType_RRSIG && SameDomainName(rr->resrec.name, &rrsigv->name))
-            {
-                RDataBody2 *rdb = (RDataBody2 *)rr->resrec.rdata->u.data;
-                rdataRRSig *sig = (rdataRRSig *)rdb->data;
-                if (rrsigv->rdlength != rr->resrec.rdlength)
-                {
-                    debugdnssec("SetTTLRRSet length mismatch");
-                    continue;
-                }
-                if (mDNSPlatformMemSame(sig, rrsig, rrsigv->rdlength))
-                {
-                    mDNSu32 remain = (now - rr->TimeRcvd)/mDNSPlatformOneSecond;
-                    rrsigTTL = rr->resrec.rroriginalttl - remain;
-                    rrsigOrigTTL = swap32(rrsig->origTTL) - remain;
-                    rrsigTimeTTL = swap32(rrsig->sigExpireTime) - swap32(rrsig->sigInceptTime);
-                    rrsigRR = rr;
-                    break;
-                }
-            }
-    }
-
-    // It is possible that there are no RRSIGs and in that case it is not an error
-    // to find the rrsigTTL.
-    if (!rrTTL || (rrsigv && (!rrsigTTL || !rrsigOrigTTL || !rrsigTimeTTL)))
-    {
-        LogDNSSEC("SetTTLRRSet: ERROR!! Bad TTL rrtl %u, rrsigTTL %u, rrsigOrigTTL %u, rrsigTimeTTL %u for %##s (%s)",
-               rrTTL, rrsigTTL, rrsigOrigTTL, rrsigTimeTTL, qname->c, DNSTypeName(qtype));
-        return;
-    }
-    LogDNSSEC("SetTTLRRSet: TTL rrtl %u, rrsigTTL %u, rrsigOrigTTL %u, rrsigTimeTTL %u for %##s (%s)",
-        rrTTL, rrsigTTL, rrsigOrigTTL, rrsigTimeTTL, qname->c, DNSTypeName(qtype));
-
-    if (status == DNSSEC_Bogus)
-    {
-        rrTTL = RR_BOGUS_TTL;
-        LogDNSSEC("SetTTLRRSet: setting to bogus TTL %d", rrTTL);
-    }
-
-    if (rrsigv)
-    {
-        if (rrsigTTL < rrTTL)
-            rrTTL = rrsigTTL;
-        if (rrsigOrigTTL < rrTTL)
-            rrTTL = rrsigOrigTTL;
-        if (rrsigTimeTTL < rrTTL)
-            rrTTL = rrsigTimeTTL;
-    }
-
-    // Set the rrsig's TTL. For NSEC records, rrsigRR is NULL which means it expires when
-    // the negative cache record expires.
-    if (rrsigRR)
-    {
-        rrsigRR->resrec.rroriginalttl = rrTTL;
-        rrsigRR->TimeRcvd = now;
-        rrsigRR->UnansweredQueries = 0;
-    }
-
-    // Find the RRset and set its TTL
-    for (rr = cg ? cg->members : mDNSNULL; rr; rr=rr->next)
-    {
-        if (SameNameCacheRecordAnswersQuestion(rr, &question))
-        {
-            LogDNSSEC("SetTTLRRSet: Setting the TTL %d for %s, question %##s (%s)", rrTTL, CRDisplayString(m, rr),
-                      question.qname.c, DNSTypeName(rr->resrec.rrtype));
-            rr->resrec.rroriginalttl = rrTTL;
-            rr->TimeRcvd = now;
-            rr->UnansweredQueries = 0;
-            SetNextCacheCheckTimeForRecord(m, rr);
-        }
-    }
-}
-
-mDNSlocal void FinishDNSSECVerification(mDNS *const m, DNSSECVerifier *dv)
-{
-    RRVerifier *resultKey;
-    RRVerifier *resultRRSig;
-
-    LogDNSSEC("FinishDNSSECVerification: all rdata sets available for sig verification for %##s (%s)",
-              dv->origName.c, DNSTypeName(dv->origType));
-
-    // Stop outstanding query if one exists
-    if (dv->q.ThisQInterval != -1)
-        mDNS_StopQuery(m, &dv->q);
-    if (ValidateSignature(dv, &resultKey, &resultRRSig) == mStatus_NoError)
-    {
-        rdataDNSKey *key;
-        mDNSu16 tag;
-        key = (rdataDNSKey *)resultKey->rdata;
-        tag = (mDNSu16)keytag((mDNSu8 *)key, resultKey->rdlength);
-
-        LogDNSSEC("FinishDNSSECVerification: RRSIG validated by DNSKEY tag %d, %##s (%s)", tag, dv->rrset->name.c,
-                  DNSTypeName(dv->rrset->rrtype));
-
-        if (TrustedKey(m, dv) == mStatus_NoError)
-        {
-            // Need to call this after we called TrustedKey, as AuthChainAdd
-            // unlinks the resultKey and resultRRSig
-            if (!AuthChainAdd(dv, resultKey, resultRRSig))
-            {
-                dv->DVCallback(m, dv, DNSSEC_Bogus);
-                return;
-            }
-            // The callback will be called when NSEC verification is done.
-            if ((dv->flags & WILDCARD_PROVES_ANSWER_EXPANDED))
-            {
-                WildcardAnswerProof(m, dv);
-                return;
-            }
-            else
-            {
-                dv->DVCallback(m, dv, DNSSEC_Secure);
-                return;
-            }
-        }
-        if (!ValidateDS(dv))
-        {
-            // Need to call this after we called ValidateDS, as AuthChainAdd
-            // unlinks the resultKey and resultRRSig
-            if (!AuthChainAdd(dv, resultKey, resultRRSig))
-            {
-                dv->DVCallback(m, dv, DNSSEC_Bogus);
-                return;
-            }
-            FreeDNSSECVerifierRRSets(dv);
-            dv->recursed++;
-            if (dv->recursed < MAX_RECURSE_COUNT)
-            {
-                LogDNSSEC("FinishDNSSECVerification: Recursion level %d for %##s (%s)", dv->recursed, dv->origName.c,
-                          DNSTypeName(dv->origType));
-                VerifySignature(m, dv, &dv->q);
-                return;
-            }
-        }
-        else
-        {
-            LogDNSSEC("FinishDNSSECVerification: ValidateDS failed %##s (%s)", dv->rrset->name.c, DNSTypeName(dv->rrset->rrtype));
-            dv->DVCallback(m, dv, DNSSEC_Bogus);
-            return;
-        }
-    }
-    else
-    {
-        LogDNSSEC("FinishDNSSECVerification: Could not validate the rrset %##s (%s)", dv->origName.c, DNSTypeName(dv->origType));
-        dv->DVCallback(m, dv, DNSSEC_Bogus);
-        return;
-    }
-}
-
-mDNSexport void StartDNSSECVerification(mDNS *const m, void *context)
-{
-    mDNSBool done;
-    DNSSECVerifier *dv = (DNSSECVerifier *)context;
-
-    done = GetAllRRSetsForVerification(m, dv);
-    if (done)
-    {
-        if (dv->next != RRVS_done)
-            LogMsg("StartDNSSECVerification: ERROR!! dv->next is not done");
-        else
-            LogDNSSEC("StartDNSSECVerification: all rdata sets available for sig verification");
-        FinishDNSSECVerification(m, dv);
-        return;
-    }
-    else debugdnssec("StartDNSSECVerification: all rdata sets not available for sig verification next %d", dv->next);
-}
-
-mDNSexport char *DNSSECStatusName(DNSSECStatus status)
-{
-    switch (status)
-    {
-    case DNSSEC_Secure: return "Secure";
-    case DNSSEC_Insecure: return "Insecure";
-    case DNSSEC_Indeterminate: return "Indeterminate";
-    case DNSSEC_Bogus: return "Bogus";
-    default: return "Invalid";
-    }
-}
-
-// We could not use GenerateNegativeResponse as it assumes m->CurrentQuestion to be set. Even if
-// we change that, we needs to fix its callers and so on. It is much simpler to call the callback.
-mDNSlocal void DeliverDNSSECStatus(mDNS *const m, DNSSECVerifier *dv, ResourceRecord *answer, DNSSECStatus status)
-{
-
-    // Can't use m->CurrentQuestion as it may already be in use
-    if (m->ValidationQuestion)
-        LogMsg("DeliverDNSSECStatus: ERROR!! m->ValidationQuestion already set: %##s (%s)",
-               m->ValidationQuestion->qname.c, DNSTypeName(m->ValidationQuestion->qtype));
-
-    BumpDNSSECStats(m, kStatsActionSet, kStatsTypeStatus, status);
-    BumpDNSSECStats(m, kStatsActionSet, kStatsTypeExtraPackets, dv->NumPackets);
-    mDNS_Lock(m);
-    BumpDNSSECStats(m, kStatsActionSet, kStatsTypeLatency, m->timenow - dv->StartTime);
-    mDNS_Unlock(m);
-
-    m->ValidationQuestion = m->Questions;
-    while (m->ValidationQuestion && m->ValidationQuestion != m->NewQuestions)
-    {
-        DNSQuestion *q = m->ValidationQuestion;
-
-        if (q->ValidatingResponse || !q->ValidationRequired ||
-           (q->ValidationState != DNSSECValInProgress) || !ResourceRecordAnswersQuestion(answer, q))
-        {
-            m->ValidationQuestion = q->next;
-            continue;
-        }
-
-        q->ValidationState = DNSSECValDone;
-        q->ValidationStatus = status;
-
-        MakeNegativeCacheRecord(m, &largerec.r, &q->qname, q->qnamehash, q->qtype, q->qclass, 60, mDNSInterface_Any, mDNSNULL);
-        if (q->qtype == answer->rrtype || status != DNSSEC_Secure)
-        {
-            LogDNSSEC("DeliverDNSSECStatus: Generating dnssec status %s for %##s (%s)", DNSSECStatusName(status),
-                q->qname.c, DNSTypeName(q->qtype));
-            if (q->QuestionCallback)
-            {
-                if (q->DNSSECAuthInfo)
-                    FreeDNSSECAuthChainInfo((AuthChain *)q->DNSSECAuthInfo);
-                q->DNSSECAuthInfo = AuthChainCopy(dv->ac);
-                q->DAIFreeCallback = FreeAuthChain;
-                q->QuestionCallback(m, q, &largerec.r.resrec, QC_dnssec);
-            }
-        }
-        else if (FollowCNAME(q, answer, QC_add))
-        {
-            LogDNSSEC("DeliverDNSSECStatus: Following CNAME dnssec status %s for %##s (%s)", DNSSECStatusName(status),
-                q->qname.c, DNSTypeName(q->qtype));
-            mDNS_Lock(m); 
-            AnswerQuestionByFollowingCNAME(m, q, answer);
-            mDNS_Unlock(m);
-        }
-
-        if (m->ValidationQuestion == q)    // If m->ValidationQuestion was not auto-advanced, do it ourselves now
-            m->ValidationQuestion = q->next;
-    }
-    m->ValidationQuestion = mDNSNULL;
-}
-
-// There is no work to be done if we could not validate DNSSEC (as the actual response for
-// the query has already been delivered) except in the case of CNAMEs where we did not follow
-// CNAMEs until we finished the DNSSEC processing.
-mDNSlocal void DNSSECNoResponse(mDNS *const m, DNSSECVerifier *dv)
-{
-    CacheGroup *cg;
-    CacheRecord *cr;
-    mDNSu32 namehash;
-    ResourceRecord *answer = mDNSNULL;
-
-    LogDNSSEC("DNSSECNoResponse: called");
-
-    if (dv->ValidationRequired != DNSSEC_VALIDATION_SECURE_OPTIONAL)
-    {
-        LogMsg("DNSSECNoResponse: ERROR!! ValidationRequired incorrect %d", dv->ValidationRequired);
-        return;
-    }
-
-    BumpDNSSECStats(m, kStatsActionSet, kStatsTypeStatus, DNSSEC_NoResponse);
-
-    namehash = DomainNameHashValue(&dv->origName);
-
-    cg = CacheGroupForName(m, namehash, &dv->origName);
-    if (!cg)
-    {
-        LogDNSSEC("DNSSECNoResponse: cg NULL for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType));
-        goto done;
-    }
-
-    InitializeQuestion(m, &dv->q, dv->InterfaceID, &dv->origName, dv->origType, mDNSNULL, mDNSNULL);
-
-    // We don't have to reset ValidatingResponse (unlike in DeliverDNSSECStatus) as there are no
-    // RRSIGs that can match the original question
-    for (cr = cg->members; cr; cr = cr->next)
-    {
-        if (SameNameCacheRecordAnswersQuestion(cr, &dv->q))
-        {
-            answer = &cr->resrec;
-            break;
-        }
-    }
-
-    // It is not an error for things to disappear underneath
-    if (!answer)
-    {
-        LogDNSSEC("DNSSECNoResponse: answer NULL for %##s, %s", dv->origName.c, DNSTypeName(dv->origType));
-        goto done;
-    }
-    if (answer->rrtype == kDNSType_RRSIG)
-    {
-        LogDNSSEC("DNSSECNoResponse: RRSIG present for %##s, %s", dv->origName.c, DNSTypeName(dv->origType));
-        goto done;
-    }
-
-    // Can't use m->CurrentQuestion as it may already be in use
-    if (m->ValidationQuestion)
-        LogMsg("DNSSECNoResponse: ERROR!! m->ValidationQuestion already set: %##s (%s)",
-               m->ValidationQuestion->qname.c, DNSTypeName(m->ValidationQuestion->qtype));
-
-    m->ValidationQuestion = m->Questions;
-    while (m->ValidationQuestion && m->ValidationQuestion != m->NewQuestions)
-    {
-        DNSQuestion *q = m->ValidationQuestion;
-
-        if (q->ValidatingResponse || !q->ValidationRequired ||
-           (q->ValidationState != DNSSECValInProgress) || !ResourceRecordAnswersQuestion(answer, q))
-        {
-            m->ValidationQuestion = q->next;
-            continue;
-        }
-
-        // If we could not validate e.g., zone was not signed or bad delegation etc., 
-        // disable validation. Ideally, for long outstanding questions, we should try again when
-        // we switch networks. But for now, keep it simple.
-        // 
-        // Note: If we followed a CNAME with no dnssec protection, it is even more important that
-        // we disable validation as we don't want to deliver a "secure" dnssec response later e.g.,
-        // it is possible that the CNAME is not secure but the address records are secure. In this
-        // case, we don't want to deliver the secure response later as we followed a CNAME that was
-        // not protected with DNSSEC.
-
-        q->ValidationRequired = 0;
-        q->ValidationState = DNSSECValNotRequired;
-        
-        if (FollowCNAME(q, answer, QC_add))
-        {
-            LogDNSSEC("DNSSECNoResponse: Following CNAME for %##s (%s)", q->qname.c, DNSTypeName(q->qtype));
-
-            mDNS_Lock(m); 
-            AnswerQuestionByFollowingCNAME(m, q, answer);
-            mDNS_Unlock(m);
-        }
-
-        if (m->ValidationQuestion == q)    // If m->ValidationQuestion was not auto-advanced, do it ourselves now
-            m->ValidationQuestion = q->next;
-    }
-    m->ValidationQuestion = mDNSNULL;
-
-done:
-    FreeDNSSECVerifier(m, dv);
-}
-
-mDNSlocal void DNSSECPositiveValidationCB(mDNS *const m, DNSSECVerifier *dv, CacheGroup *cg, ResourceRecord *answer, DNSSECStatus status)
-{
-    RRVerifier *rrset;
-    RRVerifier *rv;
-    CacheRecord *cr;
-    mDNSu16 rrtype, rrclass;
-    CacheRecord *const lrr = &largerec.r;
-
-    LogDNSSEC("DNSSECPositiveValidationCB: called %s for %##s (%s)", DNSSECStatusName(status), dv->origName.c, DNSTypeName(dv->origType));
-
-    //
-    // 1. Check to see if the rrset that was validated is the same as in cache. If they are not same,
-    //    this validation result is not valid. When the rrset changed while the validation was in
-    //    progress, the act of delivering the changed rrset again should have kicked off another
-    //    verification.
-    //
-    // 2. Walk the question list to find the matching question. The original question that started
-    //    the DNSSEC verification may or may not be there. As long as there is a matching question
-    //    and waiting for the response, deliver the response.
-    //
-    // 3. If we are answering with CNAME, it is time to follow the CNAME if the response is secure
-
-    if (!dv->ac || status == DNSSEC_Insecure)
-    {
-        // For Insecure status, the auth chain contains information about the trust
-        // chain starting from the known trust anchor. The rrsets are not related to
-        // the origName like in Bogus or Secure.
-        if (!answer)
-            LogMsg("DNSSECPositiveValidationCB: ERROR: answer NULL");
-    }
-    else
-    {
-        if (!dv->ac->rrset)
-        {
-            LogMsg("DNSSECPositiveValidationCB: ERROR!! Validated RRSET NULL");
-            goto done;
-        }
-
-        rrset = dv->ac->rrset;
-        rrtype = rrset->rrtype;
-        rrclass = rrset->rrclass;
-    
-        lrr->resrec.name = &largerec.namestorage;
-
-        for (rv = dv->ac->rrset; rv; rv = rv->next)
-            rv->found = 0;
-
-        // Check to see if we can find all the elements in the rrset
-        for (cr = cg ? cg->members : mDNSNULL; cr; cr = cr->next)
-        {
-            if (cr->resrec.rrtype == rrtype && cr->resrec.rrclass == rrclass)
-            {
-                for (rv = dv->ac->rrset; rv; rv = rv->next)
-                {
-                    if (rv->rdlength == cr->resrec.rdlength && rv->rdatahash == cr->resrec.rdatahash)
-                    {
-                        lrr->resrec.namehash = rv->namehash;
-                        lrr->resrec.rrtype = rv->rrtype;
-                        lrr->resrec.rrclass = rv->rrclass;
-                        lrr->resrec.rdata = (RData*)&lrr->smallrdatastorage;
-                        lrr->resrec.rdata->MaxRDLength = MaximumRDSize;
-
-                        // Convert the "rdata" to a suitable form before we can call SameRDataBody which expects
-                        // some of the resource records in host order and also domainnames fully expanded. We
-                        // converted the resource records into network order for verification purpose and hence
-                        // need to convert them back again before comparing them.
-                        if (!SetRData(mDNSNULL, rv->rdata, rv->rdata + rv->rdlength, &largerec, rv->rdlength))
-                        {
-                            LogMsg("DNSSECPositiveValidationCB: SetRData failed for %##s (%s)", rv->name.c, DNSTypeName(rv->rrtype));
-                        }
-                        else if (SameRDataBody(&cr->resrec, &lrr->resrec.rdata->u, SameDomainName))
-                        {
-                            answer = &cr->resrec;
-                            rv->found = 1;
-                            break;
-                        }
-                    }
-                }
-                if (!rv)
-                {
-                    // The validated rrset does not have the element in the cache, re-validate
-                    LogDNSSEC("DNSSECPositiveValidationCB: CacheRecord %s, not found in the validated set", CRDisplayString(m, cr));
-                    goto done;
-                }
-            }
-        }
-        // Check to see if we have elements that were not in the cache
-        for (rv = dv->ac->rrset; rv; rv = rv->next)
-        {
-            if (!rv->found)
-            {
-                // We had more elements in the validated set, re-validate
-                LogDNSSEC("DNSSECPositiveValidationCB: Record %##s (%s) not found in the cache", rv->name.c, DNSTypeName(rv->rrtype));
-                goto done;
-            }
-        }
-    }
-
-    // It is not an error for things to disappear underneath
-    if (!answer)
-    {
-        LogDNSSEC("DNSSECPositiveValidationCB: answer NULL for %##s, %s", dv->origName.c, DNSTypeName(dv->origType));
-        goto done;
-    }
-
-    DeliverDNSSECStatus(m, dv, answer, status);
-    SetTTLRRSet(m, dv, status);
-
-done:
-    FreeDNSSECVerifier(m, dv);
-}
-
-mDNSlocal void DNSSECNegativeValidationCB(mDNS *const m, DNSSECVerifier *dv, CacheGroup *cg, ResourceRecord *answer, DNSSECStatus status)
-{
-    RRVerifier *rv;
-    CacheRecord *cr;
-    mDNSu16 rrtype, rrclass;
-    AuthChain *ac;
-
-    LogDNSSEC("DNSSECNegativeValidationCB: called %s for %##s (%s)", DNSSECStatusName(status), dv->origName.c, DNSTypeName(dv->origType));
-
-    if (dv->parent)
-    {
-        // When NSEC/NSEC3s validation is completed, it calls the parent's DVCallback with the
-        // parent DNSSECVerifier which is the original one that started the verification. It itself
-        // should not have a parent. If the NSEC/NSEC3 validation results in another NSEC/NSEC3
-        // validation, it should chain up via the dv->parent all the way to the top.
-        LogMsg("DNSSECNegativeValidationCB: ERROR!! dv->parent is set for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType));
-        goto done;
-    }
-
-    // 1. Locate the negative cache record and check the cached NSEC/NSEC3 records to see if it matches the
-    //    NSEC/NSEC3s that were valiated. If the cached NSEC/NSEC3s changed while the validation was in progress,
-    //    we ignore the validation results.
-    //
-    // 2. Walk the question list to find the matching question. The original question that started
-    //    the DNSSEC verification may or may not be there. As long as there is a matching question
-    //    and waiting for the response, deliver the response.
-    //
-    if (!dv->ac || status == DNSSEC_Insecure)
-    {
-        // For Insecure status, the auth chain contains information about the trust
-        // chain starting from the known trust anchor. The rrsets are not related to
-        // the origName like in Bogus or Secure.
-        if (!answer)
-            LogMsg("DNSSECNegativeValidationCB: ERROR: answer NULL");
-    }
-    else
-    {
-        if (!dv->ac->rrset)
-        {
-            LogMsg("DNSSECNegativeValidationCB: ERROR!! Validated RRSET NULL");
-            goto done;
-        }
-
-        rrtype = dv->origType;
-        rrclass = dv->ac->rrset->rrclass;
-
-        for (ac = dv->ac; ac; ac = ac->next)
-        {
-            for (rv = ac->rrset; rv; rv = rv->next)
-            {
-                if (rv->rrtype == kDNSType_NSEC || rv->rrtype == kDNSType_NSEC3)
-                {
-                    LogDNSSEC("DNSSECNegativeValidationCB: Record %p %##s (%s) marking zero", rv, rv->name.c, DNSTypeName(rv->rrtype));
-                    rv->found = 0;
-                }
-            }
-        }
-
-        // Check to see if we can find all the elements in the rrset
-        for (cr = cg->members; cr; cr = cr->next)
-        {
-            if (cr->resrec.RecordType == kDNSRecordTypePacketNegative && 
-                cr->resrec.rrtype == rrtype && cr->resrec.rrclass == rrclass)
-            {
-                CacheRecord *ncr;
-                for (ncr = cr->nsec; ncr; ncr = ncr->next)
-                {
-                    // We have RRSIGs for the NSECs cached there too
-                    if (ncr->resrec.rrtype != kDNSType_NSEC && ncr->resrec.rrtype != kDNSType_NSEC3)
-                        continue;
-                    for (ac = dv->ac; ac; ac = ac->next)
-                    {
-                        for (rv = ac->rrset; rv; rv = rv->next)
-                        {
-                            if ((rv->rrtype == kDNSType_NSEC || rv->rrtype == kDNSType_NSEC3) && rv->rdlength == ncr->resrec.rdlength &&
-                                rv->rdatahash == ncr->resrec.rdatahash)
-                            {
-                                if (SameDomainName(ncr->resrec.name, &rv->name) &&
-                                    SameRDataBody(&ncr->resrec, (const RDataBody *)rv->rdata, SameDomainName))
-                                {
-                                    LogDNSSEC("DNSSECNegativeValidationCB: Record %p %##s (%s) marking one", rv, rv->name.c, DNSTypeName(rv->rrtype));
-                                    answer = &cr->resrec;
-                                    rv->found = 1;
-                                    break;
-                                }
-                            }
-                        }
-                        if (rv)
-                            break;
-                    }
-                }
-                if (!rv)
-                {
-                    // The validated rrset does not have the element in the cache, re-validate
-                    LogDNSSEC("DNSSECNegativeValidationCB: CacheRecord %s, not found in the validated set", CRDisplayString(m, cr));
-                    goto done;
-                }
-            }
-        }
-        // Check to see if we have elements that were not in the cache
-        for (ac = dv->ac; ac; ac = ac->next)
-        {
-            for (rv = ac->rrset; rv; rv = rv->next)
-            {
-                if (rv->rrtype == kDNSType_NSEC || rv->rrtype == kDNSType_NSEC3)
-                {
-                    if (!rv->found)
-                    {
-                        // We had more elements in the validated set, re-validate
-                        LogDNSSEC("DNSSECNegativeValidationCB: Record %p %##s (%s) not found in the cache", rv, rv->name.c, DNSTypeName(rv->rrtype));
-                        goto done;
-                    }
-                    rv->found = 0;
-                }
-            }
-        }
-    }
-
-    // It is not an error for things to disappear underneath
-    if (!answer)
-    {
-        LogDNSSEC("DNSSECNegativeValidationCB: answer NULL for %##s, %s", dv->origName.c, DNSTypeName(dv->origType));
-        goto done;
-    }
-
-    DeliverDNSSECStatus(m, dv, answer, status); 
-    SetTTLRRSet(m, dv, status);
-
-done:
-    FreeDNSSECVerifier(m, dv);
-}
-
-mDNSlocal void DNSSECValidationCB(mDNS *const m, DNSSECVerifier *dv, DNSSECStatus status)
-{
-    mDNSu32 namehash;
-    CacheGroup *cg;
-    CacheRecord *cr;
-
-    LogDNSSEC("DNSSECValidationCB: called %s for %##s (%s)", DNSSECStatusName(status), dv->origName.c, DNSTypeName(dv->origType));
-
-    // Currently, if we receive anything other than secure, we abort DNSSEC validation for
-    // the optional case.
-    if (dv->ValidationRequired == DNSSEC_VALIDATION_SECURE_OPTIONAL && status != DNSSEC_Secure)
-    {
-        DNSSECNoResponse(m, dv);
-        return; 
-    }
-
-    if (dv->ValidationRequired == DNSSEC_VALIDATION_SECURE && !dv->InsecureProofDone && status == DNSSEC_Bogus)
-    {
-        dv->InsecureProofDone = 1;
-        ProveInsecure(m, dv, mDNSNULL, mDNSNULL);
-        return;
-    }
-    namehash = DomainNameHashValue(&dv->origName);
-
-    cg = CacheGroupForName(m, namehash, &dv->origName);
-    if (!cg)
-    {
-        LogDNSSEC("DNSSECValidationCB: cg NULL for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType));
-        FreeDNSSECVerifier(m, dv);
-        return;
-    }
-    InitializeQuestion(m, &dv->q, dv->InterfaceID, &dv->origName, dv->origType, mDNSNULL, mDNSNULL);
-    // Need to be reset ValidatingResponse as we are looking for the cache record that would answer
-    // the original question
-    dv->q.ValidatingResponse = mDNSfalse;
-    for (cr = cg->members; cr; cr = cr->next)
-    {
-        if (SameNameCacheRecordAnswersQuestion(cr, &dv->q))
-        {
-            if (cr->resrec.RecordType == kDNSRecordTypePacketNegative)
-                DNSSECNegativeValidationCB(m, dv, cg, &cr->resrec, status);
-            else
-                DNSSECPositiveValidationCB(m, dv, cg, &cr->resrec, status);
-            return;
-        }
-    }
-}
-
-mDNSexport void VerifySignature(mDNS *const m, DNSSECVerifier *dv, DNSQuestion *q)
-{
-    CacheGroup *const cg = CacheGroupForName(m, q->qnamehash, &q->qname);
-    CacheRecord *rr;
-    mDNSBool first = mDNSfalse;
-    static mDNSBool TrustAnchorsUpdated = mDNSfalse;
-
-    LogDNSSEC("VerifySignature called for %##s (%s)", q->qname.c, DNSTypeName(q->qtype));
-    if (!TrustAnchorsUpdated)
-    {
-        TrustAnchorsUpdated = mDNStrue;
-        UpdateTrustAnchors(m);
-    }
-    if (!dv)
-    {
-        first = mDNStrue;
-        if (!q->qDNSServer || q->qDNSServer->isCell)
-        {
-            LogDNSSEC("VerifySignature: Disabled");
-            return;
-        }
-        // We assume that the verifier's question has been initialized here so that ValidateWithNSECS below
-        // knows what it has prove the non-existence of.
-        dv = AllocateDNSSECVerifier(m, &q->qname, q->qtype, q->InterfaceID, q->ValidationRequired, DNSSECValidationCB, VerifySigCallback);
-        if (!dv)
-        {
-            LogMsg("VerifySignature: ERROR!! memory alloc failed");
-            return;
-        }
-    }
-
-    // If we find a CNAME response to the question, remember what qtype
-    // caused the CNAME response. origType is not sufficient as we
-    // recursively validate the response and origType is initialized above
-    // the first time this function is called.
-    dv->currQtype = q->qtype;
-
-    // Walk the cache and get all the rrsets for verification.
-    for (rr = cg ? cg->members : mDNSNULL; rr; rr=rr->next)
-        if (SameNameCacheRecordAnswersQuestion(rr, q))
-        {
-            // We also get called for RRSIGs which matches qtype. We don't need that here as we are
-            // building rrset for matching q->qname. Checking for RRSIG type is important as otherwise
-            // we would miss the CNAME answering any qtype.
-            if (rr->resrec.rrtype == kDNSType_RRSIG && rr->resrec.rrtype != q->qtype)
-            {
-                LogDNSSEC("VerifySignature: Question %##s (%s) answered with RRSIG record %s, not using it", q->qname.c, DNSTypeName(q->qtype), CRDisplayString(m, rr));
-                continue;
-            }
-
-            // See DNSSECRecordAnswersQuestion: This should never happen. NSEC records are
-            // answered directly only when the qtype is NSEC. Otherwise, NSEC records are
-            // used only for denial of existence and hence should go through negative cache
-            // entry.
-            if (rr->resrec.rrtype == kDNSType_NSEC && q->qtype != kDNSType_NSEC)
-            {
-                LogMsg("VerifySignature: ERROR!! Question %##s (%s) answered using NSEC record %s", q->qname.c, DNSTypeName(q->qtype), CRDisplayString(m, rr));
-                continue;
-            }
-
-            // We might get a NSEC response when we first send the query out from the "core" for ValidationRequired
-            // questions. Later as part of validating the response, we might get a NSEC response.
-            if (rr->resrec.RecordType == kDNSRecordTypePacketNegative && DNSSECQuestion(q))
-            {
-                // If we can't find the NSEC, we can't validate. This can happens if we are
-                // behind a non-DNSSEC aware CPE/server.
-                if (!rr->nsec)
-                {
-                    LogDNSSEC("VerifySignature: No nsecs found for %s", CRDisplayString(m, rr));
-                    dv->DVCallback(m, dv, DNSSEC_Bogus);
-                    return;
-                }
-                ValidateWithNSECS(m, dv, rr);
-                return;
-            }
-
-            if (AddRRSetToVerifier(dv, &rr->resrec, mDNSNULL, RRVS_rr) != mStatus_NoError)
-            {
-                dv->DVCallback(m, dv, DNSSEC_Bogus);
-                return;
-            }
-        }
-    if (!dv->rrset)
-    {
-        LogMsg("VerifySignature: rrset mDNSNULL for %##s (%s)", dv->origName.c, DNSTypeName(dv->origType));
-        dv->DVCallback(m, dv, DNSSEC_Bogus);
-        return;
-    }
-    dv->next = RRVS_rrsig;
-    // Delay this so that the mDNS "core" can deliver all the results before
-    // we can deliver the dnssec result
-    if (first)
-    {
-        mDNSPlatformDispatchAsync(m, dv, StartDNSSECVerification);
-    }
-    else
-    {
-        StartDNSSECVerification(m, dv);
-    }
-}
-
-mDNSlocal mDNSBool TrustedKeyPresent(mDNS *const m, DNSSECVerifier *dv)
-{
-    rdataDS *ds;
-    rdataDNSKey *key;
-    TrustAnchor *ta;
-    RRVerifier *keyv;
-
-    // Walk all our trusted DS Records to see if we have a matching DNS KEY record that verifies
-    // the hash. If we find one, verify that this key was used to sign the KEY rrsets in
-    // this zone. Loop till we find one.
-    for (ta = m->TrustAnchors; ta; ta = ta->next)
-    {
-        ds = (rdataDS *)&ta->rds;
-        if ((ds->digestType != SHA1_DIGEST_TYPE) && (ds->digestType != SHA256_DIGEST_TYPE))
-        {
-            LogMsg("TrustedKeyPresent: Unsupported digest %d", ds->digestType);
-            continue;
-        }
-        else
-        {
-            debugdnssec("TrustedKeyPresent: digest type %d", ds->digestType);
-        }
-        for (keyv = dv->key; keyv; keyv = keyv->next)
-        {
-            key = (rdataDNSKey *)keyv->rdata;
-            mDNSu16 tag = (mDNSu16)keytag((mDNSu8 *)key, keyv->rdlength);
-            if (tag != ds->keyTag)
-            {
-                debugdnssec("TrustedKeyPresent:Not a valid keytag %d", tag);
-                continue;
-            }
-            if (!SameDomainName(&keyv->name, &ta->zone))
-            {
-                debugdnssec("TrustedKeyPresent: domainame mismatch key %##s, ta %##s", keyv->name.c, ta->zone.c);
-                continue;
-            }
-            return mDNStrue;
-        }
-    }
-    return mDNSfalse;
-}
-
-mDNSlocal mStatus TrustedKey(mDNS *const m, DNSSECVerifier *dv)
-{
-    mDNSu8 *digest;
-    int digestLen;
-    domainname name;
-    rdataRRSig *rrsig;
-    rdataDS *ds;
-    rdataDNSKey *key;
-    TrustAnchor *ta;
-    RRVerifier *keyv;
-    mStatus algRet;
-    mDNSu32 currTime = mDNSPlatformUTC();
-
-    rrsig = (rdataRRSig *)dv->rrsig->rdata;
-
-    // Walk all our trusted DS Records to see if we have a matching DNS KEY record that verifies
-    // the hash. If we find one, verify that this key was used to sign the KEY rrsets in
-    // this zone. Loop till we find one.
-    for (ta = m->TrustAnchors; ta; ta = ta->next)
-    {
-        ds = (rdataDS *)&ta->rds;
-        if ((ds->digestType != SHA1_DIGEST_TYPE) && (ds->digestType != SHA256_DIGEST_TYPE))
-        {
-            LogMsg("TrustedKey: Unsupported digest %d", ds->digestType);
-            continue;
-        }
-        else
-        {
-            debugdnssec("TrustedKey: Zone %##s, digest type %d, tag %d", ta->zone.c, ds->digestType, ds->keyTag);
-        }
-        for (keyv = dv->key; keyv; keyv = keyv->next)
-        {
-            key = (rdataDNSKey *)keyv->rdata;
-            mDNSu16 tag = (mDNSu16)keytag((mDNSu8 *)key, keyv->rdlength);
-            if (tag != ds->keyTag)
-            {
-                debugdnssec("TrustedKey:Not a valid keytag %d", tag);
-                continue;
-            }
-            if (!SameDomainName(&keyv->name, &ta->zone))
-            {
-                debugdnssec("TrustedKey: domainame mismatch key %##s, ta %##s", keyv->name.c, ta->zone.c);
-                continue;
-            }
-            if (DNS_SERIAL_LT(ta->validUntil, currTime))
-            {
-                LogDNSSEC("TrustedKey: Expired: currentTime %d, ExpireTime %d", (int)currTime, ta->validUntil);
-                continue;
-            }
-            if (DNS_SERIAL_LT(currTime, ta->validFrom))
-            {
-                LogDNSSEC("TrustedKey: Future: currentTime %d, InceptTime %d", (int)currTime, ta->validFrom);
-                continue;
-            }
-
-            if (DNSNameToLowerCase((domainname *)&rrsig->signerName, &name) != mStatus_NoError)
-            {
-                LogMsg("TrustedKey: ERROR!! cannot convert to lower case");
-                continue;
-            }
-
-            if (dv->ctx) AlgDestroy(dv->ctx);
-            dv->ctx = AlgCreate(DIGEST_ALG, ds->digestType);
-            if (!dv->ctx)
-            {
-                LogMsg("TrustedKey: ERROR!! No digest support");
-                continue;
-            }
-            digest = ds->digest;
-            digestLen = ta->digestLen;
-
-            AlgAdd(dv->ctx, name.c, DomainNameLength(&name));
-            AlgAdd(dv->ctx, (const mDNSu8 *)key, keyv->rdlength);
-
-            algRet = AlgVerify(dv->ctx, mDNSNULL, 0, digest, digestLen);
-            AlgDestroy(dv->ctx);
-            dv->ctx = mDNSNULL;
-            if (algRet == mStatus_NoError)
-            {
-                LogDNSSEC("TrustedKey: DS Validated Successfully, need to verify the key %d", tag);
-                // We found the DNS KEY that is authenticated by the DS in our parent zone. Check to see if this key
-                // was used to sign the DNS KEY RRSET. If so, then the keys in our DNS KEY RRSET are valid
-                if (ValidateSignatureWithKeyForAllRRSigs(dv, dv->key, keyv, dv->rrsigKey))
-                {
-                    LogDNSSEC("TrustedKey: DS Validated Successfully %d", tag);
-                    return mStatus_NoError;
-                }
-            }
-        }
-    }
-    return mStatus_NoSuchRecord;
-}
-
-mDNSlocal CacheRecord* NegativeCacheRecordForRR(mDNS *const m, const ResourceRecord *const rr)
-{
-    mDNSu32 namehash;
-    CacheGroup *cg;
-    CacheRecord *cr;
-
-    namehash = DomainNameHashValue(rr->name);
-    cg = CacheGroupForName(m, namehash, rr->name);
-    if (!cg)
-    {
-        LogMsg("NegativeCacheRecordForRR: cg null %##s", rr->name->c);
-        return mDNSNULL;
-    }
-    for (cr=cg->members; cr; cr=cr->next)
-    {
-        if (cr->resrec.RecordType == kDNSRecordTypePacketNegative && (&cr->resrec == rr))
-            return cr;
-    }
-    return mDNSNULL;
-}
-
-mDNSlocal void VerifySigCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord)
-{
-    DNSSECVerifier *dv = (DNSSECVerifier *)question->QuestionContext;
-    mDNSu16 rrtype;
-    CacheRecord *negcr;
-
-    debugdnssec("VerifySigCallback: AddRecord %d, dv %p", AddRecord, dv);
-
-    if (!AddRecord)
-        return;
-
-    // After the first ADD event, we should ideally stop the question. If we don't stop
-    // the question, we might get more callbacks and that can cause problems. For example,
-    // in the first callback, we could start a insecure proof and while that is in progress,
-    // if we get more callbacks, we will try to start another insecure proof. As we already
-    // started an insecure proof, we won't start another but terminate the verification
-    // process where we free the current DNSSECVerifier while the first insecure proof is
-    // still referencing it.
-    //
-    // But there are cases below which might return if we have not received the right answer
-    // yet e.g., no RRSIGs. In that case if the question is stopped, we will never get any
-    // callbacks again and also we leak "dv". Hence it is important that we either process
-    // the result or wait for more results. Note that the question eventually times out
-    // and cleans up the "dv" i.e., we don't wait forever.
-    
-    if (!answer)
-    {
-        LogDNSSEC("VerifySigCallback: Question %##s (%s) no dnssec response", question->qname.c, DNSTypeName(question->qtype));
-        mDNS_StopQuery(m, question);
-        dv->DVCallback(m, dv, DNSSEC_Bogus);
-        return;
-    }
-
-    LogDNSSEC("VerifySigCallback(%p): Called with record %s for question %##s (%s)", dv, RRDisplayString(m, answer), question->qname.c,
-        DNSTypeName(question->qtype));
-    mDNS_Lock(m);
-    if ((m->timenow - question->StopTime) >= 0)
-    {
-        mDNS_Unlock(m);
-        LogDNSSEC("VerifySigCallback: Question %##s (%s) timed out", question->qname.c, DNSTypeName(question->qtype));
-        mDNS_StopQuery(m, question);
-        dv->DVCallback(m, dv, DNSSEC_Bogus);
-        return;
-    }
-    mDNS_Unlock(m);
-
-    if (answer->RecordType == kDNSRecordTypePacketNegative)
-    {
-        CacheRecord *cr;
-        LogDNSSEC("VerifySigCallback: Received a negative answer with record %s, AddRecord %d",
-                  RRDisplayString(m, answer), AddRecord);
-        mDNS_StopQuery(m, question);
-        cr = NegativeCacheRecordForRR(m, answer);
-        if (cr && cr->nsec)
-        {
-            ValidateWithNSECS(m, dv, cr);
-        }
-        else
-        {
-            
-            LogDNSSEC("VerifySigCallback: Missing record (%s) Negative Cache Record %p", RRDisplayString(m, answer), cr);
-            dv->DVCallback(m, dv, DNSSEC_Bogus);
-        }
-        return;
-    }
-
-    if (!dv->rrset)
-    {
-        LogMsg("VerifySigCallback: ERROR!! rrset NULL");
-        mDNS_StopQuery(m, question);
-        dv->DVCallback(m, dv, DNSSEC_Bogus);
-        return;
-    }
-
-    rrtype = answer->rrtype;
-    // Check whether we got any answers for the question. If there are no answers, we
-    // can't do the verification.
-    //
-    // We need to look at the whole rrset for verifying the signatures. This callback gets
-    // called back for each record in the rrset sequentially and we won't know when to start the
-    // verification. Hence, we look for all the records in the rrset ourselves using the
-    // CheckXXX function below. The caller has to ensure that all the records in the rrset are
-    // added to the cache before calling this callback which happens naturally because all
-    // unicast records are marked for DelayDelivery and hence added to the cache before the
-    // callback is done.
-    //
-    // We also need the RRSIGs for the rrset to do the validation. It is possible that the
-    // cache contains RRSIG records but it may not be a valid record when we filter them
-    // in CheckXXX function. For example, some application can query for RRSIG records which
-    // might come back with a partial set of RRSIG records from the recursive server and
-    // they may not be the right ones for the current validation. In this case, we still
-    // need to send the query out to get the right RRSIGs but the "core" should not answer
-    // this query with the same records that we checked and found them to be unusable.
-    //
-    // We handle this in two ways:
-    //
-    // 1) AnswerNewQuestion always sends the "ValidatingResponse" query out bypassing the cache.
-    //
-    // 2) DNSSECRecordAnswersQuestion does not answer a question with RRSIGs matching the
-    //    same name as the query until the typeCovered also matches the query's type.
-    //
-    // NOTE: We use "next - 1" as next always points to what we are going to fetch next and not the one
-    // we are fetching currently
-    switch(dv->next - 1)
-    {
-    case RRVS_rr:
-        // Verification always starts at RRVS_rrsig (which means dv->next points at RRVS_key) as verification does
-        // not begin until we have the main rrset.
-        LogDNSSEC("VerifySigCallback: ERROR!! rrset %##s dv->next is RRVS_rr", dv->rrset->name.c);
-        return;
-    case RRVS_rrsig:
-        // We can get called back with rrtype matching qtype as new records are added to the cache
-        // triggered by other questions. This could potentially mean that the rrset that is being
-        // validated by this "dv" whose rrsets were initialized at the beginning of the verification
-        // may not be the right one. If this case happens, we will detect this at the end of validation
-        // and throw away the validation results. This should not be a common case.
-        if (rrtype != kDNSType_RRSIG)
-        {
-            LogDNSSEC("VerifySigCallback: RRVS_rrsig called with %s", RRDisplayString(m, answer));
-            return;
-        }
-        mDNS_StopQuery(m, question);
-        if (CheckRRSIGForRRSet(m, dv, &negcr) != mStatus_NoError)
-        {
-            LogDNSSEC("VerifySigCallback: Unable to find RRSIG for %##s (%s), question %##s", dv->rrset->name.c,
-                      DNSTypeName(dv->rrset->rrtype), question->qname.c);
-            dv->DVCallback(m, dv, DNSSEC_Bogus);
-            return;
-        }
-        break;
-    case RRVS_key:
-        // We are waiting for the DNSKEY record and hence dv->key should be NULL. If RRSIGs are being
-        // returned first, ignore them for now.
-        if (dv->key)
-            LogDNSSEC("VerifySigCallback: ERROR!! RRVS_key dv->key non-NULL for %##s", question->qname.c);
-        if (rrtype == kDNSType_RRSIG)
-        {
-            LogDNSSEC("VerifySigCallback: RRVS_key rrset type %s, %##s received before DNSKEY", DNSTypeName(rrtype), question->qname.c);
-            return;
-        }
-        if (rrtype != question->qtype)
-        {
-            LogDNSSEC("VerifySigCallback: ERROR!! RRVS_key rrset type %s, %##s not matching qtype %d", DNSTypeName(rrtype), question->qname.c,
-                question->qtype);
-            return;
-        }
-        mDNS_StopQuery(m, question);
-        if (CheckKeyForRRSIG(m, dv, &negcr) != mStatus_NoError)
-        {
-            LogDNSSEC("VerifySigCallback: Unable to find DNSKEY for %##s (%s), question %##s", dv->rrset->name.c,
-                      DNSTypeName(dv->rrset->rrtype), question->qname.c);
-            dv->DVCallback(m, dv, DNSSEC_Bogus);
-            return;
-        }
-        break;
-    case RRVS_rrsig_key:
-        // If we are in RRVS_rrsig_key, it means that we already found the relevant DNSKEYs (dv->key should be non-NULL).
-        // If DNSKEY record is being returned i.e., it means it is being added to the cache, then it can't be in our
-        // list.
-        if (!dv->key)
-            LogDNSSEC("VerifySigCallback: ERROR!! RRVS_rrsig_key dv->key NULL for %##s", question->qname.c);
-        if (rrtype == question->qtype)
-        {
-            LogDNSSEC("VerifySigCallback: RRVS_rrsig_key rrset type %s, %##s", DNSTypeName(rrtype), question->qname.c);
-            CheckOneKeyForRRSIG(dv, answer);
-            return;
-        }
-        if (rrtype != kDNSType_RRSIG)
-        {
-            LogDNSSEC("VerifySigCallback: RRVS_rrsig_key rrset type %s, %##s not matching qtype %d", DNSTypeName(rrtype), question->qname.c,
-                question->qtype);
-            return;
-        }
-        mDNS_StopQuery(m, question);
-        if (CheckRRSIGForKey(m, dv, &negcr) != mStatus_NoError)
-        {
-            LogDNSSEC("VerifySigCallback: Unable to find RRSIG for %##s (%s), question %##s", dv->rrset->name.c,
-                      DNSTypeName(dv->rrset->rrtype), question->qname.c);
-            dv->DVCallback(m, dv, DNSSEC_Bogus);
-            return;
-        }
-        break;
-    case RRVS_ds:
-        if (rrtype == question->qtype)
-        {
-            LogDNSSEC("VerifySigCallback: RRVS_ds rrset type %s, %##s", DNSTypeName(rrtype), question->qname.c);
-        }
-        else
-        {
-            LogDNSSEC("VerifySigCallback: RRVS_ds rrset type %s, %##s received before DS", DNSTypeName(rrtype), question->qname.c);
-        }
-        mDNS_StopQuery(m, question);
-        // It is not an error if we don't find the DS record as we could have
-        // a trusted key. Or this is not a secure delegation which will be handled
-        // below.
-        if (CheckDSForKey(m, dv, &negcr) != mStatus_NoError)
-        {
-            LogDNSSEC("VerifySigCallback: Unable find DS for %##s (%s), question %##s", dv->rrset->name.c,
-                      DNSTypeName(dv->rrset->rrtype), question->qname.c);
-        }
-        // dv->next is already at RRVS_done, so if we "break" from here, we will end up
-        // in FinishDNSSECVerification. We should not do that if we receive a negative
-        // response. For all other cases above, GetAllRRSetsForVerification handles
-        // negative cache record
-        if (negcr)
-        {
-            if (!negcr->nsec)
-            {
-                LogDNSSEC("VerifySigCallback: No nsec records for %##s (DS)", dv->ds->name.c);
-                dv->DVCallback(m, dv, DNSSEC_Bogus);
-                return;
-            }
-            ValidateWithNSECS(m, dv, negcr);
-            return;
-        }
-        break;
-    default:
-        LogDNSSEC("VerifySigCallback: ERROR!! default case rrset %##s question %##s", dv->rrset->name.c, question->qname.c);
-        mDNS_StopQuery(m, question);
-        dv->DVCallback(m, dv, DNSSEC_Bogus);
-        return;
-    }
-    if (dv->next != RRVS_done)
-    {
-        mDNSBool done = GetAllRRSetsForVerification(m, dv);
-        if (done)
-        {
-            if (dv->next != RRVS_done)
-                LogMsg("VerifySigCallback ERROR!! dv->next is not done");
-            else
-                LogDNSSEC("VerifySigCallback: all rdata sets available for sig verification");
-        }
-        else
-        {
-            LogDNSSEC("VerifySigCallback: all rdata sets not available for sig verification");
-            return;
-        }
-    }
-    FinishDNSSECVerification(m, dv);
-}
-
-mDNSlocal TrustAnchor *FindTrustAnchor(mDNS *const m, const domainname *const name)
-{
-    TrustAnchor *ta;
-    TrustAnchor *matchTA = mDNSNULL;
-    TrustAnchor *rootTA = mDNSNULL;
-    int currmatch = 0;
-    int match;
-    mDNSu32 currTime = mDNSPlatformUTC();
-
-    for (ta = m->TrustAnchors; ta; ta = ta->next)
-    {
-        if (DNS_SERIAL_LT(ta->validUntil, currTime))
-        {
-            LogDNSSEC("FindTrustAnchor: Expired: currentTime %d, ExpireTime %d", (int)currTime, ta->validUntil);
-            continue;
-        }
-        if (DNS_SERIAL_LT(currTime, ta->validFrom))
-        {
-            LogDNSSEC("FindTrustAnchor: Future: currentTime %d, InceptTime %d", (int)currTime, ta->validFrom);
-            continue;
-        }
-
-        if (SameDomainName((const domainname *)"\000", &ta->zone))
-            rootTA = ta;
-
-        match = CountLabelsMatch(&ta->zone, name);
-        if (match > currmatch)
-        {
-            currmatch = match;
-            matchTA = ta;
-        }
-    }
-    if (matchTA)
-    {
-        LogDNSSEC("FindTrustAnhcor: matched %##s", matchTA->zone.c);
-        return matchTA;
-    }
-    else if (rootTA)
-    {
-        LogDNSSEC("FindTrustAnhcor: matched rootTA %##s", rootTA->zone.c);
-        return rootTA;
-    }
-    else
-    {
-        LogDNSSEC("FindTrustAnhcor: No Trust Anchor");
-        return mDNSNULL;
-    }
-}
-
-mDNSlocal void DeliverInsecureProofResultAsync(mDNS *const m, void *context)
-{
-    InsecureContext *ic = (InsecureContext *)context;
-    ic->dv->DVCallback(m, ic->dv, ic->status);
-    if (ic->q.ThisQInterval != -1)
-    {
-        LogMsg("DeliverInsecureProofResultAsync: ERROR!! Question %##s (%s) not stopped already", ic->q.qname.c, DNSTypeName(ic->q.qtype));
-        mDNS_StopQuery(m, &ic->q);
-    }
-    mDNSPlatformMemFree(ic);
-}
-
-mDNSlocal void DeliverInsecureProofResult(mDNS *const m, InsecureContext *ic, DNSSECStatus status)
-{
-    // If the status is Bogus, restore the original auth chain before the insecure
-    // proof.
-    if (status == DNSSEC_Bogus)
-    {
-        LogDNSSEC("DeliverInsecureProofResult: Restoring the auth chain");
-        if (ic->dv->ac)
-        {
-            FreeDNSSECAuthChainInfo(ic->dv->ac);
-        }
-        ResetAuthChain(ic->dv);
-        ic->dv->ac = ic->dv->saveac;
-        if (ic->dv->ac)
-        {
-            AuthChain *tmp = ic->dv->ac;
-            AuthChain **tail = &tmp->next;
-            while (tmp->next)
-            {
-                tail = &tmp->next;
-                tmp = tmp->next;
-            }
-            ic->dv->actail = tail;
-        }
-        ic->dv->saveac = mDNSNULL;
-    }
-    else if (ic->dv->saveac)
-    {
-        FreeDNSSECAuthChainInfo(ic->dv->saveac);
-        ic->dv->saveac = mDNSNULL;
-    }
-    ic->status = status;
-    // Stop the question before we schedule the block so that we don't receive additional
-    // callbacks again. Once the block runs, it will free the "ic" and you can't
-    // have another block queued up. This can happen if we receive a callback after we
-    // queue the block below.
-    if (ic->q.ThisQInterval != -1)
-        mDNS_StopQuery(m, &ic->q);
-    mDNSPlatformDispatchAsync(m, ic, DeliverInsecureProofResultAsync);
-}
-
-mDNSlocal mDNSBool AlgorithmSupported(rdataDS *ds)
-{
-    switch(ds->digestType)
-    {
-    case SHA1_DIGEST_TYPE:
-    case SHA256_DIGEST_TYPE:
-        break;
-    default:
-        LogDNSSEC("AlgorithmSupported: Unsupported digest %d", ds->digestType);
-        return mDNSfalse;
-    }
-
-    switch(ds->alg)
-    {
-    case CRYPTO_RSA_NSEC3_SHA1:
-    case CRYPTO_RSA_SHA1:
-    case CRYPTO_RSA_SHA256:
-    case CRYPTO_RSA_SHA512:
-        return mDNStrue;
-    default:
-        LogDNSSEC("AlgorithmSupported: Unsupported algorithm %d", ds->alg);
-        return mDNSfalse;
-    }
-}
-
-// Note: This function is called when DNSSEC results are delivered (from DeliverDNSSECStatus) and we can't deliver DNSSEC result
-// again within this function as "m->ValidationQuestion" is already in use. Hence we should dispatch off the delivery of insecure
-// results asynchronously.
-//
-// Insecure proof callback can deliver either insecure or bogus, but never secure result.
-mDNSlocal void ProveInsecureCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord)
-{
-    InsecureContext *ic = (InsecureContext *)question->QuestionContext;
-    DNSSECVerifier *pdv = ic->dv;
-    AuthChain *ac;
-
-    (void) answer;
-
-    if (!AddRecord)
-        return;
-
-    mDNS_Lock(m);
-    if ((m->timenow - question->StopTime) >= 0)
-    {
-        mDNS_Unlock(m);
-        LogDNSSEC("ProveInsecureCallback: Question %##s (%s) timed out", question->qname.c, DNSTypeName(question->qtype));
-        DeliverInsecureProofResult(m, ic, DNSSEC_Bogus);
-        return;
-    }
-    mDNS_Unlock(m);
-
-    // We only need to handle the actual DNSSEC results and the ones that are secure. Anything else results in
-    // bogus.
-    if (AddRecord != QC_dnssec)
-    {
-        LogDNSSEC("ProveInsecureCallback: Question %##s (%s), AddRecord %d, answer %s", question->qname.c,
-            DNSTypeName(question->qtype), AddRecord, RRDisplayString(m, answer));
-        return;
-    }
-
-    LogDNSSEC("ProveInsecureCallback: ic %p Question %##s (%s), DNSSEC status %s", ic, question->qname.c, DNSTypeName(question->qtype),
-            DNSSECStatusName(question->ValidationStatus));
-
-    // Insecure is delivered for NSEC3 OptOut 
-    if (question->ValidationStatus != DNSSEC_Secure && question->ValidationStatus != DNSSEC_Insecure)
-    {
-        LogDNSSEC("ProveInsecureCallback: Question %##s (%s) returned DNSSEC status %s", question->qname.c,
-            DNSTypeName(question->qtype), DNSSECStatusName(question->ValidationStatus));
-        goto done;
-    }
-    ac = (AuthChain *)question->DNSSECAuthInfo;
-    if (!ac)
-    {
-        LogDNSSEC("ProveInsecureCallback: ac NULL for question %##s, %s", question->qname.c, DNSTypeName(question->qtype));
-        goto done;
-    }
-    if (!ac->rrset)
-    {
-        LogDNSSEC("ProveInsecureCallback: ac->rrset NULL for question %##s, %s", question->qname.c, DNSTypeName(question->qtype));
-        goto done;
-    }
-    if (ac->rrset->rrtype != kDNSType_DS && ac->rrset->rrtype != kDNSType_NSEC && ac->rrset->rrtype != kDNSType_NSEC3)
-    {
-        LogDNSSEC("ProveInsecureCallback: ac->rrset->rrtype %##s (%s) not handled", ac->rrset->name.c,
-            DNSTypeName(ac->rrset->rrtype));
-        goto done;
-    }
-    AuthChainLink(pdv, ac);
-    question->DNSSECAuthInfo = mDNSNULL;
-    if (ac->rrset->rrtype == kDNSType_DS)
-    {
-        rdataDS *ds = (rdataDS *)ac->rrset->rdata;
-
-        // If the delegation is secure, but the underlying zone is signed with an unsupported
-        // algorithm, then we can't verify it. Deliver insecure in that case.
-        if (!AlgorithmSupported(ds))
-        {
-            LogDNSSEC("ProveInsecureCallback: Unsupported algorithm %d or digest %d", ds->alg, ds->digestType);
-            DeliverInsecureProofResult(m, ic, DNSSEC_Insecure);
-            return;
-        }
-
-        // If the delegation is secure and the name that we queried for is same as the original
-        // name that started the insecure proof, then something is not right. We started the
-        // insecure proof e.g., the zone is not signed, but we are able to validate a DS for
-        // the same name which implies that the zone is signed (whose algorithm we support) and
-        // we should not have started the insecurity proof in the first place.
-        if (SameDomainName(&question->qname, &pdv->origName))
-        {
-            LogDNSSEC("ProveInsecureCallback: Insecure proof reached original name %##s, error", question->qname.c);
-            DeliverInsecureProofResult(m, ic, DNSSEC_Bogus);
-            return;
-        }
-
-        LogDNSSEC("ProveInsecureCallback: Trying one more level down");
-        ProveInsecure(m, pdv, ic, mDNSNULL);
-    }
-    else if (ac->rrset->rrtype == kDNSType_NSEC || ac->rrset->rrtype == kDNSType_NSEC3)
-    {
-        CacheRecord *cr;
-
-        if (ac->rrset->rrtype == kDNSType_NSEC)
-            cr = NSECRecordIsDelegation(m, &question->qname, question->qtype);
-        else
-            cr = NSEC3RecordIsDelegation(m, &question->qname, question->qtype);
-               if (cr)
-        {
-            LogDNSSEC("ProveInsecureCallback: Non-existence proved and %s is a delegation for %##s (%s)", CRDisplayString(m, cr),
-                               question->qname.c, DNSTypeName(question->qtype));
-            DeliverInsecureProofResult(m, ic, DNSSEC_Insecure);
-                       return;
-        }
-        // Could be a ENT. Go one more level down to see whether it is a secure delegation or not.
-        if (!SameDomainName(&question->qname, &pdv->origName))
-        {
-            LogDNSSEC("ProveInsecureCallback: Not a delegation %##s (%s), go one more level down", question->qname.c, DNSTypeName(question->qtype));
-            ProveInsecure(m, pdv, ic, mDNSNULL);
-        }
-        else
-        {
-             // Secure denial of existence and the name matches the original query. This means we should have
-             // received an NSEC (if the type does not exist) or signed records (if the name and type exists)
-             // and verified it successfully instead of starting the insecure proof. This could happen e.g.,
-             // Wildcard expanded answer received without NSEC/NSEC3s etc. Also, is it possible that the
-             // zone went from unsigned to signed in a short time ? For now, we return bogus.
-             LogDNSSEC("ProveInsecureCallback: Not a delegation %##s (%s), but reached original name", question->qname.c,
-                 DNSTypeName(question->qtype));
-             DeliverInsecureProofResult(m, ic, DNSSEC_Bogus);
-        }
-    }
-    return;
-done:
-    DeliverInsecureProofResult(m, ic, DNSSEC_Bogus);
-}
-
-// We return Insecure if we don't have a trust anchor or we have a trust anchor and
-// can prove that the delegation is not secure (and hence can't establish the trust
-// chain) or the delegation is possibly secure but we don't have the algorithm support
-// to prove that.
-mDNSexport void ProveInsecure(mDNS *const m, DNSSECVerifier *dv, InsecureContext *ic, domainname *trigger)
-{
-    TrustAnchor *ta;
-    domainname *sname;
-
-    if (ic == mDNSNULL)
-    {
-        ic = (InsecureContext *) mDNSPlatformMemAllocateClear(sizeof(*ic));
-        if (!ic)
-        {
-            LogMsg("mDNSPlatformMemAllocateClear: ERROR!! memory alloc failed for ic");
-            return;
-        }
-        
-        // Save the AuthInfo while we are proving insecure. We don't want to mix up
-        // the auth chain for Bogus and Insecure. If we prove it to be insecure, we
-        // will add the chain corresponding to the insecure proof. Otherwise, we will
-        // restore this chain.
-        if (dv->ac)
-        {
-            if (!dv->saveac)
-            {
-                LogDNSSEC("ProveInsecure: saving authinfo");
-            }
-            else
-            {
-                LogDNSSEC("ProveInsecure: ERROR!! authinfo already set");
-                FreeDNSSECAuthChainInfo(dv->saveac);
-            }
-            dv->saveac = dv->ac;
-            ResetAuthChain(dv);
-        }
-        ic->dv = dv;
-        ic->q.ThisQInterval = -1;
-
-        if (trigger)
-        {
-            LogDNSSEC("ProveInsecure: Setting Trigger %##s", trigger->c);
-            ic->triggerLabelCount = CountLabels(trigger);
-        }
-        else
-        {
-            LogDNSSEC("ProveInsecure: No Trigger");
-            ic->triggerLabelCount = CountLabels(&dv->origName);
-        }
-
-        ta = FindTrustAnchor(m, &dv->origName);
-        if (!ta)
-        {
-            LogDNSSEC("ProveInsecure: TrustAnchor NULL");
-            DeliverInsecureProofResult(m, ic, DNSSEC_Insecure);
-            return;
-        }
-        // We want to skip the labels that is already matched by the trust anchor so
-        // that the first query starts just below the trust anchor
-        ic->skip = CountLabels(&dv->origName) - CountLabels(&ta->zone);
-        if (!ic->skip)
-        {
-            LogDNSSEC("ProveInsecure: origName %##s, skip is zero", dv->origName.c);
-            DeliverInsecureProofResult(m, ic, DNSSEC_Bogus);
-            return;
-        }
-    }
-    // Look for the DS record starting just below the trust anchor.
-    //
-    // 1. If we find an NSEC record, then see if it is a delegation. If it is, then
-    //    we are done. Otherwise, go down one more level.
-    //
-    // 2. If we find a DS record and no algorithm support, return "insecure". Otherwise, go
-    //    down one more level.
-    //
-    sname = (domainname *)SkipLeadingLabels(&dv->origName, (ic->skip ? ic->skip - 1 : 0));
-    if (!sname)
-    {
-        LogDNSSEC("ProveInsecure: sname NULL, origName %##s, skip %d", dv->origName.c, ic->skip);
-        DeliverInsecureProofResult(m, ic, DNSSEC_Bogus);
-        return;
-    }
-
-    // Insecurity proof is started during the normal bottom-up validation when we have a break in the trust
-    // chain e.g., we get NSEC/NSEC3s when looking up a DS record. Insecurity proof is top-down looking
-    // for a break in the trust chain. If we have already tried the validation (before the insecurity
-    // proof started) for this "sname", then don't bother with the proof. This happens sometimes, when 
-    // we can't prove whether a zone is insecurely delegated or not. For example, if we are looking up
-    // host1.secure-nods.secure.example and when we encounter secure-nods, there is no DS record in the
-    // parent. We start the insecurity proof remembering that "secure-nods.secure.example" is the trigger
-    // point. As part of the proof we reach "secure-nods.secure.example". Even though secure.example
-    // prove that the name "secure-nods.secure.example/DS" does not exist,  it can't prove that it is a
-    // delegation. So, we continue one more level down to host1.secure-nods.secure.example and we
-    // realize that we already tried the validation and hence abort here.
-
-    if (CountLabels(sname) > ic->triggerLabelCount)
-    {
-        LogDNSSEC("ProveInsecure: Beyond the trigger current name %##s, origName %##s", sname->c, dv->origName.c);
-        DeliverInsecureProofResult(m, ic, DNSSEC_Bogus);
-        return;
-    }
-
-    LogDNSSEC("ProveInsecure: OrigName %##s (%s), Current %##s", dv->origName.c, DNSTypeName(dv->origType), sname->c);
-    ic->skip--;
-    InitializeQuestion(m, &ic->q, dv->InterfaceID, sname, kDNSType_DS, ProveInsecureCallback, ic);
-    ic->q.ValidationRequired = DNSSEC_VALIDATION_INSECURE;
-    ic->q.ValidatingResponse = 0;
-    ic->q.DNSSECAuthInfo = mDNSNULL;
-    mDNS_StartQuery(m, &ic->q);
-}
-
-mDNSexport void BumpDNSSECStats(mDNS *const m, DNSSECStatsAction action, DNSSECStatsType type, mDNSu32 value)
-{
-    switch (type)
-    {
-    case kStatsTypeMemoryUsage:
-        if (action == kStatsActionIncrement)
-        {
-            m->DNSSECStats.TotalMemUsed += value;
-        }
-        else if (action == kStatsActionDecrement)
-        {
-            m->DNSSECStats.TotalMemUsed -= value;
-        }
-        break;
-    case kStatsTypeLatency:
-        if (action == kStatsActionSet)
-        {
-            if (value <= 4)
-            {
-                m->DNSSECStats.Latency0++;
-            }
-            else if (value <= 9)
-            {
-                m->DNSSECStats.Latency5++;
-            }
-            else if (value <= 19)
-            {
-                m->DNSSECStats.Latency10++;
-            }
-            else if (value <= 49)
-            {
-                m->DNSSECStats.Latency20++;
-            }
-            else if (value <= 99)
-            {
-                m->DNSSECStats.Latency50++;
-            }
-            else
-            {
-                m->DNSSECStats.Latency100++;
-            }
-        }
-        break;
-    case kStatsTypeExtraPackets:
-        if (action == kStatsActionSet)
-        {
-            if (value <= 2)
-            {
-                m->DNSSECStats.ExtraPackets0++;
-            }
-            else if (value <= 6)
-            {
-                m->DNSSECStats.ExtraPackets3++;
-            }
-            else if (value <= 9)
-            {
-                m->DNSSECStats.ExtraPackets7++;
-            }
-            else
-            {
-                m->DNSSECStats.ExtraPackets10++;
-            }
-        }
-        break;
-    case kStatsTypeStatus:
-        if (action == kStatsActionSet)
-        {
-            switch(value)
-            {
-            case DNSSEC_Secure:
-                m->DNSSECStats.SecureStatus++;
-                break;
-            case DNSSEC_Insecure:
-                m->DNSSECStats.InsecureStatus++;
-                break;
-            case DNSSEC_Indeterminate:
-                m->DNSSECStats.IndeterminateStatus++;
-                break;
-            case DNSSEC_Bogus:
-                m->DNSSECStats.BogusStatus++;
-                break;
-            case DNSSEC_NoResponse:
-                m->DNSSECStats.NoResponseStatus++;
-                break;
-            default:
-                LogMsg("BumpDNSSECStats: unknown status %d", value);
-            }
-        }
-        break;
-    case kStatsTypeMsgSize:
-        if (action == kStatsActionSet)
-        {
-            if (value <= 1024)
-            {
-                m->DNSSECStats.MsgSize0++;
-            }
-            else if (value <= 2048)
-            {
-                m->DNSSECStats.MsgSize1++;
-            }
-            else
-            {
-                m->DNSSECStats.MsgSize2++;
-            }
-        }
-        break;
-    case kStatsTypeProbe:
-        if (action == kStatsActionIncrement)
-        {
-            m->DNSSECStats.NumProbesSent += value;
-        }
-        break;
-    default:
-        LogMsg("BumpDNSSECStats: unknown type %d", type);
-    }
-    return;
-}
-
-#else // !DNSSEC_DISABLED
-
-mDNSexport void VerifySignature(mDNS *const m, DNSSECVerifier *dv, DNSQuestion *q)
-{
-    (void)m;
-    (void)dv;
-    (void)q;
-}
-
-mDNSexport void BumpDNSSECStats(mDNS *const m, DNSSECStatsAction action, DNSSECStatsType type, mDNSu32 value)
-{
-    (void)m;
-    (void)action;
-    (void)type;
-    (void)value;
-}
-
-mDNSexport void InitializeQuestion(mDNS *const m, DNSQuestion *question, mDNSInterfaceID InterfaceID, const domainname *qname, mDNSu16 qtype, mDNSQuestionCallback *callback, void *context)
-{
-    (void) m;
-    (void) question;
-    (void) InterfaceID;
-    (void) qname;
-    (void) qtype;
-    (void) callback;
-    (void) context;
-}
-
-mDNSexport char *DNSSECStatusName(DNSSECStatus status)
-{
-    (void) status;
-    
-    return mDNSNULL;
-}
-
-#endif // !DNSSEC_DISABLED
diff --git a/mDNSCore/dnssec.h b/mDNSCore/dnssec.h
deleted file mode 100644 (file)
index c34ad67..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-/* -*- Mode: C; tab-width: 4 -*-
- *
- * Copyright (c) 2011-2013 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __DNSSEC_H
-#define __DNSSEC_H
-
-#include "CryptoAlg.h"
-
-typedef enum
-{
-    RRVS_rr, RRVS_rrsig, RRVS_key, RRVS_rrsig_key, RRVS_ds, RRVS_done,
-} RRVerifierSet;
-
-typedef struct RRVerifier_struct RRVerifier;
-typedef struct DNSSECVerifier_struct DNSSECVerifier;
-typedef struct AuthChain_struct AuthChain;
-typedef struct InsecureContext_struct InsecureContext;
-
-struct RRVerifier_struct
-{
-    RRVerifier *next;
-    mDNSu16 rrtype;
-    mDNSu16 rrclass;
-    mDNSu32 rroriginalttl;
-    mDNSu16 rdlength;
-    mDNSu16 found;
-    mDNSu32 namehash;
-    mDNSu32 rdatahash;
-    domainname name;
-    mDNSu8  *rdata;
-};
-
-// Each AuthChain element has one rrset (with multiple resource records of same type), rrsig and key
-// that validates the rrset.
-struct AuthChain_struct
-{
-       AuthChain  *next;               // Next element in the chain
-       RRVerifier *rrset;              // RRSET that is authenticated
-       RRVerifier *rrsig;              // Signature for that RRSET
-       RRVerifier *key;                // Public key for that RRSET
-};
-
-#define ResetAuthChain(dv) {    \
-    (dv)->ac = mDNSNULL;        \
-    (dv)->actail = &((dv)->ac); \
-}
-
-typedef void DNSSECVerifierCallback (mDNS *const m, DNSSECVerifier *dv, DNSSECStatus status);
-//
-// When we do a validation for a question, there might be additional validations that needs to be done e.g.,
-// wildcard expanded answer. It is also possible that in the case of nsec we need to prove both that a wildcard
-// does not apply and the closest encloser proves that name does not exist. We identify these with the following
-// flags.
-//
-// Note: In the following, by "marking the validation", we mean that as part of validation we need to prove
-// the ones that are marked with.
-//
-// A wildcard may be used to answer a question. In that case, we need to verify that the right wildcard was
-// used in answering the question. This is done by marking the validation with WILDCARD_PROVES_ANSWER_EXPANDED.
-//
-// Sometimes we get a NXDOMAIN response. In this case, we may have a wildcard where we need to prove
-// that the wildcard proves that the name does not exist. This is done by marking the validation with
-// WILDCARD_PROVES_NONAME_EXISTS.
-//
-// In the case of NODATA error, sometimes the name may exist but the query type does not exist. This is done by
-// marking the validation with NSEC_PROVES_NOTYPE_EXISTS.
-//
-// In both NXDOMAIN and NODATA proofs, we may have to prove that the NAME does not exist. This is done by marking
-// the validation with NSEC_PROVES_NONAME_EXISTS.
-//
-#define WILDCARD_PROVES_ANSWER_EXPANDED 0x00000001
-#define WILDCARD_PROVES_NONAME_EXISTS   0x00000002
-#define NSEC_PROVES_NOTYPE_EXISTS       0x00000004
-#define NSEC_PROVES_NONAME_EXISTS       0x00000008
-#define NSEC3_OPT_OUT                                  0x00000010 // OptOut was set in NSEC3
-
-struct DNSSECVerifier_struct
-{
-    domainname origName;            // Original question name that needs verification
-    mDNSu16 origType;               // Original question type corresponding to origName
-    mDNSu16 currQtype;              // Current question type that is being verified
-    mDNSInterfaceID InterfaceID;    // InterfaceID of the question
-    DNSQuestion q;
-    mDNSu8 recursed;                // Number of times recursed during validation
-    mDNSu8 ValidationRequired;      // Copy of the question's ValidationRequired status
-    mDNSu8 InsecureProofDone;
-    mDNSu8 NumPackets;              // Number of packets that we send on the wire for DNSSEC verification.
-    mDNSs32 StartTime;              // Time the DNSSEC verification starts
-    mDNSu32 flags;
-    RRVerifierSet next;
-    domainname *wildcardName;       // set if the answer is wildcard expanded
-    RRVerifier *pendingNSEC;
-    DNSSECVerifierCallback *DVCallback;
-    DNSSECVerifier *parent;
-    RRVerifier *rrset;              // rrset for which we have to verify
-    RRVerifier *rrsig;              // RRSIG for rrset
-    RRVerifier *key;                // DNSKEY for rrset
-    RRVerifier *rrsigKey;           // RRSIG for DNSKEY
-    RRVerifier *ds;                 // DS for DNSKEY set in parent zone
-    AuthChain *saveac;
-    AuthChain *ac;
-    AuthChain **actail;
-    AlgContext *ctx;
-};
-
-
-struct InsecureContext_struct
-{
-    DNSSECVerifier *dv;             // dv for which we are doing the insecure proof
-    mDNSu8 skip;                    // labels to skip for forming the name from origName
-    DNSSECStatus status;            // status to deliver when done
-    mDNSu8 triggerLabelCount;       // Label count of the name that triggered the insecure proof
-    DNSQuestion q;
-};
-
-#define LogDNSSEC LogOperation
-
-#define DNS_SERIAL_GT(a, b) ((int)((a) - (b)) > 0)
-#define DNS_SERIAL_LT(a, b) ((int)((a) - (b)) < 0)
-
-extern void StartDNSSECVerification(mDNS *const m, void *context);
-extern RRVerifier* AllocateRRVerifier(const ResourceRecord *const rr, mStatus *status);
-extern mStatus AddRRSetToVerifier(DNSSECVerifier *dv, const ResourceRecord *const rr, RRVerifier *rv, RRVerifierSet set);
-extern void VerifySignature(mDNS *const m, DNSSECVerifier *dv, DNSQuestion *q);
-extern void FreeDNSSECVerifier(mDNS *const m, DNSSECVerifier *dv);
-extern DNSSECVerifier *AllocateDNSSECVerifier(mDNS *const m, const domainname *name, mDNSu16 rrtype, mDNSInterfaceID InterfaceID,
-                                              mDNSu8 ValidationRequired, DNSSECVerifierCallback dvcallback, mDNSQuestionCallback qcallback);
-extern void InitializeQuestion(mDNS *const m, DNSQuestion *question, mDNSInterfaceID InterfaceID, const domainname *qname,
-                               mDNSu16 qtype, mDNSQuestionCallback *callback, void *context);
-extern void ValidateRRSIG(DNSSECVerifier *dv, RRVerifierSet type, const ResourceRecord *const rr);
-extern void AuthChainLink(DNSSECVerifier *dv, AuthChain *ae);
-extern mStatus DNSNameToLowerCase(domainname *d, domainname *result);
-extern int DNSMemCmp(const mDNSu8 *const m1, const mDNSu8 *const m2, int len);
-extern int DNSSECCanonicalOrder(const domainname *const d1, const domainname *const d2, int *subdomain);
-extern void ProveInsecure(mDNS *const m, DNSSECVerifier *dv, InsecureContext *ic, domainname *trigger);
-extern void BumpDNSSECStats(mDNS *const m, DNSSECStatsAction action, DNSSECStatsType type, mDNSu32 value);
-extern char *DNSSECStatusName(DNSSECStatus status);
-
-// DNSSECProbe belongs in DNSSECSupport.h but then we don't want to expose yet another plaform specific dnssec file
-// to other platforms where dnssec is not supported.
-extern void DNSSECProbe(mDNS *const m);
-
-#endif // __DNSSEC_H
old mode 100755 (executable)
new mode 100644 (file)
index 2fbea04..c32c915
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4; c-file-style: "bsd"; c-basic-offset: 4; fill-column: 108; indent-tabs-mode: nil; -*-
  *
- * Copyright (c) 2002-2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include "DNSCommon.h"                  // Defines general DNS utility routines
 #include "uDNS.h"                       // Defines entry points into unicast-specific routines
-#include "nsec.h"
-#include "dnssec.h"
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
 #include "D2D.h"
 #endif
 
-#if MDNSRESPONDER_SUPPORTS(APPLE, SYMPTOMS)
-#include "SymptomReporter.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+#include <bsm/libbsm.h>
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, CACHE_ANALYTICS)
+#include "dnssd_analytics.h"
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+#include "QuerierSupport.h"
 #endif
 
 // Disable certain benign warnings with Microsoft compilers
@@ -74,13 +80,18 @@ void WCFConnectionDealloc(WCFConnection* c) __attribute__((weak_import));
 #include "DNS64.h"
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include "dnssec_v2.h"
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
 // Forward declarations
 mDNSlocal void BeginSleepProcessing(mDNS *const m);
 mDNSlocal void RetrySPSRegistrations(mDNS *const m);
 mDNSlocal void SendWakeup(mDNS *const m, mDNSInterfaceID InterfaceID, mDNSEthAddr *EthAddr, mDNSOpaque48 *password, mDNSBool unicastOnly);
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 mDNSlocal mDNSBool LocalRecordRmvEventsForQuestion(mDNS *const m, DNSQuestion *q);
+#endif
 mDNSlocal void mDNS_PurgeBeforeResolve(mDNS *const m, DNSQuestion *q);
-mDNSlocal void CheckForDNSSECRecords(mDNS *const m, DNSQuestion *q);
 mDNSlocal void mDNS_SendKeepalives(mDNS *const m);
 mDNSlocal void mDNS_ExtractKeepaliveInfo(AuthRecord *ar, mDNSu32 *timeout, mDNSAddr *laddr, mDNSAddr *raddr, mDNSEthAddr *eth,
                                          mDNSu32 *seq, mDNSu32 *ack, mDNSIPPort *lport, mDNSIPPort *rport, mDNSu16 *win);
@@ -92,9 +103,6 @@ typedef mDNSu32 DeadvertiseFlags;
 
 mDNSlocal void DeadvertiseInterface(mDNS *const m, NetworkInterfaceInfo *set, DeadvertiseFlags flags);
 mDNSlocal void AdvertiseInterfaceIfNeeded(mDNS *const m, NetworkInterfaceInfo *set);
-mDNSlocal void FreeNSECRecords(mDNS *const m, CacheRecord *NSECRecords);
-mDNSlocal void mDNSParseNSEC3Records(mDNS *const m, const DNSMessage *const response, const mDNSu8 *end,
-                                        const mDNSInterfaceID InterfaceID, CacheRecord **NSEC3Records);
 mDNSlocal mDNSu8 *GetValueForMACAddr(mDNSu8 *ptr, mDNSu8 *limit, mDNSEthAddr *eth);
 
 // ***************************************************************************
@@ -538,7 +546,7 @@ mDNSlocal void GenerateNegativeResponseEx(mDNS *const m, mDNSInterfaceID Interfa
     q = m->CurrentQuestion;
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
            "[R%d->Q%d] GenerateNegativeResponse: Generating negative response for question " PRI_DM_NAME " (" PUB_S ")",
-           q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype));
+           q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype));
 
     MakeNegativeCacheRecord(m, &m->rec.r, &q->qname, q->qnamehash, q->qtype, q->qclass, 60, InterfaceID, mDNSNULL);
     m->rec.r.resrec.negativeRecordType = noData ? kNegativeRecordType_NoData : kNegativeRecordType_Unspecified;
@@ -564,13 +572,12 @@ mDNSexport void AnswerQuestionByFollowingCNAME(mDNS *const m, DNSQuestion *q, Re
     {
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                "[R%d->Q%d] AnswerQuestionByFollowingCNAME: %p " PRI_DM_NAME " (" PUB_S ")  NOT following CNAME referral %d" PUB_S " for " PRI_S,
-               q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype),
+               q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype),
                q->CNAMEReferrals, selfref ? " (Self-Referential)" : "", RRDisplayString(m, rr));
 
     }
     else
     {
-        const mDNSu32 c = q->CNAMEReferrals + 1;        // Stash a copy of the new q->CNAMEReferrals value
         UDPSocket *sock = q->LocalSocket;
         mDNSOpaque16 id = q->TargetQID;
 #if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
@@ -596,9 +603,16 @@ mDNSexport void AnswerQuestionByFollowingCNAME(mDNS *const m, DNSQuestion *q, Re
 
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                "[R%d->Q%d] AnswerQuestionByFollowingCNAME: %p " PRI_DM_NAME " (" PUB_S ") following CNAME referral %d for " PRI_S,
-               q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype),
+               q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype),
                q->CNAMEReferrals, RRDisplayString(m, rr));
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        if (!mDNSOpaque16IsZero(q->TargetQID))
+        {
+            // Must be called before zeroing out q->metrics below.
+            Querier_PrepareQuestionForCNAMERestart(q);
+        }
+#endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
         if ((q->CNAMEReferrals == 0) && !q->metrics.originalQName)
         {
@@ -617,6 +631,8 @@ mDNSexport void AnswerQuestionByFollowingCNAME(mDNS *const m, DNSQuestion *q, Re
             }
         }
         metrics = q->metrics;
+        // The metrics will be transplanted to the restarted question, so zero out the old copy instead of using
+        // uDNSMetricsClear(), which will free any pointers to allocated memory.
         mDNSPlatformMemZero(&q->metrics, sizeof(q->metrics));
 #endif
         mDNS_StopQuery_internal(m, q);                              // Stop old query
@@ -629,9 +645,11 @@ mDNSexport void AnswerQuestionByFollowingCNAME(mDNS *const m, DNSQuestion *q, Re
         {
             LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                    "[R%d->Q%d] AnswerQuestionByFollowingCNAME: Resolving a .local CNAME %p " PRI_DM_NAME " (" PUB_S ") Record " PRI_S,
-                   q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype), RRDisplayString(m, rr));
+                   q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), RRDisplayString(m, rr));
             q->IsUnicastDotLocal = mDNStrue;
         }
+        q->CNAMEReferrals += 1;                                     // Increment value before calling mDNS_StartQuery_internal
+        const mDNSu32 c = q->CNAMEReferrals;                        // Stash a copy of the new q->CNAMEReferrals value
         mDNS_StartQuery_internal(m, q);                             // start new query
         // Record how many times we've done this. We need to do this *after* mDNS_StartQuery_internal,
         // because mDNS_StartQuery_internal re-initializes CNAMEReferrals to zero
@@ -667,7 +685,7 @@ mDNSlocal mDNSu8 *PunycodeConvert(const mDNSu8 *const src, mDNSu8 *const dst, co
     UErrorCode errorCode = U_ZERO_ERROR;
     UIDNAInfo info = UIDNA_INFO_INITIALIZER;
     UIDNA *uts46 = uidna_openUTS46(UIDNA_USE_STD3_RULES|UIDNA_NONTRANSITIONAL_TO_UNICODE, &errorCode);
-    int32_t len = uidna_nameToASCII_UTF8(uts46, (const char *)src+1, src[0], (char *)dst+1, end-(dst+1), &info, &errorCode);
+    int32_t len = uidna_nameToASCII_UTF8(uts46, (const char *)src+1, src[0], (char *)dst+1, (int32_t)(end-(dst+1)), &info, &errorCode);
     uidna_close(uts46);
     #if DEBUG_PUNYCODE
     if (errorCode) LogMsg("uidna_nameToASCII_UTF8(%##s) failed errorCode %d", src, errorCode);
@@ -718,7 +736,7 @@ mDNSlocal mDNSBool PerformNextPunycodeConversion(const DNSQuestion *const q, dom
         const mDNSu8 remainder  = DomainNameLength((domainname*)src);
         if (dst + remainder > newname->c + MAX_DOMAIN_NAME) return mDNSfalse;  // Name too long -- cannot be converted to Punycode
 
-        mDNSPlatformMemCopy(newname->c, q->qname.c, h - q->qname.c);  // Fill in the leading part
+        mDNSPlatformMemCopy(newname->c, q->qname.c, (mDNSu32)(h - q->qname.c));  // Fill in the leading part
         mDNSPlatformMemCopy(dst, src, remainder);                     // Fill in the trailing part
         #if DEBUG_PUNYCODE
         LogMsg("PerformNextPunycodeConversion: %##s converted to %##s", q->qname.c, newname->c);
@@ -4153,9 +4171,11 @@ mDNSlocal void ResetQuestionState(mDNS *const m, DNSQuestion *q)
     q->RecentAnswerPkts = 0;
     q->ThisQInterval    = MaxQuestionInterval;
     q->RequestUnicast   = 0;
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     // Reset unansweredQueries so that we don't penalize this server later when we
     // start sending queries when the cache expires.
     q->unansweredQueries = 0;
+#endif
     debugf("ResetQuestionState: Set MaxQuestionInterval for %##s (%s)", q->qname.c, DNSTypeName(q->qtype));
 }
 
@@ -4210,25 +4230,6 @@ mDNSexport void AnswerCurrentQuestionWithResourceRecord(mDNS *const m, CacheReco
                   q->CurrentAnswers, AddRecord ? "Add" : "Rmv", MortalityDisplayString(rr->resrec.mortality),
                   rr->resrec.rroriginalttl, CRDisplayString(m, rr));
 
-    // When the response for the question was validated, the entire rrset was validated. If we deliver
-    // a RMV for a single record in the rrset, we invalidate the response. If we deliver another add
-    // in the future, we will do the revalidation again.
-    //
-    // Also, if we deliver an ADD for a negative cache record and it has no NSEC/NSEC3, the ValidationStatus needs
-    // to be reset. This happens normally when we deliver a "secure" negative response followed by an insecure
-    // negative response which can happen e.g., when disconnecting from network that leads to a negative response
-    // due to no DNS servers. As we don't deliver RMVs for negative responses that were delivered before, we need
-    // to do it on the next ADD of a negative cache record. This ADD could be the result of a timeout, no DNS servers
-    // etc. in which case we need to reset the state to make sure we don't deliver them as secure. If this is
-    // a real negative response, we would reset the state here and validate the results at the end of this function.
-    // or the real response again if we purge the cache. 
-    if (q->ValidationRequired && ((AddRecord == QC_rmv) ||
-        (rr->resrec.RecordType == kDNSRecordTypePacketNegative && (AddRecord == QC_add))))
-    {
-        q->ValidationStatus = 0;
-        q->ValidationState = DNSSECValRequired;
-    }
-
     // Normally we don't send out the unicast query if we have answered using our local only auth records e.g., /etc/hosts.
     // But if the query for "A" record has a local answer but query for "AAAA" record has no local answer, we might
     // send the AAAA query out which will come back with CNAME and will also answer the "A" query. To prevent that,
@@ -4255,16 +4256,32 @@ mDNSexport void AnswerCurrentQuestionWithResourceRecord(mDNS *const m, CacheReco
         q->allowExpired != AllowExpired_None && rr->resrec.mortality == Mortality_Mortal ) rr->resrec.mortality = Mortality_Immortal; // Update a non-expired cache record to immortal if appropriate
     
 #if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
-    if ((AddRecord == QC_add) && Question_uDNS(q) && !followcname)
+    if ((AddRecord == QC_add) && Question_uDNS(q) && !followcname && !q->metrics.answered)
     {
-        const domainname *  queryName;
-        mDNSu32             responseLatencyMs;
-        mDNSBool            isForCellular;
-
-        queryName = q->metrics.originalQName ? q->metrics.originalQName : &q->qname;
-        isForCellular = (q->qDNSServer && q->qDNSServer->isCell);
-        if (!q->metrics.answered)
+        mDNSBool skipUpdate = mDNSfalse;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        if (!q->dnsservice || (mdns_dns_service_get_resolver_type(q->dnsservice) != mdns_resolver_type_normal))
         {
+            skipUpdate = mDNStrue;
+        }
+#endif
+        if (!skipUpdate)
+        {
+            const domainname *  queryName;
+            mDNSu32             responseLatencyMs, querySendCount;
+            mDNSBool            isForCellular;
+
+            queryName = q->metrics.originalQName ? q->metrics.originalQName : &q->qname;
+            querySendCount = q->metrics.querySendCount;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            if (q->querier)
+            {
+                querySendCount += mdns_querier_get_send_count(q->querier);
+            }
+            isForCellular = mdns_dns_service_interface_is_cellular(q->dnsservice);
+#else
+            isForCellular = (q->qDNSServer && q->qDNSServer->isCell);
+#endif
             if (q->metrics.querySendCount > 0)
             {
                 responseLatencyMs = ((m->timenow - q->metrics.firstQueryTime) * 1000) / mDNSPlatformOneSecond;
@@ -4273,10 +4290,10 @@ mDNSexport void AnswerCurrentQuestionWithResourceRecord(mDNS *const m, CacheReco
             {
                 responseLatencyMs = 0;
             }
-
-            MetricsUpdateDNSQueryStats(queryName, q->qtype, &rr->resrec, q->metrics.querySendCount, q->metrics.expiredAnswerState, q->metrics.dnsOverTCPState, responseLatencyMs, isForCellular);
-            q->metrics.answered = mDNStrue;
+            MetricsUpdateDNSQueryStats(queryName, q->qtype, &rr->resrec, querySendCount, q->metrics.expiredAnswerState,
+                q->metrics.dnsOverTCPState, responseLatencyMs, isForCellular);
         }
+        q->metrics.answered = mDNStrue;
     }
 #endif
     // Note: Use caution here. In the case of records with rr->DelayDelivery set, AnswerCurrentQuestionWithResourceRecord(... mDNStrue)
@@ -4352,9 +4369,17 @@ mDNSexport void AnswerCurrentQuestionWithResourceRecord(mDNS *const m, CacheReco
         mDNS_DropLockBeforeCallback();      // Allow client (and us) to legally make mDNS API calls
         if (q->qtype != kDNSType_NSEC && RRAssertsNonexistence(&rr->resrec, q->qtype))
         {
-            CacheRecord neg;
-            MakeNegativeCacheRecord(m, &neg, &q->qname, q->qnamehash, q->qtype, q->qclass, 1, rr->resrec.InterfaceID, q->qDNSServer);
-            q->QuestionCallback(m, q, &neg.resrec, AddRecord);
+            if (mDNSOpaque16IsZero(q->TargetQID))
+            {
+                CacheRecord neg;
+            #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                mDNSPlatformMemZero(&neg, sizeof(neg));
+                MakeNegativeCacheRecord(m, &neg, &q->qname, q->qnamehash, q->qtype, q->qclass, 1, rr->resrec.InterfaceID, q->dnsservice);
+            #else
+                MakeNegativeCacheRecord(m, &neg, &q->qname, q->qnamehash, q->qtype, q->qclass, 1, rr->resrec.InterfaceID, q->qDNSServer);
+            #endif
+                q->QuestionCallback(m, q, &neg.resrec, AddRecord);
+            }
         }
         else
         {
@@ -4366,6 +4391,10 @@ mDNSexport void AnswerCurrentQuestionWithResourceRecord(mDNS *const m, CacheReco
             else
 #endif
             {
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+                get_denial_records_from_negative_cache_to_dnssec_context(q->DNSSECStatus.enable_dnssec,
+                    q->DNSSECStatus.context, rr);
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
                 q->QuestionCallback(m, q, &rr->resrec, AddRecord);
             }
         }
@@ -4376,32 +4405,7 @@ mDNSexport void AnswerCurrentQuestionWithResourceRecord(mDNS *const m, CacheReco
     // (including this one itself, or the next or previous query in the linked list),
     // registering/deregistering records, starting/stopping NAT traversals, etc.
 
-    // If this is an "Add" operation and this question needs validation, validate the response.
-    // In the case of negative responses, extra care should be taken. Negative cache records are
-    // used for many purposes. For example,
-    //
-    // 1) Suppressing questions (SuppressUnusable)
-    // 2) Timeout questions
-    // 3) The name does not exist
-    // 4) No DNS servers are available and we need a quick response for the application
-    //
-    // (1) and (2) are handled by "QC_add" check as AddRecord would be "QC_forceresponse" or "QC_suppressed"
-    // in that case. For (3), it is possible that we don't get nsecs back but we still need to call
-    // VerifySignature so that we can deliver the appropriate DNSSEC result. There is no point in verifying
-    // signature for (4) and hence the explicit check for q->qDNSServer.
-    //
-    if (m->CurrentQuestion == q && (AddRecord == QC_add) && !q->ValidatingResponse && q->ValidationRequired &&
-        q->ValidationState == DNSSECValRequired && q->qDNSServer)
-    {
-        q->ValidationState = DNSSECValInProgress;
-        // Treat it as callback call as that's what dnssec code expects
-        mDNS_DropLockBeforeCallback();      // Allow client (and us) to legally make mDNS API calls
-        VerifySignature(m, mDNSNULL, q);
-        mDNS_ReclaimLockAfterCallback();    // Decrement mDNS_reentrancy to block mDNS API calls again
-        return;
-    }
-
-    if ((m->CurrentQuestion == q) && !ValidatingQuestion(q))
+    if (m->CurrentQuestion == q)
     {
         // If we get a CNAME back while we are validating the response (i.e., CNAME for DS, DNSKEY, RRSIG),
         // don't follow them. If it is a ValidationRequired question, wait for the CNAME to be validated
@@ -4501,7 +4505,9 @@ mDNSlocal void CacheRecordAdd(mDNS *const m, CacheRecord *cr)
                                                                              cr->resrec.rDNSServer->port : zeroIPPort), q);
             q->CurrentAnswers++;
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
             q->unansweredQueries = 0;
+#endif
             if (cr->resrec.rdlength > SmallRecordLimit) q->LargeAnswers++;
             if (cr->resrec.RecordType & kDNSRecordTypePacketUniqueMask) q->UniqueAnswers++;
             if (q->CurrentAnswers > 4000)
@@ -4597,9 +4603,13 @@ mDNSlocal void CacheRecordRmv(mDNS *const m, CacheRecord *cr)
             q->FlappingInterface2 = mDNSNULL;
 
             if (q->CurrentAnswers == 0)
+            {
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
                 LogMsg("CacheRecordRmv ERROR!!: How can CurrentAnswers already be zero for %p %##s (%s) DNSServer %#a:%d",
                        q, q->qname.c, DNSTypeName(q->qtype), q->qDNSServer ? &q->qDNSServer->addr : mDNSNULL,
                        mDNSVal16(q->qDNSServer ? q->qDNSServer->port : zeroIPPort));
+#endif
+            }
             else
             {
                 q->CurrentAnswers--;
@@ -4682,8 +4692,6 @@ mDNSlocal void ReleaseAdditionalCacheRecords(mDNS *const m, CacheRecord **rp)
         if (!rr->resrec.InterfaceID)
         {
             m->rrcache_totalused_unicast -= rr->resrec.rdlength;
-            if (DNSSECRecordType(rr->resrec.rrtype))
-                BumpDNSSECStats(m, kStatsActionDecrement, kStatsTypeMemoryUsage, rr->resrec.rdlength);
         }
         ReleaseCacheEntity(m, (CacheEntity *)rr);
     }
@@ -4696,6 +4704,13 @@ mDNSexport void ReleaseCacheRecord(mDNS *const m, CacheRecord *r)
     //LogMsg("ReleaseCacheRecord: Releasing %s", CRDisplayString(m, r));
     if (r->resrec.rdata && r->resrec.rdata != (RData*)&r->smallrdatastorage) mDNSPlatformMemFree(r->resrec.rdata);
     r->resrec.rdata = mDNSNULL;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mdns_forget(&r->resrec.dnsservice);
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    release_denial_records_in_cache_record(r);
+#endif
 
     cg = CacheGroupForRecord(m, &r->resrec);
 
@@ -4717,11 +4732,8 @@ mDNSexport void ReleaseCacheRecord(mDNS *const m, CacheRecord *r)
     if (!r->resrec.InterfaceID)
     {
         m->rrcache_totalused_unicast -= r->resrec.rdlength;
-        if (DNSSECRecordType(r->resrec.rrtype))
-            BumpDNSSECStats(m, kStatsActionDecrement, kStatsTypeMemoryUsage, r->resrec.rdlength);
     }
 
-    ReleaseAdditionalCacheRecords(m, &r->nsec);
     ReleaseAdditionalCacheRecords(m, &r->soa);
 
     ReleaseCacheEntity(m, (CacheEntity *)r);
@@ -4769,6 +4781,7 @@ mDNSlocal void CheckCacheExpiration(mDNS *const m, const mDNSu32 slot, CacheGrou
             
             event += MAX_GHOST_TIME;                                                    // Adjust so we can check for a ghost expiration
             if (rr->resrec.mortality == Mortality_Mortal ||                             // Normal expired mortal record that needs released
+                rr->resrec.rroriginalttl == 0            ||                             // Non-mortal record that is set to be purged
                 (rr->resrec.mortality == Mortality_Ghost && m->timenow - event >= 0))   // A ghost record that expired more than MAX_GHOST_TIME ago
             {   //  Release as normal
                 *rp = rr->next;                                     // Cut it from the list before ReleaseCacheRecord
@@ -4970,7 +4983,7 @@ mDNSlocal void AnswerNewQuestion(mDNS *const m)
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
                "[R%d->Q%d] AnswerNewQuestion ERROR m->CurrentQuestion already set: " PRI_DM_NAME " (" PUB_S ")",
                m->CurrentQuestion->request_id, mDNSVal16(m->CurrentQuestion->TargetQID),
-               DM_NAME_PARAM(m->CurrentQuestion->qname.c), DNSTypeName(m->CurrentQuestion->qtype));
+               DM_NAME_PARAM(&m->CurrentQuestion->qname), DNSTypeName(m->CurrentQuestion->qtype));
     }
 
     m->CurrentQuestion = q;     // Indicate which question we're answering, so we'll know if it gets deleted
@@ -4979,9 +4992,13 @@ mDNSlocal void AnswerNewQuestion(mDNS *const m)
     {
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
                "[R%d->Q%d] AnswerNewQuestion: NoAnswer_Fail " PRI_DM_NAME " (" PUB_S ")",
-               q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype));
+               q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype));
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        MakeNegativeCacheRecord(m, &m->rec.r, &q->qname, q->qnamehash, q->qtype, q->qclass, 60, mDNSInterface_Any, q->dnsservice);
+#else
         MakeNegativeCacheRecord(m, &m->rec.r, &q->qname, q->qnamehash, q->qtype, q->qclass, 60, mDNSInterface_Any, q->qDNSServer);
+#endif
         q->NoAnswer = NoAnswer_Normal;      // Temporarily turn off answer suppression
         AnswerCurrentQuestionWithResourceRecord(m, &m->rec.r, QC_addnocache);
         // Don't touch the question if it has been stopped already
@@ -5007,7 +5024,7 @@ mDNSlocal void AnswerNewQuestion(mDNS *const m)
     {
         AnswerSuppressedQuestion(m, q);
     }
-    else if (!q->ValidatingResponse)
+    else
     {
         CacheRecord *cr;
         for (cr = cg ? cg->members : mDNSNULL; cr; cr=cr->next)
@@ -5027,6 +5044,10 @@ mDNSlocal void AnswerNewQuestion(mDNS *const m)
                 if (cr->resrec.RecordType & kDNSRecordTypePacketUniqueMask) q->UniqueAnswers++;
 #if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
                 if (q->metrics.expiredAnswerState == ExpiredAnswer_Allowed) q->metrics.expiredAnswerState = IsExpired ? ExpiredAnswer_AnsweredWithExpired : ExpiredAnswer_AnsweredWithCache;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, CACHE_ANALYTICS)
+                cr->LastCachedAnswerTime = m->timenow;
+                dnssd_analytics_update_cache_request(mDNSOpaque16IsZero(q->TargetQID) ? CacheRequestType_multicast : CacheRequestType_unicast, CacheState_hit);
 #endif
                 AnswerCurrentQuestionWithResourceRecord(m, cr, QC_add);
                 if (m->CurrentQuestion != q) break;     // If callback deleted q, then we're finished here
@@ -5038,6 +5059,9 @@ mDNSlocal void AnswerNewQuestion(mDNS *const m)
     // it's not remotely remarkable, and therefore unlikely to be of much help tracking down bugs.
     if (m->CurrentQuestion != q) { debugf("AnswerNewQuestion: Question deleted while giving cache answers"); goto exit; }
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, CACHE_ANALYTICS)
+    dnssd_analytics_update_cache_request(mDNSOpaque16IsZero(q->TargetQID) ? CacheRequestType_multicast : CacheRequestType_unicast, CacheState_miss);
+#endif
     q->InitialCacheMiss  = mDNStrue;                                    // Initial cache check is done, so mark as a miss from now on
     if (q->allowExpired == AllowExpired_AllowExpiredAnswers)
     {
@@ -5046,8 +5070,14 @@ mDNSlocal void AnswerNewQuestion(mDNS *const m)
         {
             LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
                    "[R%d->Q%d] AnswerNewQuestion: Restarting original question %p firstExpiredQname " PRI_DM_NAME " for allowExpiredAnswers question",
-                   q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(&q->firstExpiredQname.c));
+                   q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(&q->firstExpiredQname));
             mDNS_StopQuery_internal(m, q);                              // Stop old query
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            if (!SameDomainName(&q->qname, &q->firstExpiredQname))
+            {
+                Querier_PrepareQuestionForUnwindRestart(q);
+            }
+#endif
             AssignDomainName(&q->qname, &q->firstExpiredQname);         // Update qname
             q->qnamehash = DomainNameHashValue(&q->qname);              // and namehash
             mDNS_StartQuery_internal(m, q);                             // start new query
@@ -5894,7 +5924,13 @@ mDNSexport void mDNSCoreRestartQueries(mDNS *const m)
     {
         q = m->CurrentQuestion;
         m->CurrentQuestion = m->CurrentQuestion->next;
-        if (!mDNSOpaque16IsZero(q->TargetQID) && ActiveQuestion(q)) ActivateUnicastQuery(m, q, mDNStrue);
+        if (!mDNSOpaque16IsZero(q->TargetQID) && ActiveQuestion(q))
+        {
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            mdns_querier_forget(&q->querier);
+#endif
+            ActivateUnicastQuery(m, q, mDNStrue);
+        }
     }
 #endif
 
@@ -6758,9 +6794,6 @@ mDNSlocal void BeginSleepProcessing(mDNS *const m)
                     // which is okay because with no outstanding resolves, or updates in flight,
                     // mDNSCoreReadyForSleep() will conclude correctly that all the updates have already completed
 
-                    // Setting this flag activates the SleepLimit which delays sleep by 5 seconds and
-                    // will allow the system to deregister any BTMM records.
-                    m->NextScheduledSPRetry  = m->timenow + (5 * mDNSPlatformOneSecond);
                     registeredIntfIDS[registeredCount] = intf->InterfaceID;
                     registeredCount++;
                 }
@@ -6914,6 +6947,9 @@ mDNSexport void mDNSCoreMachineSleep(mDNS *const m, mDNSBool sleep)
             m->DelaySleep = 0;
             m->SleepLimit = NonZeroTime(m->timenow + mDNSPlatformOneSecond * 10);
             m->mDNSStats.Sleeps++;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            Querier_HandleSleep();
+#endif
             BeginSleepProcessing(m);
         }
 
@@ -6959,6 +6995,9 @@ mDNSexport void mDNSCoreMachineSleep(mDNS *const m, mDNSBool sleep)
         // ... and the same for NextSPSAttempt
         for (intf = GetFirstActiveInterface(m->HostInterfaces); intf; intf = GetFirstActiveInterface(intf->next)) intf->NextSPSAttempt = -1;
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        Querier_HandleWake();
+#endif
         // Restart unicast and multicast queries
         mDNSCoreRestartQueries(m);
 
@@ -7164,7 +7203,7 @@ notready:
     return mDNSfalse;
 }
 
-mDNSexport mDNSs32 mDNSCoreIntervalToNextWake(mDNS *const m, mDNSs32 now)
+mDNSexport mDNSs32 mDNSCoreIntervalToNextWake(mDNS *const m, mDNSs32 now, mDNSNextWakeReason *outReason)
 {
     AuthRecord *ar;
 
@@ -7173,13 +7212,19 @@ mDNSexport mDNSs32 mDNSCoreIntervalToNextWake(mDNS *const m, mDNSs32 now)
     // E.g. we might wake up and find no wireless network because the base station got rebooted just at that moment,
     // and if that happens we don't want to just give up and go back to sleep and never try again.
     mDNSs32 e = now + (120 * 60 * mDNSPlatformOneSecond);       // Sleep for at most 120 minutes
+    mDNSNextWakeReason reason = mDNSNextWakeReason_UpkeepWake;
 
     NATTraversalInfo *nat;
     for (nat = m->NATTraversals; nat; nat=nat->next)
+    {
         if (nat->Protocol && nat->ExpiryTime && nat->ExpiryTime - now > mDNSPlatformOneSecond*4)
         {
             mDNSs32 t = nat->ExpiryTime - (nat->ExpiryTime - now) / 10;     // Wake up when 90% of the way to the expiry time
-            if (e - t > 0) e = t;
+            if ((e - t) > 0)
+            {
+                e = t;
+                reason = mDNSNextWakeReason_NATPortMappingRenewal;
+            }
             LogSPS("ComputeWakeTime: %p %s Int %5d Ext %5d Err %d Retry %5d Interval %5d Expire %5d Wake %5d",
                    nat, nat->Protocol == NATOp_MapTCP ? "TCP" : "UDP",
                    mDNSVal16(nat->IntPort), mDNSVal16(nat->ExternalPort), nat->Result,
@@ -7188,21 +7233,30 @@ mDNSexport mDNSs32 mDNSCoreIntervalToNextWake(mDNS *const m, mDNSs32 now)
                    nat->ExpiryTime ? (nat->ExpiryTime - now) / mDNSPlatformOneSecond : 0,
                    (t - now) / mDNSPlatformOneSecond);
         }
-
+    }
     // This loop checks both the time we need to renew wide-area registrations,
     // and the time we need to renew Sleep Proxy registrations
     for (ar = m->ResourceRecords; ar; ar = ar->next)
+    {
         if (ar->expire && ar->expire - now > mDNSPlatformOneSecond*4)
         {
             mDNSs32 t = ar->expire - (ar->expire - now) / 10;       // Wake up when 90% of the way to the expiry time
-            if (e - t > 0) e = t;
+            if ((e - t) > 0)
+            {
+                e = t;
+                reason = mDNSNextWakeReason_RecordRegistrationRenewal;
+            }
             LogSPS("ComputeWakeTime: %p Int %7d Next %7d Expire %7d Wake %7d %s",
                    ar, ar->ThisAPInterval / mDNSPlatformOneSecond,
                    (ar->LastAPTime + ar->ThisAPInterval - now) / mDNSPlatformOneSecond,
                    ar->expire ? (ar->expire - now) / mDNSPlatformOneSecond : 0,
                    (t - now) / mDNSPlatformOneSecond, ARDisplayString(m, ar));
         }
-
+    }
+    if (outReason)
+    {
+        *outReason = reason;
+    }
     return(e - now);
 }
 
@@ -7461,9 +7515,13 @@ mDNSlocal CacheRecord *FindIdenticalRecordInCache(const mDNS *const m, const Res
     {
         if (!pktrr->InterfaceID)
         {
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            match = (pktrr->dnsservice == rr->resrec.dnsservice) ? mDNStrue : mDNSfalse;
+#else
             const mDNSu32 id1 = (pktrr->rDNSServer ? pktrr->rDNSServer->resGroupID : 0);
             const mDNSu32 id2 = (rr->resrec.rDNSServer ? rr->resrec.rDNSServer->resGroupID : 0);
             match = (id1 == id2);
+#endif
         }
         else match = (pktrr->InterfaceID == rr->resrec.InterfaceID);
 
@@ -7592,7 +7650,6 @@ mDNSlocal mDNSu8 *ProcessQuery(mDNS *const m, const DNSMessage *const query, con
     mDNSu8       *responseptr        = mDNSNULL;
     AuthRecord   *rr;
     int i;
-    CacheRecord *McastNSEC3Records   = mDNSNULL;
 
     // ***
     // *** 1. Look in Additional Section for an OPT record
@@ -7617,12 +7674,6 @@ mDNSlocal mDNSu8 *ProcessQuery(mDNS *const m, const DNSMessage *const query, con
         m->rec.r.resrec.RecordType = 0;     // Clear RecordType to show we're not still using it
     }
 
-    //
-    // Look in Authority Section for NSEC3 record
-    //
-
-    mDNSParseNSEC3Records(m, query, end, InterfaceID, &McastNSEC3Records);
-
     // ***
     // *** 2. Parse Question Section and mark potential answers
     // ***
@@ -8054,13 +8105,6 @@ exit:
         debugf("ProcessQuery: Recorded DSI for %##s (%s) on %p/%s", q->qname.c, DNSTypeName(q->qtype), InterfaceID,
                srcaddr->type == mDNSAddrType_IPv4 ? "v4" : "v6");
     }
-
-    if (McastNSEC3Records)
-    {
-        debugf("ProcessQuery: McastNSEC3Records not used");
-        FreeNSECRecords(m, McastNSEC3Records);
-    }
-
     return(responseptr);
 }
 
@@ -8124,7 +8168,8 @@ struct UDPSocket_struct
     mDNSIPPort port; // MUST BE FIRST FIELD -- mDNSCoreReceive expects every UDPSocket_struct to begin with mDNSIPPort port
 };
 
-mDNSlocal DNSQuestion *ExpectingUnicastResponseForQuestion(const mDNS *const m, const mDNSIPPort port, const mDNSOpaque16 id, const DNSQuestion *const question, mDNSBool tcp, DNSQuestion ** suspiciousQ)
+mDNSlocal DNSQuestion *ExpectingUnicastResponseForQuestion(const mDNS *const m, const mDNSIPPort port,
+    const mDNSOpaque16 id, const DNSQuestion *const question, mDNSBool tcp)
 {
     DNSQuestion *q;
     for (q = m->Questions; q; q=q->next)
@@ -8139,18 +8184,6 @@ mDNSlocal DNSQuestion *ExpectingUnicastResponseForQuestion(const mDNS *const m,
             if (mDNSSameOpaque16(q->TargetQID, id)) return(q);
             else
             {
-                // Everything but the QIDs match up, should we be suspicious?
-                if (!tcp && suspiciousQ)
-                {
-#if MDNSRESPONDER_SUPPORTS(APPLE, SUSPICIOUS_REPLY_DEFENSE)
-                    if (mDNSSameOpaque16(q->LastTargetQID, id)) // Only be suspicious if this is also not the "last" used QID (on a DNS server set change)
-                    {
-                        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR, "Ignored but not suspicious LastTargetQID %d TargetQID %d", mDNSVal16(q->LastTargetQID), mDNSVal16(q->TargetQID));
-                    }
-                    else
-#endif
-                    *suspiciousQ = q;
-                }
                 return(mDNSNULL);
             }
         }
@@ -8234,11 +8267,17 @@ mDNSexport CacheRecord *CreateNewCacheEntry(mDNS *const m, const mDNSu32 slot, C
     if (!rr) NoCacheAnswer(m, &m->rec.r);
     else
     {
-        RData *saveptr         = rr->resrec.rdata;   // Save the rr->resrec.rdata pointer
-        *rr                    = m->rec.r;           // Block copy the CacheRecord object
-        rr->resrec.rdata       = saveptr;            // Restore rr->resrec.rdata after the structure assignment
-        rr->resrec.name        = cg->name;           // And set rr->resrec.name to point into our CacheGroup header
-        rr->resrec.mortality   = Mortality_Mortal;
+        RData *saveptr              = rr->resrec.rdata;     // Save the rr->resrec.rdata pointer
+        *rr                         = m->rec.r;             // Block copy the CacheRecord object
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        mdns_retain_null_safe(rr->resrec.dnsservice);
+#endif
+        rr->resrec.rdata            = saveptr;              // Restore rr->resrec.rdata after the structure assignment
+        rr->resrec.name             = cg->name;             // And set rr->resrec.name to point into our CacheGroup header
+        rr->resrec.mortality        = Mortality_Mortal;
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+        rr->resrec.dnssec_result    = dnssec_indeterminate; // Set the DNSSEC validation result of a record as "indeterminate" by default.
+#endif
 
         rr->DelayDelivery = delay;
 
@@ -8251,7 +8290,6 @@ mDNSexport CacheRecord *CreateNewCacheEntry(mDNS *const m, const mDNSu32 slot, C
             mDNSPlatformMemCopy(rr->resrec.rdata, m->rec.r.resrec.rdata, sizeofRDataHeader + RDLength);
 
         rr->next = mDNSNULL;                    // Clear 'next' pointer
-        rr->nsec = mDNSNULL;
         rr->soa  = mDNSNULL;
 
         if (sourceAddress)
@@ -8260,10 +8298,15 @@ mDNSexport CacheRecord *CreateNewCacheEntry(mDNS *const m, const mDNSu32 slot, C
         if (!rr->resrec.InterfaceID)
         {
             m->rrcache_totalused_unicast += rr->resrec.rdlength;
-            if (DNSSECRecordType(rr->resrec.rrtype))
-                BumpDNSSECStats(m, kStatsActionIncrement, kStatsTypeMemoryUsage, rr->resrec.rdlength);
         }
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+        if (rr != mDNSNULL)
+        {
+            rr->denial_of_existence_records = mDNSNULL;
+        }
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
         if (Add)
         {
             *(cg->rrcache_tail) = rr;               // Append this record to tail of cache slot list
@@ -8368,13 +8411,12 @@ mDNSlocal mDNSu32 GetEffectiveTTL(const uDNS_LLQType LLQType, mDNSu32 ttl)
 
 // When the response does not match the question directly, we still want to cache them sometimes. The current response is
 // in m->rec.
-mDNSlocal mDNSBool IsResponseAcceptable(mDNS *const m, const CacheRecord *crlist, DNSQuestion *q, mDNSBool *nseclist)
+mDNSlocal mDNSBool IsResponseAcceptable(mDNS *const m, const CacheRecord *crlist)
 {
     CacheRecord *const newcr = &m->rec.r;
     ResourceRecord *rr = &newcr->resrec;
     const CacheRecord *cr;
 
-    *nseclist = mDNSfalse;
     for (cr = crlist; cr != (CacheRecord*)1; cr = cr->NextInCFList)
     {
         domainname *target = GetRRDomainNameTarget(&cr->resrec);
@@ -8390,138 +8432,18 @@ mDNSlocal mDNSBool IsResponseAcceptable(mDNS *const m, const CacheRecord *crlist
             return (mDNStrue);
         }
     }
-
-    // Either the question requires validation or we are validating a response with DNSSEC in which case
-    // we need to accept the RRSIGs also so that we can validate the response. It is also possible that
-    // we receive NSECs for our query which does not match the qname and we need to cache in that case
-    // too. nseclist is set if they have to be cached as part of the negative cache record.
-    if (q && DNSSECQuestion(q))
-    {
-        mDNSBool same = SameDomainName(&q->qname, rr->name);
-        if (same && (q->qtype == rr->rrtype || rr->rrtype == kDNSType_CNAME))
-        {
-            LogInfo("IsResponseAcceptable: Accepting, same name and qtype %s, CR %s", DNSTypeName(q->qtype),
-                    CRDisplayString(m, newcr));
-            return mDNStrue;
-        }
-        // We cache RRSIGS if it covers the question type or NSEC.  If it covers a NSEC,
-        // "nseclist" is set
-        if (rr->rrtype == kDNSType_RRSIG)
-        {
-            RDataBody2 *const rdb = (RDataBody2 *)newcr->smallrdatastorage.data;
-            rdataRRSig *rrsig = &rdb->rrsig;
-            mDNSu16 typeCovered = swap16(rrsig->typeCovered);
-
-            // Note the ordering. If we are looking up the NSEC record, then the RRSIG's typeCovered
-            // would match the qtype and they are cached normally as they are not used to prove the
-            // non-existence of any name. In that case, it is like any other normal dnssec validation
-            // and hence nseclist should not be set.
-
-            if (same && ((typeCovered == q->qtype) || (typeCovered == kDNSType_CNAME)))
-            {
-                LogInfo("IsResponseAcceptable: Accepting RRSIG %s matches question type %s", CRDisplayString(m, newcr),
-                        DNSTypeName(q->qtype));
-                return mDNStrue;
-            }
-            else if (typeCovered == kDNSType_NSEC || typeCovered == kDNSType_NSEC3)
-            {
-                LogInfo("IsResponseAcceptable: Accepting RRSIG %s matches %s type (nseclist = 1)", CRDisplayString(m, newcr), DNSTypeName(typeCovered));
-                *nseclist = mDNStrue;
-                return mDNStrue;
-            }
-            else if (typeCovered == kDNSType_SOA)
-            {
-                LogInfo("IsResponseAcceptable: Accepting RRSIG %s matches SOA type (nseclist = 1)", CRDisplayString(m, newcr));
-                *nseclist = mDNStrue;
-                return mDNStrue;
-            }
-            else return mDNSfalse;
-        }
-        if (rr->rrtype == kDNSType_NSEC)
-        {
-            if (!UNICAST_NSEC(rr))
-            {
-                LogMsg("IsResponseAcceptable: ERROR!! Not a unicast NSEC %s", CRDisplayString(m, newcr));
-                return mDNSfalse;
-            }
-            LogInfo("IsResponseAcceptable: Accepting NSEC %s (nseclist = 1)", CRDisplayString(m, newcr));
-            *nseclist = mDNStrue;
-            return mDNStrue;
-        }
-        if (rr->rrtype == kDNSType_SOA)
-        {
-            LogInfo("IsResponseAcceptable: Accepting SOA %s (nseclist = 1)", CRDisplayString(m, newcr));
-            *nseclist = mDNStrue;
-            return mDNStrue;
-        }
-        else if (rr->rrtype == kDNSType_NSEC3)
-        {
-            LogInfo("IsResponseAcceptable: Accepting NSEC3 %s (nseclist = 1)", CRDisplayString(m, newcr));
-            *nseclist = mDNStrue;
-            return mDNStrue;
-        }
-    }
     return mDNSfalse;
 }
 
-mDNSlocal void FreeNSECRecords(mDNS *const m, CacheRecord *NSECRecords)
-{
-    CacheRecord *rp, *next;
-
-    for (rp = NSECRecords; rp; rp = next)
-    {
-        next = rp->next;
-        ReleaseCacheRecord(m, rp);
-    }
-}
-
-// If we received zero DNSSEC records even when the DO/EDNS0 bit was set, we need to provide this
-// information to ValidatingResponse question to indicate the DNSSEC status to the application
-mDNSlocal void mDNSCoreReceiveNoDNSSECAnswers(mDNS *const m, const DNSMessage *const response, const mDNSu8 *end, const mDNSAddr *dstaddr,
-    mDNSIPPort dstport, const mDNSInterfaceID InterfaceID)
-{
-    int i;
-    const mDNSu8 *ptr = response->data;
-
-    for (i = 0; i < response->h.numQuestions && ptr && ptr < end; i++)
-    {
-        DNSQuestion pktq;
-        DNSQuestion *qptr = mDNSNULL;
-        ptr = getQuestion(response, ptr, end, InterfaceID, &pktq);
-        if (ptr && (qptr = ExpectingUnicastResponseForQuestion(m, dstport, response->h.id, &pktq, !dstaddr, mDNSNULL)) &&
-            qptr->ValidatingResponse)
-        {
-            DNSQuestion *next, *q;
-
-            if (qptr->DuplicateOf)
-                LogMsg("mDNSCoreReceiveNoDNSSECAnswers: ERROR!! qptr %##s (%s) Duplicate question matching response", qptr->qname.c, DNSTypeName(qptr->qtype));
-
-            // Be careful to call the callback for duplicate questions first and then the original
-            // question. If we called the callback on the original question, it could stop and 
-            // a duplicate question would become the original question.
-            mDNS_DropLockBeforeCallback();      // Allow client (and us) to legally make mDNS API calls
-            for (q = qptr->next ; q && q != m->NewQuestions; q = next)
-            {
-                next = q->next;
-                if (q->DuplicateOf == qptr)
-                {
-                    if (q->ValidatingResponse)
-                        LogInfo("mDNSCoreReceiveNoDNSSECAnswers: qptr %##s (%s) Duplicate question found", q->qname.c, DNSTypeName(q->qtype));
-                    else
-                        LogMsg("mDNSCoreReceiveNoDNSSECAnswers: ERROR!! qptr %##s (%s) Duplicate question not ValidatingResponse", q->qname.c, DNSTypeName(q->qtype));
-                    if (q->QuestionCallback)
-                        q->QuestionCallback(m, q, mDNSNULL, QC_nodnssec);
-                }
-            }
-            if (qptr->QuestionCallback)
-                qptr->QuestionCallback(m, qptr, mDNSNULL, QC_nodnssec);
-            mDNS_ReclaimLockAfterCallback();    // Decrement mDNS_reentrancy to block mDNS API calls again
-        }
-    }
-}
-
-mDNSlocal void mDNSCoreReceiveNoUnicastAnswers(mDNS *const m, const DNSMessage *const response, const mDNSu8 *end, const mDNSAddr *dstaddr,
-                                               mDNSIPPort dstport, const mDNSInterfaceID InterfaceID, uDNS_LLQType LLQType, mDNSu8 rcode, CacheRecord *NSECRecords)
+mDNSlocal void mDNSCoreReceiveNoUnicastAnswers(mDNS *const m, const DNSMessage *const response, const mDNSu8 *end,
+    const mDNSAddr *dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID,
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    const mdns_querier_t querier, const mdns_dns_service_t uDNSService,
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    denial_of_existence_records_t **denial_of_existence_records_ptr,
+#endif
+    const uDNS_LLQType LLQType)
 {
     int i;
     const mDNSu8 *ptr   = response->data;
@@ -8530,20 +8452,54 @@ mDNSlocal void mDNSCoreReceiveNoUnicastAnswers(mDNS *const m, const DNSMessage *
     for (i = 0; i < response->h.numQuestions && ptr && ptr < end; i++)
     {
         DNSQuestion q;
-        DNSQuestion *qptr = mDNSNULL;
         ptr = getQuestion(response, ptr, end, InterfaceID, &q);
-        if (ptr && (qptr = ExpectingUnicastResponseForQuestion(m, dstport, response->h.id, &q, !dstaddr, mDNSNULL)))
+        if (ptr)
         {
+            DNSQuestion *qptr;
             CacheRecord *cr, *neg = mDNSNULL;
-            CacheGroup *cg = CacheGroupForName(m, q.qnamehash, &q.qname);
+            CacheGroup *cg;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            if (querier)
+            {
+                qptr = Querier_GetDNSQuestion(querier);
+            }
+            else
+#endif
+            {
+                qptr = ExpectingUnicastResponseForQuestion(m, dstport, response->h.id, &q, !dstaddr);
+                if (!qptr)
+                {
+                    continue;
+                }
+            }
+            cg = CacheGroupForName(m, q.qnamehash, &q.qname);
             for (cr = cg ? cg->members : mDNSNULL; cr; cr=cr->next)
             {
-                if (SameNameCacheRecordAnswersQuestion(cr, qptr))
+                mDNSBool isAnswer;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                if (querier)
+                {
+                    isAnswer = (cr->resrec.dnsservice == uDNSService) && Querier_SameNameCacheRecordIsAnswer(cr, querier);
+                }
+                else
+#endif
+                {
+                    isAnswer = SameNameCacheRecordAnswersQuestion(cr, qptr);
+                }
+                if (isAnswer)
                 {
                     // 1. If we got a fresh answer to this query, then don't need to generate a negative entry
                     if (RRExpireTime(cr) - m->timenow > 0) break;
                     // 2. If we already had a negative entry, keep track of it so we can resurrect it instead of creating a new one
                     if (cr->resrec.RecordType == kDNSRecordTypePacketNegative) neg = cr;
+                    else if (cr->resrec.mortality == Mortality_Ghost)
+                    {
+                        // 3. If the existing entry is expired, mark it to be purged
+                        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+                            "[R%u->Q%u] mDNSCoreReceiveNoUnicastAnswers: Removing expired record" PRI_S,
+                            q.request_id, mDNSVal16(q.TargetQID), CRDisplayString(m, cr));
+                        mDNS_PurgeCacheResourceRecord(m, cr);
+                   }
                 }
             }
             // When we're doing parallel unicast and multicast queries for dot-local names (for supporting Microsoft
@@ -8566,24 +8522,27 @@ mDNSlocal void mDNSCoreReceiveNoUnicastAnswers(mDNS *const m, const DNSMessage *
             {
                 if (!cr)
                 {
-                    const mDNSBool noData = ((response->h.flags.b[1] & kDNSFlag1_RC_Mask) == kDNSFlag1_RC_NoErr) ? mDNStrue : mDNSfalse;
-                    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-                              "[R%u->Q%u] mDNSCoreReceiveNoUnicastAnswers: Generate negative response for " PRI_DM_NAME " (" PUB_S ")",
-                              q.request_id, mDNSVal16(q.TargetQID), DM_NAME_PARAM(q.qname.c), DNSTypeName(q.qtype));
-                    m->CurrentQuestion = qptr;
-                    // We are not creating a cache record in this case, we need to pass back
-                    // the error we got so that the proxy code can return the right one to
-                    // the application
-                    if (qptr->ProxyQuestion)
-                        qptr->responseFlags = response->h.flags;
-                    GenerateNegativeResponseEx(m, mDNSInterface_Any, QC_forceresponse, noData);
-                    m->CurrentQuestion = mDNSNULL;
+                    if (qptr)
+                    {
+                        const mDNSBool noData = ((response->h.flags.b[1] & kDNSFlag1_RC_Mask) == kDNSFlag1_RC_NoErr) ? mDNStrue : mDNSfalse;
+                        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+                            "[R%u->Q%u] mDNSCoreReceiveNoUnicastAnswers: Generate negative response for " PRI_DM_NAME " (" PUB_S ")",
+                            q.request_id, mDNSVal16(q.TargetQID), DM_NAME_PARAM(&q.qname), DNSTypeName(q.qtype));
+                        m->CurrentQuestion = qptr;
+                        // We are not creating a cache record in this case, we need to pass back
+                        // the error we got so that the proxy code can return the right one to
+                        // the application
+                        if (qptr->ProxyQuestion)
+                            qptr->responseFlags = response->h.flags;
+                        GenerateNegativeResponseEx(m, mDNSInterface_Any, QC_forceresponse, noData);
+                        m->CurrentQuestion = mDNSNULL;
+                    }
                 }
                 else
                 {
                     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-                              "[R%u->Q%u] mDNSCoreReceiveNoUnicastAnswers: Skipping check and not creating a negative cache entry for " PRI_DM_NAME " (" PUB_S ")",
-                              q.request_id, mDNSVal16(q.TargetQID), DM_NAME_PARAM(q.qname.c), DNSTypeName(q.qtype));
+                        "[R%u->Q%u] mDNSCoreReceiveNoUnicastAnswers: Skipping check and not creating a negative cache entry for " PRI_DM_NAME " (" PUB_S ")",
+                        q.request_id, mDNSVal16(q.TargetQID), DM_NAME_PARAM(&q.qname), DNSTypeName(q.qtype));
                 }
             }
             else
@@ -8632,7 +8591,7 @@ mDNSlocal void mDNSCoreReceiveNoUnicastAnswers(mDNS *const m, const DNSMessage *
                             //
                             // For ProxyQuestions, we don't do this as we need to create additional SOA records to cache them
                             // along with the negative cache record. For simplicity, we don't create the additional records.
-                            if (!qptr->ProxyQuestion && q.qtype == kDNSType_SOA)
+                            if ((!qptr || !qptr->ProxyQuestion) && (q.qtype == kDNSType_SOA))
                             {
                                 int qcount = CountLabels(&q.qname);
                                 int scount = CountLabels(m->rec.r.resrec.name);
@@ -8664,32 +8623,26 @@ mDNSlocal void mDNSCoreReceiveNoUnicastAnswers(mDNS *const m, const DNSMessage *
                     if (neg)
                     {
                         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-                                  "[R%u->Q%u] mDNSCoreReceiveNoUnicastAnswers: Renewing negative TTL from %d to %d " PRI_S,
-                                  q.request_id, mDNSVal16(q.TargetQID), neg->resrec.rroriginalttl, negttl, CRDisplayString(m, neg));
+                            "[R%u->Q%u] mDNSCoreReceiveNoUnicastAnswers: Renewing negative TTL from %d to %d " PRI_S,
+                            q.request_id, mDNSVal16(q.TargetQID), neg->resrec.rroriginalttl, negttl, CRDisplayString(m, neg));
                         RefreshCacheRecord(m, neg, negttl);
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+                        // replace the old records with the new ones
+                        // If qptr is NULL, it means the question is no longer active, and we do not process the record
+                        // for DNSSEC.
+                        if ((qptr != mDNSNULL) && qptr->DNSSECStatus.enable_dnssec)
+                        {
+                            update_denial_records_in_cache_record(neg, denial_of_existence_records_ptr);
+                        }
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
                         // When we created the cache for the first time and answered the question, the question's
                         // interval was set to MaxQuestionInterval. If the cache is about to expire and we are resending
                         // the queries, the interval should still be at MaxQuestionInterval. If the query is being
                         // restarted (setting it to InitialQuestionInterval) for other reasons e.g., wakeup,
                         // we should reset its question interval here to MaxQuestionInterval.
-                        ResetQuestionState(m, qptr);
-                        if (DNSSECQuestion(qptr))
-                            neg->CRDNSSECQuestion = 1;
-                        // Update the NSEC records again.
-                        // TBD: Need to purge and revalidate if the cached NSECS and the new set are not same.
-                        if (NSECRecords)
+                        if (qptr)
                         {
-                            if (!AddNSECSForCacheRecord(m, NSECRecords, neg, rcode))
-                            {
-                                // We might just have an SOA record for zones that are not signed and hence don't log
-                                // this as an error
-                                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
-                                          "[R%u->Q%d] mDNSCoreReceiveNoUnicastAnswers: AddNSECSForCacheRecord failed to add NSEC for negcr " PRI_S" during refresh",
-                                          q.request_id, mDNSVal16(q.TargetQID), CRDisplayString(m, neg));
-                                FreeNSECRecords(m, NSECRecords);
-                                neg->CRDNSSECQuestion = 0;
-                            }
-                            NSECRecords = mDNSNULL;
+                            ResetQuestionState(m, qptr);
                         }
                         if (SOARecord)
                         {
@@ -8703,7 +8656,11 @@ mDNSlocal void mDNSCoreReceiveNoUnicastAnswers(mDNS *const m, const DNSMessage *
                         {
                             CacheRecord *negcr;
                             debugf("mDNSCoreReceiveNoUnicastAnswers making negative cache entry TTL %d for %##s (%s)", negttl, name->c, DNSTypeName(q.qtype));
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                            MakeNegativeCacheRecord(m, &m->rec.r, name, hash, q.qtype, q.qclass, negttl, mDNSInterface_Any, uDNSService);
+#else
                             MakeNegativeCacheRecord(m, &m->rec.r, name, hash, q.qtype, q.qclass, negttl, mDNSInterface_Any, qptr->qDNSServer);
+#endif
                             m->rec.r.responseFlags = response->h.flags;
                             // We create SOA records above which might create new cache groups. Earlier
                             // in the function we looked up the cache group for the name and it could have
@@ -8711,55 +8668,29 @@ mDNSlocal void mDNSCoreReceiveNoUnicastAnswers(mDNS *const m, const DNSMessage *
                             // it will create additional cache groups for the same name. To avoid that,
                             // look up the cache group again to re-initialize cg again.
                             cg = CacheGroupForName(m, hash, name);
-                            if (NSECRecords && DNSSECQuestion(qptr))
+                            // Need to add with a delay so that we can tag the SOA record
+                            negcr = CreateNewCacheEntry(m, HashSlotFromNameHash(hash), cg, 1, mDNStrue, mDNSNULL);
+
+                            if (negcr)
                             {
-                                // Create the cache entry with delay and then add the NSEC records
-                                // to it and add it immediately.
-                                negcr = CreateNewCacheEntry(m, HashSlotFromNameHash(hash), cg, 1, mDNStrue, mDNSNULL);
-                                if (negcr)
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+                                // If qptr is NULL, it means the question is no longer active, and we do not process the
+                                // record for DNSSEC.
+                                if (qptr != mDNSNULL && qptr->DNSSECStatus.enable_dnssec)
                                 {
-                                    negcr->CRDNSSECQuestion = 0;
-                                    if (!AddNSECSForCacheRecord(m, NSECRecords, negcr, rcode))
-                                    {
-                                        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
-                                                  "[R%u->Q%u] mDNSCoreReceiveNoUnicastAnswers: AddNSECSForCacheRecord failed to add NSEC for negcr " PRI_S,
-                                                  q.request_id, mDNSVal16(q.TargetQID), CRDisplayString(m, negcr));
-                                        FreeNSECRecords(m, NSECRecords);
-                                    }
-                                    else
-                                    {
-                                        negcr->CRDNSSECQuestion = 1;
-                                        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-                                                  "[R%u->Q%u] mDNSCoreReceiveNoUnicastAnswers: AddNSECSForCacheRecord added neg NSEC for " PRI_S,
-                                                  q.request_id, mDNSVal16(q.TargetQID), CRDisplayString(m, negcr));
-                                    }
-                                    NSECRecords = mDNSNULL;
-                                    negcr->DelayDelivery = 0;
-                                    CacheRecordDeferredAdd(m, negcr);
+                                    update_denial_records_in_cache_record(negcr, denial_of_existence_records_ptr);
                                 }
-                                m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it
-                                break;
-                            }
-                            else
-                            {
-                                // Need to add with a delay so that we can tag the SOA record
-                                negcr = CreateNewCacheEntry(m, HashSlotFromNameHash(hash), cg, 1, mDNStrue, mDNSNULL);
-                                if (negcr)
-                                {
-                                    negcr->CRDNSSECQuestion = 0;
-                                    if (DNSSECQuestion(qptr))
-                                        negcr->CRDNSSECQuestion = 1;
-                                    negcr->DelayDelivery = 0;
+#endif
+                                negcr->DelayDelivery = 0;
 
-                                    if (SOARecord)
-                                    {
-                                        if (negcr->soa)
-                                            ReleaseCacheRecord(m, negcr->soa);
-                                        negcr->soa = SOARecord;
-                                        SOARecord = mDNSNULL;
-                                    }
-                                    CacheRecordDeferredAdd(m, negcr);
+                                if (SOARecord)
+                                {
+                                    if (negcr->soa)
+                                        ReleaseCacheRecord(m, negcr->soa);
+                                    negcr->soa = SOARecord;
+                                    SOARecord = mDNSNULL;
                                 }
+                                CacheRecordDeferredAdd(m, negcr);
                             }
                             m->rec.r.responseFlags = zeroID;
                             m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it
@@ -8772,11 +8703,6 @@ mDNSlocal void mDNSCoreReceiveNoUnicastAnswers(mDNS *const m, const DNSMessage *
             }
         }
     }
-    if (NSECRecords)
-    {
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "mDNSCoreReceiveNoUnicastAnswers: NSECRecords not used");
-        FreeNSECRecords(m, NSECRecords);
-    }
     if (SOARecord)
     {
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "mDNSCoreReceiveNoUnicastAnswers: SOARecord not used");
@@ -8812,8 +8738,7 @@ mDNSlocal mDNSBool mDNSCoreRegisteredProxyRecord(mDNS *const m, AuthRecord *rr)
 }
 
 mDNSexport CacheRecord* mDNSCoreReceiveCacheCheck(mDNS *const m, const DNSMessage *const response, uDNS_LLQType LLQType,
-    const mDNSu32 slot, CacheGroup *cg, DNSQuestion *unicastQuestion, CacheRecord ***cfp, CacheRecord **NSECCachePtr,
-    mDNSInterfaceID InterfaceID)
+    const mDNSu32 slot, CacheGroup *cg, CacheRecord ***cfp, mDNSInterfaceID InterfaceID)
 {
     CacheRecord *cr;
     CacheRecord **cflocal = *cfp;
@@ -8824,183 +8749,159 @@ mDNSexport CacheRecord* mDNSCoreReceiveCacheCheck(mDNS *const m, const DNSMessag
         // Resource record received via unicast, the resGroupID should match ?
         if (!InterfaceID)
         {
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            match = (cr->resrec.dnsservice == m->rec.r.resrec.dnsservice) ? mDNStrue : mDNSfalse;
+#else
             const mDNSu32 id1 = (cr->resrec.rDNSServer ? cr->resrec.rDNSServer->resGroupID : 0);
             const mDNSu32 id2 = (m->rec.r.resrec.rDNSServer ? m->rec.r.resrec.rDNSServer->resGroupID : 0);
             match = (id1 == id2);
+#endif
         }
         else
             match = (cr->resrec.InterfaceID == InterfaceID);
         // If we found this exact resource record, refresh its TTL
-        if (match && IdenticalSameNameRecord(&m->rec.r.resrec, &cr->resrec))
+        if (match)
         {
-            if (m->rec.r.resrec.rdlength > InlineCacheRDSize)
-                verbosedebugf("mDNSCoreReceiveCacheCheck: Found record size %5d interface %p already in cache: %s",
-                              m->rec.r.resrec.rdlength, InterfaceID, CRDisplayString(m, &m->rec.r));
-
-            if (m->rec.r.resrec.RecordType & kDNSRecordTypePacketUniqueMask)
+            if (IdenticalSameNameRecord(&m->rec.r.resrec, &cr->resrec))
             {
-                // If this packet record has the kDNSClass_UniqueRRSet flag set, then add it to our cache flushing list
-                if (cr->NextInCFList == mDNSNULL && *cfp != &cr->NextInCFList && LLQType != uDNS_LLQ_Events)
+                if (m->rec.r.resrec.rdlength > InlineCacheRDSize)
+                    verbosedebugf("mDNSCoreReceiveCacheCheck: Found record size %5d interface %p already in cache: %s",
+                                  m->rec.r.resrec.rdlength, InterfaceID, CRDisplayString(m, &m->rec.r));
+
+                if (m->rec.r.resrec.RecordType & kDNSRecordTypePacketUniqueMask)
                 {
-                    *cflocal = cr;
-                    cflocal = &cr->NextInCFList;
-                    *cflocal = (CacheRecord*)1;
-                    *cfp = &cr->NextInCFList;
+                    // If this packet record has the kDNSClass_UniqueRRSet flag set, then add it to our cache flushing list
+                    if (cr->NextInCFList == mDNSNULL && *cfp != &cr->NextInCFList && LLQType != uDNS_LLQ_Events)
+                    {
+                        *cflocal = cr;
+                        cflocal = &cr->NextInCFList;
+                        *cflocal = (CacheRecord*)1;
+                        *cfp = &cr->NextInCFList;
+                    }
+
+                    // If this packet record is marked unique, and our previous cached copy was not, then fix it
+                    if (!(cr->resrec.RecordType & kDNSRecordTypePacketUniqueMask))
+                    {
+                        DNSQuestion *q;
+                        for (q = m->Questions; q; q=q->next)
+                        {
+                            if (CacheRecordAnswersQuestion(cr, q))
+                                q->UniqueAnswers++;
+                        }
+                        cr->resrec.RecordType = m->rec.r.resrec.RecordType;
+                    }
                 }
 
-                // If this packet record is marked unique, and our previous cached copy was not, then fix it
-                if (!(cr->resrec.RecordType & kDNSRecordTypePacketUniqueMask))
+                if (!SameRDataBody(&m->rec.r.resrec, &cr->resrec.rdata->u, SameDomainNameCS))
+                {
+                    // If the rdata of the packet record differs in name capitalization from the record in our cache
+                    // then mDNSPlatformMemSame will detect this. In this case, throw the old record away, so that clients get
+                    // a 'remove' event for the record with the old capitalization, and then an 'add' event for the new one.
+                    // <rdar://problem/4015377> mDNS -F returns the same domain multiple times with different casing
+                    cr->resrec.rroriginalttl = 0;
+                    cr->TimeRcvd = m->timenow;
+                    cr->UnansweredQueries = MaxUnansweredQueries;
+                    SetNextCacheCheckTimeForRecord(m, cr);
+                    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "mDNSCoreReceiveCacheCheck: Discarding due to domainname case change old: " PRI_S, CRDisplayString(m, cr));
+                    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "mDNSCoreReceiveCacheCheck: Discarding due to domainname case change new: " PRI_S, CRDisplayString(m, &m->rec.r));
+                    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "mDNSCoreReceiveCacheCheck: Discarding due to domainname case change in %d slot %3d in %d %d",
+                              NextCacheCheckEvent(cr) - m->timenow, slot, m->rrcache_nextcheck[slot] - m->timenow, m->NextCacheCheck - m->timenow);
+                    // DO NOT break out here -- we want to continue as if we never found it
+                }
+                else if (m->rec.r.resrec.rroriginalttl > 0)
                 {
                     DNSQuestion *q;
-                    for (q = m->Questions; q; q=q->next)
+
+                    m->mDNSStats.CacheRefreshed++;
+
+                    if ((cr->resrec.mortality == Mortality_Ghost) && !cr->DelayDelivery)
                     {
-                        if (CacheRecordAnswersQuestion(cr, q))
-                            q->UniqueAnswers++;
+                        cr->DelayDelivery = NonZeroTime(m->timenow);
+                        debugf("mDNSCoreReceiveCacheCheck: Reset DelayDelivery for mortalityExpired EXP:%d RR %s", m->timenow - RRExpireTime(cr), CRDisplayString(m, cr));
                     }
-                    cr->resrec.RecordType = m->rec.r.resrec.RecordType;
-                }
-            }
-
-            if (!SameRDataBody(&m->rec.r.resrec, &cr->resrec.rdata->u, SameDomainNameCS))
-            {
-                // If the rdata of the packet record differs in name capitalization from the record in our cache
-                // then mDNSPlatformMemSame will detect this. In this case, throw the old record away, so that clients get
-                // a 'remove' event for the record with the old capitalization, and then an 'add' event for the new one.
-                // <rdar://problem/4015377> mDNS -F returns the same domain multiple times with different casing
-                cr->resrec.rroriginalttl = 0;
-                cr->TimeRcvd = m->timenow;
-                cr->UnansweredQueries = MaxUnansweredQueries;
-                SetNextCacheCheckTimeForRecord(m, cr);
-                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "mDNSCoreReceiveCacheCheck: Discarding due to domainname case change old: " PRI_S, CRDisplayString(m, cr));
-                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "mDNSCoreReceiveCacheCheck: Discarding due to domainname case change new: " PRI_S, CRDisplayString(m, &m->rec.r));
-                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "mDNSCoreReceiveCacheCheck: Discarding due to domainname case change in %d slot %3d in %d %d",
-                          NextCacheCheckEvent(cr) - m->timenow, slot, m->rrcache_nextcheck[slot] - m->timenow, m->NextCacheCheck - m->timenow);
-                // DO NOT break out here -- we want to continue as if we never found it
-            }
-            else if (m->rec.r.resrec.rroriginalttl > 0)
-            {
-                DNSQuestion *q;
 
-                m->mDNSStats.CacheRefreshed++;
-                
-                if (cr->resrec.mortality == Mortality_Ghost && unicastQuestion && (unicastQuestion->allowExpired != AllowExpired_AllowExpiredAnswers) && !cr->DelayDelivery)
-                {
-                    cr->DelayDelivery = NonZeroTime(m->timenow);
-                    debugf("mDNSCoreReceiveCacheCheck: Reset DelayDelivery for mortalityExpired EXP:%d RR %s", m->timenow - RRExpireTime(cr), CRDisplayString(m, cr));
-                }
-
-                if (cr->resrec.rroriginalttl == 0) debugf("uDNS rescuing %s", CRDisplayString(m, cr));
-                RefreshCacheRecord(m, cr, m->rec.r.resrec.rroriginalttl);
-                // RefreshCacheRecordCacheGroupOrder will modify the cache group member list that is currently being iterated over in this for-loop.
-                // It is safe to call because the else-if body will unconditionally break out of the for-loop now that it has found the entry to update.
-                RefreshCacheRecordCacheGroupOrder(cg, cr);
-                cr->responseFlags = response->h.flags;
-
-                // If we may have NSEC records returned with the answer (which we don't know yet as it
-                // has not been processed), we need to cache them along with the first cache
-                // record in the list that answers the question so that it can be used for validation
-                // later. The "type" check below is to make sure that we cache on the cache record
-                // that would answer the question. It is possible that we might cache additional things
-                // e.g., MX question might cache A records also, and we want to cache the NSEC on
-                // the record that answers the question.
-                if (response->h.numAnswers && unicastQuestion && unicastQuestion->qtype == cr->resrec.rrtype
-                    && !(*NSECCachePtr))
-                {
-                    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "mDNSCoreReceiveCacheCheck: rescuing RR " PRI_S, CRDisplayString(m, cr));
-                    *NSECCachePtr = cr;
-                }
-                // We have to reset the question interval to MaxQuestionInterval so that we don't keep
-                // polling the network once we get a valid response back. For the first time when a new
-                // cache entry is created, AnswerCurrentQuestionWithResourceRecord does that.
-                // Subsequently, if we reissue questions from within the mDNSResponder e.g., DNS server
-                // configuration changed, without flushing the cache, we reset the question interval here.
-                // Currently, we do this for for both multicast and unicast questions as long as the record
-                // type is unique. For unicast, resource record is always unique and for multicast it is
-                // true for records like A etc. but not for PTR.
-                if (cr->resrec.RecordType & kDNSRecordTypePacketUniqueMask)
-                {
-                    for (q = m->Questions; q; q=q->next)
+                    if (cr->resrec.rroriginalttl == 0) debugf("uDNS rescuing %s", CRDisplayString(m, cr));
+                    RefreshCacheRecord(m, cr, m->rec.r.resrec.rroriginalttl);
+                    // RefreshCacheRecordCacheGroupOrder will modify the cache group member list that is currently being iterated over in this for-loop.
+                    // It is safe to call because the else-if body will unconditionally break out of the for-loop now that it has found the entry to update.
+                    RefreshCacheRecordCacheGroupOrder(cg, cr);
+                    cr->responseFlags = response->h.flags;
+
+                    // If we may have NSEC records returned with the answer (which we don't know yet as it
+                    // has not been processed), we need to cache them along with the first cache
+                    // record in the list that answers the question so that it can be used for validation
+                    // later. The "type" check below is to make sure that we cache on the cache record
+                    // that would answer the question. It is possible that we might cache additional things
+                    // e.g., MX question might cache A records also, and we want to cache the NSEC on
+                    // the record that answers the question.
+                    if (!InterfaceID)
+                    {
+                        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "mDNSCoreReceiveCacheCheck: rescuing RR " PRI_S, CRDisplayString(m, cr));
+                    }
+                    // We have to reset the question interval to MaxQuestionInterval so that we don't keep
+                    // polling the network once we get a valid response back. For the first time when a new
+                    // cache entry is created, AnswerCurrentQuestionWithResourceRecord does that.
+                    // Subsequently, if we reissue questions from within the mDNSResponder e.g., DNS server
+                    // configuration changed, without flushing the cache, we reset the question interval here.
+                    // Currently, we do this for for both multicast and unicast questions as long as the record
+                    // type is unique. For unicast, resource record is always unique and for multicast it is
+                    // true for records like A etc. but not for PTR.
+                    if (cr->resrec.RecordType & kDNSRecordTypePacketUniqueMask)
                     {
-                        if (!q->DuplicateOf && !q->LongLived &&
-                            ActiveQuestion(q) && CacheRecordAnswersQuestion(cr, q))
+                        for (q = m->Questions; q; q=q->next)
                         {
-                            ResetQuestionState(m, q);
-                            debugf("mDNSCoreReceiveCacheCheck: Set MaxQuestionInterval for %p %##s (%s)", q, q->qname.c, DNSTypeName(q->qtype));
-                            break;      // Why break here? Aren't there other questions we might want to look at?-- SC July 2010
+                            if (!q->DuplicateOf && !q->LongLived &&
+                                ActiveQuestion(q) && CacheRecordAnswersQuestion(cr, q))
+                            {
+                                ResetQuestionState(m, q);
+                                debugf("mDNSCoreReceiveCacheCheck: Set MaxQuestionInterval for %p %##s (%s)", q, q->qname.c, DNSTypeName(q->qtype));
+                                break;      // Why break here? Aren't there other questions we might want to look at?-- SC July 2010
+                            }
                         }
                     }
+                    break;  // Check usage of RefreshCacheRecordCacheGroupOrder before removing (See note above)
+                }
+                else
+                {
+                    // If the packet TTL is zero, that means we're deleting this record.
+                    // To give other hosts on the network a chance to protest, we push the deletion
+                    // out one second into the future. Also, we set UnansweredQueries to MaxUnansweredQueries.
+                    // Otherwise, we'll do final queries for this record at 80% and 90% of its apparent
+                    // lifetime (800ms and 900ms from now) which is a pointless waste of network bandwidth.
+                    // If record's current expiry time is more than a second from now, we set it to expire in one second.
+                    // If the record is already going to expire in less than one second anyway, we leave it alone --
+                    // we don't want to let the goodbye packet *extend* the record's lifetime in our cache.
+                    debugf("DE for %s", CRDisplayString(m, cr));
+                    if (RRExpireTime(cr) - m->timenow > mDNSPlatformOneSecond)
+                    {
+                        cr->resrec.rroriginalttl = 1;
+                        cr->TimeRcvd = m->timenow;
+                        cr->UnansweredQueries = MaxUnansweredQueries;
+                        SetNextCacheCheckTimeForRecord(m, cr);
+                    }
+                    break;
                 }
-                break;  // Check usage of RefreshCacheRecordCacheGroupOrder before removing (See note above)
             }
-            else
+            else if (cr->resrec.rroriginalttl != 0                  &&      // Not already marked for discarding
+                     m->rec.r.resrec.rrclass == cr->resrec.rrclass  &&
+                        (m->rec.r.resrec.rrtype != cr->resrec.rrtype    &&
+                         (m->rec.r.resrec.rrtype == kDNSType_CNAME || cr->resrec.rrtype == kDNSType_CNAME)))
             {
-                // If the packet TTL is zero, that means we're deleting this record.
-                // To give other hosts on the network a chance to protest, we push the deletion
-                // out one second into the future. Also, we set UnansweredQueries to MaxUnansweredQueries.
-                // Otherwise, we'll do final queries for this record at 80% and 90% of its apparent
-                // lifetime (800ms and 900ms from now) which is a pointless waste of network bandwidth.
-                // If record's current expiry time is more than a second from now, we set it to expire in one second.
-                // If the record is already going to expire in less than one second anyway, we leave it alone --
-                // we don't want to let the goodbye packet *extend* the record's lifetime in our cache.
-                debugf("DE for %s", CRDisplayString(m, cr));
-                if (RRExpireTime(cr) - m->timenow > mDNSPlatformOneSecond)
-                {
-                    cr->resrec.rroriginalttl = 1;
-                    cr->TimeRcvd = m->timenow;
-                    cr->UnansweredQueries = MaxUnansweredQueries;
-                    SetNextCacheCheckTimeForRecord(m, cr);
-                }
-                break;
+                // If the cache record rrtype doesn't match and one is a CNAME, then flush this record
+                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "mDNSCoreReceiveCacheCheck: Discarding (%s) " PRI_S " rrtype change from (%s) to (%s)",
+                          MortalityDisplayString(cr->resrec.mortality), CRDisplayString(m, cr), DNSTypeName(cr->resrec.rrtype), DNSTypeName(m->rec.r.resrec.rrtype));
+                mDNS_PurgeCacheResourceRecord(m, cr);
+                // DO NOT break out here -- we want to continue iterating the cache entries
             }
         }
     }
     return cr;
 }
 
-mDNSlocal void mDNSParseNSEC3Records(mDNS *const m, const DNSMessage *const response, const mDNSu8 *end,
-    const mDNSInterfaceID InterfaceID, CacheRecord **NSEC3Records)
+mDNSlocal void mDNSCoreResetRecord(mDNS *const m)
 {
-    const mDNSu8 *ptr;
-    CacheRecord *rr;
-    int i;
-
-    if (!response->h.numAuthorities)
-        return;
-    ptr = LocateAuthorities(response, end);
-    if (!ptr)
-    {
-        LogInfo("mDNSParseNSEC3Records: ERROR can't locate authorities");
-        return;
-    }
-    for (i = 0; i < response->h.numAuthorities && ptr && ptr < end; i++)
-    {
-        CacheGroup *cg;
-
-        ptr = GetLargeResourceRecord(m, response, ptr, end, InterfaceID, kDNSRecordTypePacketAuth, &m->rec);
-        if (!ptr || m->rec.r.resrec.RecordType == kDNSRecordTypePacketNegative || m->rec.r.resrec.rrtype != kDNSType_NSEC3)
-        {
-            debugf("mDNSParseNSEC3Records: ptr %p, Record %s, ignoring", ptr, CRDisplayString(m, &m->rec.r));
-            m->rec.r.resrec.RecordType = 0;
-            continue;
-        }
-        cg = CacheGroupForRecord(m, &m->rec.r.resrec);
-        // Create the cache entry but don't add it to the cache it. We need
-        // to cache this along with the main cache record.
-        rr = CreateNewCacheEntry(m, HashSlotFromNameHash(m->rec.r.resrec.namehash), cg, 0, mDNSfalse, mDNSNULL);
-        if (rr)
-        {
-            debugf("mDNSParseNSEC3Records: %s", CRDisplayString(m, rr));
-            *NSEC3Records = rr;
-            NSEC3Records = &rr->next;
-        }
-        m->rec.r.resrec.RecordType = 0;     // Clear RecordType to show we're not still using it
-    }
-}
-
-mDNSlocal void mDNSCoreResetRecord(mDNS *const m)
-{
-    m->rec.r.resrec.RecordType = 0;     // Clear RecordType to show we're not still using it
-}
+    m->rec.r.resrec.RecordType = 0;     // Clear RecordType to show we're not still using it
+}
 
 // Note: mDNSCoreReceiveResponse calls mDNS_Deregister_internal which can call a user callback, which may change
 // the record list and/or question list.
@@ -9008,16 +8909,17 @@ mDNSlocal void mDNSCoreResetRecord(mDNS *const m)
 // InterfaceID non-NULL tells us the interface this multicast response was received on
 // InterfaceID NULL tells us this was a unicast response
 // dstaddr NULL tells us we received this over an outgoing TCP connection we made
-mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
-                                       const DNSMessage *const response, const mDNSu8 *end,
-                                       const mDNSAddr *srcaddr, const mDNSIPPort srcport, const mDNSAddr *dstaddr, mDNSIPPort dstport,
-                                       const mDNSInterfaceID InterfaceID)
+mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m, const DNSMessage *const response, const mDNSu8 *end,
+    const mDNSAddr *srcaddr, const mDNSIPPort srcport, const mDNSAddr *dstaddr, mDNSIPPort dstport,
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mdns_querier_t querier, mdns_dns_service_t uDNSService,
+#endif
+    const mDNSInterfaceID InterfaceID)
 {
     int i;
-    const mDNSBool ResponseMCast = dstaddr && mDNSAddrIsDNSMulticast(dstaddr);
-    mDNSBool ResponseSrcLocal = !srcaddr || mDNS_AddressIsLocalSubnet(m, InterfaceID, srcaddr);
+    const mDNSBool ResponseMCast    = dstaddr && mDNSAddrIsDNSMulticast(dstaddr);
+    const mDNSBool ResponseSrcLocal = !srcaddr || mDNS_AddressIsLocalSubnet(m, InterfaceID, srcaddr);
     DNSQuestion *llqMatch = mDNSNULL;
-    DNSQuestion *unicastQuestion = mDNSNULL;
     uDNS_LLQType LLQType      = uDNS_recvLLQResponse(m, response, end, srcaddr, srcport, &llqMatch);
 
     // "(CacheRecord*)1" is a special (non-zero) end-of-list marker
@@ -9025,14 +8927,6 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
     // set non-zero, and that tells GetCacheEntity() that they're not, at this moment, eligible for recycling.
     CacheRecord *CacheFlushRecords = (CacheRecord*)1;
     CacheRecord **cfp = &CacheFlushRecords;
-    CacheRecord *NSECRecords = mDNSNULL;
-    CacheRecord *NSECCachePtr = mDNSNULL;
-    CacheRecord **nsecp = &NSECRecords;
-    CacheRecord *McastNSEC3Records = mDNSNULL;
-    mDNSBool nseclist;
-    mDNSu8 rcode = '\0';
-    mDNSBool rrsigsCreated = mDNSfalse;
-    mDNSBool DNSSECQuestion = mDNSfalse;
     NetworkInterfaceInfo *llintf = FirstIPv4LLInterfaceForID(m, InterfaceID);
     mDNSBool    recordAcceptedInResponse = mDNSfalse; // Set if a record is accepted from a unicast mDNS response that answers an existing question.
 
@@ -9043,7 +8937,23 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
     int firstadditional = firstauthority  + response->h.numAuthorities;
     int totalrecords    = firstadditional + response->h.numAdditionals;
     const mDNSu8 *ptr   = response->data;
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     DNSServer *uDNSServer = mDNSNULL;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    denial_of_existence_records_t *denial_of_existence_records = mDNSNULL;
+    mDNSBool not_answer_but_required_for_dnssec = mDNSfalse;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    // Determine whether the response is mDNS, as opposed to DNS.
+    // Thus far, the code has assumed that responses with IDs set to zero are mDNS responses. However, this condition
+    // isn't sufficient because queriers, which are used exclusively for DNS queries, may set the IDs of their queries
+    // to zero. And consequently, their responses may have their IDs set to zero. Specifically, zero-valued IDs are used
+    // for DNS over HTTPs, as specified by <https://tools.ietf.org/html/rfc8484#section-4.1>.
+    const mDNSBool ResponseIsMDNS = mDNSOpaque16IsZero(response->h.id) && !querier;
+#else
+    const mDNSBool ResponseIsMDNS = mDNSOpaque16IsZero(response->h.id);
+#endif
 
     debugf("Received Response from %#-15a addressed to %#-15a on %p with "
            "%2d Question%s %2d Answer%s %2d Authorit%s %2d Additional%s %d bytes LLQType %d",
@@ -9053,7 +8963,7 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
            response->h.numAuthorities, response->h.numAuthorities == 1 ? "y,  " : "ies,",
            response->h.numAdditionals, response->h.numAdditionals == 1 ? " "    : "s", end - response->data, LLQType);
 
-#if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
+#if MDNSRESPONDER_SUPPORTS(APPLE, METRICS) && !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     if (mDNSSameIPPort(srcport, UnicastDNSPort))
     {
         MetricsUpdateDNSResponseSize((mDNSu32)(end - (mDNSu8 *)response));
@@ -9082,7 +8992,11 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
     //    should start at the end of the response and work forward in the
     //    datagram.  Thus if there is any data for the authority section, the
     //    answer section is guaranteed to be unique.
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    if (!InterfaceID && (response->h.flags.b[0] & kDNSFlag0_TC) && !querier &&
+#else
     if (!InterfaceID && (response->h.flags.b[0] & kDNSFlag0_TC) &&
+#endif
         ((response->h.numAnswers == 0) || ((response->h.numAuthorities == 0) && (response->h.numAdditionals == 0)))) return;
 
     if (LLQType == uDNS_LLQ_Ignore) return;
@@ -9097,9 +9011,23 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
     else
     {
         mDNSBool failure, returnEarly;
-        rcode = (mDNSu8)(response->h.flags.b[1] & kDNSFlag1_RC_Mask);
+        const int rcode = response->h.flags.b[1] & kDNSFlag1_RC_Mask;
         failure = !(rcode == kDNSFlag1_RC_NoErr || rcode == kDNSFlag1_RC_NXDomain || rcode == kDNSFlag1_RC_NotAuth);
         returnEarly = mDNSfalse;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        // When the QUERIER functionality is enabled, DNS transport is handled exclusively by querier objects. If this
+        // response was provided by a querier, but the RCODE is considered a failure, then set failure to false so that
+        // we don't return early. The logic of returning early was so that uDNS_CheckCurrentQuestion() could handle
+        // resending the query and generate a negative cache record if all servers were tried. If the querier provides a
+        // response, then it's the best response that it could provide. If the RCODE is considered a failure,
+        // mDNSCoreReceiveResponse() needs to create negative cache entries for the unanwered question, so totalrecords
+        // is set to 0 to ignore any records that the response may contain.
+        if (querier && failure)
+        {
+            totalrecords = 0;
+            failure = mDNSfalse;
+        }
+#endif
         // We could possibly combine this with the similar loop at the end of this function --
         // instead of tagging cache records here and then rescuing them if we find them in the answer section,
         // we could instead use the "m->PktNum" mechanism to tag each cache record with the packet number in
@@ -9108,98 +9036,67 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
         // packet number, then we deduce they are old and delete them
         for (i = 0; i < response->h.numQuestions && ptr && ptr < end; i++)
         {
-            DNSQuestion q, *qptr = mDNSNULL, *suspiciousForQ = mDNSNULL;
+            DNSQuestion q;
+            DNSQuestion *qptr;
+            mDNSBool expectingResponse;
             ptr = getQuestion(response, ptr, end, InterfaceID, &q);
-            if (ptr && (qptr = ExpectingUnicastResponseForQuestion(m, dstport, response->h.id, &q, !dstaddr, &suspiciousForQ)))
+            if (!ptr)
+            {
+                continue;
+            }
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            if (querier)
+            {
+                expectingResponse = mDNStrue;
+                qptr = mDNSNULL;
+            }
+            else
+#endif
+            {
+                qptr = ExpectingUnicastResponseForQuestion(m, dstport, response->h.id, &q, !dstaddr);
+                expectingResponse = qptr ? mDNStrue : mDNSfalse;
+            }
+            if (!expectingResponse)
             {
-                if (!failure)
+                continue;
+            }
+            if (!failure)
+            {
+                CacheRecord *cr;
+                CacheGroup *cg = CacheGroupForName(m, q.qnamehash, &q.qname);
+                for (cr = cg ? cg->members : mDNSNULL; cr; cr=cr->next)
                 {
-                    CacheRecord *cr;
-                    // Remember the unicast question that we found, which we use to make caching
-                    // decisions later on in this function
-                    CacheGroup *cg = CacheGroupForName(m, q.qnamehash, &q.qname);
-                    if (!mDNSOpaque16IsZero(response->h.id))
+                    mDNSBool isAnswer;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                    if (querier)
                     {
-                        unicastQuestion = qptr;
-                        if (qptr->qDNSServer && DNSSECQuestion(qptr))
-                        {
-                            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-                                   "[R%d->Q%d] mDNSCoreReceiveResponse: Setting aware for " PRI_DM_NAME " (" PUB_S ") on " PRI_IP_ADDR,
-                                   qptr->request_id, mDNSVal16(qptr->TargetQID), DM_NAME_PARAM(qptr->qname.c),
-                                   DNSTypeName(qptr->qtype), &qptr->qDNSServer->addr);
-
-                            qptr->qDNSServer->DNSSECAware = mDNStrue;
-                            qptr->qDNSServer->req_DO = mDNStrue;
-                        }
-                        if (qptr->ValidatingResponse)
-                            DNSSECQuestion = mDNStrue;
+                        isAnswer = (cr->resrec.dnsservice == uDNSService) && Querier_SameNameCacheRecordIsAnswer(cr, querier);
                     }
-                    for (cr = cg ? cg->members : mDNSNULL; cr; cr=cr->next)
+                    else
+#endif
                     {
-                        if (SameNameCacheRecordAnswersQuestion(cr, qptr))
-                        {
-                            debugf("uDNS marking %p %##s (%s) %p %s", q.InterfaceID, q.qname.c, DNSTypeName(q.qtype),
-                                   cr->resrec.InterfaceID, CRDisplayString(m, cr));
-                            // Don't want to disturb rroriginalttl here, because code below might need it for the exponential backoff doubling algorithm
-                            cr->TimeRcvd          = m->timenow - TicksTTL(cr) - 1;
-                            cr->UnansweredQueries = MaxUnansweredQueries;
-                            cr->CRDNSSECQuestion = 0;
-                            if (unicastQuestion && DNSSECQuestion(unicastQuestion))
-                            {
-                                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-                                       "[R%d->Q%d] mDNSCoreReceiveResponse: CRDNSSECQuestion set for record " PRI_S ", question " PRI_DM_NAME " (" PUB_S ")",
-                                       unicastQuestion->request_id, mDNSVal16(unicastQuestion->TargetQID),
-                                       CRDisplayString(m, cr), DM_NAME_PARAM(unicastQuestion->qname.c),
-                                       DNSTypeName(unicastQuestion->qtype));
-                                cr->CRDNSSECQuestion = 1;
-                            }
-                        }
+                        isAnswer = SameNameCacheRecordAnswersQuestion(cr, qptr);
                     }
-                }
-                else
-                {
-                    if (qptr)
+                    if (isAnswer)
                     {
-                        // If we recv any error from the DNSServer for a DNSSEC Query and if we know that the server
-                        // is not DNSSEC aware, stop doing DNSSEC for that DNSServer. Note that by setting the
-                        // req_DO to false here, the next retransmission for this question will turn off validation
-                        // and hence retransmit without the EDNS0/DOK option.
-                        if (DNSSECOptionalQuestion(qptr) && qptr->qDNSServer && !qptr->qDNSServer->DNSSECAware)
-                        {
-                            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-                                   "[R%d->Q%d] mDNSCoreReceiveResponse: Server %p responded with code %d to DNSSEC Query " PRI_DM_NAME " (" PUB_S "), clear DO flag",
-                                   qptr->request_id, mDNSVal16(qptr->TargetQID), qptr->qDNSServer, rcode,
-                                   DM_NAME_PARAM(q.qname.c), DNSTypeName(q.qtype));
-                            qptr->qDNSServer->req_DO = mDNSfalse;
-                        }
-                        // For Unicast DNS Queries, penalize the DNSServer
-                        else
-                        {
-                            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-                                   "[R%d->Q%d] mDNSCoreReceiveResponse: Server %p responded with code %d to query " PRI_DM_NAME " (" PUB_S ")",
-                                   qptr->request_id, mDNSVal16(qptr->TargetQID), qptr->qDNSServer, rcode,
-                                   DM_NAME_PARAM(q.qname.c), DNSTypeName(q.qtype));
-                            PenalizeDNSServer(m, qptr, response->h.flags);
-                        }
+                        debugf("uDNS marking %p %##s (%s) %p %s", q.InterfaceID, q.qname.c, DNSTypeName(q.qtype),
+                               cr->resrec.InterfaceID, CRDisplayString(m, cr));
+                        // Don't want to disturb rroriginalttl here, because code below might need it for the exponential backoff doubling algorithm
+                        cr->TimeRcvd          = m->timenow - TicksTTL(cr) - 1;
+                        cr->UnansweredQueries = MaxUnansweredQueries;
                     }
-                    returnEarly = mDNStrue;
                 }
             }
-            else if (!InterfaceID && suspiciousForQ)
+            else
             {
-                // If a response is suspicious for a question, then reissue the question via TCP
-                LogInfo("[R%d->Q%d] mDNSCoreReceiveResponse: Server %p responded suspiciously to query %##s (%s) qID %d != rID: %d",
-                        suspiciousForQ->request_id, mDNSVal16(suspiciousForQ->TargetQID),
-                        suspiciousForQ->qDNSServer, q.qname.c, DNSTypeName(q.qtype),
-                        mDNSVal16(suspiciousForQ->TargetQID), mDNSVal16(response->h.id));
-#if MDNSRESPONDER_SUPPORTS(APPLE, SUSPICIOUS_REPLY_DEFENSE)
-                m->NextSuspiciousTimeout = NonZeroTime(m->timenow + (SUSPICIOUS_REPLY_DEFENSE_SECS * mDNSPlatformOneSecond));
-#endif
-                uDNS_RestartQuestionAsTCP(m, suspiciousForQ, srcaddr, srcport);
-#if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
-                suspiciousForQ->metrics.dnsOverTCPState = DNSOverTCP_Suspicious;
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+                    "[R%d->Q%d] mDNSCoreReceiveResponse: Server %p responded with code %d to query " PRI_DM_NAME " (" PUB_S ")",
+                    qptr->request_id, mDNSVal16(qptr->TargetQID), qptr->qDNSServer, rcode,
+                    DM_NAME_PARAM(&q.qname), DNSTypeName(q.qtype));
+                PenalizeDNSServer(m, qptr, response->h.flags);
 #endif
-                return;
+                returnEarly = mDNStrue;
             }
         }
         if (returnEarly)
@@ -9214,25 +9111,19 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
             // generate negative cache entries (we want to query the next server)
             return;
         }
-        if (unicastQuestion && DNSSECQuestion(unicastQuestion))
-        {
-            BumpDNSSECStats(m, kStatsActionSet, kStatsTypeMsgSize, (end - response->data));
-        }
     }
 
-    // Parse the NSEC3 records from the Authority section before we process
-    // the Answer section so that we can cache them along with the proper
-    // cache records we create.
-    if (mDNSOpaque16IsZero(response->h.id))
-        mDNSParseNSEC3Records(m, response, end, InterfaceID, &McastNSEC3Records);
-
     for (i = 0; i < totalrecords && ptr && ptr < end; i++)
     {
         // All responses sent via LL multicast are acceptable for caching
         // All responses received over our outbound TCP connections are acceptable for caching
         // We accept all records in a unicast response to a multicast query once we find one that
         // answers an active question.
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        mDNSBool AcceptableResponse = ResponseMCast || (!querier && !dstaddr) || LLQType || recordAcceptedInResponse;
+#else
         mDNSBool AcceptableResponse = ResponseMCast || !dstaddr || LLQType || recordAcceptedInResponse;
+#endif
         // (Note that just because we are willing to cache something, that doesn't necessarily make it a trustworthy answer
         // to any specific question -- any code reading records from the cache needs to make that determination for itself.)
 
@@ -9248,13 +9139,6 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
             continue;
         }
 
-        // We have already parsed the NSEC3 records and cached them approrpriately for
-        // multicast responses.
-        if (mDNSOpaque16IsZero(response->h.id) && m->rec.r.resrec.rrtype == kDNSType_NSEC3)
-        {
-            mDNSCoreResetRecord(m);
-            continue;
-        }
         // Don't want to cache OPT or TSIG pseudo-RRs
         if (m->rec.r.resrec.rrtype == kDNSType_TSIG)
         {
@@ -9287,8 +9171,10 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
         // When we receive uDNS LLQ responses, we assume a long cache lifetime --
         // In the case of active LLQs, we'll get remove events when the records actually do go away
         // In the case of polling LLQs, we assume the record remains valid until the next poll
-        if (!mDNSOpaque16IsZero(response->h.id))
+        if (!ResponseIsMDNS)
+        {
             m->rec.r.resrec.rroriginalttl = GetEffectiveTTL(LLQType, m->rec.r.resrec.rroriginalttl);
+        }
 
         // If response was not sent via LL multicast,
         // then see if it answers a recent query of ours, which would also make it acceptable for caching.
@@ -9302,15 +9188,14 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
                 // ExpectingUnicastResponseForRecord as the port numbers don't match. uDNS_recvLLQRespose
                 // has already matched the question using the 64 bit Id in the packet and we use that here.
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                if (querier)
+                {
+                    mdns_replace(&m->rec.r.resrec.dnsservice, uDNSService);
+                }
+#else
                 if (llqMatch != mDNSNULL) m->rec.r.resrec.rDNSServer = uDNSServer = llqMatch->qDNSServer;
-
-                // If this is a DNSSEC question that is also LongLived, don't accept records from the
-                // Additional/Authority section blindly. We need to go through IsAcceptableResponse below
-                // so that NSEC/NSEC3 record are cached in the nseclist if we accept them. This can happen
-                // for both negative responses and wildcard expanded positive responses as both of come
-                // back with NSEC/NSEC3s.
-                if (unicastQuestion && DNSSECQuestion(unicastQuestion))
-                    AcceptableResponse = mDNSfalse;
+#endif
             }
             else if (!AcceptableResponse || !dstaddr)
             {
@@ -9318,44 +9203,67 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
                 // that are not long lived e.g., AAAA lookup in a Private domain), it is indicated by !dstaddr.
                 // Even though it is AcceptableResponse, we still need a DNSServer pointer for the resource records that
                 // we create.
-
-                DNSQuestion *q = ExpectingUnicastResponseForRecord(m, srcaddr, ResponseSrcLocal, dstport, response->h.id, &m->rec.r, !dstaddr);
-
-                // Initialize the DNS server on the resource record which will now filter what questions we answer with
-                // this record.
-                //
-                // We could potentially lookup the DNS server based on the source address, but that may not work always
-                // and that's why ExpectingUnicastResponseForRecord does not try to verify whether the response came
-                // from the DNS server that queried. We follow the same logic here. If we can find a matching quetion based
-                // on the "id" and "source port", then this response answers the question and assume the response
-                // came from the same DNS server that we sent the query to.
-
-                if (q != mDNSNULL)
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                if (querier)
                 {
-                    AcceptableResponse = mDNStrue;
-                    if (!InterfaceID)
-                    {
-                        debugf("mDNSCoreReceiveResponse: InterfaceID %p %##s (%s)", q->InterfaceID, q->qname.c, DNSTypeName(q->qtype));
-                        m->rec.r.resrec.rDNSServer = uDNSServer = q->qDNSServer;
-                        if (!unicastQuestion) unicastQuestion = q;      //  Acceptable responses to unicast questions need to have (unicastQuestion != nil)
-                    }
-                    else
+                    ResourceRecord *const rr = &m->rec.r.resrec;
+                    if (Querier_ResourceRecordIsAnswer(rr, querier))
                     {
-                        // Accept all remaining records in this unicast response to an mDNS query.
-                        recordAcceptedInResponse = mDNStrue;
-                        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-                               "[R%d->Q%d] mDNSCoreReceiveResponse: Accepting response for query: " PRI_DM_NAME " (" PUB_S ")",
-                               q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype));
+                        const mdns_resolver_type_t resolver_type = mdns_querier_get_resolver_type(querier);
+                        if ((resolver_type == mdns_resolver_type_normal) &&
+                            (mdns_querier_get_over_tcp_reason(querier) != mdns_query_over_tcp_reason_null))
+                        {
+                            rr->protocol = mdns_resolver_type_tcp;
+                        }
+                        else
+                        {
+                            rr->protocol = resolver_type;
+                        }
+                        mdns_replace(&rr->dnsservice, uDNSService);
+                        AcceptableResponse = mDNStrue;
                     }
                 }
                 else
+#endif
                 {
-                    // If we can't find a matching question, we need to see whether we have seen records earlier that matched
-                    // the question. The code below does that. So, make this record unacceptable for now
-                    if (!InterfaceID)
+                    const DNSQuestion *q;
+                    // Initialize the DNS server on the resource record which will now filter what questions we answer with
+                    // this record.
+                    //
+                    // We could potentially lookup the DNS server based on the source address, but that may not work always
+                    // and that's why ExpectingUnicastResponseForRecord does not try to verify whether the response came
+                    // from the DNS server that queried. We follow the same logic here. If we can find a matching quetion based
+                    // on the "id" and "source port", then this response answers the question and assume the response
+                    // came from the same DNS server that we sent the query to.
+                    q = ExpectingUnicastResponseForRecord(m, srcaddr, ResponseSrcLocal, dstport, response->h.id, &m->rec.r, !dstaddr);
+                    if (q != mDNSNULL)
+                    {
+                        AcceptableResponse = mDNStrue;
+                        if (!InterfaceID)
+                        {
+                            debugf("mDNSCoreReceiveResponse: InterfaceID %p %##s (%s)", q->InterfaceID, q->qname.c, DNSTypeName(q->qtype));
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                            m->rec.r.resrec.rDNSServer = uDNSServer = q->qDNSServer;
+#endif
+                        }
+                        else
+                        {
+                            // Accept all remaining records in this unicast response to an mDNS query.
+                            recordAcceptedInResponse = mDNStrue;
+                            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+                                "[R%d->Q%d] mDNSCoreReceiveResponse: Accepting response for query: " PRI_DM_NAME " (" PUB_S ")",
+                                q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype));
+                        }
+                    }
+                    else
                     {
-                        debugf("mDNSCoreReceiveResponse: Can't find question for record name %##s", m->rec.r.resrec.name->c);
-                        AcceptableResponse = mDNSfalse;
+                        // If we can't find a matching question, we need to see whether we have seen records earlier that matched
+                        // the question. The code below does that. So, make this record unacceptable for now
+                        if (!InterfaceID)
+                        {
+                            debugf("mDNSCoreReceiveResponse: Can't find question for record name %##s", m->rec.r.resrec.name->c);
+                            AcceptableResponse = mDNSfalse;
+                        }
                     }
                 }
             }
@@ -9398,7 +9306,7 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
         }
 
         // 1. Check that this packet resource record does not conflict with any of ours
-        if (mDNSOpaque16IsZero(response->h.id) && m->rec.r.resrec.rrtype != kDNSType_NSEC)
+        if (ResponseIsMDNS && m->rec.r.resrec.rrtype != kDNSType_NSEC)
         {
             if (m->CurrentRecord)
                 LogMsg("mDNSCoreReceiveResponse ERROR m->CurrentRecord already set %s", ARDisplayString(m, m->CurrentRecord));
@@ -9527,27 +9435,49 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
                     // If the packet record has the cache-flush bit set, then we check to see if we
                     // have any record(s) of the same type that we should re-assert to rescue them
                     // (see note about "multi-homing and bridged networks" at the end of this function).
-                    else if (m->rec.r.resrec.rrtype == rr->resrec.rrtype)
-                        if ((m->rec.r.resrec.RecordType & kDNSRecordTypePacketUniqueMask) && (mDNSu32)(m->timenow - rr->LastMCTime) > (mDNSu32)mDNSPlatformOneSecond/2)
-                        { rr->ImmedAnswer = mDNSInterfaceMark; m->NextScheduledResponse = m->timenow; }
+                    else if ((m->rec.r.resrec.rrtype == rr->resrec.rrtype) &&
+                        (m->rec.r.resrec.RecordType & kDNSRecordTypePacketUniqueMask) &&
+                        ((mDNSu32)(m->timenow - rr->LastMCTime) > (mDNSu32)mDNSPlatformOneSecond/2) &&
+                        ResourceRecordIsValidAnswer(rr))
+                    {
+                        rr->ImmedAnswer = mDNSInterfaceMark;
+                        m->NextScheduledResponse = m->timenow;
+                    }
                 }
             }
         }
 
-        nseclist = mDNSfalse;
         if (!AcceptableResponse)
         {
-            AcceptableResponse = IsResponseAcceptable(m, CacheFlushRecords, unicastQuestion, &nseclist);
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            not_answer_but_required_for_dnssec = adds_denial_records_in_cache_record(&m->rec.r.resrec,
+                querier != mDNSNULL && mdns_querier_get_dnssec_ok(querier), &denial_of_existence_records);
+    #else
+            not_answer_but_required_for_dnssec = mDNSfalse;
+    #endif
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+            AcceptableResponse = IsResponseAcceptable(m, CacheFlushRecords);
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            if (AcceptableResponse) mdns_replace(&m->rec.r.resrec.dnsservice, uDNSService);
+#else
             if (AcceptableResponse) m->rec.r.resrec.rDNSServer = uDNSServer;
+#endif
         }
 
         // 2. See if we want to add this packet resource record to our cache
         // We only try to cache answers if we have a cache to put them in
         // Also, we ignore any apparent attempts at cache poisoning unicast to us that do not answer any outstanding active query
         if (!AcceptableResponse) {
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "[Q%d] mDNSCoreReceiveResponse ignoring " PRI_S,
-                   mDNSVal16(response->h.id), CRDisplayString(m, &m->rec.r));
+            const char* savedString = "";
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+            savedString = (not_answer_but_required_for_dnssec ? "Saved for DNSSEC" : "");
+#endif
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "[Q%d] mDNSCoreReceiveResponse ignoring " PRI_S " %s",
+                mDNSVal16(response->h.id), CRDisplayString(m, &m->rec.r), savedString);
         }
+
         if (m->rrcache_size && AcceptableResponse)
         {
             const mDNSu32 slot = HashSlotFromNameHash(m->rec.r.resrec.namehash);
@@ -9555,14 +9485,7 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
             CacheRecord *rr = mDNSNULL;
 
             // 2a. Check if this packet resource record is already in our cache.
-            //
-            // If this record should go in the nseclist, don't look in the cache for updating it.
-            // They are supposed to be cached under the "nsec" field of the cache record for
-            // validation. Just create the cache record. 
-            if (!nseclist)
-            {
-                rr = mDNSCoreReceiveCacheCheck(m, response, LLQType, slot, cg, unicastQuestion, &cfp, &NSECCachePtr, InterfaceID);
-            }
+            rr = mDNSCoreReceiveCacheCheck(m, response, LLQType, slot, cg, &cfp, InterfaceID);
 
             // If packet resource record not in our cache, add it now
             // (unless it is just a deletion of a record we never had, in which case we don't care)
@@ -9580,38 +9503,16 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
                 // Below, where we walk the CacheFlushRecords list, we either call CacheRecordDeferredAdd()
                 // to immediately to generate answer callbacks, or we call ScheduleNextCacheCheckTime()
                 // to schedule an mDNS_Execute task at the appropriate time.
-                rr = CreateNewCacheEntry(m, slot, cg, delay, !nseclist, srcaddr);
+                rr = CreateNewCacheEntry(m, slot, cg, delay, mDNStrue, srcaddr);
                 if (rr)
                 {
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+                    set_denial_records_in_cache_record(rr, &denial_of_existence_records);
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
                     rr->responseFlags = response->h.flags;
-                    // If we are not creating signatures, then we need to inform DNSSEC so that
-                    // it does not wait forever. Don't do this if we got NSEC records
-                    // as it indicates that this name does not exist.
-                    if (rr->resrec.rrtype == kDNSType_RRSIG && !nseclist)
-                    {
-                        rrsigsCreated = mDNStrue;
-                    }
-                    // Remember whether we created a cache record in response to a DNSSEC question.
-                    // This helps DNSSEC code not to reissue the question to fetch the DNSSEC records.
-                    rr->CRDNSSECQuestion = 0;
-                    if (unicastQuestion && DNSSECQuestion(unicastQuestion))
-                    {
-                        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-                               "[R%d->Q%d] mDNSCoreReceiveResponse: CRDNSSECQuestion set for new record " PRI_S ", question " PRI_DM_NAME " (" PUB_S ")",
-                               unicastQuestion->request_id, mDNSVal16(unicastQuestion->TargetQID), CRDisplayString(m, rr),
-                               DM_NAME_PARAM(unicastQuestion->qname.c), DNSTypeName(unicastQuestion->qtype));
-                        rr->CRDNSSECQuestion = 1;
-                    }
-                    // NSEC/NSEC3 records and its signatures are cached with the negative cache entry
-                    // which we should be creating below. It is also needed in the wildcard
-                    // expanded answer case and in that case it is cached along with the answer.
-                    if (nseclist)
-                    {
-                        rr->TimeRcvd = m->timenow;
-                        *nsecp = rr;
-                        nsecp = &rr->next;
-                    }
-                    else if (AddToCFList)
+
+                    if (AddToCFList)
                     {
                         *cfp = rr;
                         cfp = &rr->NextInCFList;
@@ -9656,119 +9557,125 @@ exit:
         // *decrease* a record's remaining lifetime, never *increase* it.
         for (r2 = cg ? cg->members : mDNSNULL; r2; r2=r2->next)
         {
-            mDNSu16 id1;
-            mDNSu16 id2;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            mDNSBool match;
+#else
+            mDNSu32 id1;
+            mDNSu32 id2;
+#endif
             if (!r1->resrec.InterfaceID)
             {
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                match = (r1->resrec.dnsservice == r2->resrec.dnsservice) ? mDNStrue : mDNSfalse;
+#else
                 id1 = (r1->resrec.rDNSServer ? r1->resrec.rDNSServer->resGroupID : 0);
                 id2 = (r2->resrec.rDNSServer ? r2->resrec.rDNSServer->resGroupID : 0);
+#endif
             }
             else
             {
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                match = mDNStrue;
+#else
                 id1 = id2 = 0;
+#endif
             }
-            // When we receive new RRSIGs e.g., for DNSKEY record, we should not flush the old
-            // RRSIGS e.g., for TXT record. To do so, we need to look at the typeCovered field of
-            // the new RRSIG that we received. Process only if the typeCovered matches.
-            if ((r1->resrec.rrtype == r2->resrec.rrtype) && (r1->resrec.rrtype == kDNSType_RRSIG))
-            {
-                rdataRRSig *rrsig1 = (rdataRRSig *)(((RDataBody2 *)(r1->resrec.rdata->u.data))->data);
-                rdataRRSig *rrsig2 = (rdataRRSig *)(((RDataBody2 *)(r2->resrec.rdata->u.data))->data);
-                if (swap16(rrsig1->typeCovered) != swap16(rrsig2->typeCovered))
-                {
-                    debugf("mDNSCoreReceiveResponse: Received RRSIG typeCovered %s, found %s, not processing",
-                        DNSTypeName(swap16(rrsig1->typeCovered)), DNSTypeName(swap16(rrsig2->typeCovered)));
-                    continue;
-                }
-            }
-
             // For Unicast (null InterfaceID) the resolver IDs should also match
             if ((r1->resrec.InterfaceID == r2->resrec.InterfaceID) &&
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                (r1->resrec.InterfaceID || match) &&
+#else
                 (r1->resrec.InterfaceID || (id1 == id2)) &&
+#endif
                 r1->resrec.rrtype      == r2->resrec.rrtype &&
-                r1->resrec.rrclass     == r2->resrec.rrclass)
+                r1->resrec.rrclass     == r2->resrec.rrclass
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+                // 2 RRSIGs need to cover the same DNS type to be identified as one RRSET, and have the same TTL
+                && are_records_in_the_same_cache_set_for_dnssec(&r1->resrec, &r2->resrec)
+#endif
+                )
             {
-                if (r1->resrec.mortality == Mortality_Mortal && r2->resrec.mortality != Mortality_Mortal)
-                {
-                    verbosedebugf("mDNSCoreReceiveResponse: R1(%p) is being immortalized by R2(%p)", r1, r2);
-                    r1->resrec.mortality = Mortality_Immortal;   //  Immortalize the replacement record
-                }
-
-                // If record is recent, just ensure the whole RRSet has the same TTL (as required by DNS semantics)
-                // else, if record is old, mark it to be flushed
-                if (m->timenow - r2->TimeRcvd < mDNSPlatformOneSecond && RRExpireTime(r2) - m->timenow > mDNSPlatformOneSecond)
-                {
-                    // If we find mismatched TTLs in an RRSet, correct them.
-                    // We only do this for records with a TTL of 2 or higher. It's possible to have a
-                    // goodbye announcement with the cache flush bit set (or a case-change on record rdata,
-                    // which we treat as a goodbye followed by an addition) and in that case it would be
-                    // inappropriate to synchronize all the other records to a TTL of 0 (or 1).
-
-                    // We suppress the message for the specific case of correcting from 240 to 60 for type TXT,
-                    // because certain early Bonjour devices are known to have this specific mismatch, and
-                    // there's no point filling syslog with messages about something we already know about.
-                    // We also don't log this for uDNS responses, since a caching name server is obliged
-                    // to give us an aged TTL to correct for how long it has held the record,
-                    // so our received TTLs are expected to vary in that case
-
-                    // We also suppress log message in the case of SRV records that are received
-                    // with a TTL of 4500 that are already cached with a TTL of 120 seconds, since
-                    // this behavior was observed for a number of discoveryd based AppleTV's in iOS 8
-                    // GM builds.
-                    if (r2->resrec.rroriginalttl != r1->resrec.rroriginalttl && r1->resrec.rroriginalttl > 1)
+                    if (r1->resrec.mortality == Mortality_Mortal && r2->resrec.mortality != Mortality_Mortal)
                     {
-                        if (!(r2->resrec.rroriginalttl == 240 && r1->resrec.rroriginalttl == 60 && r2->resrec.rrtype == kDNSType_TXT) &&
-                            !(r2->resrec.rroriginalttl == 120 && r1->resrec.rroriginalttl == 4500 && r2->resrec.rrtype == kDNSType_SRV) &&
-                            mDNSOpaque16IsZero(response->h.id))
-                            LogInfo("Correcting TTL from %4d to %4d for %s",
-                                    r2->resrec.rroriginalttl, r1->resrec.rroriginalttl, CRDisplayString(m, r2));
-                        r2->resrec.rroriginalttl = r1->resrec.rroriginalttl;
+                        verbosedebugf("mDNSCoreReceiveResponse: R1(%p) is being immortalized by R2(%p)", r1, r2);
+                        r1->resrec.mortality = Mortality_Immortal;   //  Immortalize the replacement record
                     }
-                    r2->TimeRcvd = m->timenow;
-                    SetNextCacheCheckTimeForRecord(m, r2);
-                }
-                else if (r2->resrec.InterfaceID) // else, if record is old, mark it to be flushed
-                {
-                    verbosedebugf("Cache flush new %p age %d expire in %d %s", r1, m->timenow - r1->TimeRcvd, RRExpireTime(r1) - m->timenow, CRDisplayString(m, r1));
-                    verbosedebugf("Cache flush old %p age %d expire in %d %s", r2, m->timenow - r2->TimeRcvd, RRExpireTime(r2) - m->timenow, CRDisplayString(m, r2));
-                    // We set stale records to expire in one second.
-                    // This gives the owner a chance to rescue it if necessary.
-                    // This is important in the case of multi-homing and bridged networks:
-                    //   Suppose host X is on Ethernet. X then connects to an AirPort base station, which happens to be
-                    //   bridged onto the same Ethernet. When X announces its AirPort IP address with the cache-flush bit
-                    //   set, the AirPort packet will be bridged onto the Ethernet, and all other hosts on the Ethernet
-                    //   will promptly delete their cached copies of the (still valid) Ethernet IP address record.
-                    //   By delaying the deletion by one second, we give X a change to notice that this bridging has
-                    //   happened, and re-announce its Ethernet IP address to rescue it from deletion from all our caches.
-
-                    // We set UnansweredQueries to MaxUnansweredQueries to avoid expensive and unnecessary
-                    // final expiration queries for this record.
-
-                    // If a record is deleted twice, first with an explicit DE record, then a second time by virtue of the cache
-                    // flush bit on the new record replacing it, then we allow the record to be deleted immediately, without the usual
-                    // one-second grace period. This improves responsiveness for mDNS_Update(), as used for things like iChat status updates.
-                    // <rdar://problem/5636422> Updating TXT records is too slow
-                    // We check for "rroriginalttl == 1" because we want to include records tagged by the "packet TTL is zero" check above,
-                    // which sets rroriginalttl to 1, but not records tagged by the rdata case-change check, which sets rroriginalttl to 0.
-                    if (r2->TimeRcvd == m->timenow && r2->resrec.rroriginalttl == 1 && r2->UnansweredQueries == MaxUnansweredQueries)
+
+                    // If record is recent, just ensure the whole RRSet has the same TTL (as required by DNS semantics)
+                    // else, if record is old, mark it to be flushed
+                    if (m->timenow - r2->TimeRcvd < mDNSPlatformOneSecond && RRExpireTime(r2) - m->timenow > mDNSPlatformOneSecond)
                     {
-                        LogInfo("Cache flush for DE record %s", CRDisplayString(m, r2));
-                        r2->resrec.rroriginalttl = 0;
+                        // If we find mismatched TTLs in an RRSet, correct them.
+                        // We only do this for records with a TTL of 2 or higher. It's possible to have a
+                        // goodbye announcement with the cache flush bit set (or a case-change on record rdata,
+                        // which we treat as a goodbye followed by an addition) and in that case it would be
+                        // inappropriate to synchronize all the other records to a TTL of 0 (or 1).
+
+                        // We suppress the message for the specific case of correcting from 240 to 60 for type TXT,
+                        // because certain early Bonjour devices are known to have this specific mismatch, and
+                        // there's no point filling syslog with messages about something we already know about.
+                        // We also don't log this for uDNS responses, since a caching name server is obliged
+                        // to give us an aged TTL to correct for how long it has held the record,
+                        // so our received TTLs are expected to vary in that case
+
+                        // We also suppress log message in the case of SRV records that are received
+                        // with a TTL of 4500 that are already cached with a TTL of 120 seconds, since
+                        // this behavior was observed for a number of discoveryd based AppleTV's in iOS 8
+                        // GM builds.
+                        if (r2->resrec.rroriginalttl != r1->resrec.rroriginalttl && r1->resrec.rroriginalttl > 1)
+                        {
+                            if (!(r2->resrec.rroriginalttl == 240 && r1->resrec.rroriginalttl == 60 && r2->resrec.rrtype == kDNSType_TXT) &&
+                                !(r2->resrec.rroriginalttl == 120 && r1->resrec.rroriginalttl == 4500 && r2->resrec.rrtype == kDNSType_SRV) &&
+                                ResponseIsMDNS)
+                                LogInfo("Correcting TTL from %4d to %4d for %s",
+                                        r2->resrec.rroriginalttl, r1->resrec.rroriginalttl, CRDisplayString(m, r2));
+                            r2->resrec.rroriginalttl = r1->resrec.rroriginalttl;
+                        }
+                        r2->TimeRcvd = m->timenow;
+                        SetNextCacheCheckTimeForRecord(m, r2);
                     }
-                    else if (RRExpireTime(r2) - m->timenow > mDNSPlatformOneSecond)
+                    else if (r2->resrec.InterfaceID) // else, if record is old, mark it to be flushed
                     {
-                        // We only set a record to expire in one second if it currently has *more* than a second to live
-                        // If it's already due to expire in a second or less, we just leave it alone
-                        r2->resrec.rroriginalttl = 1;
-                        r2->UnansweredQueries = MaxUnansweredQueries;
-                        r2->TimeRcvd = m->timenow - 1;
-                        // We use (m->timenow - 1) instead of m->timenow, because we use that to identify records
-                        // that we marked for deletion via an explicit DE record
+                        verbosedebugf("Cache flush new %p age %d expire in %d %s", r1, m->timenow - r1->TimeRcvd, RRExpireTime(r1) - m->timenow, CRDisplayString(m, r1));
+                        verbosedebugf("Cache flush old %p age %d expire in %d %s", r2, m->timenow - r2->TimeRcvd, RRExpireTime(r2) - m->timenow, CRDisplayString(m, r2));
+                        // We set stale records to expire in one second.
+                        // This gives the owner a chance to rescue it if necessary.
+                        // This is important in the case of multi-homing and bridged networks:
+                        //   Suppose host X is on Ethernet. X then connects to an AirPort base station, which happens to be
+                        //   bridged onto the same Ethernet. When X announces its AirPort IP address with the cache-flush bit
+                        //   set, the AirPort packet will be bridged onto the Ethernet, and all other hosts on the Ethernet
+                        //   will promptly delete their cached copies of the (still valid) Ethernet IP address record.
+                        //   By delaying the deletion by one second, we give X a change to notice that this bridging has
+                        //   happened, and re-announce its Ethernet IP address to rescue it from deletion from all our caches.
+
+                        // We set UnansweredQueries to MaxUnansweredQueries to avoid expensive and unnecessary
+                        // final expiration queries for this record.
+
+                        // If a record is deleted twice, first with an explicit DE record, then a second time by virtue of the cache
+                        // flush bit on the new record replacing it, then we allow the record to be deleted immediately, without the usual
+                        // one-second grace period. This improves responsiveness for mDNS_Update(), as used for things like iChat status updates.
+                        // <rdar://problem/5636422> Updating TXT records is too slow
+                        // We check for "rroriginalttl == 1" because we want to include records tagged by the "packet TTL is zero" check above,
+                        // which sets rroriginalttl to 1, but not records tagged by the rdata case-change check, which sets rroriginalttl to 0.
+                        if (r2->TimeRcvd == m->timenow && r2->resrec.rroriginalttl == 1 && r2->UnansweredQueries == MaxUnansweredQueries)
+                        {
+                            LogInfo("Cache flush for DE record %s", CRDisplayString(m, r2));
+                            r2->resrec.rroriginalttl = 0;
+                        }
+                        else if (RRExpireTime(r2) - m->timenow > mDNSPlatformOneSecond)
+                        {
+                            // We only set a record to expire in one second if it currently has *more* than a second to live
+                            // If it's already due to expire in a second or less, we just leave it alone
+                            r2->resrec.rroriginalttl = 1;
+                            r2->UnansweredQueries = MaxUnansweredQueries;
+                            r2->TimeRcvd = m->timenow - 1;
+                            // We use (m->timenow - 1) instead of m->timenow, because we use that to identify records
+                            // that we marked for deletion via an explicit DE record
+                        }
+                        SetNextCacheCheckTimeForRecord(m, r2);
                     }
-                    SetNextCacheCheckTimeForRecord(m, r2);
-                }
-                else
-                {
+                    else
+                    {
 #if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
                     if (r2->resrec.mortality == Mortality_Ghost)
                     {
@@ -9785,39 +9692,14 @@ exit:
                     }
 #endif
                     // Old uDNS records are scheduled to be purged instead of given at most one second to live.
-                    r2->resrec.mortality = Mortality_Mortal;       //  We want it purged, so remove any immortality
                     mDNS_PurgeCacheResourceRecord(m, r2);
                     purgedRecords = mDNStrue;
                 }
             }
-        }
+       }
 
         if (r1->DelayDelivery)  // If we were planning to delay delivery of this record, see if we still need to
         {
-            // If we had a unicast question for this response with at least one positive answer and we
-            // have NSECRecords, it is most likely a wildcard expanded answer. Cache the NSEC and its
-            // signatures along with the cache record which will be used for validation later. If
-            // we rescued a few records earlier in this function, then NSECCachePtr would be set. In that
-            // use that instead.
-            if (response->h.numAnswers && unicastQuestion && NSECRecords)
-            {
-                if (!NSECCachePtr)
-                {
-                    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-                           "[R%d->Q%d] mDNSCoreReceiveResponse: Updating NSECCachePtr to " PRI_S,
-                           unicastQuestion->request_id, mDNSVal16(unicastQuestion->TargetQID), CRDisplayString(m, r1));
-                    NSECCachePtr = r1;
-                }
-                // Note: We need to do this before we call CacheRecordDeferredAdd as this
-                // might start the verification process which needs these NSEC records
-                if (!AddNSECSForCacheRecord(m, NSECRecords, NSECCachePtr, rcode))
-                {
-                    LogInfo("mDNSCoreReceiveResponse: AddNSECSForCacheRecord failed to add NSEC for %s", CRDisplayString(m, NSECCachePtr));
-                    FreeNSECRecords(m, NSECRecords);
-                }
-                NSECRecords = mDNSNULL;
-                NSECCachePtr = mDNSNULL;
-            }
             if (r1->resrec.InterfaceID)
             {
                 r1->DelayDelivery = CheckForSoonToExpireRecords(m, r1->resrec.name, r1->resrec.namehash);
@@ -9834,41 +9716,19 @@ exit:
         }
     }
 
-    // If we have not consumed the NSEC records yet e.g., just refreshing the cache,
-    // update them now for future validations.
-    if (NSECRecords && NSECCachePtr)
-    {
-        LogInfo("mDNSCoreReceieveResponse: Updating NSEC records in %s", CRDisplayString(m, NSECCachePtr));
-        if (!AddNSECSForCacheRecord(m, NSECRecords, NSECCachePtr, rcode))
-        {
-            LogInfo("mDNSCoreReceiveResponse: AddNSECSForCacheRecord failed to add NSEC for %s", CRDisplayString(m, NSECCachePtr));
-            FreeNSECRecords(m, NSECRecords);
-        }
-        NSECRecords = mDNSNULL;
-        NSECCachePtr = mDNSNULL;
-    }
-
-    // If there is at least one answer and we did not create RRSIGs and there was a
-    // ValidatingResponse question waiting for this response, give a hint that no RRSIGs
-    // were created. We don't need to give a hint:
-    //
-    // - if we have no answers, the mDNSCoreReceiveNoUnicastAnswers below should
-    //   generate a negative response
-    //
-    // - if we have NSECRecords, it means we might have a potential proof for
-    //   non-existence of name that we are looking for
-    //
-    if (response->h.numAnswers && !rrsigsCreated && DNSSECQuestion && !NSECRecords)
-        mDNSCoreReceiveNoDNSSECAnswers(m, response, end, dstaddr, dstport, InterfaceID);
-
     // See if we need to generate negative cache entries for unanswered unicast questions
-    mDNSCoreReceiveNoUnicastAnswers(m, response, end, dstaddr, dstport, InterfaceID, LLQType, rcode, NSECRecords);
+    mDNSCoreReceiveNoUnicastAnswers(m, response, end, dstaddr, dstport, InterfaceID,
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        querier, uDNSService,
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+        &denial_of_existence_records,
+#endif
+        LLQType);
 
-    if (McastNSEC3Records)
-    {
-        debugf("mDNSCoreReceiveResponse: McastNSEC3Records not used");
-        FreeNSECRecords(m, McastNSEC3Records);
-    }
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+        destroy_denial_of_existence_records_t_if_nonnull(denial_of_existence_records);
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
 }
 
 // ScheduleWakeup causes all proxy records with WakeUp.HMAC matching mDNSEthAddr 'e' to be deregistered, causing
@@ -10561,7 +10421,7 @@ mDNSlocal mDNSu32 mDNSGenerateOwnerOptForInterface(mDNS *const m, const mDNSInte
     {
         // Put all the integer values in IETF byte-order (MSB first, LSB second)
         SwapDNSHeaderBytes(msg);
-        length = (end - msg->data);
+        length = (mDNSu32)(end - msg->data);
     }
     else
         LogSPS("mDNSGenerateOwnerOptForInterface: Failed to generate owner OPT record");
@@ -10640,8 +10500,13 @@ mDNSlocal void mDNSCoreReceiveUpdateR(mDNS *const m, const DNSMessage *const msg
     if (m->SleepLimit) m->NextScheduledSPRetry = m->timenow;
 }
 
-mDNSexport void MakeNegativeCacheRecord(mDNS *const m, CacheRecord *const cr,
-                                        const domainname *const name, const mDNSu32 namehash, const mDNSu16 rrtype, const mDNSu16 rrclass, mDNSu32 ttl_seconds, mDNSInterfaceID InterfaceID, DNSServer *dnsserver)
+mDNSexport void MakeNegativeCacheRecord(mDNS *const m, CacheRecord *const cr, const domainname *const name,
+    const mDNSu32 namehash, const mDNSu16 rrtype, const mDNSu16 rrclass, mDNSu32 ttl_seconds, mDNSInterfaceID InterfaceID,
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mdns_dns_service_t service)
+#else
+    DNSServer *dnsserver)
+#endif
 {
     if (cr == &m->rec.r && m->rec.r.resrec.RecordType)
         LogFatalError("MakeNegativeCacheRecord: m->rec appears to be already in use for %s", CRDisplayString(m, &m->rec.r));
@@ -10649,7 +10514,11 @@ mDNSexport void MakeNegativeCacheRecord(mDNS *const m, CacheRecord *const cr,
     // Create empty resource record
     cr->resrec.RecordType    = kDNSRecordTypePacketNegative;
     cr->resrec.InterfaceID   = InterfaceID;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mdns_replace(&cr->resrec.dnsservice, service);
+#else
     cr->resrec.rDNSServer    = dnsserver;
+#endif
     cr->resrec.name          = name;    // Will be updated to point to cg->name when we call CreateNewCacheEntry
     cr->resrec.rrtype        = rrtype;
     cr->resrec.rrclass       = rrclass;
@@ -10665,18 +10534,30 @@ mDNSexport void MakeNegativeCacheRecord(mDNS *const m, CacheRecord *const cr,
     cr->TimeRcvd           = m->timenow;
     cr->DelayDelivery      = 0;
     cr->NextRequiredQuery  = m->timenow;
+#if MDNSRESPONDER_SUPPORTS(APPLE, CACHE_ANALYTICS)
+    cr->LastCachedAnswerTime= 0;
+#endif
     cr->CRActiveQuestion   = mDNSNULL;
     cr->UnansweredQueries  = 0;
     cr->LastUnansweredTime = 0;
     cr->NextInCFList       = mDNSNULL;
-    cr->nsec               = mDNSNULL;
     cr->soa                = mDNSNULL;
-    cr->CRDNSSECQuestion   = 0;
     // Initialize to the basic one and the caller can set it to more
     // specific based on the response if any
     cr->responseFlags      = ResponseFlags;
 }
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+mDNSexport void mDNSCoreReceiveForQuerier(mDNS *const m, DNSMessage *const msg, const mDNSu8 *const end,
+    mdns_querier_t querier, mdns_dns_service_t dnsservice)
+{
+    SwapDNSHeaderBytes(msg);
+    mDNS_Lock(m);
+    mDNSCoreReceiveResponse(m, msg, end, mDNSNULL, zeroIPPort, mDNSNULL, zeroIPPort, querier, dnsservice, mDNSNULL);
+    mDNS_Unlock(m);
+}
+#endif
+
 mDNSexport void mDNSCoreReceive(mDNS *const m, DNSMessage *const msg, const mDNSu8 *const end,
                                 const mDNSAddr *const srcaddr, const mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSIPPort dstport,
                                 const mDNSInterfaceID InterfaceID)
@@ -10765,7 +10646,11 @@ mDNSexport void mDNSCoreReceive(mDNS *const m, DNSMessage *const msg, const mDNS
         }
 #endif
     if      (QR_OP == StdQ) mDNSCoreReceiveQuery   (m, msg, end, srcaddr, srcport, dstaddr, dstport, ifid);
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    else if (QR_OP == StdR) mDNSCoreReceiveResponse(m, msg, end, srcaddr, srcport, dstaddr, dstport, mDNSNULL, mDNSNULL, ifid);
+#else
     else if (QR_OP == StdR) mDNSCoreReceiveResponse(m, msg, end, srcaddr, srcport, dstaddr, dstport, ifid);
+#endif
     else if (QR_OP == UpdQ) mDNSCoreReceiveUpdate  (m, msg, end, srcaddr, srcport, dstaddr, dstport, InterfaceID);
     else if (QR_OP == UpdR) mDNSCoreReceiveUpdateR (m, msg, end, srcaddr,                            InterfaceID);
     else
@@ -10834,19 +10719,20 @@ mDNSlocal DNSQuestion *FindDuplicateQuestion(const mDNS *const m, const DNSQuest
     // further in the list.
     for (q = m->Questions; q && (q != question); q = q->next)
     {
-        if (!SameQuestionKind(q, question))                         continue;
-        if (q->qnamehash          != question->qnamehash)           continue;
-        if (q->InterfaceID        != question->InterfaceID)         continue;
-        if (q->qtype              != question->qtype)               continue;
-        if (q->qclass             != question->qclass)              continue;
-        if (IsLLQ(q)              != IsLLQ(question))               continue;
-        if (q->AuthInfo && !question->AuthInfo)                     continue;
-        if (q->ValidationRequired != question->ValidationRequired)  continue;
-        if (q->ValidatingResponse != question->ValidatingResponse)  continue;
-        if (!q->Suppressed        != !question->Suppressed)         continue;
-        if (q->BrowseThreshold    != question->BrowseThreshold)     continue;
-        if (AWDLIsIncluded(q)     != AWDLIsIncluded(question))      continue;
-        if (!SameDomainName(&q->qname, &question->qname))           continue;
+        if (!SameQuestionKind(q, question))                             continue;
+        if (q->qnamehash          != question->qnamehash)               continue;
+        if (q->InterfaceID        != question->InterfaceID)             continue;
+        if (q->qtype              != question->qtype)                   continue;
+        if (q->qclass             != question->qclass)                  continue;
+        if (IsLLQ(q)              != IsLLQ(question))                   continue;
+        if (q->AuthInfo && !question->AuthInfo)                         continue;
+        if (!q->Suppressed        != !question->Suppressed)             continue;
+        if (q->BrowseThreshold    != question->BrowseThreshold)         continue;
+        if (AWDLIsIncluded(q)     != AWDLIsIncluded(question))          continue;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        if (q->dnsservice         != question->dnsservice)              continue;
+#endif
+        if (!SameDomainName(&q->qname, &question->qname))               continue;
         return(q);
     }
     return(mDNSNULL);
@@ -10862,11 +10748,11 @@ mDNSlocal void UpdateQuestionDuplicates(mDNS *const m, DNSQuestion *const questi
     // question as a duplicate.
     if (question->DuplicateOf)
     {
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
                "[R%d->DupQ%d->Q%d] UpdateQuestionDuplicates: question %p " PRI_DM_NAME " (" PUB_S ") duplicate of %p " PRI_DM_NAME " (" PUB_S ")",
-               question->request_id, mDNSVal16(question->DuplicateOf->TargetQID), mDNSVal16(question->TargetQID),
-               question, DM_NAME_PARAM(question->qname.c), DNSTypeName(question->qtype), question->DuplicateOf,
-               DM_NAME_PARAM(question->DuplicateOf->qname.c), DNSTypeName(question->DuplicateOf->qtype));
+               question->request_id, mDNSVal16(question->TargetQID), mDNSVal16(question->DuplicateOf->TargetQID),
+               question, DM_NAME_PARAM(&question->qname), DNSTypeName(question->qtype), question->DuplicateOf,
+               DM_NAME_PARAM(&question->DuplicateOf->qname), DNSTypeName(question->DuplicateOf->qtype));
         return;
     }
 
@@ -10890,15 +10776,25 @@ mDNSlocal void UpdateQuestionDuplicates(mDNS *const m, DNSQuestion *const questi
                 q->nta               = question->nta;
                 q->servAddr          = question->servAddr;
                 q->servPort          = question->servPort;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                mdns_replace(&q->dnsservice, question->dnsservice);
+                mdns_forget(&question->dnsservice);
+                mdns_querier_forget(&q->querier);
+                mdns_replace(&q->querier, question->querier);
+                mdns_forget(&question->querier);
+#else
                 q->qDNSServer        = question->qDNSServer;
                 q->validDNSServers   = question->validDNSServers;
                 q->unansweredQueries = question->unansweredQueries;
                 q->noServerResponse  = question->noServerResponse;
                 q->triedAllServersOnce = question->triedAllServersOnce;
+#endif
 
                 q->TargetQID         = question->TargetQID;
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
                 q->LocalSocket       = question->LocalSocket;
                 // No need to close old q->LocalSocket first -- duplicate questions can't have their own sockets
+#endif
 
                 q->state             = question->state;
                 //  q->tcp               = question->tcp;
@@ -10907,13 +10803,16 @@ mDNSlocal void UpdateQuestionDuplicates(mDNS *const m, DNSQuestion *const questi
                 q->ntries            = question->ntries;
                 q->id                = question->id;
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
                 question->LocalSocket = mDNSNULL;
+#endif
                 question->nta        = mDNSNULL;    // If we've got a GetZoneData in progress, transfer it to the newly active question
                 //  question->tcp        = mDNSNULL;
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
                 if (q->LocalSocket)
                     debugf("UpdateQuestionDuplicates transferred LocalSocket pointer for %##s (%s)", q->qname.c, DNSTypeName(q->qtype));
-
+#endif
                 if (q->nta)
                 {
                     LogInfo("UpdateQuestionDuplicates transferred nta pointer for %##s (%s)", q->qname.c, DNSTypeName(q->qtype));
@@ -10978,6 +10877,7 @@ mDNSexport McastResolver *mDNS_AddMcastResolver(mDNS *const m, const domainname
     return(*p);
 }
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 mDNSinline mDNSs32 PenaltyTimeForServer(mDNS *m, DNSServer *server)
 {
     mDNSs32 ptime = 0;
@@ -10997,6 +10897,7 @@ mDNSinline mDNSs32 PenaltyTimeForServer(mDNS *m, DNSServer *server)
     }
     return ptime;
 }
+#endif
 
 //Checks to see whether the newname is a better match for the name, given the best one we have
 //seen so far (given in bestcount).
@@ -11105,6 +11006,7 @@ mDNSexport mDNSBool DomainEnumQuery(const domainname *qname)
     return mDNStrue;
 }
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 // Note: InterfaceID is the InterfaceID of the question
 mDNSlocal mDNSBool DNSServerMatch(DNSServer *d, mDNSInterfaceID InterfaceID, mDNSs32 ServiceID)
 {
@@ -11215,8 +11117,7 @@ mDNSexport mDNSu32 SetValidDNSServers(mDNS *m, DNSQuestion *question)
     debugf("SetValidDNSServers: ValidDNSServer bits 0x%08x%08x%08x%08x for question %p %##s (%s)",
            question->validDNSServers.l[3], question->validDNSServers.l[2], question->validDNSServers.l[1], question->validDNSServers.l[0], question, question->qname.c, DNSTypeName(question->qtype));
     // If there are no matching resolvers, then use the default timeout value.
-    // For ProxyQuestion, shorten the timeout so that dig does not timeout on us in case of no response.
-    return ((question->ProxyQuestion || question->ValidatingResponse) ? DEFAULT_UDNSSEC_TIMEOUT : timeout ? timeout : DEFAULT_UDNS_TIMEOUT);
+    return (timeout ? timeout : DEFAULT_UDNS_TIMEOUT);
 }
 
 // Get the Best server that matches a name. If you find penalized servers, look for the one
@@ -11362,6 +11263,7 @@ mDNSexport DNSServer *GetServerForQuestion(mDNS *m, DNSQuestion *question)
 
     return(curmatch);
 }
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 
 // Called in normal client context (lock not held)
 mDNSlocal void LLQNATCallback(mDNS *m, NATTraversalInfo *n)
@@ -11379,16 +11281,30 @@ mDNSlocal void LLQNATCallback(mDNS *m, NATTraversalInfo *n)
 // This function takes the DNSServer as a separate argument because sometimes the
 // caller has not yet assigned the DNSServer, but wants to evaluate the Suppressed
 // status before switching to it.
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+mDNSexport mDNSBool ShouldSuppressUnicastQuery(const DNSQuestion *const q, const mdns_dns_service_t dnsservice)
+#else
 mDNSlocal mDNSBool ShouldSuppressUnicastQuery(const DNSQuestion *const q, const DNSServer *const server)
+#endif
 {
     mDNSBool suppress = mDNSfalse;
-    const char *reason = NULL;
+    const char *reason = mDNSNULL;
 
     if (q->BlockedByPolicy)
     {
         suppress = mDNStrue;
         reason   = " (blocked by policy)";
     }
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    else if (!dnsservice)
+    {
+        if (!q->IsUnicastDotLocal)
+        {
+            suppress = mDNStrue;
+            reason   = " (no DNS service)";
+        }
+    }
+#else
     else if (!server)
     {
         if (!q->IsUnicastDotLocal)
@@ -11397,17 +11313,30 @@ mDNSlocal mDNSBool ShouldSuppressUnicastQuery(const DNSQuestion *const q, const
             reason   = " (no DNS server)";
         }
     }
-    else if (server->isCell && (q->flags & kDNSServiceFlagsDenyCellular))
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    else if ((q->flags & kDNSServiceFlagsDenyCellular) && mdns_dns_service_interface_is_cellular(dnsservice))
+#else
+    else if ((q->flags & kDNSServiceFlagsDenyCellular) && server->isCell)
+#endif
     {
         suppress = mDNStrue;
         reason   = " (interface is cellular)";
     }
-    else if (server->isExpensive && (q->flags & kDNSServiceFlagsDenyExpensive))
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    else if ((q->flags & kDNSServiceFlagsDenyExpensive) && mdns_dns_service_interface_is_expensive(dnsservice))
+#else
+    else if ((q->flags & kDNSServiceFlagsDenyExpensive) && server->isExpensive)
+#endif
     {
         suppress = mDNStrue;
         reason   = " (interface is expensive)";
     }
-    else if (server->isConstrained && (q->flags & kDNSServiceFlagsDenyConstrained))
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    else if ((q->flags & kDNSServiceFlagsDenyConstrained) && mdns_dns_service_interface_is_constrained(dnsservice))
+#else
+    else if ((q->flags & kDNSServiceFlagsDenyConstrained) && server->isConstrained)
+#endif
     {
         suppress = mDNStrue;
         reason   = " (interface is constrained)";
@@ -11420,7 +11349,11 @@ mDNSlocal mDNSBool ShouldSuppressUnicastQuery(const DNSQuestion *const q, const
     {
         if (q->qtype == kDNSType_A)
         {
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            if (!mdns_dns_service_a_queries_advised(dnsservice))
+#else
             if (!server->usableA)
+#endif
             {
                 suppress = mDNStrue;
                 reason   = " (A records are unusable)";
@@ -11429,7 +11362,11 @@ mDNSlocal mDNSBool ShouldSuppressUnicastQuery(const DNSQuestion *const q, const
             //     1. the interface associated with the server is CLAT46; and
             //     2. the query has the kDNSServiceFlagsPathEvaluationDone flag, indicating that it's from libnetwork.
             // See <rdar://problem/42672030> for more info.
-            else if (server->isCLAT46 && (q->flags & kDNSServiceFlagsPathEvaluationDone))
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            else if ((q->flags & kDNSServiceFlagsPathEvaluationDone) && mdns_dns_service_interface_is_clat46(dnsservice))
+#else
+            else if ((q->flags & kDNSServiceFlagsPathEvaluationDone) && server->isCLAT46)
+#endif
             {
                 suppress = mDNStrue;
                 reason   = " (CLAT46 A records are unusable)";
@@ -11437,7 +11374,11 @@ mDNSlocal mDNSBool ShouldSuppressUnicastQuery(const DNSQuestion *const q, const
         }
         else if (q->qtype == kDNSType_AAAA)
         {
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            if (!mdns_dns_service_aaaa_queries_advised(dnsservice))
+#else
             if (!server->usableAAAA)
+#endif
             {
                 suppress = mDNStrue;
                 reason   = " (AAAA records are unusable)";
@@ -11455,15 +11396,16 @@ mDNSlocal mDNSBool ShouldSuppressUnicastQuery(const DNSQuestion *const q, const
 
 mDNSlocal mDNSBool ShouldSuppressQuery(DNSQuestion *q)
 {
-    if (q->InterfaceID == mDNSInterface_LocalOnly)
-    {
-        return mDNSfalse;
-    }
-    if (!q->IsUnicastDotLocal && IsLocalDomain(&q->qname))
+    // Multicast queries are never suppressed.
+    if (mDNSOpaque16IsZero(q->TargetQID))
     {
         return mDNSfalse;
     }
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    return (ShouldSuppressUnicastQuery(q, q->dnsservice));
+#else
     return (ShouldSuppressUnicastQuery(q, q->qDNSServer));
+#endif
 }
 
 mDNSlocal void CacheRecordRmvEventsForCurrentQuestion(mDNS *const m, DNSQuestion *q)
@@ -11479,7 +11421,7 @@ mDNSlocal void CacheRecordRmvEventsForCurrentQuestion(mDNS *const m, DNSQuestion
         {
             LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                       "[R%u->Q%u] CacheRecordRmvEventsForCurrentQuestion: CacheRecord " PRI_S " Suppressing RMV events for question %p " PRI_DM_NAME " (" PUB_S "), CRActiveQuestion %p, CurrentAnswers %d",
-                      q->request_id, mDNSVal16(q->TargetQID), CRDisplayString(m, cr), q, DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype), cr->CRActiveQuestion, q->CurrentAnswers);
+                      q->request_id, mDNSVal16(q->TargetQID), CRDisplayString(m, cr), q, DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), cr->CRActiveQuestion, q->CurrentAnswers);
             continue;
         }
 
@@ -11505,7 +11447,11 @@ mDNSlocal mDNSBool IsQuestionNew(mDNS *const m, DNSQuestion *question)
     return mDNSfalse;
 }
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+mDNSexport mDNSBool LocalRecordRmvEventsForQuestion(mDNS *const m, DNSQuestion *q)
+#else
 mDNSlocal mDNSBool LocalRecordRmvEventsForQuestion(mDNS *const m, DNSQuestion *q)
+#endif
 {
     AuthRecord *rr;
     AuthGroup *ag;
@@ -11624,7 +11570,7 @@ mDNSlocal void SuppressStatusChanged(mDNS *const m, DNSQuestion *q, DNSQuestion
 
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
         "[R%u->Q%u] SuppressStatusChanged: Stop question %p " PRI_DM_NAME " (" PUB_S ")",
-        q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype));
+        q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype));
     mDNS_StopQuery_internal(m, q);
     q->next = *restart;
     *restart = q;
@@ -11678,6 +11624,7 @@ mDNSexport void CheckSuppressUnusableQuestions(mDNS *const m)
     }
 }
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 mDNSlocal void RestartUnicastQuestions(mDNS *const m)
 {
     DNSQuestion *q;
@@ -11706,10 +11653,11 @@ mDNSlocal void RestartUnicastQuestions(mDNS *const m)
         q->next = mDNSNULL;
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
             "[R%u->Q%u] RestartUnicastQuestions: Start question %p " PRI_DM_NAME " (" PUB_S ")",
-             q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype));
+             q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype));
         mDNS_StartQuery_internal(m, q);
     }
 }
+#endif
 
 // ValidateParameters() is called by mDNS_StartQuery_internal() to check the client parameters of 
 // DNS Question that are already set by the client before calling mDNS_StartQuery() 
@@ -11738,10 +11686,14 @@ mDNSlocal mStatus ValidateParameters(mDNS *const m, DNSQuestion *const question)
 mDNSlocal void InitDNSConfig(mDNS *const m, DNSQuestion *const question)
 {
     // First reset all DNS Configuration
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mdns_forget(&question->dnsservice);
+#else
     question->qDNSServer          = mDNSNULL;
     question->validDNSServers     = zeroOpaque128;
     question->triedAllServersOnce = mDNSfalse;
     question->noServerResponse    = mDNSfalse;
+#endif
     question->StopTime            = (question->TimeoutQuestion) ? question->StopTime : 0;
 #if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
     mDNSPlatformMemZero(&question->metrics, sizeof(question->metrics));
@@ -11754,7 +11706,11 @@ mDNSlocal void InitDNSConfig(mDNS *const m, DNSQuestion *const question)
     // Proceed to initialize DNS Configuration (some are set in SetValidDNSServers())
     if (!mDNSOpaque16IsZero(question->TargetQID))
     {
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        mDNSu32 timeout = 30;
+#else
         mDNSu32 timeout = SetValidDNSServers(m, question);
+#endif
         // We set the timeout value the first time mDNS_StartQuery_internal is called for a question.
         // So if a question is restarted when a network change occurs, the StopTime is not reset.
         // Note that we set the timeout for all questions. If this turns out to be a duplicate,
@@ -11764,15 +11720,19 @@ mDNSlocal void InitDNSConfig(mDNS *const m, DNSQuestion *const question)
             question->StopTime = NonZeroTime(m->timenow + timeout * mDNSPlatformOneSecond);
             LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                 "[Q%u] InitDNSConfig: Setting StopTime on the uDNS question %p " PRI_DM_NAME " (" PUB_S ")",
-                mDNSVal16(question->TargetQID), question, DM_NAME_PARAM(question->qname.c), DNSTypeName(question->qtype));
+                mDNSVal16(question->TargetQID), question, DM_NAME_PARAM(&question->qname), DNSTypeName(question->qtype));
         }
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        Querier_SetDNSServiceForQuestion(question);
+#else
         question->qDNSServer = GetServerForQuestion(m, question);
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
             "[R%u->Q%u] InitDNSConfig: question %p " PRI_DM_NAME " " PUB_S " Timeout %d, DNS Server " PRI_IP_ADDR ":%d",
-            question->request_id, mDNSVal16(question->TargetQID), question, DM_NAME_PARAM(question->qname.c),
+            question->request_id, mDNSVal16(question->TargetQID), question, DM_NAME_PARAM(&question->qname),
             DNSTypeName(question->qtype), timeout, question->qDNSServer ? &question->qDNSServer->addr : mDNSNULL,
             mDNSVal16(question->qDNSServer ? question->qDNSServer->port : zeroIPPort));
+#endif
     }
     else if (question->TimeoutQuestion && !question->StopTime)
     {
@@ -11783,7 +11743,7 @@ mDNSlocal void InitDNSConfig(mDNS *const m, DNSQuestion *const question)
         question->StopTime = NonZeroTime(m->timenow + timeout * mDNSPlatformOneSecond);
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                   "[R%u->Q%u] InitDNSConfig: Setting StopTime on the uDNS question %p " PRI_DM_NAME " (" PUB_S ")",
-                  question->request_id, mDNSVal16(question->TargetQID), question, DM_NAME_PARAM(question->qname.c), DNSTypeName(question->qtype));
+                  question->request_id, mDNSVal16(question->TargetQID), question, DM_NAME_PARAM(&question->qname), DNSTypeName(question->qtype));
     }
     // Set StopTime here since it is a part of DNS Configuration 
     if (question->StopTime)
@@ -11799,7 +11759,6 @@ mDNSlocal void InitDNSConfig(mDNS *const m, DNSQuestion *const question)
 mDNSlocal void InitCommonState(mDNS *const m, DNSQuestion *const question)
 {
     int i;
-    mDNSBool isBlocked = mDNSfalse;
 
     // Note: In the case where we already have the answer to this question in our cache, that may be all the client
     // wanted, and they may immediately cancel their question. In this case, sending an actual query on the wire would
@@ -11813,7 +11772,7 @@ mDNSlocal void InitCommonState(mDNS *const m, DNSQuestion *const question)
     // stopped and can't be on the list. The question is already on the list and ThisQInterval
     // can be negative if the caller just stopped it and starting it again. Hence, it always has to
     // be initialized. CheckForSoonToExpireRecords below prints the cache records when logging is
-    // turned ON which can allocate memory e.g., base64 encoding, in the case of DNSSEC.
+    // turned ON which can allocate memory e.g., base64 encoding.
     question->ThisQInterval     = InitialQuestionInterval;                  // MUST be > zero for an active question
     question->qnamehash         = DomainNameHashValue(&question->qname);
     question->DelayAnswering    = mDNSOpaque16IsZero(question->TargetQID) ? CheckForSoonToExpireRecords(m, &question->qname, question->qnamehash) : 0;
@@ -11848,14 +11807,26 @@ mDNSlocal void InitCommonState(mDNS *const m, DNSQuestion *const question)
     question->FlappingInterface1 = mDNSNULL;
     question->FlappingInterface2 = mDNSNULL;
 
+    // mDNSPlatformGetDNSRoutePolicy() and InitDNSConfig() may set a DNSQuestion's BlockedByPolicy value,
+    // so they should be called before calling ShouldSuppressQuery(), which checks BlockedByPolicy.
+    question->BlockedByPolicy = mDNSfalse;
+
     // if kDNSServiceFlagsServiceIndex flag is SET by the client, then do NOT call mDNSPlatformGetDNSRoutePolicy()
     // since we would already have the question->ServiceID in that case.
     if (!(question->flags & kDNSServiceFlagsServiceIndex))
     {
-#if APPLE_OSX_mDNSResponder
-        mDNSPlatformGetDNSRoutePolicy(question, &isBlocked);
-#else
         question->ServiceID = -1;
+#if APPLE_OSX_mDNSResponder
+        if (!(question->flags & kDNSServiceFlagsPathEvaluationDone) || question->ForcePathEval)
+        {
+            if (question->flags & kDNSServiceFlagsPathEvaluationDone)
+            {
+                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+                    "[R%u->Q%u] Forcing another path evaluation", question->request_id, mDNSVal16(question->TargetQID));
+            }
+            question->ForcePathEval = mDNSfalse;
+            mDNSPlatformGetDNSRoutePolicy(question);
+        }
 #endif
     }
     else
@@ -11863,11 +11834,7 @@ mDNSlocal void InitCommonState(mDNS *const m, DNSQuestion *const question)
                 DNSTypeName(question->qtype), question->pid, question->euid, question->ServiceID);
 
     InitDNSConfig(m, question);
-
     question->AuthInfo          = GetAuthInfoForQuestion(m, question);
-
-    // The question's BlockedByPolicy value must be set before calling ShouldSuppressQuery().
-    question->BlockedByPolicy   = isBlocked ? mDNStrue : mDNSfalse;
     question->Suppressed        = ShouldSuppressQuery(question);
     question->NextInDQList      = mDNSNULL;
     question->SendQNow          = mDNSNULL;
@@ -11896,7 +11863,9 @@ mDNSlocal void InitCommonState(mDNS *const m, DNSQuestion *const question)
     for (i=0; i<DupSuppressInfoSize; i++)
         question->DupSuppress[i].InterfaceID = mDNSNULL;
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     question->Restart = mDNSfalse;
+#endif
 
     debugf("InitCommonState: Question %##s (%s) Interface %p Now %d Send in %d Answer in %d (%p) %s (%p)",
             question->qname.c, DNSTypeName(question->qtype), question->InterfaceID, m->timenow,
@@ -11917,7 +11886,11 @@ mDNSlocal void InitWABState(DNSQuestion *const question)
     // We also don't need one for LLQs because (when we're using NAT) we want them all to share a single
     // NAT mapping for receiving inbound add/remove events.
     question->LocalSocket       = mDNSNULL;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mdns_querier_forget(&question->querier);
+#else
     question->unansweredQueries = 0;
+#endif
     question->nta               = mDNSNULL;
     question->servAddr          = zeroAddr;
     question->servPort          = zeroIPPort;
@@ -11953,29 +11926,7 @@ mDNSlocal void InitLLQState(DNSQuestion *const question)
 mDNSlocal void InitDNSSECProxyState(mDNS *const m, DNSQuestion *const question)
 {
     (void) m;
-
-    // DNS server selection affects DNSSEC. Turn off validation if req_DO is not set
-    // or the request is going over cellular interface.
-    //
-    // Note: This needs to be done here before we call FindDuplicateQuestion as it looks
-    // at ValidationRequired setting also.
-    if (question->qDNSServer)
-    {
-        if (question->qDNSServer->isCell)
-        {
-            debugf("InitDNSSECProxyState: Turning off validation for %##s (%s); going over cell", question->qname.c, DNSTypeName(question->qtype));
-            question->ValidationRequired = mDNSfalse;
-        }
-        if (DNSSECOptionalQuestion(question) && !(question->qDNSServer->req_DO))
-        {
-            LogInfo("InitDNSSECProxyState: Turning off validation for %##s (%s); req_DO false",
-                question->qname.c, DNSTypeName(question->qtype));
-            question->ValidationRequired = DNSSEC_VALIDATION_NONE;
-        }
-    }
-    question->ValidationState = (question->ValidationRequired ? DNSSECValRequired : DNSSECValNotRequired);
-    question->ValidationStatus = 0;
-    question->responseFlags    = zeroID;
+    question->responseFlags = zeroID;
 }
 
 // Once the question is completely initialized including the duplicate logic, this function
@@ -11986,37 +11937,41 @@ mDNSlocal void FinalizeUnicastQuestion(mDNS *const m, DNSQuestion *question)
     // Ensure DNS related info of duplicate question is same as the orig question
     if (question->DuplicateOf)
     {
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        const DNSQuestion *const duplicateOf = question->DuplicateOf;
+        mdns_replace(&question->dnsservice, duplicateOf->dnsservice);
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+           "[R%u->DupQ%u->Q%u] Duplicate question " PRI_DM_NAME " (" PUB_S ")",
+           question->request_id, mDNSVal16(question->TargetQID), mDNSVal16(duplicateOf->TargetQID),
+           DM_NAME_PARAM(&question->qname), DNSTypeName(question->qtype));
+#else
         question->validDNSServers = question->DuplicateOf->validDNSServers;
         // If current(dup) question has DNS Server assigned but the original question has no DNS Server assigned to it,
         // then we log a line as it could indicate an issue
         if (question->DuplicateOf->qDNSServer == mDNSNULL)
         {
-            if (question->qDNSServer) {
+            if (question->qDNSServer)
+            {
                 LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                        "[R%d->Q%d] FinalizeUnicastQuestion: Current(dup) question %p has DNSServer(" PRI_IP_ADDR ":%d) but original question(%p) has no DNS Server! " PRI_DM_NAME " (" PUB_S ")",
                        question->request_id, mDNSVal16(question->TargetQID), question,
                        question->qDNSServer ? &question->qDNSServer->addr : mDNSNULL,
                        mDNSVal16(question->qDNSServer ? question->qDNSServer->port : zeroIPPort), question->DuplicateOf,
-                       DM_NAME_PARAM(question->qname.c), DNSTypeName(question->qtype));
+                       DM_NAME_PARAM(&question->qname), DNSTypeName(question->qtype));
             }
-
         }
         question->qDNSServer = question->DuplicateOf->qDNSServer;
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                "[R%d->DupQ%d->Q%d] FinalizeUnicastQuestion: Duplicate question %p (%p) " PRI_DM_NAME " (" PUB_S "), DNS Server " PRI_IP_ADDR ":%d",
-               question->request_id, mDNSVal16(question->DuplicateOf->TargetQID), mDNSVal16(question->TargetQID),
-               question, question->DuplicateOf, DM_NAME_PARAM(question->qname.c), DNSTypeName(question->qtype),
+               question->request_id, mDNSVal16(question->TargetQID), mDNSVal16(question->DuplicateOf->TargetQID),
+               question, question->DuplicateOf, DM_NAME_PARAM(&question->qname), DNSTypeName(question->qtype),
                question->qDNSServer ? &question->qDNSServer->addr : mDNSNULL,
                mDNSVal16(question->qDNSServer ? question->qDNSServer->port : zeroIPPort));
+#endif
     }
 
     ActivateUnicastQuery(m, question, mDNSfalse);
 
-    if (!question->DuplicateOf && DNSSECQuestion(question))
-    {
-        // For DNSSEC questions, we need to have the RRSIGs also for verification.
-        CheckForDNSSECRecords(m, question);
-    }
     if (question->LongLived)
     {
         // Unlike other initializations, InitLLQNATState should be done after
@@ -12056,9 +12011,6 @@ mDNSexport mStatus mDNS_StartQuery_internal(mDNS *const m, DNSQuestion *const qu
 
 #ifndef UNICAST_DISABLED
     question->TargetQID = Question_uDNS(question) ? mDNS_NewMessageID(m) : zeroID;
-#if MDNSRESPONDER_SUPPORTS(APPLE, SUSPICIOUS_REPLY_DEFENSE)
-    question->LastTargetQID = zeroID;
-#endif
 #else
     question->TargetQID = zeroID;
 #endif
@@ -12196,16 +12148,31 @@ mDNSexport mStatus mDNS_StopQuery_internal(mDNS *const m, DNSQuestion *const que
 #endif
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
-    if (Question_uDNS(question) && !question->metrics.answered && (question->metrics.querySendCount > 0))
+    if (Question_uDNS(question) && !question->metrics.answered && (question->metrics.firstQueryTime != 0))
     {
-        const domainname *  queryName;
-        mDNSBool            isForCell;
-        mDNSu32             durationMs;
+        mDNSu32 querySendCount = question->metrics.querySendCount;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        if (question->querier)
+        {
+            querySendCount += mdns_querier_get_send_count(question->querier);
+        }
+#endif
+        if (querySendCount > 0)
+        {
+            const domainname *  queryName;
+            mDNSBool            isForCell;
+            mDNSu32             durationMs;
 
-        queryName  = question->metrics.originalQName ? question->metrics.originalQName : &question->qname;
-        isForCell  = (question->qDNSServer && question->qDNSServer->isCell);
-        durationMs = ((m->timenow - question->metrics.firstQueryTime) * 1000) / mDNSPlatformOneSecond;
-        MetricsUpdateDNSQueryStats(queryName, question->qtype, mDNSNULL, question->metrics.querySendCount, question->metrics.expiredAnswerState, question->metrics.dnsOverTCPState, durationMs, isForCell);
+            queryName  = question->metrics.originalQName ? question->metrics.originalQName : &question->qname;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            isForCell = (question->dnsservice && mdns_dns_service_interface_is_cellular(question->dnsservice));
+#else
+            isForCell  = (question->qDNSServer && question->qDNSServer->isCell);
+#endif
+            durationMs = ((m->timenow - question->metrics.firstQueryTime) * 1000) / mDNSPlatformOneSecond;
+            MetricsUpdateDNSQueryStats(queryName, question->qtype, mDNSNULL, querySendCount,
+                question->metrics.expiredAnswerState, question->metrics.dnsOverTCPState, durationMs, isForCell);
+        }
     }
 #endif
     // Take care to cut question from list *before* calling UpdateQuestionDuplicates
@@ -12273,13 +12240,6 @@ mDNSexport mStatus mDNS_StopQuery_internal(mDNS *const m, DNSQuestion *const que
         m->RestartQuestion = question->next;
     }
 
-    if (m->ValidationQuestion == question)
-    {
-        LogInfo("mDNS_StopQuery_internal: Just deleted the current Validation question: %##s (%s)",
-               question->qname.c, DNSTypeName(question->qtype));
-        m->ValidationQuestion = question->next;
-    }
-
     // Take care not to trash question->next until *after* we've updated m->CurrentQuestion and m->NewQuestions
     question->next = mDNSNULL;
 
@@ -12292,6 +12252,9 @@ mDNSexport mStatus mDNS_StopQuery_internal(mDNS *const m, DNSQuestion *const que
     // *first*, then they're all ready to be updated a second time if necessary when we cancel our GetZoneData query.
     if (question->tcp) { DisposeTCPConn(question->tcp); question->tcp = mDNSNULL; }
     if (question->LocalSocket) { mDNSPlatformUDPClose(question->LocalSocket); question->LocalSocket = mDNSNULL; }
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    Querier_HandleStoppedDNSQuestion(question);
+#endif
     if (!mDNSOpaque16IsZero(question->TargetQID) && question->LongLived)
     {
         // Scan our list to see if any more wide-area LLQs remain. If not, stop our NAT Traversal.
@@ -12337,18 +12300,8 @@ mDNSexport mStatus mDNS_StopQuery_internal(mDNS *const m, DNSQuestion *const que
     // wait until we send the refresh above which needs the nta
     if (question->nta) { CancelGetZoneData(m, question->nta); question->nta = mDNSNULL; }
 
-    if (question->ValidationRequired && question->DNSSECAuthInfo)
-    {
-        LogInfo("mDNS_StopQuery_internal: freeing DNSSECAuthInfo %##s", question->qname.c);
-        question->DAIFreeCallback(m, question->DNSSECAuthInfo);
-        question->DNSSECAuthInfo = mDNSNULL;
-    }
 #if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
-    if (question->metrics.originalQName)
-    {
-        mDNSPlatformMemFree(question->metrics.originalQName);
-        question->metrics.originalQName = mDNSNULL;
-    }
+    uDNSMetricsClear(&question->metrics);
 #endif
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNS64)
@@ -12451,8 +12404,6 @@ mDNSlocal mStatus mDNS_StartBrowse_internal(mDNS *const m, DNSQuestion *const qu
     question->TimeoutQuestion  = 0;
     question->WakeOnResolve    = 0;
     question->UseBackgroundTraffic = useBackgroundTrafficClass;
-    question->ValidationRequired = 0;
-    question->ValidatingResponse = 0;
     question->ProxyQuestion    = 0;
     question->QuestionCallback = Callback;
     question->QuestionContext  = Context;
@@ -12493,8 +12444,6 @@ mDNSexport mStatus mDNS_GetDomains(mDNS *const m, DNSQuestion *const question, m
     question->TimeoutQuestion  = 0;
     question->WakeOnResolve    = 0;
     question->UseBackgroundTraffic = mDNSfalse;
-    question->ValidationRequired = 0;
-    question->ValidatingResponse = 0;
     question->ProxyQuestion    = 0;
     question->pid              = mDNSPlatformGetPID();
     question->euid             = 0;
@@ -13040,10 +12989,19 @@ mDNSexport mStatus mDNS_RegisterInterface(mDNS *const m, NetworkInterfaceInfo *s
     NetworkInterfaceInfo **p = &m->HostInterfaces;
 
     if (!set->InterfaceID)
-    { LogMsg("mDNS_RegisterInterface: Error! Tried to register a NetworkInterfaceInfo %#a with zero InterfaceID", &set->ip); return(mStatus_Invalid); }
+    {
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR,
+            "Tried to register a NetworkInterfaceInfo with zero InterfaceID - ifaddr: " PRI_IP_ADDR, &set->ip);
+        return(mStatus_Invalid);
+    }
 
     if (!mDNSAddressIsValidNonZero(&set->mask))
-    { LogMsg("mDNS_RegisterInterface: Error! Tried to register a NetworkInterfaceInfo %#a with invalid mask %#a", &set->ip, &set->mask); return(mStatus_Invalid); }
+    {
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR,
+            "Tried to register a NetworkInterfaceInfo with invalid mask - ifaddr: " PRI_IP_ADDR ", ifmask: " PUB_IP_ADDR,
+            &set->ip, &set->mask);
+        return(mStatus_Invalid);
+    }
 
     mDNS_Lock(m);
 
@@ -13059,7 +13017,9 @@ mDNSexport mStatus mDNS_RegisterInterface(mDNS *const m, NetworkInterfaceInfo *s
     {
         if (*p == set)
         {
-            LogMsg("mDNS_RegisterInterface: Error! Tried to register a NetworkInterfaceInfo that's already in the list");
+            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR,
+                "Tried to register a NetworkInterfaceInfo that's already in the list - "
+                "ifname: " PUB_S ", ifaddr: " PRI_IP_ADDR, set->ifname, &set->ip);
             mDNS_Unlock(m);
             return(mStatus_AlreadyRegistered);
         }
@@ -13081,11 +13041,18 @@ mDNSexport mStatus mDNS_RegisterInterface(mDNS *const m, NetworkInterfaceInfo *s
 
     if (set->Advertise) AdvertiseInterfaceIfNeeded(m, set);
 
-    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-            "mDNS_RegisterInterface: InterfaceID %u " PUB_S " (" PRI_IP_ADDR ") " PUB_S,
-            IIDPrintable(set->InterfaceID), set->ifname, &set->ip, set->InterfaceActive ?
-            "not represented in list; marking active and retriggering queries" :
-            "already represented in list; marking inactive for now");
+    if (set->InterfaceActive)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_INFO,
+            "Interface not represented in list; marking active and retriggering queries - "
+            "ifid: %d, ifname: " PUB_S ", ifaddr: " PRI_IP_ADDR, IIDPrintable(set->InterfaceID), set->ifname, &set->ip);
+    }
+    else
+    {
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_INFO,
+            "Interface already represented in list - "
+            "ifid: %d, ifname: " PUB_S ", ifaddr: " PRI_IP_ADDR, IIDPrintable(set->InterfaceID), set->ifname, &set->ip);
+    }
 
     if (set->NetWake) mDNS_ActivateNetWake_internal(m, set);
 
@@ -13110,14 +13077,18 @@ mDNSexport mStatus mDNS_RegisterInterface(mDNS *const m, NetworkInterfaceInfo *s
             case FastActivation:
                 probedelay = (mDNSs32)0;
                 numannounce = InitialAnnounceCount;
-                LogMsg("mDNS_RegisterInterface: Using fast activation for DirectLink interface %s (%#a)", set->ifname, &set->ip);
+                LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+                    "Using fast activation for DirectLink interface - ifname: " PUB_S ", ifaddr: " PRI_IP_ADDR,
+                    set->ifname, &set->ip);
                 break;
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, SLOW_ACTIVATION)
             case SlowActivation:
                 probedelay = mDNSPlatformOneSecond * 5;
                 numannounce = (mDNSu8)1;
-                LogMsg("mDNS_RegisterInterface: Frequent transitions for interface %s (%#a), doing slow activation", set->ifname, &set->ip);
+                LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+                    "Frequent transitions for interface, doing slow activation - "
+                    "ifname: " PUB_S ", ifaddr: " PRI_IP_ADDR, set->ifname, &set->ip);
                 m->mDNSStats.InterfaceUpFlap++;
                 break;
 #endif
@@ -13129,7 +13100,9 @@ mDNSexport mStatus mDNS_RegisterInterface(mDNS *const m, NetworkInterfaceInfo *s
                 break;
         }
 
-        LogInfo("mDNS_RegisterInterface: %s (%#a) probedelay %d", set->ifname, &set->ip, probedelay);
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_INFO,
+            "Interface probe will be delayed - ifname: " PUB_S ", ifaddr: " PRI_IP_ADDR ", probe delay: %d",
+            set->ifname, &set->ip, probedelay);
 
         // No probe or sending suppression on DirectLink type interfaces.
         if (activationSpeed == FastActivation)
@@ -13161,7 +13134,7 @@ mDNSexport mStatus mDNS_RegisterInterface(mDNS *const m, NetworkInterfaceInfo *s
         // us to reconnect to the network. If we do this as part of the wake up code, it is possible
         // that the network link comes UP after 60 seconds and we never set the OWNER option
         m->AnnounceOwner = NonZeroTime(m->timenow + 60 * mDNSPlatformOneSecond);
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG, "mDNS_RegisterInterface: Setting AnnounceOwner");
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEBUG, "Setting AnnounceOwner");
 
         m->mDNSStats.InterfaceUp++;
         for (q = m->Questions; q; q=q->next)                                // Scan our list of questions
@@ -13175,7 +13148,12 @@ mDNSexport mStatus mDNS_RegisterInterface(mDNS *const m, NetworkInterfaceInfo *s
                     mDNSBool dodelay = (activationSpeed == SlowActivation) && (q->FlappingInterface1 == set->InterfaceID || q->FlappingInterface2 == set->InterfaceID);
                     mDNSs32 initial  = dodelay ? InitialQuestionInterval * QuestionIntervalStep2 : InitialQuestionInterval;
                     mDNSs32 qdelay   = dodelay ? kDefaultQueryDelayTimeForFlappingInterface : 0;
-                    if (dodelay) LogInfo("No cache records expired for %##s (%s); delaying questions by %d seconds", q->qname.c, DNSTypeName(q->qtype), qdelay);
+                    if (dodelay)
+                    {
+                        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_INFO,
+                            "No cache records expired for the question " PRI_DM_NAME " (" PUB_S ");"
+                            " delaying it by %d seconds", DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), qdelay);
+                    }
 #else
                     mDNSs32 initial  = InitialQuestionInterval;
                     mDNSs32 qdelay   = 0;
@@ -13202,9 +13180,6 @@ mDNSexport mStatus mDNS_RegisterInterface(mDNS *const m, NetworkInterfaceInfo *s
                 mDNSCoreRestartRegistration(m, rr, numannounce);
             }
         }
-#if APPLE_OSX_mDNSResponder && !TARGET_OS_IPHONE
-        DNSSECProbe(m);
-#endif
     }
 
     RestartRecordGetZoneData(m);
@@ -13261,7 +13236,12 @@ mDNSexport void mDNS_DeregisterInterface(mDNS *const m, NetworkInterfaceInfo *se
 
     // Find this record in our list
     while (*p && *p != set) p=&(*p)->next;
-    if (!*p) { debugf("mDNS_DeregisterInterface: NetworkInterfaceInfo not found in list"); mDNS_Unlock(m); return; }
+    if (!*p)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEBUG, "NetworkInterfaceInfo not found in list");
+        mDNS_Unlock(m);
+        return;
+    }
 
     mDNS_DeactivateNetWake_internal(m, set);
 
@@ -13281,10 +13261,15 @@ mDNSexport void mDNS_DeregisterInterface(mDNS *const m, NetworkInterfaceInfo *se
         intf = FirstInterfaceForID(m, set->InterfaceID);
         if (intf)
         {
-            LogInfo("mDNS_DeregisterInterface: Another representative of InterfaceID %d %s (%#a) exists;"
-                    " making it active", IIDPrintable(set->InterfaceID), set->ifname, &set->ip);
+            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_INFO,
+                "Another representative of InterfaceID exists - ifid: %d, ifname: " PUB_S ", ifaddr: " PRI_IP_ADDR,
+                IIDPrintable(set->InterfaceID), set->ifname, &set->ip);
             if (intf->InterfaceActive)
-                LogMsg("mDNS_DeregisterInterface: ERROR intf->InterfaceActive already set for %s (%#a)", set->ifname, &set->ip);
+            {
+                LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR,
+                    "intf->InterfaceActive already set for interface - ifname: " PUB_S ", ifaddr: " PRI_IP_ADDR,
+                    set->ifname, &set->ip);
+            }
             intf->InterfaceActive = mDNStrue;
             UpdateInterfaceProtocols(m, intf);
 
@@ -13303,16 +13288,25 @@ mDNSexport void mDNS_DeregisterInterface(mDNS *const m, NetworkInterfaceInfo *se
             CacheGroup *cg;
             CacheRecord *rr;
             DNSQuestion *q;
-
-            LogInfo("mDNS_DeregisterInterface: Last representative of InterfaceID %d %s (%#a) deregistered;"
-                    " marking questions etc. dormant", IIDPrintable(set->InterfaceID), set->ifname, &set->ip);
+#if MDNSRESPONDER_SUPPORTS(APPLE, CACHE_ANALYTICS)
+            mDNSu32     cacheHitMulticastCount = 0;
+            mDNSu32     cacheMissMulticastCount = 0;
+            mDNSu32     cacheHitUnicastCount = 0;
+            mDNSu32     cacheMissUnicastCount = 0;
+#endif
+            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_INFO,
+                "Last representative of InterfaceID deregistered; marking questions etc. dormant - "
+                "ifid: %d, ifname: " PUB_S ", ifaddr: " PRI_IP_ADDR,
+                IIDPrintable(set->InterfaceID), set->ifname, &set->ip);
 
             m->mDNSStats.InterfaceDown++;
  
 #if MDNSRESPONDER_SUPPORTS(APPLE, SLOW_ACTIVATION)
             if (set->McastTxRx && (activationSpeed == SlowActivation))
             {
-                LogMsg("mDNS_DeregisterInterface: Frequent transitions for interface %s (%#a)", set->ifname, &set->ip);
+                LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+                    "Frequent transitions for interface - ifname: " PUB_S ", ifaddr: " PRI_IP_ADDR,
+                    set->ifname, &set->ip);
                 m->mDNSStats.InterfaceDownFlap++;
             }
 #endif
@@ -13353,11 +13347,33 @@ mDNSexport void mDNS_DeregisterInterface(mDNS *const m, NetworkInterfaceInfo *se
                     else
 #endif
                     {
-                        rr->resrec.mortality = Mortality_Mortal;
+#if MDNSRESPONDER_SUPPORTS(APPLE, CACHE_ANALYTICS)
+                        if (rr->LastCachedAnswerTime)
+                        {
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                            if (rr->resrec.dnsservice)  cacheHitUnicastCount++;
+#else
+                            if (rr->resrec.rDNSServer)  cacheHitUnicastCount++;
+#endif
+                            else                        cacheHitMulticastCount++;
+                        }
+                        else
+                        {
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                            if (rr->resrec.dnsservice)  cacheMissUnicastCount++;
+#else
+                            if (rr->resrec.rDNSServer)  cacheMissUnicastCount++;
+#endif
+                            else                        cacheMissMulticastCount++;
+                        }
+#endif
                         mDNS_PurgeCacheResourceRecord(m, rr);
                     }
                 }
             }
+#if MDNSRESPONDER_SUPPORTS(APPLE, CACHE_ANALYTICS)
+            dnssd_analytics_update_cache_usage_counts(cacheHitMulticastCount, cacheMissMulticastCount, cacheHitUnicastCount, cacheMissUnicastCount);
+#endif
         }
     }
 
@@ -14464,7 +14480,6 @@ mDNSlocal mStatus mDNS_InitStorage(mDNS *const m, mDNS_PlatformSupport *const p,
 
     if (!rrcachestorage) rrcachesize = 0;
 
-    m->next_request_id               = 1;
     m->p                             = p;
     m->NetworkChanged                = 0;
     m->CanReceiveUnicastOn5353       = mDNSfalse; // Assume we can't receive unicasts on 5353, unless platform layer tells us otherwise
@@ -14510,9 +14525,6 @@ mDNSlocal mStatus mDNS_InitStorage(mDNS *const m, mDNS_PlatformSupport *const p,
     m->NextBonjourDisableTime  = 0; // Timer active when non zero.
     m->BonjourEnabled          = 0; // Set when Bonjour on Demand is enabled and Bonjour is currently enabled.  
 #endif
-#if MDNSRESPONDER_SUPPORTS(APPLE, SUSPICIOUS_REPLY_DEFENSE)
-    m->NextSuspiciousTimeout   = 0;
-#endif
 
     m->RandomQueryDelay        = 0;
     m->RandomReconfirmDelay    = 0;
@@ -14539,7 +14551,6 @@ mDNSlocal mStatus mDNS_InitStorage(mDNS *const m, mDNS_PlatformSupport *const p,
     m->LocalOnlyQuestions      = mDNSNULL;
     m->NewLocalOnlyQuestions   = mDNSNULL;
     m->RestartQuestion         = mDNSNULL;
-    m->ValidationQuestion      = mDNSNULL;
     m->rrcache_size            = 0;
     m->rrcache_totalused       = 0;
     m->rrcache_active          = 0;
@@ -14581,7 +14592,9 @@ mDNSlocal mStatus mDNS_InitStorage(mDNS *const m, mDNS_PlatformSupport *const p,
     m->NextuDNSEvent            = timenow + FutureTime;
     m->NextSRVUpdate            = timenow + FutureTime;
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     m->DNSServers               = mDNSNULL;
+#endif
 
     m->Router                   = zeroAddr;
     m->AdvertisedV4             = zeroAddr;
@@ -14652,6 +14665,11 @@ mDNSlocal mStatus mDNS_InitStorage(mDNS *const m, mDNS_PlatformSupport *const p,
         if (!m->WCF) { LogMsg("WCFConnectionNew failed"); return -1; }
     }
 #endif
+    
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    result = init_and_load_trust_anchors();
+    if (result != mStatus_NoError) return(result);
+#endif
 
     return(result);
 }
@@ -14712,6 +14730,7 @@ mDNSlocal void DynDNSHostNameCallback(mDNS *const m, AuthRecord *const rr, mStat
     mDNSPlatformDynDNSHostNameStatusChanged(rr->resrec.name, result);
 }
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 mDNSlocal void PurgeOrReconfirmCacheRecord(mDNS *const m, CacheRecord *cr)
 {
     mDNSBool purge = cr->resrec.RecordType == kDNSRecordTypePacketNegative ||
@@ -14729,7 +14748,6 @@ mDNSlocal void PurgeOrReconfirmCacheRecord(mDNS *const m, CacheRecord *cr)
     if (purge)
     {
         LogInfo("PurgeorReconfirmCacheRecord: Purging Resourcerecord %s, RecordType %x", CRDisplayString(m, cr), cr->resrec.RecordType);
-        cr->resrec.mortality = Mortality_Mortal;
         mDNS_PurgeCacheResourceRecord(m, cr);
     }
     else
@@ -14738,19 +14756,12 @@ mDNSlocal void PurgeOrReconfirmCacheRecord(mDNS *const m, CacheRecord *cr)
         mDNS_Reconfirm_internal(m, cr, kDefaultReconfirmTimeForNoAnswer);
     }
 }
+#endif
 
 mDNSlocal void mDNS_PurgeBeforeResolve(mDNS *const m, DNSQuestion *q)
 {
     CacheGroup *const cg = CacheGroupForName(m, q->qnamehash, &q->qname);
     CacheRecord *rp;
-    mDNSu8 validatingResponse = 0;
-
-    // For DNSSEC questions, purge the corresponding RRSIGs also.
-    if (DNSSECQuestion(q))
-    {
-        validatingResponse = q->ValidatingResponse;
-        q->ValidatingResponse = mDNStrue;
-    }
     for (rp = cg ? cg->members : mDNSNULL; rp; rp = rp->next)
     {
         if (SameNameCacheRecordAnswersQuestion(rp, q))
@@ -14759,42 +14770,9 @@ mDNSlocal void mDNS_PurgeBeforeResolve(mDNS *const m, DNSQuestion *q)
             mDNS_PurgeCacheResourceRecord(m, rp);
         }
     }
-    if (DNSSECQuestion(q))
-    {
-        q->ValidatingResponse = validatingResponse;
-    }
-}
-
-// For DNSSEC question, we need the DNSSEC records also. If the cache does not
-// have the DNSSEC records, we need to re-issue the question with EDNS0/DO bit set.
-// Just re-issuing the question for RRSIGs does not work in practice as the response
-// may not contain the RRSIGs whose typeCovered field matches the question's qtype.
-//
-// For negative responses, we need the NSECs to prove the non-existence. If we don't
-// have the cached NSECs, purge them. For positive responses, if we don't have the
-// RRSIGs and if we have not already issued the question with EDNS0/DO bit set, purge
-// them.
-mDNSlocal void CheckForDNSSECRecords(mDNS *const m, DNSQuestion *q)
-{
-    CacheGroup *const cg = CacheGroupForName(m, q->qnamehash, &q->qname);
-    CacheRecord *rp;
-
-    for (rp = cg ? cg->members : mDNSNULL; rp; rp = rp->next)
-    {
-        if (SameNameCacheRecordAnswersQuestion(rp, q))
-        {
-            if (rp->resrec.RecordType != kDNSRecordTypePacketNegative || !rp->nsec)
-            {
-                if (!rp->CRDNSSECQuestion)
-                {
-                    LogInfo("CheckForDNSSECRecords: Flushing %s", CRDisplayString(m, rp));
-                    mDNS_PurgeCacheResourceRecord(m, rp);
-                }
-            }
-        }
-    }
 }
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 mDNSexport void DNSServerChangeForQuestion(mDNS *const m, DNSQuestion *q, DNSServer *new)
 {
     DNSQuestion *qptr;
@@ -14813,14 +14791,18 @@ mDNSexport void DNSServerChangeForQuestion(mDNS *const m, DNSQuestion *q, DNSSer
         if (qptr->DuplicateOf == q) { qptr->validDNSServers = q->validDNSServers; qptr->qDNSServer = new; }
     }
 }
+#endif
 
 mDNSlocal void SetConfigState(mDNS *const m, mDNSBool delete)
 {
     McastResolver *mr;
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     DNSServer *ptr;
+#endif
 
     if (delete)
     {
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
         for (ptr = m->DNSServers; ptr; ptr = ptr->next)
         {
             ptr->penaltyTime = 0;
@@ -14830,6 +14812,7 @@ mDNSlocal void SetConfigState(mDNS *const m, mDNSBool delete)
                 NumUnreachableDNSServers--;
 #endif
         }
+#endif
         // We handle the mcast resolvers here itself as mDNSPlatformSetDNSConfig looks at
         // mcast resolvers. Today we get both mcast and ucast configuration using the same
         // API
@@ -14838,6 +14821,7 @@ mDNSlocal void SetConfigState(mDNS *const m, mDNSBool delete)
     }
     else
     {
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
         for (ptr = m->DNSServers; ptr; ptr = ptr->next)
         {
             ptr->penaltyTime = 0;
@@ -14847,6 +14831,7 @@ mDNSlocal void SetConfigState(mDNS *const m, mDNSBool delete)
                 NumUnreachableDNSServers++;
 #endif
         }
+#endif
         for (mr = m->McastResolvers; mr; mr = mr->next)
             mr->flags &= ~McastResolver_FlagDelete;
     }
@@ -14873,16 +14858,20 @@ mDNSlocal void SetDynDNSHostNameIfChanged(mDNS *const m, domainname *const fqdn)
 // It’s actually called multiple times, every time there’s a configuration change.
 mDNSexport mStatus uDNS_SetupDNSConfig(mDNS *const m)
 {
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     mDNSu32 slot;
     CacheGroup *cg;
     CacheRecord *cr;
+#endif
     mDNSAddr v4, v6, r;
     domainname fqdn;
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     DNSServer   *ptr, **p = &m->DNSServers;
     const DNSServer *oldServers = m->DNSServers;
     DNSQuestion *q;
+#endif
     McastResolver *mr, **mres = &m->McastResolvers;
-#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
+#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH) && !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     DNSPushNotificationServer **psp;
 #endif
     
@@ -14909,10 +14898,6 @@ mDNSexport mStatus uDNS_SetupDNSConfig(mDNS *const m)
         LogInfo("uDNS_SetupDNSConfig: No configuration change");
         return mStatus_NoError;
     }
-#if MDNSRESPONDER_SUPPORTS(APPLE, SUSPICIOUS_REPLY_DEFENSE)
-    // Reset suspicious mode on any DNS configuration change
-    m->NextSuspiciousTimeout = 0;
-#endif
 
     // For now, we just delete the mcast resolvers. We don't deal with cache or
     // questions here. Neither question nor cache point to mcast resolvers. Questions
@@ -14934,6 +14919,9 @@ mDNSexport mStatus uDNS_SetupDNSConfig(mDNS *const m)
         }
     }
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    Querier_ProcessDNSServiceChanges();
+#else
     // Update our qDNSServer pointers before we go and free the DNSServer object memory
     //
     // All non-scoped resolvers share the same resGroupID. At no point in time a cache entry using DNSServer
@@ -14988,9 +14976,9 @@ mDNSexport mStatus uDNS_SetupDNSConfig(mDNS *const m)
                 "[R%u->Q%u] uDNS_SetupDNSConfig: Updating DNS server from " PRI_IP_ADDR ":%d (" PRI_DM_NAME ") to "
                 PRI_IP_ADDR ":%d (" PRI_DM_NAME ") for question " PRI_DM_NAME " (" PUB_S ") (scope:%p)",
                 q->request_id, mDNSVal16(q->TargetQID),
-                t ? &t->addr : mDNSNULL, mDNSVal16(t ? t->port : zeroIPPort), DM_NAME_PARAM(t ? t->domain.c : (mDNSu8*)""),
-                s ? &s->addr : mDNSNULL, mDNSVal16(s ? s->port : zeroIPPort), DM_NAME_PARAM(s ? s->domain.c : (mDNSu8*)""),
-                DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype), q->InterfaceID);
+                t ? &t->addr : mDNSNULL, mDNSVal16(t ? t->port : zeroIPPort), DM_NAME_PARAM(t ? &t->domain : mDNSNULL),
+                s ? &s->addr : mDNSNULL, mDNSVal16(s ? s->port : zeroIPPort), DM_NAME_PARAM(s ? &s->domain : mDNSNULL),
+                DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), q->InterfaceID);
 #if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
             // If this question had a DNS Push server associated with it, substitute the new server for the
             // old one.   If there is no new server, then we'll clean up the push server later.
@@ -15032,9 +15020,6 @@ mDNSexport mStatus uDNS_SetupDNSConfig(mDNS *const m)
 
         q->Suppressed = ShouldSuppressUnicastQuery(q, s);
         q->unansweredQueries = 0;
-#if MDNSRESPONDER_SUPPORTS(APPLE, SUSPICIOUS_REPLY_DEFENSE)
-        q->LastTargetQID = q->TargetQID;
-#endif
         q->TargetQID = mDNS_NewMessageID(m);
         if (!q->Suppressed) ActivateUnicastQuery(m, q, mDNStrue);
     }
@@ -15092,11 +15077,8 @@ mDNSexport mStatus uDNS_SetupDNSConfig(mDNS *const m)
         // Note: If GetServerForName returns NULL, it could either mean that there are no
         // DNS servers or no matching DNS servers for this question. In either case,
         // the cache should get purged below when we process deleted DNS servers.
-        //
-        // If it is a DNSSEC question, purge the cache as the DNSSEC capabilities of the
-        // DNS server may have changed.
 
-        if (cr->CRActiveQuestion && !DNSSECQuestion(cr->CRActiveQuestion))
+        if (cr->CRActiveQuestion)
         {
             // Purge or Reconfirm if this cache entry would use the new DNS server
             ptr = GetServerForName(m, cr->resrec.name, cr->CRActiveQuestion->InterfaceID, cr->CRActiveQuestion->ServiceID);
@@ -15149,7 +15131,6 @@ mDNSexport mStatus uDNS_SetupDNSConfig(mDNS *const m)
                 cr->resrec.rDNSServer ? &cr->resrec.rDNSServer->addr : mDNSNULL,
                 cr->resrec.rDNSServer ? DNSScopeToString(cr->resrec.rDNSServer->scopeType) : "" );
             cr->resrec.rDNSServer = mDNSNULL;
-            cr->resrec.mortality = Mortality_Mortal;
             mDNS_PurgeCacheResourceRecord(m, cr);
         }
     }
@@ -15192,6 +15173,7 @@ mDNSexport mStatus uDNS_SetupDNSConfig(mDNS *const m)
         // Force anything that needs to get zone data to get that information again
         RestartRecordGetZoneData(m);
     }
+#endif // !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 
     SetDynDNSHostNameIfChanged(m, &fqdn);
 
@@ -15260,7 +15242,7 @@ mDNSexport void mDNS_StartExit(mDNS *const m)
 
     mDNS_Lock(m);
 
-    LogInfo("mDNS_StartExit");
+    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_StartExit");
     m->ShutdownTime = NonZeroTime(m->timenow + mDNSPlatformOneSecond * 5);
 
     mDNSCoreBeSleepProxyServer_internal(m, 0, 0, 0, 0, 0);
@@ -15268,7 +15250,11 @@ mDNSexport void mDNS_StartExit(mDNS *const m)
 #if MDNSRESPONDER_SUPPORTS(APPLE, WEB_CONTENT_FILTER)
     if (WCFConnectionDealloc)
     {
-        if (m->WCF) WCFConnectionDealloc(m->WCF);
+        if (m->WCF)
+        {
+            WCFConnectionDealloc(m->WCF);
+            m->WCF = mDNSNULL;
+        }
     }
 #endif
 
@@ -15317,15 +15303,18 @@ mDNSexport void mDNS_StartExit(mDNS *const m)
 
     // Make sure there are nothing but deregistering records remaining in the list
     if (m->CurrentRecord)
-        LogMsg("mDNS_StartExit: ERROR m->CurrentRecord already set %s", ARDisplayString(m, m->CurrentRecord));
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+            "mDNS_StartExit: ERROR m->CurrentRecord already set " PRI_S, ARDisplayString(m, m->CurrentRecord));
+    }
 
     // We're in the process of shutting down, so queries, etc. are no longer available.
     // Consequently, determining certain information, e.g. the uDNS update server's IP
     // address, will not be possible.  The records on the main list are more likely to
     // already contain such information, so we deregister the duplicate records first.
-    LogInfo("mDNS_StartExit: Deregistering duplicate resource records");
+    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_StartExit: Deregistering duplicate resource records");
     DeregLoop(m, m->DuplicateRecords);
-    LogInfo("mDNS_StartExit: Deregistering resource records");
+    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_StartExit: Deregistering resource records");
     DeregLoop(m, m->ResourceRecords);
 
     // If we scheduled a response to send goodbye packets, we set NextScheduledResponse to now. Normally when deregistering records,
@@ -15336,18 +15325,28 @@ mDNSexport void mDNS_StartExit(mDNS *const m)
         m->SuppressSending = 0;
     }
 
-    if (m->ResourceRecords) LogInfo("mDNS_StartExit: Sending final record deregistrations");
-    else LogInfo("mDNS_StartExit: No deregistering records remain");
+    if (m->ResourceRecords)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_StartExit: Sending final record deregistrations");
+    }
+    else
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_StartExit: No deregistering records remain");
+    }
 
     for (rr = m->DuplicateRecords; rr; rr = rr->next)
-        LogMsg("mDNS_StartExit: Should not still have Duplicate Records remaining: %02X %s", rr->resrec.RecordType, ARDisplayString(m, rr));
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+            "mDNS_StartExit: Should not still have Duplicate Records remaining: %02X " PRI_S,
+            rr->resrec.RecordType, ARDisplayString(m, rr));
+    }
 
     // If any deregistering records remain, send their deregistration announcements before we exit
     if (m->mDNSPlatformStatus != mStatus_NoError) DiscardDeregistrations(m);
 
     mDNS_Unlock(m);
 
-    LogInfo("mDNS_StartExit: done");
+    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_StartExit: done");
 }
 
 mDNSexport void mDNS_FinalExit(mDNS *const m)
@@ -15357,7 +15356,7 @@ mDNSexport void mDNS_FinalExit(mDNS *const m)
     mDNSu32 slot;
     AuthRecord *rr;
 
-    LogInfo("mDNS_FinalExit: mDNSPlatformClose");
+    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_FinalExit: mDNSPlatformClose");
     mDNSPlatformClose(m);
 
     for (slot = 0; slot < CACHE_HASH_SLOTS; slot++)
@@ -15383,7 +15382,11 @@ mDNSexport void mDNS_FinalExit(mDNS *const m)
     for (rr = m->ResourceRecords; rr; rr = rr->next)
         LogMsg("mDNS_FinalExit failed to send goodbye for: %p %02X %s", rr, rr->resrec.RecordType, ARDisplayString(m, rr));
 
-    LogInfo("mDNS_FinalExit: done");
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    uninit_trust_anchors();
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    
+    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_FinalExit: done");
 }
 
 #ifdef UNIT_TEST
index be0a4c1e4268c4551d05df9b7cd9aef9627cc473..25e684d47657ffd56bde12f5338eb4edc7d5da7b 100755 (executable)
@@ -70,6 +70,8 @@ typedef enum
     extern os_log_t mDNSLogCategory_uDNS;
     extern os_log_t mDNSLogCategory_SPS;
     extern os_log_t mDNSLogCategory_XPC;
+    extern os_log_t mDNSLogCategory_Analytics;
+    extern os_log_t mDNSLogCategory_DNSSEC;
 
     #define MDNS_LOG_CATEGORY_DEFINITION(NAME)  mDNSLogCategory_ ## NAME
 #else
@@ -81,6 +83,8 @@ typedef enum
 #define MDNS_LOG_CATEGORY_UDNS      MDNS_LOG_CATEGORY_DEFINITION(uDNS)
 #define MDNS_LOG_CATEGORY_SPS       MDNS_LOG_CATEGORY_DEFINITION(SPS)
 #define MDNS_LOG_CATEGORY_XPC       MDNS_LOG_CATEGORY_DEFINITION(XPC)
+#define MDNS_LOG_CATEGORY_ANALYTICS MDNS_LOG_CATEGORY_DEFINITION(Analytics)
+#define MDNS_LOG_CATEGORY_DNSSEC    MDNS_LOG_CATEGORY_DEFINITION(DNSSEC)
 
 // Set this symbol to 1 to answer remote queries for our Address, and reverse mapping PTR
 #define ANSWER_REMOTE_HOSTNAME_QUERIES 0
@@ -296,12 +300,14 @@ extern void LogMemCorruption(const char *format, ...);
 // The followings are the customized log specifier defined in os_log. For compatibility, we have to define it when it is
 // not on the Apple platform, for example, the Posix platform. The keyword "public" or "private" is used to control whether
 // the content would be redacted when the redaction is turned on: "public" means the content will always be printed;
-// "private" means the content will be printed as <private> if the redaction is turned on, only when the redaction is
-// turned off, the content will be printed as what it should be.
+// "private" means the content will be printed as <mask.hash: '<The hashed string from binary data>'> if the redaction is turned on,
+// only when the redaction is turned off, the content will be printed as what it should be. Note that the hash performed
+// to the data is a salted hashing transformation, and the salt is generated randomly on a per-process basis, meaning
+// that hashes cannot be correlated across processes or devices.
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
     #define PUB_S "%{public}s"
-    #define PRI_S "%{private}s"
+    #define PRI_S "%{private, mask.hash}s"
 #else
     #define PUB_S "%s"
     #define PRI_S PUB_S
@@ -309,9 +315,9 @@ extern void LogMemCorruption(const char *format, ...);
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
     #define PUB_DM_NAME "%{public, mdnsresponder:domain_name}.*P"
-    #define PRI_DM_NAME "%{private, mdnsresponder:domain_name}.*P"
+    #define PRI_DM_NAME "%{private, mask.hash, mdnsresponder:domain_name}.*P"
     // When DM_NAME_PARAM is used, the file where the function is defined must include DNSEmbeddedAPI.h
-    #define DM_NAME_PARAM(name) ((name) ? ((int)DomainNameLength((const domainname *)(name))) : 0), (name)
+    #define DM_NAME_PARAM(name) ((name) ? ((int)DomainNameLength((name))) : 0), (name)
 #else
     #define PUB_DM_NAME "%##s"
     #define PRI_DM_NAME PUB_DM_NAME
@@ -320,13 +326,13 @@ extern void LogMemCorruption(const char *format, ...);
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
     #define PUB_IP_ADDR "%{public, mdnsresponder:ip_addr}.20P"
-    #define PRI_IP_ADDR "%{private, mdnsresponder:ip_addr}.20P"
+    #define PRI_IP_ADDR "%{private, mask.hash, mdnsresponder:ip_addr}.20P"
 
     #define PUB_IPv4_ADDR "%{public, network:in_addr}.4P"
-    #define PRI_IPv4_ADDR "%{private, network:in_addr}.4P"
+    #define PRI_IPv4_ADDR "%{private, mask.hash, network:in_addr}.4P"
 
     #define PUB_IPv6_ADDR "%{public, network:in6_addr}.16P"
-    #define PRI_IPv6_ADDR "%{private, network:in6_addr}.16P"
+    #define PRI_IPv6_ADDR "%{private, mask.hash, network:in6_addr}.16P"
 #else
     #define PUB_IP_ADDR "%#a"
     #define PRI_IP_ADDR PUB_IP_ADDR
@@ -340,12 +346,72 @@ extern void LogMemCorruption(const char *format, ...);
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
     #define PUB_MAC_ADDR "%{public, mdnsresponder:mac_addr}.6P"
-    #define PRI_MAC_ADDR "%{private, mdnsresponder:mac_addr}.6P"
+    #define PRI_MAC_ADDR "%{private, mask.hash, mdnsresponder:mac_addr}.6P"
 #else
     #define PUB_MAC_ADDR "%.6a"
     #define PRI_MAC_ADDR PUB_MAC_ADDR
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
+    #define PUB_DNSKEY "%{public, mdns:rd.dnskey}.*P"
+    #define PRI_DNSKEY "%{private, mask.hash, mdns:rd.dnskey}.*P"
+    #define DNSKEY_PARAM(rdata, rdata_length) (rdata_length), (rdata)
+#else
+    #define PUB_DNSKEY "%p"
+    #define PRI_DNSKEY PUB_DNSKEY
+    #define DNSKEY_PARAM(rdata, rdata_length) (rdata)
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
+    #define PUB_DS "%{public, mdns:rd.ds}.*P"
+    #define PRI_DS "%{private, mask.hash, mdns:rd.ds}.*P"
+    #define DS_PARAM(rdata, rdata_length) (rdata_length), (rdata)
+#else
+    #define PUB_DS "%p"
+    #define PRI_DS PUB_DS
+    #define DS_PARAM(rdata, rdata_length) (rdata)
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
+    #define PUB_NSEC "%{public, mdns:rd.nsec}.*P"
+    #define PRI_NSEC "%{private, mask.hash, mdns:rd.nsec}.*P"
+    #define NSEC_PARAM(rdata, rdata_length) (rdata_length), (rdata)
+#else
+    #define PUB_NSEC "%p"
+    #define PRI_NSEC PUB_NSEC
+    #define NSEC_PARAM(rdata, rdata_length) (rdata)
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
+    #define PUB_NSEC3 "%{public, mdns:rd.nsec3}.*P"
+    #define PRI_NSEC3 "%{private, mask.hash, mdns:rd.nsec3}.*P"
+    #define NSEC3_PARAM(rdata, rdata_length) (rdata_length), (rdata)
+#else
+    #define PUB_NSEC3 "%p"
+    #define PRI_NSEC3 PUB_NSEC3
+    #define NSEC3_PARAM(rdata, rdata_length) (rdata)
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
+    #define PUB_RRSIG "%{public, mdns:rd.rrsig}.*P"
+    #define PRI_RRSIG "%{private, mask.hash, mdns:rd.rrsig}.*P"
+    #define RRSIG_PARAM(rdata, rdata_length) (rdata_length), (rdata)
+#else
+    #define PUB_RRSIG "%p"
+    #define PRI_RRSIG PUB_RRSIG
+    #define RRSIG_PARAM(rdata, rdata_length) (rdata)
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
+    #define PUB_SVCB "%{public, mdns:rd.svcb}.*P"
+    #define PRI_SVCB "%{private, mask.hash, mdns:rd.svcb}.*P"
+    #define SVCB_PARAM(rdata, rdata_length) (rdata_length), (rdata)
+#else
+    #define PUB_SVCB "%p"
+    #define PRI_SVCB PUB_SVCB
+    #define SVCB_PARAM(rdata, rdata_length) (rdata)
+#endif
+
 extern void LogToFD(int fd, const char *format, ...);
 
 #endif // __mDNSDebug_h
index 78d957ba46e1d54e2b2ba2720cb7eeba8820a501..27deefddd862618a88ba788047d83eac954137ae 100755 (executable)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002-2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -71,6 +71,7 @@
 #endif
 
 #include "mDNSFeatures.h"
+#include "mDNSDebug.h"
 
 // ***************************************************************************
 // Feature removal compile options & limited resource targets
@@ -79,7 +80,6 @@
 // memory footprint for use in embedded systems with limited resources.
 
 // UNICAST_DISABLED - disables unicast DNS functionality, including Wide Area Bonjour
-// DNSSEC_DISABLED - disables DNSSEC functionality
 // SPC_DISABLED - disables Bonjour Sleep Proxy client
 // IDLESLEEPCONTROL_DISABLED - disables sleep control for Bonjour Sleep Proxy clients
 
 #include <WebFilterDNS/WebFilterDNS.h>
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include "dnssec_v2_embedded.h"
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
 // Additionally, the LIMITED_RESOURCES_TARGET compile option will reduce the maximum DNS message sizes.
 
 #ifdef LIMITED_RESOURCES_TARGET
 #define MaximumRDSize               264
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+#include "mdns_private.h"
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -238,6 +246,9 @@ typedef enum                // From RFC 1035
 
     kDNSType_HIP = 55,      // 55 Host Identity Protocol
 
+    kDNSType_SVCB = 64,     // 64 Service Binding
+    kDNSType_HTTPS,         // 65 HTTPS Service Binding
+
     kDNSType_SPF = 99,      // 99 Sender Policy Framework for E-Mail
     kDNSType_UINFO,         // 100 IANA-Reserved
     kDNSType_UID,           // 101 IANA-Reserved
@@ -250,7 +261,7 @@ typedef enum                // From RFC 1035
     kDNSType_AXFR,          // 252 Transfer zone of authority
     kDNSType_MAILB,         // 253 Transfer mailbox records
     kDNSType_MAILA,         // 254 Transfer mail agent records
-    kDNSQType_ANY           // Not a DNS type, but a DNS query type, meaning "all types"
+    kDNSQType_ANY          // Not a DNS type, but a DNS query type, meaning "all types"
 } DNS_TypeValues;
 
 // ***************************************************************************
@@ -278,8 +289,6 @@ typedef   signed int mDNSs32;
 typedef unsigned int mDNSu32;
 #endif
 
-#include "mDNSDebug.h"
-
 // To enforce useful type checking, we make mDNSInterfaceID be a pointer to a dummy struct
 // This way, mDNSInterfaceIDs can be assigned, and compared with each other, but not with other types
 // Declaring the type to be the typical generic "void *" would lack this type checking
@@ -393,7 +402,8 @@ enum
     mStatus_PollingMode               = -65567,
     mStatus_Timeout                   = -65568,
     mStatus_DefunctConnection         = -65569,
-    // -65570 to -65785 currently unused; available for allocation
+    mStatus_PolicyDenied              = -65570,
+    // -65571 to -65785 currently unused; available for allocation
 
     // udp connection status
     mStatus_HostUnreachErr    = -65786,
@@ -407,10 +417,12 @@ enum
     mStatus_GrowCache         = -65790,
     mStatus_ConfigChanged     = -65791,
     mStatus_MemFree           = -65792      // Last value: 0xFFFE FF00
-                                // mStatus_MemFree is the last legal mDNS error code, at the end of the range allocated for mDNS
+
+    // mStatus_MemFree is the last legal mDNS error code, at the end of the range allocated for mDNS
 };
 
 typedef mDNSs32 mStatus;
+
 #define MaxIp 5 // Needs to be consistent with MaxInputIf in dns_services.h
 
 typedef enum { q_stop = 0, q_start } q_state;
@@ -793,116 +805,6 @@ typedef packedstruct
     mDNSu32 min;        // Nominally the minimum record TTL for this zone, in seconds; also used for negative caching.
 } rdataSOA;
 
-// http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
-// Algorithm used for RRSIG, DS and DNS KEY
-#define CRYPTO_RSA_SHA1             0x05
-#define CRYPTO_DSA_NSEC3_SHA1       0x06
-#define CRYPTO_RSA_NSEC3_SHA1       0x07
-#define CRYPTO_RSA_SHA256           0x08
-#define CRYPTO_RSA_SHA512           0x0A
-
-#define CRYPTO_ALG_MAX              0x0B
-
-// alg - same as in RRSIG, DNS KEY or DS.
-// RFC 4034 defines SHA1
-// RFC 4509 defines SHA256
-// Note: NSEC3 also uses 1 for SHA1 and hence we will reuse for now till a new
-// value is assigned.
-//
-#define SHA1_DIGEST_TYPE        1
-#define SHA256_DIGEST_TYPE      2
-#define DIGEST_TYPE_MAX         3
-
-// We need support for base64 and base32 encoding for displaying KEY, NSEC3
-// To make this platform agnostic, we define two types which the platform
-// needs to support
-#define ENC_BASE32              1
-#define ENC_BASE64              2
-#define ENC_ALG_MAX             3
-
-#define DS_FIXED_SIZE           4
-typedef packedstruct
-{
-    mDNSu16 keyTag;
-    mDNSu8 alg;
-    mDNSu8 digestType;
-    mDNSu8  *digest;
-} rdataDS;
-
-typedef struct TrustAnchor
-{
-    struct TrustAnchor *next;
-    int digestLen;
-    mDNSu32 validFrom;
-    mDNSu32 validUntil;
-    domainname zone;
-    rdataDS rds;
-} TrustAnchor;
-
-//size of rdataRRSIG excluding signerName and signature (which are variable fields)
-#define RRSIG_FIXED_SIZE      18
-typedef struct
-{
-    mDNSu16 typeCovered;
-    mDNSu8 alg;
-    mDNSu8 labels;
-    mDNSu32 origTTL;
-    mDNSu32 sigExpireTime;
-    mDNSu32 sigInceptTime;
-    mDNSu16 keyTag;
-    mDNSu8  signerName[1]; // signerName is a dynamically-sized array
-    // mDNSu8 *signature
-} rdataRRSig;
-
-// RFC 4034: For DNS Key RR
-// flags - the valid value for DNSSEC is 256 (Zone signing key - ZSK) and 257 (Secure Entry Point) which also
-// includes the ZSK bit
-//
-#define DNSKEY_ZONE_SIGN_KEY        0x100
-#define DNSKEY_SECURE_ENTRY_POINT   0x101
-
-// proto - the only valid value for protocol is 3 (See RFC 4034)
-#define DNSKEY_VALID_PROTO_VALUE    0x003
-
-// alg - The only mandatory algorithm that we support is RSA/SHA-1
-// DNSSEC_RSA_SHA1_ALG
-
-#define DNSKEY_FIXED_SIZE          4
-typedef packedstruct
-{
-    mDNSu16 flags;
-    mDNSu8 proto;
-    mDNSu8 alg;
-    mDNSu8  *data;
-} rdataDNSKey;
-
-#define NSEC3_FIXED_SIZE          5
-#define NSEC3_FLAGS_OPTOUT        1
-#define NSEC3_MAX_ITERATIONS      2500
-typedef packedstruct
-{
-    mDNSu8 alg;
-    mDNSu8 flags;
-    mDNSu16 iterations;
-    mDNSu8 saltLength;
-    mDNSu8 *salt;
-    // hashLength, nxt, bitmap
-} rdataNSEC3;
-
-// We define it here instead of dnssec.h so that these values can be used
-// in files without bringing in all of dnssec.h unnecessarily.
-typedef enum
-{
-    DNSSEC_Secure = 1,      // Securely validated and has a chain up to the trust anchor
-    DNSSEC_Insecure,        // Cannot build a chain up to the trust anchor
-    DNSSEC_Indeterminate,   // Not used currently
-    DNSSEC_Bogus,           // failed to validate signatures
-    DNSSEC_NoResponse       // No DNSSEC records to start with
-} DNSSECStatus;
-
-#define DNSSECRecordType(rrtype) (((rrtype) == kDNSType_RRSIG) || ((rrtype) == kDNSType_NSEC) || ((rrtype) == kDNSType_DNSKEY) || ((rrtype) == kDNSType_DS) || \
-                                  ((rrtype) == kDNSType_NSEC3))
-
 typedef enum
 {
     platform_OSX = 1,   // OSX Platform 
@@ -1051,9 +953,6 @@ typedef union
     mDNSv6Addr ipv6;        // For 'AAAA' record
     rdataSRV srv;
     rdataOPT opt[2];        // For EDNS0 OPT record; RDataBody may contain multiple variable-length rdataOPT objects packed together
-    rdataDS ds;
-    rdataDNSKey key;
-    rdataRRSig rrsig;
 } RDataBody2;
 
 typedef struct
@@ -1345,19 +1244,13 @@ typedef enum
                                    // have a matching serviceID
 } ScopeType;
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 typedef mDNSu32 DNSServerFlags;
 #define DNSServerFlag_Delete        (1U << 0)
 #if MDNSRESPONDER_SUPPORTS(APPLE, SYMPTOMS)
 #define DNSServerFlag_Unreachable   (1U << 1)
 #endif
 
-// Note: DNSSECAware is set if we are able to get a valid response to
-// a DNSSEC question. In some cases it is possible that the proxy
-// strips the EDNS0 option and we just get a plain response with no
-// signatures. But we still mark DNSSECAware in that case. As DNSSECAware
-// is only used to determine whether DNSSEC_VALIDATION_SECURE_OPTIONAL
-// should be turned off or not, it is sufficient that we are getting
-// responses back.
 typedef struct DNSServer
 {
     struct DNSServer *next;
@@ -1376,11 +1269,9 @@ typedef struct DNSServer
     mDNSBool isExpensive;       // True if the interface to this server is expensive.
     mDNSBool isConstrained;     // True if the interface to this server is constrained.
     mDNSBool isCLAT46;          // True if the interface to this server supports CLAT46.
-    mDNSBool req_DO;            // If set, okay to send DNSSEC queries (EDNS DO bit is supported)
-    mDNSBool DNSSECAware;       // Set if we are able to receive a response to a request sent with DO option.
-    mDNSu8 retransDO;           // Total Retransmissions for queries sent with DO option
     domainname domain;          // name->server matching for "split dns"
 } DNSServer;
+#endif
 
 #define kNegativeRecordType_Unspecified 0 // Initializer of ResourceRecord didn't specify why the record is negative.
 #define kNegativeRecordType_NoData      1 // The record's name exists, but there are no records of this type.
@@ -1410,7 +1301,21 @@ struct ResourceRecord_struct
                                         // that are interface-specific (e.g. address records, especially linklocal addresses)
     const domainname *name;
     RData           *rdata;             // Pointer to storage for this rdata
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+       mdns_dns_service_t dnsservice;
+       mdns_resolver_type_t protocol;
+#else
     DNSServer       *rDNSServer;        // Unicast DNS server authoritative for this entry; null for multicast
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    dnssec_result_t dnssec_result;      // DNSSEC validation result of the current resource record.
+                                        // For all DNSSEC-disabled queries, the result would always be dnssec_indeterminate.
+                                        // For DNSSEC-enabled queries, the result would be dnssec_indeterminate,
+                                        // dnssec_secure, dnssec_insecure, or dnssec_bogus, see
+                                        // <https://tools.ietf.org/html/rfc4033#section-5> for the detailed meaning of
+                                        // each state.
+#endif
 };
 
 
@@ -1638,15 +1543,21 @@ struct CacheRecord_struct
     mDNSs32 TimeRcvd;                   // In platform time units
     mDNSs32 DelayDelivery;              // Set if we want to defer delivery of this answer to local clients
     mDNSs32 NextRequiredQuery;          // In platform time units
+#if MDNSRESPONDER_SUPPORTS(APPLE, CACHE_ANALYTICS)
+    mDNSs32 LastCachedAnswerTime;       // Last time this record was used as an answer from the cache (before a query)
+                                        // In platform time units
+#else
     // Extra four bytes here (on 64bit)
+#endif
     DNSQuestion    *CRActiveQuestion;   // Points to an active question referencing this answer. Can never point to a NewQuestion.
     mDNSs32 LastUnansweredTime;         // In platform time units; last time we incremented UnansweredQueries
     mDNSu8  UnansweredQueries;          // Number of times we've issued a query for this record without getting an answer
-    mDNSu8  CRDNSSECQuestion;           // Set to 1 if this was created in response to a DNSSEC question
     mDNSOpaque16 responseFlags;         // Second 16 bit in the DNS response
     CacheRecord    *NextInCFList;       // Set if this is in the list of records we just received with the cache flush bit set
-    CacheRecord    *nsec;               // NSEC records needed for non-existence proofs
     CacheRecord    *soa;                // SOA record to return for proxy questions
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    void *denial_of_existence_records;  // denial_of_existence_records_t
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
 
     mDNSAddr sourceAddress;             // node from which we received this record
     // Size to here is 76 bytes when compiling 32-bit; 104 bytes when compiling 64-bit (now 160 bytes for 64-bit)
@@ -1845,46 +1756,27 @@ typedef struct DomainAuthInfo
 // layer. These values are used within mDNSResponder and not sent across to the application. QC_addnocache is for
 // delivering a response without adding to the cache. QC_forceresponse is superset of QC_addnocache where in
 // addition to not entering in the cache, it also forces the negative response through.
-typedef enum { QC_rmv = 0, QC_add, QC_addnocache, QC_forceresponse, QC_dnssec , QC_nodnssec, QC_suppressed } QC_result;
+typedef enum { QC_rmv = 0, QC_add, QC_addnocache, QC_forceresponse, QC_suppressed } QC_result;
 typedef void mDNSQuestionCallback (mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord);
 typedef void (*mDNSQuestionResetHandler)(DNSQuestion *question);
 typedef void AsyncDispatchFunc(mDNS *const m, void *context);
-typedef void DNSSECAuthInfoFreeCallback(mDNS *const m, void *context);
 extern void mDNSPlatformDispatchAsync(mDNS *const m, void *context, AsyncDispatchFunc func);
 
 #define NextQSendTime(Q)  ((Q)->LastQTime + (Q)->ThisQInterval)
 #define ActiveQuestion(Q) ((Q)->ThisQInterval > 0 && !(Q)->DuplicateOf)
 #define TimeToSendThisQuestion(Q,time) (ActiveQuestion(Q) && (time) - NextQSendTime(Q) >= 0)
 
-// q->ValidationStatus is either DNSSECValNotRequired or DNSSECValRequired and then moves onto DNSSECValInProgress.
-// When Validation is done, we mark all "DNSSECValInProgress" questions "DNSSECValDone". If we are answering
-// questions from /etc/hosts, then we go straight to DNSSECValDone from the initial state.
-typedef enum { DNSSECValNotRequired = 0, DNSSECValRequired, DNSSECValInProgress, DNSSECValDone } DNSSECValState;
-
-// ValidationRequired can be set to the following values:
-//
-// SECURE validation is set to determine whether something is secure or bogus 
-// INSECURE validation is set internally by dnssec code to indicate that it is currently proving something
-// is insecure
-#define DNSSEC_VALIDATION_NONE              0x00
-#define DNSSEC_VALIDATION_SECURE            0x01
-#define DNSSEC_VALIDATION_SECURE_OPTIONAL   0x02
-#define DNSSEC_VALIDATION_INSECURE          0x03
-
-// For both ValidationRequired and ValidatingResponse question, we validate DNSSEC responses.
-// For ProxyQuestion with DNSSECOK, we just receive the DNSSEC records to pass them along without
-// validation and if the CD bit is not set, we also validate.
-#define DNSSECQuestion(q) ((q)->ValidationRequired || (q)->ValidatingResponse || ((q)->ProxyQuestion && (q)->ProxyDNSSECOK))
-
-// ValidatingQuestion is used when we need to know whether we are validating the DNSSEC responses for a question
-#define ValidatingQuestion(q) ((q)->ValidationRequired || (q)->ValidatingResponse)
-
-#define DNSSECOptionalQuestion(q) ((q)->ValidationRequired == DNSSEC_VALIDATION_SECURE_OPTIONAL)
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#define FollowCNAMEOptionDNSSEC(Q)      !(Q)->DNSSECStatus.enable_dnssec
+#else // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#define FollowCNAMEOptionDNSSEC(Q)      mDNStrue
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
 
 // Given the resource record and the question, should we follow the CNAME ?
 #define FollowCNAME(q, rr, AddRecord)   (AddRecord && (q)->qtype != kDNSType_CNAME && \
                                          (rr)->RecordType != kDNSRecordTypePacketNegative && \
-                                         (rr)->rrtype == kDNSType_CNAME)
+                                         (rr)->rrtype == kDNSType_CNAME \
+                                         && FollowCNAMEOptionDNSSEC(q))
 
 // RFC 4122 defines it to be 16 bytes 
 #define UUID_SIZE       16
@@ -1976,23 +1868,21 @@ struct DNSQuestion_struct
     mDNSBool InitialCacheMiss;              // True after the question cannot be answered from the cache
     mDNSs32 StopTime;                       // Time this question should be stopped by giving them a negative answer
 
-    // DNSSEC fields
-    DNSSECValState ValidationState;            // Current state of the Validation process
-    DNSSECStatus ValidationStatus;             // Validation status for "ValidationRequired" questions (dnssec)
-    mDNSu8 ValidatingResponse;                 // Question trying to validate a response (dnssec) on behalf of
-                                               // ValidationRequired question
-    void *DNSSECAuthInfo;
-    DNSSECAuthInfoFreeCallback *DAIFreeCallback;
-
     // Wide Area fields. These are used internally by the uDNS core (Unicast)
     UDPSocket            *LocalSocket;
 
     // |-> DNS Configuration related fields used in uDNS (Subset of Wide Area/Unicast fields)
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mdns_dns_service_t    dnsservice;       // The current DNS service.
+    mdns_dns_service_id_t lastDNSServiceID; // The ID of the previous DNS service before a CNAME restart.
+    mdns_querier_t        querier;          // The current querier.
+#else
     DNSServer            *qDNSServer;       // Caching server for this query (in the absence of an SRV saying otherwise)
     mDNSOpaque128 validDNSServers;          // Valid DNSServers for this question
     mDNSu16 noServerResponse;               // At least one server did not respond.
     mDNSBool triedAllServersOnce;           // True if all DNS servers have been tried once.
     mDNSu8 unansweredQueries;               // The number of unanswered queries to this server
+#endif
     AllowExpiredState allowExpired;         // Allow expired answers state (see enum AllowExpired_None, etc. above)
 
     ZoneData             *nta;              // Used for getting zone data for private or LLQ query
@@ -2002,7 +1892,9 @@ struct DNSQuestion_struct
     mDNSIPPort tcpSrcPort;                  // Local Port TCP packet received on;need this as tcp struct is disposed
                                             // by tcpCallback before calling into mDNSCoreReceive
     mDNSu8 NoAnswer;                        // Set if we want to suppress answers until tunnel setup has completed
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     mDNSBool Restart;                       // This question should be restarted soon.
+#endif
 
     // LLQ-specific fields. These fields are only meaningful when LongLived flag is set
     LLQ_State state;
@@ -2023,6 +1915,10 @@ struct DNSQuestion_struct
                                             // till we populate in the cache
     mDNSBool     BlockedByPolicy;           // True if the question is blocked by policy rule evaluation.
     mDNSs32      ServiceID;                 // Service identifier to match against the DNS server
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mDNSu8       ResolverUUID[UUID_SIZE];   // Resolver UUID to match against the DNS server
+    mdns_dns_service_id_t CustomID;
+#endif
 
     // Client API fields: The client must set up these fields *before* calling mDNS_StartQuery()
     mDNSInterfaceID InterfaceID;            // Non-zero if you want to issue queries only on a single specific IP interface
@@ -2042,9 +1938,16 @@ struct DNSQuestion_struct
     mDNSBool WakeOnResolve;                 // Send wakeup on resolve
     mDNSBool UseBackgroundTraffic;          // Set by client to use background traffic class for request
     mDNSBool AppendSearchDomains;           // Search domains can be appended for this query
-    mDNSu8 ValidationRequired;              // Requires DNSSEC validation.
+    mDNSBool ForcePathEval;                 // Perform a path evaluation even if kDNSServiceFlagsPathEvaluationDone is set.
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mDNSBool RequireEncryption;             // Set by client to require encrypted queries
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+    mDNSBool inAppBrowserRequest;           // Is request associated with an in-app-browser
+    audit_token_t  peerAuditToken;          // audit token of the peer requesting the question
+    audit_token_t  delegateAuditToken;      // audit token of the delegated client the question is for
+#endif
     mDNSu8 ProxyQuestion;                   // Proxy Question
-    mDNSu8 ProxyDNSSECOK;                   // Proxy Question with EDNS0 DNSSEC OK bit set
     mDNSs32 pid;                            // Process ID of the client that is requesting the question
     mDNSu8  uuid[UUID_SIZE];                // Unique ID of the client that is requesting the question (valid only if pid is zero)
     mDNSu32 euid;                           // Effective User Id of the client that is requesting the question
@@ -2052,15 +1955,15 @@ struct DNSQuestion_struct
     mDNSQuestionCallback *QuestionCallback;
     mDNSQuestionResetHandler ResetHandler;
     void                 *QuestionContext;
-#if MDNSRESPONDER_SUPPORTS(APPLE, SUSPICIOUS_REPLY_DEFENSE)
-    mDNSOpaque16 LastTargetQID;             // Last used QID, to help determine suspicion with valid in-flight replies.
-#endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
     uDNSMetrics metrics;                    // Data used for collecting unicast DNS query metrics.
 #endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNS64)
     DNS64 dns64;                            // DNS64 state for performing IPv6 address synthesis on networks with NAT64.
 #endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    dnssec_status_t DNSSECStatus;           // DNSSEC state for fectching DNSSEC records and doing validation
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
 };
 
 typedef enum { ZoneServiceUpdate, ZoneServiceQuery, ZoneServiceLLQ, ZoneServiceDNSPush } ZoneService;
@@ -2222,48 +2125,6 @@ enum
     SleepState_Sleeping = 2
 };
 
-typedef enum
-{
-    kStatsActionIncrement,
-    kStatsActionDecrement,
-    kStatsActionClear,
-    kStatsActionSet
-} DNSSECStatsAction;
-
-typedef enum
-{
-    kStatsTypeMemoryUsage,
-    kStatsTypeLatency,
-    kStatsTypeExtraPackets,
-    kStatsTypeStatus,
-    kStatsTypeProbe,
-    kStatsTypeMsgSize
-} DNSSECStatsType;
-
-typedef struct
-{
-    mDNSu32 TotalMemUsed;
-    mDNSu32 Latency0;           // 0 to 4 ms
-    mDNSu32 Latency5;           // 5 to  9 ms
-    mDNSu32 Latency10;          // 10 to 19 ms
-    mDNSu32 Latency20;          // 20 to 49 ms
-    mDNSu32 Latency50;          // 50 to 99 ms
-    mDNSu32 Latency100;         // >= 100 ms
-    mDNSu32 ExtraPackets0;      // 0 to 2 packets
-    mDNSu32 ExtraPackets3;      // 3 to 6 packets
-    mDNSu32 ExtraPackets7;      // 7 to 9 packets
-    mDNSu32 ExtraPackets10;     // >= 10 packets
-    mDNSu32 SecureStatus;
-    mDNSu32 InsecureStatus;
-    mDNSu32 IndeterminateStatus;
-    mDNSu32 BogusStatus;
-    mDNSu32 NoResponseStatus;
-    mDNSu32 NumProbesSent;      // Number of probes sent
-    mDNSu32 MsgSize0;           // DNSSEC message size <= 1024
-    mDNSu32 MsgSize1;           // DNSSEC message size <= 2048
-    mDNSu32 MsgSize2;           // DNSSEC message size > 2048
-} DNSSECStatistics;
-
 typedef struct
 {
     mDNSu32 NameConflicts;                  // Normal Name conflicts
@@ -2320,10 +2181,6 @@ struct mDNS_struct
     mDNSu8 lock_rrcache;                // For debugging: Set at times when these lists may not be modified
     mDNSu8 lock_Questions;
     mDNSu8 lock_Records;
-#ifndef MaxMsg
-    #define MaxMsg 512
-#endif
-    char MsgBuffer[MaxMsg];             // Temp storage used while building error log messages
 
     // Task Scheduling variables
     mDNSs32 timenow_adjust;             // Correction applied if we ever discover time went backwards
@@ -2342,9 +2199,6 @@ struct mDNS_struct
 #if MDNSRESPONDER_SUPPORTS(APPLE, BONJOUR_ON_DEMAND)
     mDNSs32 NextBonjourDisableTime;     // Next time to leave multicast group if Bonjour on Demand is enabled
     mDNSu8 BonjourEnabled;              // Non zero if Bonjour is currently enabled by the Bonjour on Demand logic
-#endif
-#if MDNSRESPONDER_SUPPORTS(APPLE, SUSPICIOUS_REPLY_DEFENSE)
-    mDNSs32 NextSuspiciousTimeout;      // Time until suspicious reply defense will timeout
 #endif
     mDNSs32 RandomQueryDelay;           // For de-synchronization of query packets on the wire
     mDNSu32 RandomReconfirmDelay;       // For de-synchronization of reconfirmation queries on the wire
@@ -2380,7 +2234,6 @@ struct mDNS_struct
     DNSQuestion *LocalOnlyQuestions;    // Questions with InterfaceID set to mDNSInterface_LocalOnly or mDNSInterface_P2P
     DNSQuestion *NewLocalOnlyQuestions; // Fresh local-only or P2P questions not yet answered
     DNSQuestion *RestartQuestion;       // Questions that are being restarted (stop followed by start)
-    DNSQuestion *ValidationQuestion;    // Questions that are being validated (dnssec)
     mDNSu32 rrcache_size;               // Total number of available cache entries
     mDNSu32 rrcache_totalused;          // Number of cache entries currently occupied
     mDNSu32 rrcache_totalused_unicast;  // Number of cache entries currently occupied by unicast
@@ -2422,7 +2275,9 @@ struct mDNS_struct
     mDNSs32 NextuDNSEvent;                  // uDNS next event
     mDNSs32 NextSRVUpdate;                  // Time to perform delayed update
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     DNSServer        *DNSServers;           // list of DNS servers
+#endif
     McastResolver    *McastResolvers;       // list of Mcast Resolvers
 
     mDNSAddr Router;
@@ -2500,7 +2355,6 @@ struct mDNS_struct
     mDNSu32 dp_ipintf[MaxIp];                   // input interface index list from the DNS Proxy Client
     mDNSu32 dp_opintf;                          // output interface index from the DNS Proxy Client
 
-    TrustAnchor     *TrustAnchors;
     int             notifyToken;
     int             uds_listener_skt;           // Listening socket for incoming UDS clients. This should not be here -- it's private to uds_daemon.c and nothing to do with mDNSCore -- SC
     mDNSu32         AutoTargetServices;         // # of services that have AutoTarget set
@@ -2511,7 +2365,6 @@ struct mDNS_struct
     mDNSu32         NumAllInterfaceQuestions;   // Right now we count *all* multicast questions here. Later we may want to change to count interface-specific questions separately.
 #endif
 
-    DNSSECStatistics DNSSECStats;
     mDNSStatistics   mDNSStats;
 
     // Fixed storage, to avoid creating large objects on the stack
@@ -2520,7 +2373,10 @@ struct mDNS_struct
     DNSMessage omsg;                        // Outgoing message we're building
     LargeCacheRecord rec;                   // Resource Record extracted from received message
 
-    mDNSu32 next_request_id;
+#ifndef MaxMsg
+    #define MaxMsg 512
+#endif
+    char MsgBuffer[MaxMsg];                 // Temp storage used while building error log messages (keep at end of struct)
 };
 
 #define FORALL_CACHERECORDS(SLOT,CG,CR)                           \
@@ -2579,12 +2435,12 @@ extern const mDNSOpaque16 zeroID;
 extern const mDNSOpaque16 onesID;
 extern const mDNSOpaque16 QueryFlags;
 extern const mDNSOpaque16 uQueryFlags;
-extern const mDNSOpaque16 DNSSecQFlags;
 extern const mDNSOpaque16 ResponseFlags;
 extern const mDNSOpaque16 UpdateReqFlags;
 extern const mDNSOpaque16 UpdateRespFlags;
 extern const mDNSOpaque16 SubscribeFlags;
 extern const mDNSOpaque16 UnSubscribeFlags;
+extern const mDNSOpaque16 uDNSSecQueryFlags;
 
 extern const mDNSOpaque64 zeroOpaque64;
 extern const mDNSOpaque128 zeroOpaque128;
@@ -2610,7 +2466,9 @@ extern mDNSBool StrictUnicastOrdering;
 
 // If we're not doing inline functions, then this header needs to have the extern declarations
 #if !defined(mDNSinline)
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 extern int          CountOfUnicastDNSServers(mDNS *const m);
+#endif
 extern mDNSs32      NonZeroTime(mDNSs32 t);
 extern mDNSu16      mDNSVal16(mDNSOpaque16 x);
 extern mDNSOpaque16 mDNSOpaque16fromIntVal(mDNSu16 v);
@@ -2624,6 +2482,7 @@ extern mDNSOpaque16 mDNSOpaque16fromIntVal(mDNSu16 v);
 
 #ifdef mDNSinline
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 mDNSinline int CountOfUnicastDNSServers(mDNS *const m)
 {
     int count = 0;
@@ -2631,6 +2490,7 @@ mDNSinline int CountOfUnicastDNSServers(mDNS *const m)
     while(ptr) { if(!(ptr->flags & DNSServerFlag_Delete)) count++; ptr = ptr->next; }
     return (count);
 }
+#endif
 
 mDNSinline mDNSs32 NonZeroTime(mDNSs32 t) { if (t) return(t);else return(1);}
 
@@ -2646,10 +2506,6 @@ mDNSinline mDNSOpaque16 mDNSOpaque16fromIntVal(mDNSu16 v)
 
 #endif
     
-#if MDNSRESPONDER_SUPPORTS(APPLE, SUSPICIOUS_REPLY_DEFENSE)
-#define SUSPICIOUS_REPLY_DEFENSE_SECS   10
-#endif
-
 // ***************************************************************************
 #if 0
 #pragma mark -
@@ -2840,8 +2696,14 @@ extern mStatus mDNS_AdvertiseDomains(mDNS *const m, AuthRecord *rr, mDNS_DomainT
 extern mDNSOpaque16 mDNS_NewMessageID(mDNS *const m);
 extern mDNSBool mDNS_AddressIsLocalSubnet(mDNS *const m, const mDNSInterfaceID InterfaceID, const mDNSAddr *addr);
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 extern DNSServer *GetServerForQuestion(mDNS *m, DNSQuestion *question);
+#endif
 extern mDNSu32 SetValidDNSServers(mDNS *m, DNSQuestion *question);
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+extern mDNSBool ShouldSuppressUnicastQuery(const DNSQuestion *q, mdns_dns_service_t dnsservice);
+extern mDNSBool LocalRecordRmvEventsForQuestion(mDNS *m, DNSQuestion *q);
+#endif
 
 // ***************************************************************************
 #if 0
@@ -2954,6 +2816,7 @@ extern mDNSu32 mDNS_snprintf(char *sbuffer, mDNSu32 buflen, const char *fmt, ...
 extern void mDNS_snprintf_add(char **dst, const char *lim, const char *fmt, ...) IS_A_PRINTF_STYLE_FUNCTION(3,4);
 extern mDNSu32 NumCacheRecordsForInterfaceID(const mDNS *const m, mDNSInterfaceID id);
 extern char *DNSTypeName(mDNSu16 rrtype);
+extern const char *mStatusDescription(mStatus error);
 extern char *GetRRDisplayString_rdb(const ResourceRecord *const rr, const RDataBody *const rd1, char *const buffer);
 #define RRDisplayString(m, rr) GetRRDisplayString_rdb(rr, &(rr)->rdata->u, (m)->MsgBuffer)
 #define ARDisplayString(m, rr) GetRRDisplayString_rdb(&(rr)->resrec, &(rr)->resrec.rdata->u, (m)->MsgBuffer)
@@ -3059,10 +2922,12 @@ extern void RecreateNATMappings(mDNS *const m, const mDNSu32 waitTicks);
 extern void mDNS_AddDynDNSHostName(mDNS *m, const domainname *fqdn, mDNSRecordCallback *StatusCallback, const void *StatusContext);
 extern void mDNS_RemoveDynDNSHostName(mDNS *m, const domainname *fqdn);
 extern void mDNS_SetPrimaryInterfaceInfo(mDNS *m, const mDNSAddr *v4addr,  const mDNSAddr *v6addr, const mDNSAddr *router);
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 extern DNSServer *mDNS_AddDNSServer(mDNS *const m, const domainname *d, const mDNSInterfaceID interface, mDNSs32 serviceID, const mDNSAddr *addr,
                                     const mDNSIPPort port, ScopeType scopeType, mDNSu32 timeout, mDNSBool cellIntf, mDNSBool isExpensive, mDNSBool isConstrained, mDNSBool isCLAT46,
                                     mDNSu32 resGroupID, mDNSBool reqA, mDNSBool reqAAAA, mDNSBool reqDO);
 extern void PenalizeDNSServer(mDNS *const m, DNSQuestion *q, mDNSOpaque16 responseFlags);
+#endif
 extern void mDNS_AddSearchDomain(const domainname *const domain, mDNSInterfaceID InterfaceID);
 
 extern McastResolver *mDNS_AddMcastResolver(mDNS *const m, const domainname *d, const mDNSInterfaceID interface, mDNSu32 timeout);
@@ -3344,6 +3209,9 @@ extern void     mDNSCoreInitComplete(mDNS *const m, mStatus result);
 extern void     mDNSCoreReceive(mDNS *const m, DNSMessage *const msg, const mDNSu8 *const end,
                                 const mDNSAddr *const srcaddr, const mDNSIPPort srcport,
                                 const mDNSAddr *dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID);
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+extern void     mDNSCoreReceiveForQuerier(mDNS *m, DNSMessage *msg, const mDNSu8 *end, mdns_querier_t querier, mdns_dns_service_t service);
+#endif
 extern CacheRecord *mDNSCheckCacheFlushRecords(mDNS *m, CacheRecord *CacheFlushRecords, mDNSBool id_is_zero, int numAnswers,
                                                                                           DNSQuestion *unicastQuestion, CacheRecord *NSECCachePtr, CacheRecord *NSECRecords,
                                                                                           mDNSu8 rcode);
@@ -3357,7 +3225,18 @@ extern void     mDNSCoreRestartAddressQueries(mDNS *const m, mDNSBool SearchDoma
 extern mDNSBool mDNSCoreHaveAdvertisedMulticastServices(mDNS *const m);
 extern void     mDNSCoreMachineSleep(mDNS *const m, mDNSBool wake);
 extern mDNSBool mDNSCoreReadyForSleep(mDNS *m, mDNSs32 now);
-extern mDNSs32  mDNSCoreIntervalToNextWake(mDNS *const m, mDNSs32 now);
+
+typedef enum
+{
+    mDNSNextWakeReason_Null                        = 0,
+    mDNSNextWakeReason_NATPortMappingRenewal       = 1,
+    mDNSNextWakeReason_RecordRegistrationRenewal   = 2,
+    mDNSNextWakeReason_UpkeepWake                  = 3,
+    mDNSNextWakeReason_DHCPLeaseRenewal            = 4,
+    mDNSNextWakeReason_SleepProxyRegistrationRetry = 5
+} mDNSNextWakeReason;
+
+extern mDNSs32  mDNSCoreIntervalToNextWake(mDNS *const m, mDNSs32 now, mDNSNextWakeReason *outReason);
 
 extern void     mDNSCoreReceiveRawPacket  (mDNS *const m, const mDNSu8 *const p, const mDNSu8 *const end, const mDNSInterfaceID InterfaceID);
 
@@ -3369,14 +3248,20 @@ extern void ReleaseCacheRecord(mDNS *const m, CacheRecord *r);
 extern void ScheduleNextCacheCheckTime(mDNS *const m, const mDNSu32 slot, const mDNSs32 event);
 extern void SetNextCacheCheckTimeForRecord(mDNS *const m, CacheRecord *const rr);
 extern void GrantCacheExtensions(mDNS *const m, DNSQuestion *q, mDNSu32 lease);
-extern void MakeNegativeCacheRecord(mDNS *const m, CacheRecord *const cr,
-                                    const domainname *const name, const mDNSu32 namehash, const mDNSu16 rrtype, const mDNSu16 rrclass, mDNSu32 ttl_seconds,
-                                    mDNSInterfaceID InterfaceID, DNSServer *dnsserver);
+extern void MakeNegativeCacheRecord(mDNS *const m, CacheRecord *const cr, const domainname *const name,
+    const mDNSu32 namehash, const mDNSu16 rrtype, const mDNSu16 rrclass, mDNSu32 ttl_seconds, mDNSInterfaceID InterfaceID,
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mdns_dns_service_t service);
+#else
+    DNSServer *dnsserver);
+#endif
 extern void CompleteDeregistration(mDNS *const m, AuthRecord *rr);
 extern void AnswerCurrentQuestionWithResourceRecord(mDNS *const m, CacheRecord *const rr, const QC_result AddRecord);
 extern void AnswerQuestionByFollowingCNAME(mDNS *const m, DNSQuestion *q, ResourceRecord *rr);
 extern char *InterfaceNameForID(mDNS *const m, const mDNSInterfaceID InterfaceID);
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 extern void DNSServerChangeForQuestion(mDNS *const m, DNSQuestion *q, DNSServer *newServer);
+#endif
 extern void ActivateUnicastRegistration(mDNS *const m, AuthRecord *const rr);
 extern void CheckSuppressUnusableQuestions(mDNS *const m);
 extern void RetrySearchDomainQuestions(mDNS *const m);
@@ -3408,7 +3293,7 @@ extern void mDNSPlatformDisposeProxyContext(void *context);
 extern mDNSu8 *DNSProxySetAttributes(DNSQuestion *q, DNSMessageHeader *h, DNSMessage *msg, mDNSu8 *start, mDNSu8 *limit);
 
 #if APPLE_OSX_mDNSResponder
-extern void mDNSPlatformGetDNSRoutePolicy(DNSQuestion *q, mDNSBool *isBlocked);
+extern void mDNSPlatformGetDNSRoutePolicy(DNSQuestion *q);
 #endif
 extern void mDNSPlatformSetSocktOpt(void *sock, mDNSTransport_Type transType, mDNSAddr_Type addrType, const DNSQuestion *q);
 extern mDNSs32 mDNSPlatformGetPID(void);
@@ -3418,6 +3303,9 @@ extern mDNSBool CacheRecordRmvEventsForQuestion(mDNS *const m, DNSQuestion *q);
 extern void GetRandomUUIDLabel(domainlabel *label);
 extern void GetRandomUUIDLocalHostname(domainname *hostname);
 #endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
+extern void uDNSMetricsClear(uDNSMetrics *metrics);
+#endif
 
 // ***************************************************************************
 #if 0
@@ -3626,7 +3514,6 @@ struct CompileTimeAssertionChecks_mDNS
     char assertL[(sizeof(IKEHeader     )   ==   28                         ) ? 1 : -1];
     char assertM[(sizeof(TCPHeader     )   ==   20                         ) ? 1 : -1];
        char assertN[(sizeof(rdataOPT)             ==   24                         ) ? 1 : -1];
-       char assertO[(sizeof(rdataRRSig)           ==   20                         ) ? 1 : -1];
        char assertP[(sizeof(PCPMapRequest)    ==   60                         ) ? 1 : -1];
        char assertQ[(sizeof(PCPMapReply)      ==   60                         ) ? 1 : -1];
 
@@ -3636,20 +3523,21 @@ struct CompileTimeAssertionChecks_mDNS
     // cause structure sizes (and therefore memory usage) to balloon unreasonably.
     char sizecheck_RDataBody           [(sizeof(RDataBody)            ==   264) ? 1 : -1];
     char sizecheck_ResourceRecord      [(sizeof(ResourceRecord)       <=    72) ? 1 : -1];
-    char sizecheck_AuthRecord          [(sizeof(AuthRecord)           <=  1168) ? 1 : -1];
+    char sizecheck_AuthRecord          [(sizeof(AuthRecord)           <=  1176) ? 1 : -1];
     char sizecheck_CacheRecord         [(sizeof(CacheRecord)          <=   232) ? 1 : -1];
     char sizecheck_CacheGroup          [(sizeof(CacheGroup)           <=   232) ? 1 : -1];
-    char sizecheck_DNSQuestion         [(sizeof(DNSQuestion)          <=  1136) ? 1 : -1];
-
-    char sizecheck_ZoneData            [(sizeof(ZoneData)             <=  2000) ? 1 : -1];
+    char sizecheck_DNSQuestion         [(sizeof(DNSQuestion)          <=  1216) ? 1 : -1];
+    char sizecheck_ZoneData            [(sizeof(ZoneData)             <=  2048) ? 1 : -1];
     char sizecheck_NATTraversalInfo    [(sizeof(NATTraversalInfo)     <=   200) ? 1 : -1];
     char sizecheck_HostnameInfo        [(sizeof(HostnameInfo)         <=  3050) ? 1 : -1];
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     char sizecheck_DNSServer           [(sizeof(DNSServer)            <=   328) ? 1 : -1];
-    char sizecheck_NetworkInterfaceInfo[(sizeof(NetworkInterfaceInfo) <=  8272) ? 1 : -1];
-    char sizecheck_ServiceRecordSet    [(sizeof(ServiceRecordSet)     <=  4728) ? 1 : -1];
+#endif
+    char sizecheck_NetworkInterfaceInfo[(sizeof(NetworkInterfaceInfo) <=  9000) ? 1 : -1];
+    char sizecheck_ServiceRecordSet    [(sizeof(ServiceRecordSet)     <=  4760) ? 1 : -1];
     char sizecheck_DomainAuthInfo      [(sizeof(DomainAuthInfo)       <=   944) ? 1 : -1];
 #if APPLE_OSX_mDNSResponder
-    char sizecheck_ClientTunnel        [(sizeof(ClientTunnel)         <=  1512) ? 1 : -1];
+    char sizecheck_ClientTunnel        [(sizeof(ClientTunnel)         <=  1560) ? 1 : -1];
 #endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
     // structure size is assumed by LogRedact routine.
diff --git a/mDNSCore/nsec.c b/mDNSCore/nsec.c
deleted file mode 100644 (file)
index 8c782b5..0000000
+++ /dev/null
@@ -1,1263 +0,0 @@
-/* -*- Mode: C; tab-width: 4 -*-
- *
- * Copyright (c) 2011-2019 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// ***************************************************************************
-// nsec.c: This file contains support functions to validate NSEC records for
-// NODATA and NXDOMAIN error.
-// ***************************************************************************
-
-#include "mDNSEmbeddedAPI.h"
-#include "DNSCommon.h"
-#include "nsec.h"
-#include "nsec3.h"
-
-// Define DNSSEC_DISABLED to remove all the DNSSEC functionality
-// and use the stub functions implemented later in this file.
-
-#ifndef DNSSEC_DISABLED
-
-// Implementation Notes
-//
-// NSEC records in DNSSEC are used for authenticated denial of existence i.e., if the response to a query
-// results in NXDOMAIN or NODATA error, the response also contains NSEC records in the additional section
-// to prove the non-existence of the original name. In most of the cases, NSEC records don't have any
-// relationship to the original name queried i.e, if they are cached based on the name like other records,
-// it can't be located to prove the non-existence of the original name. Hence, we create a negative cache
-// record like we do for the NXDOMAIN/NODATA error and then cache the NSEC records as part of that. Sometimes,
-// NSEC records are also used for wildcard expanded answer in which case they are cached with the cache record
-// that is created for the original name. NSEC records are freed when the parent cache (the record that they
-// are attached to is expired).
-//
-// NSEC records also can be queried like any other record and hence can exist independent of the negative
-// cache record. It exists as part of negative cache record only when we get a NXDOMAIN/NODATA error with
-// NSEC records. When a query results in NXDOMAIN/NODATA error and needs to be validated, the NSEC
-// records (and its RRSIGS) are cached as part of the negative cache record. The NSEC records that
-// exist separately from the negative cache record should not be used to answer ValidationRequired/
-// ValidatingResponse questions as it may not be sufficient to prove the non-existence of the name.
-// The exception is when the NSEC record is looked up explicitly. See DNSSECRecordAnswersQuestion
-// for more details.
-//
-
-mDNSlocal CacheRecord *NSECParentForQuestion(mDNS *const m, DNSQuestion *q)
-{
-    CacheGroup *cg;
-    CacheRecord *cr;
-    mDNSu32 namehash;
-
-    namehash = DomainNameHashValue(&q->qname);
-    cg = CacheGroupForName(m, namehash, &q->qname);
-    if (!cg)
-    {
-        LogDNSSEC("NSECParentForQuestion: Cannot find cg for %##s (%s)", q->qname.c, DNSTypeName(q->qtype));
-        return mDNSNULL;
-    }
-    for (cr = cg->members; cr; cr = cr->next)
-        if (SameNameCacheRecordAnswersQuestion(cr, q))
-            return cr;
-    return mDNSNULL;
-}
-
-mDNSlocal void UpdateParent(DNSSECVerifier *dv)
-{
-    AuthChainLink(dv->parent, dv->ac);
-    ResetAuthChain(dv);
-    dv->parent->NumPackets += dv->NumPackets;
-}
-
-// Note: This should just call the parent callback which will free the DNSSECVerifier.
-mDNSlocal void VerifyNSECCallback(mDNS *const m, DNSSECVerifier *dv, DNSSECStatus status)
-{
-    if (!dv->parent)
-    {
-        LogMsg("VerifyNSECCCallback: ERROR!! no parent DV\n");
-        FreeDNSSECVerifier(m, dv);
-        return;
-    }
-    if (dv->ac)
-    {
-        // Before we free the "dv", we need to update the
-        // parent with our AuthChain information
-        UpdateParent(dv);
-    }
-    // "status" indicates whether we are able to successfully verify
-    // the NSEC/NSEC3 signatures. For NSEC3, the OptOut flag may be set
-    // for which we need to deliver insecure result.
-    if ((dv->parent->flags & NSEC3_OPT_OUT) && (status == DNSSEC_Secure))
-    {
-        dv->parent->DVCallback(m, dv->parent, DNSSEC_Insecure);
-    }
-    else
-    {
-        dv->parent->DVCallback(m, dv->parent, status);
-    }
-    // The callback we called in the previous line should recursively
-    // free all the DNSSECVerifiers starting from dv->parent and above.
-    // So, set that to NULL and free the "dv" itself here.
-    dv->parent = mDNSNULL;
-    FreeDNSSECVerifier(m, dv);
-}
-
-// If the caller provides a callback, it takes the responsibility of calling the original callback
-// in "pdv" when it is done.
-//
-// INPUT:
-//
-// rr: The NSEC record that should be verified
-// rv: The NSEC record can also be provided like this
-// pdv: Parent DNSSECVerifier which will be called when the verification is done.
-// callback:  As part of the proof, we need multiple NSEC verifications before we call the "pdv" callback in
-// which case a intermediate "callback" is provided which can be used to do multiple verifications.
-// ncr: The cache record where the RRSIGS are cached
-//
-// NSEC records and signatures are cached along with the cache record so that we can expire them all together. We can't cache
-// them based on the name hash like other records as in most cases the returned NSECs has a different name than we asked for
-// (except for NODATA error where the name exists but type does not exist).
-//
-mDNSexport void VerifyNSEC(mDNS *const m, ResourceRecord *rr, RRVerifier *rv, DNSSECVerifier *pdv, CacheRecord *ncr, DNSSECVerifierCallback callback)
-{
-    DNSSECVerifier *dv = mDNSNULL;
-    CacheRecord **rp;
-    const domainname *name;
-    mDNSu16 rrtype;
-
-    if (!rv && !rr)
-    {
-        LogDNSSEC("VerifyNSEC: Both rr and rv are NULL");
-        goto error;
-    }
-    if (!pdv)
-    {
-        LogDNSSEC("VerifyNSEC: ERROR!! pdv is NULL");
-        return;
-    }
-    // Remember the name and type for which we are verifying, so that when we are done processing all
-    // the verifications, we can trace it back.
-    //
-    // Note: Currently it is not used because when the verification completes as we just
-    // call the "pdv" callback which has its origName and origType.
-    if (rr)
-    {
-        name = rr->name;
-        rrtype = rr->rrtype;
-    }
-    else
-    {
-        name = &rv->name;
-        rrtype = rv->rrtype;
-    }
-
-    dv = AllocateDNSSECVerifier(m, name, rrtype, pdv->q.InterfaceID, DNSSEC_VALIDATION_SECURE,
-        (callback ? callback : VerifyNSECCallback), mDNSNULL);
-    if (!dv)
-    {
-        LogMsg("VerifyNSEC: mDNSPlatformMemAlloc failed");
-        return;
-    }
-
-    dv->parent = pdv;
-
-    if (AddRRSetToVerifier(dv, rr, rv, RRVS_rr) != mStatus_NoError)
-    {
-        LogMsg("VerifyNSEC: ERROR!! AddRRSetToVerifier failed to add NSEC");
-        goto error;
-    }
-
-    // Add the signatures after validating them
-    rp = &(ncr->nsec);
-    while (*rp)
-    {
-        if ((*rp)->resrec.rrtype == kDNSType_RRSIG)
-        {
-            ValidateRRSIG(dv, RRVS_rrsig, &(*rp)->resrec);
-        }
-        rp=&(*rp)->next;
-    }
-
-    if (!dv->rrset)
-    {
-        LogMsg("VerifyNSEC: ERROR!! AddRRSetToVerifier missing rrset");
-        goto error;
-    }
-    // Expired signatures.
-    if (!dv->rrsig)
-        goto error;
-
-    // Next step is to fetch the keys
-    dv->next = RRVS_key;
-
-    StartDNSSECVerification(m, dv);
-    return;
-error:
-    pdv->DVCallback(m, pdv, DNSSEC_Bogus);
-    if (dv)
-    {
-        dv->parent = mDNSNULL;
-        FreeDNSSECVerifier(m, dv);
-    }
-    return;
-}
-
-mDNSlocal void DeleteCachedNSECS(mDNS *const m, CacheRecord *cr)
-{
-    CacheRecord *rp, *next;
-
-    if (cr->nsec) LogDNSSEC("DeleteCachedNSECS: Deleting NSEC Records\n");
-    for (rp = cr->nsec; rp; rp = next)
-    {
-        next  = rp->next;
-        ReleaseCacheRecord(m, rp);
-    }
-    cr->nsec = mDNSNULL;
-}
-
-// Returns success if it adds the nsecs and the rrsigs to the cache record. Otherwise, it returns
-// failure (mDNSfalse)
-mDNSexport mDNSBool AddNSECSForCacheRecord(mDNS *const m, CacheRecord *crlist, CacheRecord *negcr, mDNSu8 rcode)
-{
-    CacheRecord *cr;
-    mDNSBool nsecs_seen = mDNSfalse;
-    mDNSBool nsec3s_seen = mDNSfalse;
-
-    if (rcode != kDNSFlag1_RC_NoErr && rcode != kDNSFlag1_RC_NXDomain)
-    {
-        LogMsg("AddNSECSForCacheRecord: Addings nsecs for rcode %d", rcode);
-        return mDNSfalse;
-    }
-
-    // Sanity check the list to see if we have anything else other than
-    // NSECs and its RRSIGs
-    for (cr = crlist; cr; cr = cr->next)
-    {
-        if (cr->resrec.rrtype != kDNSType_NSEC && cr->resrec.rrtype != kDNSType_NSEC3 &&
-            cr->resrec.rrtype != kDNSType_SOA && cr->resrec.rrtype != kDNSType_RRSIG)
-        {
-            LogMsg("AddNSECSForCacheRecord: ERROR!! Adding Wrong record %s", CRDisplayString(m, cr));
-            return mDNSfalse;
-        }
-        if (cr->resrec.rrtype == kDNSType_RRSIG)
-        {
-            RDataBody2 *const rdb = (RDataBody2 *)cr->smallrdatastorage.data;
-            rdataRRSig *rrsig = &rdb->rrsig;
-            mDNSu16 tc = swap16(rrsig->typeCovered);
-            if (tc != kDNSType_NSEC && tc != kDNSType_NSEC3 && tc != kDNSType_SOA)
-            {
-                LogMsg("AddNSECSForCacheRecord:ERROR!! Adding RRSIG with Wrong type %s", CRDisplayString(m, cr));
-                return mDNSfalse;
-            }
-        }
-        else if (cr->resrec.rrtype == kDNSType_NSEC)
-        {
-            nsecs_seen = mDNStrue;
-        }
-        else if (cr->resrec.rrtype == kDNSType_NSEC3)
-        {
-            nsec3s_seen = mDNStrue;
-        }
-        LogDNSSEC("AddNSECSForCacheRecord: Found a valid record %s", CRDisplayString(m, cr));
-    }
-    if ((nsecs_seen && nsec3s_seen) || (!nsecs_seen && !nsec3s_seen))
-    {
-        LogDNSSEC("AddNSECSForCacheRecord:ERROR  nsecs_seen %d, nsec3s_seen %d", nsecs_seen, nsec3s_seen);
-        return mDNSfalse;
-    }
-    DeleteCachedNSECS(m, negcr);
-    LogDNSSEC("AddNSECSForCacheRecord: Adding NSEC Records for %s", CRDisplayString(m, negcr));
-    negcr->nsec = crlist;
-    return mDNStrue;
-}
-
-// Return the number of labels that matches starting from the right (excluding the
-// root label)
-mDNSexport int CountLabelsMatch(const domainname *const d1, const domainname *const d2)
-{
-    int count, c1, c2;
-    int match, i, skip1, skip2;
-
-    c1 = CountLabels(d1);
-    skip1 = c1 - 1;
-    c2 = CountLabels(d2);
-    skip2 = c2 - 1;
-
-    // Root label always matches. And we don't include it here to
-    // match CountLabels
-    match = 0;
-
-    // Compare as many labels as possible starting from the rightmost
-    count = c1 < c2 ? c1 : c2;
-    for (i = count; i > 0; i--)
-    {
-        const domainname *da, *db;
-
-        da = SkipLeadingLabels(d1, skip1);
-        db = SkipLeadingLabels(d2, skip2);
-        if (!SameDomainName(da, db)) return match;
-        skip1--;
-        skip2--;
-        match++;
-    }
-    return match;
-}
-
-// Empty Non-Terminal (ENT): if the qname is bigger than nsec owner's name and a
-// subdomain of the nsec's nxt field, then the qname is a empty non-terminal. For
-// example, if you are looking for (in RFC 4035 example zone) "y.w.example  A"
-// record, if it is a ENT, then it would return
-//
-// x.w.example. 3600 NSEC x.y.w.example. MX RRSIG NSEC
-//
-// This function is normally called before checking for wildcard matches. If you
-// find this NSEC, there is no need to look for a wildcard record
-// that could possibly answer the question.
-mDNSlocal mDNSBool NSECAnswersENT(const ResourceRecord *const rr, domainname *qname)
-{
-    const domainname *oname = rr->name;
-    const RDataBody2 *const rdb = (RDataBody2 *)rr->rdata->u.data;
-    const domainname *nxt = (const domainname *)&rdb->data;
-    int ret;
-    int subdomain;
-
-    // Is the owner name smaller than qname?
-    ret = DNSSECCanonicalOrder(oname, qname, mDNSNULL);
-    if (ret < 0)
-    {
-        // Is the next domain field a subdomain of qname ?
-        ret = DNSSECCanonicalOrder(nxt, qname, &subdomain);
-        if (subdomain)
-        {
-            if (ret <= 0)
-            {
-                LogMsg("NSECAnswersENT: ERROR!! DNSSECCanonicalOrder subdomain set "
-                       " qname %##s, NSEC %##s", qname->c, rr->name->c);
-            }
-            return mDNStrue;
-        }
-    }
-    return mDNSfalse;
-}
-
-mDNSlocal const domainname *NSECClosestEncloser(ResourceRecord *rr, domainname *qname)
-{
-    const domainname *oname = rr->name;
-    const RDataBody2 *const rdb = (RDataBody2 *)rr->rdata->u.data;
-    const domainname *nxt = (const domainname *)&rdb->data;
-    int match1, match2;
-
-    match1 = CountLabelsMatch(oname, qname);
-    match2 = CountLabelsMatch(nxt, qname);
-    // Return the closest i.e the one that matches more labels
-    if (match1 > match2)
-        return SkipLeadingLabels(oname, CountLabels(oname) - match1);
-    else
-        return SkipLeadingLabels(nxt, CountLabels(nxt) - match2);
-}
-
-// Assumption: NSEC has been validated outside of this function
-//
-// Does the name exist given the name and NSEC rr ?
-//
-// Returns -1 if it is an inappropriate nsec
-// Returns 1 if the name exists
-// Returns 0 if the name does not exist
-//
-mDNSlocal int NSECNameExists(mDNS *const m, ResourceRecord *rr, domainname *name, mDNSu16 qtype)
-{
-    const RDataBody2 *const rdb = (RDataBody2 *)rr->rdata->u.data;
-    const domainname *nxt = (const domainname *)&rdb->data;
-    const domainname *oname = rr->name; // owner name
-    int ret1, subdomain1;
-    int ret2, subdomain2;
-    int ret3, subdomain3;
-
-    ret1 = DNSSECCanonicalOrder(oname, name, &subdomain1);
-    if (ret1 > 0)
-    {
-        LogDNSSEC("NSECNameExists: owner name %##s is bigger than name %##s", oname->c, name->c);
-        return -1;
-    }
-
-    // Section 4.1 of draft-ietf-dnsext-dnssec-bis-updates-14:
-    //
-    //   Ancestor delegation NSEC or NSEC3 RRs MUST NOT be used to assume non-
-    //   existence of any RRs below that zone cut, which include all RRs at
-    //   that (original) owner name other than DS RRs, and all RRs below that
-    //   owner name regardless of type.
-    //
-    // This also implies that we can't use the child side NSEC for DS question.
-
-    if (!ret1)
-    {
-        mDNSBool soa = RRAssertsExistence(rr, kDNSType_SOA);
-        mDNSBool ns = RRAssertsExistence(rr, kDNSType_NS);
-
-        // We are here because the owner name is the same as "name". Make sure the
-        // NSEC has the right NS and SOA bits set.
-        if (qtype != kDNSType_DS && ns && !soa)
-        {
-            LogDNSSEC("NSECNameExists: Parent side NSEC %s can't be used for question %##s (%s)",
-                      RRDisplayString(m, rr), name->c, DNSTypeName(qtype));
-            return -1;
-        }
-        else if (qtype == kDNSType_DS && soa)
-        {
-            LogDNSSEC("NSECNameExists: Child side NSEC %s can't be used for question %##s (%s)",
-                      RRDisplayString(m, rr), name->c, DNSTypeName(qtype));
-            return -1;
-        }
-        LogDNSSEC("NSECNameExists: owner name %##s is same as name %##s", oname->c, name->c);
-        return 1;
-    }
-
-    // If the name is a.b.com and NSEC's owner name is b.com i.e., a subdomain
-    // and nsec comes from the parent (NS is set and SOA is not set), then this
-    // NSEC can't be used for names below the owner name.
-    //
-    // Similarly if DNAME is set, we can't use it here. See RFC2672-bis-dname
-    // appendix.
-    if (subdomain1 && (RRAssertsExistence(rr, kDNSType_DNAME) ||
-                       (RRAssertsNonexistence(rr, kDNSType_SOA) && RRAssertsExistence(rr, kDNSType_NS))))
-    {
-        LogDNSSEC("NSECNameExists: NSEC %s comes from the parent, can't use it here",
-                  RRDisplayString(m, rr));
-        return -1;
-    }
-
-    // At this stage, we know that name is greater than the owner name and
-    // the nsec is not from the parent side.
-    //
-    // Compare with the next field in the nsec.
-    //
-    ret2 = DNSSECCanonicalOrder(name, nxt, &subdomain2);
-
-    // Exact match with the nsec next name
-    if (!ret2)
-    {
-        LogDNSSEC("NSECNameExists: name %##s is same as nxt name %##s", name->c, nxt->c);
-        return 1;
-    }
-
-    ret3 = DNSSECCanonicalOrder(oname, nxt, &subdomain3);
-
-    if (!ret3)
-    {
-        // Pathological case of a single name in the domain. This means only the
-        // apex of the zone itself exists. Nothing below it. "subdomain2" indicates
-        // that name is a subdmain of "next" and hence below the zone.
-        if (subdomain2)
-        {
-            LogDNSSEC("NSECNameExists: owner name %##s subdomain of nxt name %##s", oname->c, nxt->c);
-            return 0;
-        }
-        else
-        {
-            LogDNSSEC("NSECNameExists: Single name in zone, owner name %##s is same as nxt name %##s", oname->c, nxt->c);
-            return -1;
-        }
-    }
-
-    if (ret3 < 0)
-    {
-        // Regular NSEC in the zone. Make sure that the "name" lies within
-        // oname and next. oname < name and name < next
-        if (ret1 < 0 && ret2 < 0)
-        {
-            LogDNSSEC("NSECNameExists: Normal NSEC name %##s lies within owner %##s and nxt name %##s",
-                      name->c, oname->c, nxt->c);
-            return 0;
-        }
-        else
-        {
-            LogDNSSEC("NSECNameExists: Normal NSEC name %##s does not lie within owner %##s and nxt name %##s",
-                      name->c, oname->c, nxt->c);
-            return -1;
-        }
-    }
-    else
-    {
-        // Last NSEC in the zone. The "next" is pointing to the apex. All names
-        // should be a subdomain of that and the name should be bigger than
-        // oname
-        if (ret1 < 0 && subdomain2)
-        {
-            LogDNSSEC("NSECNameExists: Last NSEC name %##s lies within owner %##s and nxt name %##s",
-                      name->c, oname->c, nxt->c);
-            return 0;
-        }
-        else
-        {
-            LogDNSSEC("NSECNameExists: Last NSEC name %##s does not lie within owner %##s and nxt name %##s",
-                      name->c, oname->c, nxt->c);
-            return -1;
-        }
-    }
-
-    LogDNSSEC("NSECNameExists: NSEC %s did not match any case", RRDisplayString(m, rr));
-    return -1;
-}
-
-// If the answer was result of a wildcard match, then this function proves
-// that a proper wildcard was used to answer the question and that the
-// original name does not exist
-mDNSexport void WildcardAnswerProof(mDNS *const m, DNSSECVerifier *dv)
-{
-    CacheRecord *ncr;
-    CacheRecord **rp;
-    const domainname *ce;
-    DNSQuestion q;
-    CacheRecord **nsec3 = mDNSNULL;
-
-    LogDNSSEC("WildcardAnswerProof: Question %##s (%s)", dv->origName.c, DNSTypeName(dv->origType));
-    //
-    // RFC 4035: Section 3.1.3.3
-    //
-    // 1) We used a wildcard because the qname does not exist, so verify
-    //    that the qname does not exist
-    //
-    // 2) Is the wildcard the right one ?
-    //
-    // Unfortunately, this is not well explained in that section. Refer to
-    // RFC 5155 section 7.2.6.
-
-    // Walk the list of nsecs we received and see if they prove that
-    // the name does not exist
-
-    mDNSPlatformMemZero(&q, sizeof(DNSQuestion));
-    q.ThisQInterval = -1;
-    InitializeQuestion(m, &q, dv->InterfaceID, &dv->origName, dv->origType, mDNSNULL, mDNSNULL);
-
-    ncr = NSECParentForQuestion(m, &q);
-    if (!ncr)
-    {
-        LogMsg("WildcardAnswerProof: Can't find NSEC Parent for %##s (%s)", q.qname.c, DNSTypeName(q.qtype));
-        goto error;
-    }
-    else
-    {
-        LogDNSSEC("WildcardAnswerProof: found %s", CRDisplayString(m, ncr));
-    }
-    rp = &(ncr->nsec);
-    while (*rp)
-    {
-        if ((*rp)->resrec.rrtype == kDNSType_NSEC)
-        {
-            CacheRecord *cr = *rp;
-            if (!NSECNameExists(m, &cr->resrec, &dv->origName, dv->origType))
-                break;
-        }
-        else if ((*rp)->resrec.rrtype == kDNSType_NSEC3)
-        {
-            nsec3 = rp;
-        }
-        rp=&(*rp)->next;
-    }
-    if (!(*rp))
-    {
-        mDNSBool ret = mDNSfalse;
-        if (nsec3)
-        {
-            ret = NSEC3WildcardAnswerProof(m, ncr, dv);
-        }
-        if (!ret)
-        {
-            LogDNSSEC("WildcardAnswerProof: NSEC3 wildcard proof failed for %##s (%s)", q.qname.c, DNSTypeName(q.qtype));
-            goto error;
-        }
-        rp = nsec3;
-    }
-    else
-    {
-        ce = NSECClosestEncloser(&((*rp)->resrec), &dv->origName);
-        if (!ce)
-        {
-            LogMsg("WildcardAnswerProof: ERROR!! Closest Encloser NULL for %##s (%s)", q.qname.c, DNSTypeName(q.qtype));
-            goto error;
-        }
-        if (!SameDomainName(ce, dv->wildcardName))
-        {
-            LogMsg("WildcardAnswerProof: ERROR!! Closest Encloser %##s does not match wildcard name %##s", q.qname.c, dv->wildcardName->c);
-            goto error;
-        }
-    }
-
-    VerifyNSEC(m, &((*rp)->resrec), mDNSNULL, dv, ncr, mDNSNULL);
-    return;
-error:
-    dv->DVCallback(m, dv, DNSSEC_Bogus);
-}
-
-// We have a NSEC. Need to see if it proves that NODATA exists for the given name. Note that this
-// function does not prove anything as proof may require more than one NSEC and this function
-// processes only one NSEC at a time.
-//
-// Returns mDNSfalse if the NSEC does not prove the NODATA error
-// Returns mDNStrue if the NSEC proves the NODATA error
-//
-mDNSlocal mDNSBool NSECNoDataError(mDNS *const m, ResourceRecord *rr, domainname *name, mDNSu16 qtype, domainname **wildcard)
-{
-    const domainname *oname = rr->name; // owner name
-
-    *wildcard = mDNSNULL;
-    // RFC 4035
-    //
-    // section 3.1.3.1 : Name matches. Prove that the type does not exist and also CNAME is
-    // not set as in that case CNAME should have been returned ( CNAME part is mentioned in
-    // section 4.3 of dnssec-bis-updates.) Without the CNAME check, a positive response can
-    // be converted to a NODATA/NOERROR response.
-    //
-    // section 3.1.3.4 : No exact match for the name but there is a wildcard that could match
-    // the name but not the type. There are two NSECs in this case. One of them is a wildcard
-    // NSEC and another NSEC proving that the qname does not exist. We are called with one
-    // NSEC at a time. We return what we matched and the caller should decide whether all
-    // conditions are met for the proof.
-    if (SameDomainName(oname, name))
-    {
-        mDNSBool soa = RRAssertsExistence(rr, kDNSType_SOA);
-        mDNSBool ns = RRAssertsExistence(rr, kDNSType_NS);
-        if (qtype != kDNSType_DS)
-        {
-            // For non-DS type questions, we don't want to use the parent side records to
-            // answer it
-            if (ns && !soa)
-            {
-                LogDNSSEC("NSECNoDataError: Parent side NSEC %s, can't use for child qname %##s (%s)",
-                          RRDisplayString(m, rr), name->c, DNSTypeName(qtype));
-                return mDNSfalse;
-            }
-        }
-        else
-        {
-            if (soa)
-            {
-                LogDNSSEC("NSECNoDataError: Child side NSEC %s, can't use for parent qname %##s (%s)",
-                          RRDisplayString(m, rr), name->c, DNSTypeName(qtype));
-                return mDNSfalse;
-            }
-        }
-        if (RRAssertsExistence(rr, qtype) || RRAssertsExistence(rr, kDNSType_CNAME))
-        {
-            LogMsg("NSECNoDataError: ERROR!! qtype %s exists in %s", DNSTypeName(qtype), RRDisplayString(m, rr));
-            return mDNSfalse;
-        }
-        LogDNSSEC("NSECNoDataError: qype %s does not exist in %s", DNSTypeName(qtype), RRDisplayString(m, rr));
-        return mDNStrue;
-    }
-    else
-    {
-        // Name does not exist. Before we check for a wildcard match, make sure that
-        // this is not an ENT.
-        if (NSECAnswersENT(rr, name))
-        {
-            LogDNSSEC("NSECNoDataError: name %##s exists %s", name->c, RRDisplayString(m, rr));
-            return mDNSfalse;
-        }
-
-        // Wildcard check. If this is a wildcard NSEC, then check to see if we could
-        // have answered the question using this wildcard and it should not have the
-        // "qtype" passed in with its bitmap.
-        //
-        // See RFC 4592, on how wildcards are used to synthesize answers. Find the
-        // closest encloser and the qname should be a subdomain i.e if the wildcard
-        // is *.x.example, x.example is the closest encloser and the qname should be
-        // a subdomain e.g., y.x.example or z.y.x.example and so on.
-        if (oname->c[0] == 1 && oname->c[1] == '*')
-        {
-            int s;
-            const domainname *ce = SkipLeadingLabels(oname, 1);
-
-            DNSSECCanonicalOrder(name, ce, &s);
-            if (s)
-            {
-                if (RRAssertsExistence(rr, qtype) || RRAssertsExistence(rr, kDNSType_CNAME))
-                {
-                    LogMsg("NSECNoDataError: ERROR!! qtype %s exists in wildcard %s", DNSTypeName(qtype), RRDisplayString(m, rr));
-                    return mDNSfalse;
-                }
-                if (qtype == kDNSType_DS && RRAssertsExistence(rr, kDNSType_SOA))
-                {
-                    LogDNSSEC("NSECNoDataError: Child side wildcard NSEC %s, can't use for parent qname %##s (%s)",
-                              RRDisplayString(m, rr), name->c, DNSTypeName(qtype));
-                    return mDNSfalse;
-                }
-                else if (qtype != kDNSType_DS && RRAssertsNonexistence(rr, kDNSType_SOA) &&
-                    RRAssertsExistence(rr, kDNSType_NS))
-                {
-                    // Don't use the parent side record for this
-                    LogDNSSEC("NSECNoDataError: Parent side wildcard NSEC %s, can't use for child qname %##s (%s)",
-                              RRDisplayString(m, rr), name->c, DNSTypeName(qtype));
-                    return mDNSfalse;
-                }
-                *wildcard = (domainname *)ce;
-                LogDNSSEC("NSECNoDataError: qtype %s does not exist in wildcard %s", DNSTypeName(qtype), RRDisplayString(m, rr));
-                return mDNStrue;
-            }
-        }
-        return mDNSfalse;
-    }
-}
-
-mDNSexport void NoDataNSECCallback(mDNS *const m, DNSSECVerifier *dv, DNSSECStatus status)
-{
-    RRVerifier *rv;
-    DNSSECVerifier *pdv;
-    CacheRecord *ncr;
-
-    LogDNSSEC("NoDataNSECCallback: called");
-    if (!dv->parent)
-    {
-        LogMsg("NoDataNSECCCallback: no parent DV");
-        FreeDNSSECVerifier(m, dv);
-        return;
-    }
-
-    if (dv->ac)
-    {
-        // Before we free the "dv", we need to update the
-        // parent with our AuthChain information
-        UpdateParent(dv);
-    }
-
-    pdv = dv->parent;
-
-    // We don't care about the "dv" that was allocated in VerifyNSEC
-    // as it just verifies one of the nsecs. Get the original verifier and
-    // verify the other NSEC like we did the first time.
-    dv->parent = mDNSNULL;
-    FreeDNSSECVerifier(m, dv);
-
-    if (status != DNSSEC_Secure)
-    {
-        goto error;
-    }
-
-    ncr = NSECParentForQuestion(m, &pdv->q);
-    if (!ncr)
-    {
-        LogMsg("NoDataNSECCallback: Can't find NSEC Parent for %##s (%s)", pdv->q.qname.c, DNSTypeName(pdv->q.qtype));
-        goto error;
-    }
-    rv = pdv->pendingNSEC;
-    pdv->pendingNSEC = rv->next;
-    // We might have more than one pendingNSEC in the case of NSEC3. If this is the last one,
-    // we don't need to come back here; let the regular NSECCallback call the original callback.
-    rv->next = mDNSNULL;
-    LogDNSSEC("NoDataNSECCallback: Verifying %##s (%s)", rv->name.c, DNSTypeName(rv->rrtype));
-    if (!pdv->pendingNSEC)
-        VerifyNSEC(m, mDNSNULL, rv, pdv, ncr, mDNSNULL);
-    else
-        VerifyNSEC(m, mDNSNULL, rv, pdv, ncr, NoDataNSECCallback);
-    return;
-
-error:
-    pdv->DVCallback(m, pdv, status);
-}
-
-mDNSexport void NameErrorNSECCallback(mDNS *const m, DNSSECVerifier *dv, DNSSECStatus status)
-{
-    RRVerifier *rv;
-    DNSSECVerifier *pdv;
-    CacheRecord *ncr;
-
-    LogDNSSEC("NameErrorNSECCallback: called");
-    if (!dv->parent)
-    {
-        LogMsg("NameErrorNSECCCallback: no parent DV");
-        FreeDNSSECVerifier(m, dv);
-        return;
-    }
-
-    if (dv->ac)
-    {
-        // Before we free the "dv", we need to update the
-        // parent with our AuthChain information
-        UpdateParent(dv);
-    }
-
-    pdv = dv->parent;
-    // We don't care about the "dv" that was allocated in VerifyNSEC
-    // as it just verifies one of the nsecs. Get the original verifier and
-    // verify the other NSEC like we did the first time.
-    dv->parent = mDNSNULL;
-    FreeDNSSECVerifier(m, dv);
-
-    if (status != DNSSEC_Secure)
-    {
-        goto error;
-    }
-
-    ncr = NSECParentForQuestion(m, &pdv->q);
-    if (!ncr)
-    {
-        LogMsg("NameErrorNSECCallback: Can't find NSEC Parent for %##s (%s)", pdv->q.qname.c, DNSTypeName(pdv->q.qtype));
-        goto error;
-    }
-    rv = pdv->pendingNSEC;
-    pdv->pendingNSEC = rv->next;
-    // We might have more than one pendingNSEC in the case of NSEC3. If this is the last one,
-    // we don't need to come back here; let the regular NSECCallback call the original callback.
-    rv->next = mDNSNULL;
-    LogDNSSEC("NameErrorNSECCallback: Verifying %##s (%s)", rv->name.c, DNSTypeName(rv->rrtype));
-    if (!pdv->pendingNSEC)
-        VerifyNSEC(m, mDNSNULL, rv, pdv, ncr, mDNSNULL);
-    else
-        VerifyNSEC(m, mDNSNULL, rv, pdv, ncr, NameErrorNSECCallback);
-
-    return;
-
-error:
-    pdv->DVCallback(m, pdv, status);
-}
-
-// We get a NODATA error with no records in answer section. This proves
-// that qname does not exist.
-mDNSlocal void NoDataProof(mDNS *const m, DNSSECVerifier *dv, CacheRecord *ncr)
-{
-    CacheRecord **rp;
-    domainname *wildcard = mDNSNULL;
-    const domainname *ce = mDNSNULL;
-    ResourceRecord *nsec_wild = mDNSNULL;
-    ResourceRecord *nsec_noname = mDNSNULL;
-
-    // NODATA Error could mean two things. The name exists with no type or there is a
-    // wildcard that matches the name but no type. This is done by NSECNoDataError.
-    //
-    // If it is the case of wildcard, there are two NSECs. One is the wildcard NSEC and
-    // the other NSEC to prove that there is no other closer match.
-
-    wildcard = mDNSNULL;
-    rp = &(ncr->nsec);
-    while (*rp)
-    {
-        if ((*rp)->resrec.rrtype == kDNSType_NSEC)
-        {
-            CacheRecord *cr = *rp;
-            if (NSECNoDataError(m, &cr->resrec, &dv->q.qname, dv->q.qtype, &wildcard))
-            {
-                if (wildcard)
-                {
-                    dv->flags |= WILDCARD_PROVES_NONAME_EXISTS;
-                    LogDNSSEC("NoDataProof: NSEC %s proves NODATA error for %##s (%s)",
-                              RRDisplayString(m, &(*rp)->resrec), dv->q.qname.c, DNSTypeName(dv->q.qtype));
-                }
-                else
-                {
-                    dv->flags |= NSEC_PROVES_NOTYPE_EXISTS;
-                    LogDNSSEC("NoDataProof: NSEC %s proves NOTYPE error for %##s (%s)",
-                              RRDisplayString(m, &(*rp)->resrec), dv->q.qname.c, DNSTypeName(dv->q.qtype));
-                }
-                nsec_wild = &cr->resrec;
-            }
-            if (!NSECNameExists(m, &cr->resrec, &dv->q.qname, dv->q.qtype))
-            {
-                LogDNSSEC("NoDataProof: NSEC %s proves that  name %##s (%s) does not exist",
-                          RRDisplayString(m, &(*rp)->resrec), dv->q.qname.c, DNSTypeName(dv->q.qtype));
-                // If we have a wildcard, then we should check to see if the closest
-                // encloser is the same as the wildcard.
-                ce = NSECClosestEncloser(&cr->resrec, &dv->q.qname);
-                dv->flags |= NSEC_PROVES_NONAME_EXISTS;
-                nsec_noname = &cr->resrec;
-            }
-        }
-        rp=&(*rp)->next;
-    }
-    if (!nsec_noname && !nsec_wild)
-    {
-        LogDNSSEC("NoDataProof: No valid NSECs for %##s (%s)", dv->q.qname.c, DNSTypeName(dv->q.qtype));
-        goto error;
-    }
-    // If the type exists, then we have to verify just that NSEC
-    if (!(dv->flags & NSEC_PROVES_NOTYPE_EXISTS))
-    {
-        // If we have a wildcard, then we should have a "ce" which matches the wildcard
-        // If we don't have a wildcard, then we should have proven that the name does not
-        // exist which means we would have set the "ce".
-        if (wildcard && !ce)
-        {
-            LogMsg("NoDataProof: Cannot prove that the name %##s (%s) does not exist", dv->q.qname.c, DNSTypeName(dv->q.qtype));
-            goto error;
-        }
-        if (wildcard && !SameDomainName(wildcard, ce))
-        {
-            LogMsg("NoDataProof: wildcard %##s does not match closest encloser %##s", wildcard->c, ce->c);
-            goto error;
-        }
-        // If a single NSEC can prove both, then we just have validate that one NSEC.
-        if (nsec_wild == nsec_noname)
-        {
-            nsec_noname = mDNSNULL;
-            dv->flags &= ~NSEC_PROVES_NONAME_EXISTS;
-        }
-    }
-
-    if ((dv->flags & (WILDCARD_PROVES_NONAME_EXISTS|NSEC_PROVES_NONAME_EXISTS)) ==
-        (WILDCARD_PROVES_NONAME_EXISTS|NSEC_PROVES_NONAME_EXISTS))
-    {
-        mStatus status;
-        RRVerifier *r = AllocateRRVerifier(nsec_noname, &status);
-        if (!r) goto error;
-        // First verify wildcard NSEC and then when we are done, we
-        // will verify the noname nsec
-        dv->pendingNSEC = r;
-        LogDNSSEC("NoDataProof: Verifying wild and noname %s", nsec_wild ? RRDisplayString(m, nsec_wild) : "NULL");
-        VerifyNSEC(m, nsec_wild, mDNSNULL, dv, ncr, NoDataNSECCallback);
-    }
-    else if ((dv->flags & WILDCARD_PROVES_NONAME_EXISTS) ||
-             (dv->flags & NSEC_PROVES_NOTYPE_EXISTS))
-    {
-        LogDNSSEC("NoDataProof: Verifying wild %s", nsec_wild ? RRDisplayString(m, nsec_wild) : "NULL");
-        VerifyNSEC(m, nsec_wild, mDNSNULL, dv, ncr, mDNSNULL);
-    }
-    else if (dv->flags & NSEC_PROVES_NONAME_EXISTS)
-    {
-        LogDNSSEC("NoDataProof: Verifying noname %s", nsec_noname ? RRDisplayString(m, nsec_noname) : "NULL");
-        VerifyNSEC(m, nsec_noname, mDNSNULL, dv, ncr, mDNSNULL);
-    }
-    return;
-error:
-    LogDNSSEC("NoDataProof: Error return");
-    dv->DVCallback(m, dv, DNSSEC_Bogus);
-}
-
-mDNSlocal mDNSBool NSECNoWildcard(mDNS *const m, ResourceRecord *rr, domainname *qname, mDNSu16 qtype)
-{
-    const domainname *ce;
-    domainname wild;
-
-    // If the query name is c.x.w.example and if the name does not exist, we should get
-    // get a nsec back that looks something like this:
-    //
-    //      w.example NSEC a.w.example
-    //
-    // First, we need to get the closest encloser which in this case is w.example. Wild
-    // card synthesis works by finding the closest encloser first and then look for
-    // a "*" label (assuming * label does not appear in the question). If it does not
-    // exists, it would return the NSEC at that name. And the wildcard name at the
-    // closest encloser "*.w.example" would be covered by such an NSEC. (Appending "*"
-    // makes it bigger than w.example and "* is smaller than "a" for the above NSEC)
-    //
-    ce = NSECClosestEncloser(rr, qname);
-    if (!ce) { LogMsg("NSECNoWildcard: No closest encloser for rr %s, qname %##s (%s)", qname->c, DNSTypeName(qtype)); return mDNSfalse; }
-
-    wild.c[0] = 1;
-    wild.c[1] = '*';
-    wild.c[2] = 0;
-    if (!AppendDomainName(&wild, ce))
-    {
-        LogMsg("NSECNoWildcard: ERROR!! Can't append domainname closest encloser name %##s, qname %##s (%s)", ce->c, qname->c, DNSTypeName(qtype));
-        return mDNSfalse;
-    }
-    if (NSECNameExists(m, rr, &wild, qtype) != 0)
-    {
-        LogDNSSEC("NSECNoWildcard: Wildcard name %##s exists or not valid qname %##s (%s)", wild.c, qname->c, DNSTypeName(qtype));
-        return mDNSfalse;
-    }
-    LogDNSSEC("NSECNoWildcard: Wildcard name %##s does not exist for record %s, qname %##s (%s)", wild.c,
-              RRDisplayString(m, rr), qname->c, DNSTypeName(qtype));
-    return mDNStrue;
-}
-
-// We get a NXDOMAIN error with no records in answer section. This proves
-// that qname does not exist.
-mDNSlocal void NameErrorProof(mDNS *const m, DNSSECVerifier *dv, CacheRecord *ncr)
-{
-    CacheRecord **rp;
-    ResourceRecord *nsec_wild = mDNSNULL;
-    ResourceRecord *nsec_noname = mDNSNULL;
-    mStatus status;
-
-    // NXDOMAIN Error. We need to prove that the qname does not exist and there
-    // is no wildcard that can be used to answer the question.
-
-    rp = &(ncr->nsec);
-    while (*rp)
-    {
-        if ((*rp)->resrec.rrtype == kDNSType_NSEC)
-        {
-            CacheRecord *cr = *rp;
-            if (!NSECNameExists(m, &cr->resrec, &dv->q.qname, dv->q.qtype))
-            {
-                LogDNSSEC("NameErrorProof: NSEC %s proves name does not exist for %##s (%s)",
-                          RRDisplayString(m, &(*rp)->resrec), dv->q.qname.c, DNSTypeName(dv->q.qtype));
-                // If we have a wildcard, then we should check to see if the closest
-                // encloser is the same as the wildcard.
-                dv->flags |= NSEC_PROVES_NONAME_EXISTS;
-                nsec_noname = &cr->resrec;
-            }
-            if (NSECNoWildcard(m, &cr->resrec, &dv->q.qname, dv->q.qtype))
-            {
-                dv->flags |= WILDCARD_PROVES_NONAME_EXISTS;
-                nsec_wild = &cr->resrec;
-                LogDNSSEC("NameErrorProof: NSEC %s proves wildcard cannot answer question for %##s (%s)",
-                          RRDisplayString(m, &(*rp)->resrec), dv->q.qname.c, DNSTypeName(dv->q.qtype));
-            }
-        }
-        rp=&(*rp)->next;
-    }
-    if (!nsec_noname || !nsec_wild)
-    {
-        LogMsg("NameErrorProof: Proof failed for %##s (%s) noname %p, wild %p", dv->q.qname.c, DNSTypeName(dv->q.qtype), nsec_noname, nsec_wild);
-        goto error;
-    }
-
-    // First verify wildcard NSEC and then when we are done, we will verify the noname nsec.
-    // Sometimes a single NSEC can prove both that the "qname" does not exist and a wildcard
-    // could not have produced qname. These are a few examples where this can happen.
-    //
-    // 1. If the zone is example.com and you look up *.example.com and if there are no wildcards,
-    //    you will get a NSEC back "example.com NSEC a.example.com". This proves that both the
-    //    name does not exist and *.example.com also does not exist
-    //
-    // 2. If the zone is example.com and it has a record like this:
-    //
-    //                                 example.com NSEC d.example.com
-    //
-    // any name you lookup in between like a.example.com,b.example.com etc. you will get a single
-    // NSEC back. In that case we just have to verify only once.
-    //
-    if (nsec_wild != nsec_noname)
-    {
-        RRVerifier *r = AllocateRRVerifier(nsec_noname, &status);
-        if (!r) goto error;
-        dv->pendingNSEC = r;
-        LogDNSSEC("NoDataProof: Verifying wild %s", RRDisplayString(m, nsec_wild));
-        VerifyNSEC(m, nsec_wild, mDNSNULL, dv, ncr, NameErrorNSECCallback);
-    }
-    else
-    {
-        LogDNSSEC("NoDataProof: Verifying only one %s", RRDisplayString(m, nsec_wild));
-        VerifyNSEC(m, nsec_wild, mDNSNULL, dv, ncr, mDNSNULL);
-    }
-    return;
-error:
-    dv->DVCallback(m, dv, DNSSEC_Bogus);
-}
-
-mDNSexport CacheRecord *NSECRecordIsDelegation(mDNS *const m, domainname *name, mDNSu16 qtype)
-{
-    CacheGroup *cg;
-    CacheRecord *cr;
-    mDNSu32 namehash;
-
-    namehash = DomainNameHashValue(name);
-
-    cg = CacheGroupForName(m, namehash, name);
-    if (!cg)
-    {
-        LogDNSSEC("NSECRecordForName: cg NULL for %##s", name);
-        return mDNSNULL;
-    }
-    for (cr = cg->members; cr; cr = cr->next)
-    {
-        if (cr->resrec.RecordType == kDNSRecordTypePacketNegative && cr->resrec.rrtype == qtype)
-        {
-            CacheRecord *ncr;
-            for (ncr = cr->nsec; ncr; ncr = ncr->next)
-            {
-                if (ncr->resrec.rrtype == kDNSType_NSEC &&
-                    SameDomainName(ncr->resrec.name, name))
-                {
-                    // See the Insecure Delegation Proof section in dnssec-bis: DS bit and SOA bit
-                    // should be absent
-                    if (RRAssertsExistence(&ncr->resrec, kDNSType_SOA) ||
-                        RRAssertsExistence(&ncr->resrec, kDNSType_DS))
-                    {
-                        LogDNSSEC("NSECRecordForName: found record %s for %##s (%s), but DS or SOA bit set", CRDisplayString(m, ncr), name,
-                            DNSTypeName(qtype));
-                        return mDNSNULL;
-                    }
-                    // Section 2.3 of RFC 4035 states that:
-                    //
-                    // Each owner name in the zone that has authoritative data or a delegation point NS RRset MUST
-                    // have an NSEC resource record. 
-                    //
-                    // So, if we have an NSEC record matching the question name with the NS bit set,
-                    // then this is a delegation.
-                    //
-                    if (RRAssertsExistence(&ncr->resrec, kDNSType_NS))
-                    {
-                        LogDNSSEC("NSECRecordForName: found record %s for %##s (%s)", CRDisplayString(m, ncr), name, DNSTypeName(qtype));
-                        return ncr;
-                    }
-                    else
-                    {
-                        LogDNSSEC("NSECRecordForName: found record %s for %##s (%s), but NS bit is not set", CRDisplayString(m, ncr), name,
-                            DNSTypeName(qtype));
-                        return mDNSNULL;
-                    }
-                }
-            }
-        }
-    }
-    return mDNSNULL;
-}
-
-mDNSlocal void StartInsecureProof(mDNS * const m, DNSSECVerifier *dv)
-{
-    domainname trigger;
-    DNSSECVerifier *prevdv = mDNSNULL;
-
-    // Remember the name that triggered the insecure proof
-    AssignDomainName(&trigger, &dv->q.qname);
-    while (dv->parent)
-    {
-        prevdv = dv;
-        dv = dv->parent;
-    }
-    if (prevdv)
-    {
-        prevdv->parent = mDNSNULL;
-        FreeDNSSECVerifier(m, prevdv);
-    }
-    // For Optional DNSSEC, we are opportunistically verifying dnssec. We don't care
-    // if something results in bogus as we still want to deliver results to the
-    // application e.g., CNAME processing results in bogus because the path is broken,
-    // but we still want to follow CNAMEs so that we can deliver the final results to
-    // the application.
-    if (dv->ValidationRequired == DNSSEC_VALIDATION_SECURE_OPTIONAL)
-    {
-        LogDNSSEC("StartInsecureProof: Aborting insecure proof for %##s (%s)", dv->q.qname.c, DNSTypeName(dv->q.qtype));
-        dv->DVCallback(m, dv, DNSSEC_Bogus);
-        return;
-    }
-
-    LogDNSSEC("StartInsecureProof for %##s (%s)", dv->q.qname.c, DNSTypeName(dv->q.qtype));
-    // Don't start the insecure proof again after we finish the one that we start here by
-    // setting InsecureProofDone.
-    dv->InsecureProofDone = 1;
-    ProveInsecure(m, dv, mDNSNULL, &trigger);
-    return;
-}
-
-mDNSexport void ValidateWithNSECS(mDNS *const m, DNSSECVerifier *dv, CacheRecord *cr)
-{
-    LogDNSSEC("ValidateWithNSECS: called for %s", CRDisplayString(m, cr));
-
-    // If we are encountering a break in the chain of trust i.e., NSEC/NSEC3s for
-    // DS query, then do the insecure proof. This is important because if we
-    // validate these NSECs normally and prove that they are "secure", we will
-    // end up delivering the secure result to the original question where as
-    // these NSEC/NSEC3s actually prove that DS does not exist and hence insecure.
-    //
-    // This break in the chain can happen after we have partially validated the
-    // path (dv->ac is non-NULL) or the first time (dv->ac is NULL) after we
-    // fetched the DNSKEY (dv->key is non-NULL). We don't want to do this
-    // if we have just started the non-existence proof (dv->key is NULL) as
-    // it does not indicate a break in the chain of trust.
-    //
-    // If we are already doing a insecurity proof, don't start another one. In
-    // the case of NSECs, it is possible that insecurity proof starts and it
-    // gets NSECs and as part of validating that we receive more NSECS in which
-    // case we don't want to start another insecurity proof.
-    if (dv->ValidationRequired != DNSSEC_VALIDATION_INSECURE &&
-        (!dv->parent || dv->parent->ValidationRequired != DNSSEC_VALIDATION_INSECURE))
-    {
-         if ((dv->ac && dv->q.qtype == kDNSType_DS) ||
-             (!dv->ac && dv->key && dv->q.qtype == kDNSType_DS))
-        {
-            LogDNSSEC("ValidateWithNSECS: Starting insecure proof: name %##s ac %p, key %p, parent %p", dv->q.qname.c,
-                dv->ac, dv->key, dv->parent);
-            StartInsecureProof(m, dv);
-            return;
-        }
-    }
-    // "parent" is set when we are validating a NSEC and we should not be here in
-    // the normal case when parent is set. For example, we are looking up the A
-    // record for www.example.com and following can happen.
-    //
-    // a) Record does not exist and we get a NSEC
-    // b) While validating (a), we get an NSEC for the first DS record that we look up
-    // c) Record exists but we get NSECs for the first DS record
-    // d) We are able to partially validate (a) or (b), but we get NSECs somewhere in
-    //    the chain
-    //
-    // For (a), parent is not set as we are not validating the NSEC yet. Hence we would
-    // start the validation now.
-    //
-    // For (b), the parent is set, but should be caught by the above "if" block because we 
-    // should have gotten the DNSKEY at least. In the case of nested insecurity proof,
-    // we would end up here and fail with bogus.
-    //
-    // For (c), the parent is not set and should be caught by the above "if" block because we 
-    // should have gotten the DNSKEY at least.
-    //
-    // For (d), the above "if" block would catch it as "dv->ac" is non-NULL.
-    // 
-    // Hence, we should not come here in the normal case. Possible pathological cases are:
-    // Insecure proof getting NSECs while validating NSECs, getting NSECs for DNSKEY for (c)
-    // above etc.
-    if (dv->parent)
-    {
-        LogDNSSEC("ValidateWithNSECS: dv parent set for %##s (%s)", dv->q.qname.c, DNSTypeName(dv->q.qtype));
-        dv->DVCallback(m, dv, DNSSEC_Bogus);
-        return;
-    }
-    if (cr->resrec.RecordType == kDNSRecordTypePacketNegative)
-    {
-        mDNSu8 rcode;
-        CacheRecord *neg = cr->nsec;
-        mDNSBool nsecs_seen = mDNSfalse;
-
-        while (neg)
-        {
-            // The list can only have NSEC or NSEC3s. This was checked when we added the
-            // NSECs to the cache record.
-            if (neg->resrec.rrtype == kDNSType_NSEC)
-                nsecs_seen = mDNStrue;
-            LogDNSSEC("ValidateWithNSECS: NSECCached Record %s", CRDisplayString(m, neg));
-            neg = neg->next;
-        }
-
-        rcode = (mDNSu8)(cr->responseFlags.b[1] & kDNSFlag1_RC_Mask);
-        if (rcode == kDNSFlag1_RC_NoErr)
-        {
-            if (nsecs_seen)
-                NoDataProof(m, dv, cr);
-            else
-                NSEC3NoDataProof(m, dv, cr);
-        }
-        else if (rcode == kDNSFlag1_RC_NXDomain)
-        {
-            if (nsecs_seen)
-                NameErrorProof(m, dv, cr);
-            else
-                NSEC3NameErrorProof(m, dv, cr);
-        }
-        else
-        {
-            LogDNSSEC("ValidateWithNSECS: Rcode %d invalid", rcode);
-            dv->DVCallback(m, dv, DNSSEC_Bogus);
-        }
-    }
-    else
-    {
-        LogMsg("ValidateWithNSECS: Not a valid cache record %s for NSEC proofs", CRDisplayString(m, cr));
-        dv->DVCallback(m, dv, DNSSEC_Bogus);
-        return;
-    }
-}
-
-#else // !DNSSEC_DISABLED
-
-mDNSexport mDNSBool AddNSECSForCacheRecord(mDNS *const m, CacheRecord *crlist, CacheRecord *negcr, mDNSu8 rcode)
-{
-    (void)m;
-    (void)crlist;
-    (void)negcr;
-    (void)rcode;
-
-    return mDNSfalse;
-}
-
-#endif // !DNSSEC_DISABLED
diff --git a/mDNSCore/nsec.h b/mDNSCore/nsec.h
deleted file mode 100644 (file)
index 198f57d..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/* -*- Mode: C; tab-width: 4 -*-
- *
- * Copyright (c) 2011-2012 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __NSEC_H
-#define __NSEC_H
-
-#include "dnssec.h"
-
-extern mDNSBool AddNSECSForCacheRecord(mDNS *const m, CacheRecord *crlist, CacheRecord *negcr, mDNSu8 rcode);
-extern void WildcardAnswerProof(mDNS *const m, DNSSECVerifier *dv);
-extern void ValidateWithNSECS(mDNS *const m, DNSSECVerifier *dv, CacheRecord *rr);
-extern mDNSBool NSECAnswersDS(mDNS *const m, ResourceRecord *rr, DNSQuestion *q);
-extern int CountLabelsMatch(const domainname *const d1, const domainname *const d2);
-extern void NameErrorNSECCallback(mDNS *const m, DNSSECVerifier *dv, DNSSECStatus status);
-extern void VerifyNSEC(mDNS *const m, ResourceRecord *rr, RRVerifier *rv, DNSSECVerifier *pdv, CacheRecord *ncr,
-       DNSSECVerifierCallback callback);
-extern CacheRecord *NSECRecordIsDelegation(mDNS *const m, domainname *name, mDNSu16 qtype);
-extern void NoDataNSECCallback(mDNS *const m, DNSSECVerifier *dv, DNSSECStatus status);
-
-#endif // __NSEC_H
diff --git a/mDNSCore/nsec3.c b/mDNSCore/nsec3.c
deleted file mode 100644 (file)
index 4bacd52..0000000
+++ /dev/null
@@ -1,823 +0,0 @@
-/* -*- Mode: C; tab-width: 4 -*-
- *
- * Copyright (c) 2011-2018 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// ***************************************************************************
-// nsec3.c: This file contains support functions to validate NSEC3 records for
-// NODATA and NXDOMAIN error.
-// ***************************************************************************
-
-#include "mDNSEmbeddedAPI.h"
-#include "DNSCommon.h"
-#include "CryptoAlg.h"
-#include "nsec3.h"
-#include "nsec.h"
-
-// Base32 encoding takes 5 bytes of the input and encodes as 8 bytes of output.
-// For example, SHA-1 hash of 20 bytes will be encoded as 20/5 * 8 = 32 base32
-// bytes. For a max domain name size of 255 bytes of base32 encoding : (255/8)*5
-// is the max hash length possible.
-#define NSEC3_MAX_HASH_LEN  155
-// In NSEC3, the names are hashed and stored in the first label and hence cannot exceed label
-// size.
-#define NSEC3_MAX_B32_LEN   MAX_DOMAIN_LABEL
-
-// Define DNSSEC_DISABLED to remove all the DNSSEC functionality
-// and use the stub functions implemented later in this file.
-
-#ifndef DNSSEC_DISABLED
-
-typedef enum 
-{
-    NSEC3ClosestEncloser,
-    NSEC3Covers,
-    NSEC3CEProof
-} NSEC3FindValues;
-
-//#define NSEC3_DEBUG 1
-
-#if NSEC3_DEBUG
-mDNSlocal void PrintHash(mDNSu8 *digest, int digestlen, char *buffer, int buflen)
-{
-    int length = 0;
-    for (int j = 0; j < digestlen; j++)
-    {
-        length += mDNS_snprintf(buffer+length, buflen-length-1, "%x", digest[j]);
-    }
-}
-#endif
-
-mDNSlocal mDNSBool NSEC3OptOut(CacheRecord *cr)
-{
-    const RDataBody2 *const rdb = (RDataBody2 *)cr->resrec.rdata->u.data;
-    rdataNSEC3 *nsec3 = (rdataNSEC3 *)rdb->data;
-    return (nsec3->flags & NSEC3_FLAGS_OPTOUT);
-}
-
-mDNSlocal int NSEC3SameName(const mDNSu8 *name, int namelen, const mDNSu8 *nsecName, int nsecLen)
-{
-    int i;
-
-    // Note: With NSEC3, the lengths should always be same. 
-    if (namelen != nsecLen)
-    {
-        LogMsg("NSEC3SameName: ERROR!! namelen %d, nsecLen %d", namelen, nsecLen);
-        return ((namelen < nsecLen) ? -1 : 1);
-    }
-
-    for (i = 0; i < namelen; i++)
-    {
-        mDNSu8 ac = *name++;
-        mDNSu8 bc = *nsecName++;
-        if (mDNSIsUpperCase(ac)) ac += 'a' - 'A';
-        if (mDNSIsUpperCase(bc)) bc += 'a' - 'A';
-        if (ac != bc)
-        {
-            verbosedebugf("NSEC3SameName: returning ac %c, bc %c", ac, bc);
-            return ((ac < bc) ? -1 : 1);
-        }
-    }
-    return 0;
-}
-
-// Does the NSEC3 in "ncr" covers the "name" ?
-// hashName is hash of the "name" and b32Name is the base32 encoded equivalent.
-mDNSlocal mDNSBool NSEC3CoversName(mDNS *const m, CacheRecord *ncr, const mDNSu8 *hashName, int hashLen, const mDNSu8 *b32Name,
-       int b32len)
-{
-    mDNSu8 *nxtName;
-    int nxtLength;
-    int ret, ret1, ret2;
-    const mDNSu8 b32nxtname[NSEC3_MAX_B32_LEN+1];
-    int b32nxtlen;
-
-    NSEC3Parse(&ncr->resrec, mDNSNULL, &nxtLength, &nxtName, mDNSNULL, mDNSNULL);
-
-    if (nxtLength != hashLen || ncr->resrec.name->c[0] != b32len)
-        return mDNSfalse;
-
-    // Compare the owner names and the "nxt" names.
-    //
-    // Owner name is base32 encoded and hence use the base32 encoded name b32name.
-    // nxt name is binary and hence use the binary value in hashName. 
-    ret1 = NSEC3SameName(&ncr->resrec.name->c[1], ncr->resrec.name->c[0], b32Name, b32len);
-    ret2 = DNSMemCmp(nxtName, hashName, hashLen);
-
-#if NSEC3_DEBUG
-    {
-        char nxtbuf1[50];
-        char nxtbuf2[50];
-
-        PrintHash(nxtName, nxtLength, nxtbuf1, sizeof(nxtbuf1));
-        PrintHash((mDNSu8 *)hashName, hashLen, nxtbuf2, sizeof(nxtbuf2));
-        LogMsg("NSEC3CoversName: Owner name %s, name %s", &ncr->resrec.name->c[1], b32Name);
-        LogMsg("NSEC3CoversName: Nxt hash name %s, name %s", nxtbuf1, nxtbuf2);
-    }
-#endif
-
-    // "name" is greater than the owner name and smaller than nxtName. This also implies
-       // that nxtName > owner name implying that it is normal NSEC3.
-    if (ret1 < 0 && ret2 > 0)
-    {
-        LogDNSSEC("NSEC3CoversName: NSEC3 %s covers %s (Normal)", CRDisplayString(m, ncr), b32Name);
-        return mDNStrue;
-    }
-    // Need to compare the owner name and "nxt" to see if this is the last
-    // NSEC3 in the zone. Only the owner name is in base32 and hence we need to
-    // convert the nxtName to base32.
-    b32nxtlen = baseEncode((char *)b32nxtname, sizeof(b32nxtname), nxtName, nxtLength, ENC_BASE32);
-    if (!b32nxtlen)
-    {
-        LogDNSSEC("NSEC3CoversName: baseEncode of nxtName of %s failed", CRDisplayString(m, ncr));
-        return mDNSfalse;
-    }
-    if (b32len != b32nxtlen)
-    {
-        LogDNSSEC("NSEC3CoversName: baseEncode of nxtName for %s resulted in wrong length b32nxtlen %d, b32len %d",
-            CRDisplayString(m, ncr), b32len, b32nxtlen);
-        return mDNSfalse;
-    }
-    LogDNSSEC("NSEC3CoversName: Owner name %s, b32nxtname %s, ret1 %d, ret2 %d", &ncr->resrec.name->c[1], b32nxtname, ret1, ret2);
-
-    // If it is the last NSEC3 in the zone nxt < "name" and NSEC3SameName returns -1.
-    //
-    // - ret1 < 0 means "name > owner"
-    // - ret2 > 0 means "name < nxt"
-    // 
-    // Note: We also handle the case of only NSEC3 in the zone where NSEC3SameName returns zero.
-    ret = NSEC3SameName(b32nxtname, b32nxtlen, &ncr->resrec.name->c[1], ncr->resrec.name->c[0]);
-    if (ret <= 0 &&
-        (ret1 < 0 || ret2 > 0))
-    {
-        LogDNSSEC("NSEC3CoversName: NSEC3 %s covers %s (Last), ret1 %d, ret2 %d", CRDisplayString(m, ncr), b32Name, ret1, ret2);
-        return mDNStrue;
-    }
-
-    return mDNSfalse;
-}
-
-mDNSlocal const mDNSu8 *NSEC3HashName(const domainname *name, rdataNSEC3 *nsec3, const mDNSu8 hash[NSEC3_MAX_HASH_LEN], int *dlen)
-{
-    AlgContext *ctx;
-    unsigned int i;
-    unsigned int iterations;
-    domainname lname;
-    mDNSu8 *p = (mDNSu8 *)&nsec3->salt;
-    const mDNSu8 *digest;
-    int digestlen;
-    mDNSBool first = mDNStrue;
-
-    if (DNSNameToLowerCase((domainname *)name, &lname) != mStatus_NoError)
-    {
-        LogMsg("NSEC3HashName: ERROR!! DNSNameToLowerCase failed");
-        return mDNSNULL;
-    }
-
-    digest = lname.c;
-    digestlen = DomainNameLength(&lname);
-
-    // Note that it is "i <=". The first iteration is for digesting the name and salt.
-    // The iteration count does not include that.
-    iterations = swap16(nsec3->iterations);
-    for (i = 0; i <= iterations; i++)
-    {
-        ctx = AlgCreate(DIGEST_ALG, nsec3->alg);
-        if (!ctx)
-        {
-            LogMsg("NSEC3HashName: ERROR!! Cannot allocate context");
-            return mDNSNULL;
-        }
-
-        AlgAdd(ctx, digest, digestlen);
-        if (nsec3->saltLength)
-            AlgAdd(ctx, p, nsec3->saltLength);
-        if (first)
-        {
-            first = mDNSfalse;
-            digest = hash;
-            digestlen = AlgLength(ctx);
-        }
-        AlgFinal(ctx, (void *)digest, digestlen);
-        AlgDestroy(ctx);
-    }
-    *dlen = digestlen;
-    return digest;
-}
-
-// This function can be called with NSEC3ClosestEncloser, NSEC3Covers and NSEC3CEProof
-//
-// Passing in NSEC3ClosestEncloser means "find an exact match for the origName".
-// Passing in NSEC3Covers means "find an NSEC3 that covers the origName".
-//
-// i.e., in both cases the nsec3 records are iterated to find the best match and returned.
-// With NSEC3ClosestEncloser, as we are just looking for a name match, extra checks for
-// the types being present or absent will not be checked.
-//
-// If NSEC3CEProof is passed, the name is tried as such first by iterating through all NSEC3s
-// finding a ClosestEncloser or CloserEncloser and then one label skipped from the left and
-// retried again till both the closest and closer encloser is found.
-//
-// ncr is the negative cache record that has the NSEC3 chain
-// origName is the name for which we are trying to find the ClosestEncloser etc.
-// closestEncloser and closerEncloser are the return values of the function
-// ce is the closest encloser that will be returned if we find one
-mDNSlocal mDNSBool NSEC3Find(mDNS *const m, NSEC3FindValues val, CacheRecord *ncr, domainname *origName, CacheRecord **closestEncloser,
-       CacheRecord **closerEncloser, const domainname **ce, mDNSu16 qtype)
-{
-    int i;
-    int labelCount = CountLabels(origName);
-    CacheRecord *cr;
-    rdataNSEC3 *nsec3;
-
-    (void) qtype; // unused
-    // Pick the first NSEC for the iterations, salt etc.
-    for (cr = ncr->nsec; cr; cr = cr->next)
-    {
-        if (cr->resrec.rrtype == kDNSType_NSEC3)
-        {
-            const RDataBody2 *const rdb = (RDataBody2 *)cr->resrec.rdata->u.data;
-            nsec3 = (rdataNSEC3 *)rdb->data;
-            break;
-        }
-    }
-    if (!cr)
-    {
-        LogMsg("NSEC3Find: cr NULL");
-        return mDNSfalse;
-    }
-
-    // Note: The steps defined in this function are for "NSEC3CEProof". As part of NSEC3CEProof,
-    // we need to find both the closestEncloser and closerEncloser which can also be found
-    // by passing NSEC3ClosestEncloser and NSEC3Covers respectively.
-    //
-    // Section 8.3 of RFC 5155.
-    // 1.  Set SNAME=QNAME.  Clear the flag.
-    //
-    // closerEncloser is the "flag". "name" below is SNAME.
-
-    if (closestEncloser)
-    {
-        *ce = mDNSNULL;
-        *closestEncloser = mDNSNULL;
-    }
-    if (closerEncloser)
-        *closerEncloser = mDNSNULL;
-
-    // If we are looking for a closestEncloser or a covering NSEC3, we don't have
-    // to truncate the name. For the give name, try to find the closest or closer
-    // encloser.
-    if (val != NSEC3CEProof)
-    {
-        labelCount = 0;
-    }
-
-    for (i = 0; i < labelCount + 1; i++)
-    { 
-        int hlen;
-        const mDNSu8 hashName[NSEC3_MAX_HASH_LEN];
-        const domainname *name;
-        const mDNSu8 b32Name[NSEC3_MAX_B32_LEN+1];
-        int b32len;
-
-        name = SkipLeadingLabels(origName, i);
-        if (!NSEC3HashName(name, nsec3, hashName, &hlen))
-        {
-            LogMsg("NSEC3Find: NSEC3HashName failed for %##s", name->c);
-            continue;
-        }
-
-        b32len = baseEncode((char *)b32Name, sizeof(b32Name), (mDNSu8 *)hashName, hlen, ENC_BASE32);
-        if (!b32len)
-        {
-            LogMsg("NSEC3Find: baseEncode of name %##s failed", name->c);
-            continue;
-        }
-
-
-        for (cr = ncr->nsec; cr; cr = cr->next)
-        {
-            const domainname *nsecZone;
-            int result, subdomain;
-
-            if (cr->resrec.rrtype != kDNSType_NSEC3)
-                continue;
-
-            nsecZone = SkipLeadingLabels(cr->resrec.name, 1);
-            if (!nsecZone)
-            {
-                LogMsg("NSEC3Find: SkipLeadingLabel failed for %s, current name %##s",
-                    CRDisplayString(m, cr), name->c);
-                continue;
-            }
-
-            // NSEC3 owner names are formed by hashing the owner name and then appending
-            // the zone name to it. If we skip the first label, the rest should be
-            // the zone name. See whether it is the subdomain of the name we are looking
-            // for. 
-            result = DNSSECCanonicalOrder(origName, nsecZone, &subdomain);
-            
-            // The check can't be a strict subdomain check. When NSEC3ClosestEncloser is
-            // passed in, there can be an exact match. If it is a subdomain or an exact
-            // match, we should continue with the proof.
-            if (!(subdomain || !result))
-            {
-                LogMsg("NSEC3Find: NSEC3 %s not a subdomain of %##s, result %d", CRDisplayString(m, cr),
-                    origName->c, result);
-                continue;
-            }
-
-            // 2.1) If there is no NSEC3 RR in the response that matches SNAME
-            // (i.e., an NSEC3 RR whose owner name is the same as the hash of
-            // SNAME, prepended as a single label to the zone name), clear
-            // the flag.
-            //
-            // Note: We don't try to determine the actual zone name. We know that
-            // the labels following the hash (nsecZone) is the ancestor and we don't
-            // know where the zone cut is. Hence, we verify just the hash to be
-            // the same.
-
-            if (val == NSEC3ClosestEncloser || val == NSEC3CEProof)
-            {
-                if (!NSEC3SameName(&cr->resrec.name->c[1], cr->resrec.name->c[0], (const mDNSu8 *)b32Name, b32len))
-                {
-                    int bmaplen;
-                    mDNSu8 *bmap;
-
-                    // For NSEC3ClosestEncloser, we are finding an exact match and
-                    // "type" specific checks should be done by the caller.
-                    if (val != NSEC3ClosestEncloser)
-                    {
-                        // DNAME bit must not be set and NS bit may be set only if SOA bit is set
-                        NSEC3Parse(&cr->resrec, mDNSNULL, mDNSNULL, mDNSNULL, &bmaplen, &bmap);
-                        if (BitmapTypeCheck(bmap, bmaplen, kDNSType_DNAME))
-                        {
-                            LogDNSSEC("NSEC3Find: DNAME bit set in %s, ignoring", CRDisplayString(m, cr));
-                            return mDNSfalse;
-                        }
-                        // This is the closest encloser and should come from the right zone.
-                        if (BitmapTypeCheck(bmap, bmaplen, kDNSType_NS) &&
-                            !BitmapTypeCheck(bmap, bmaplen, kDNSType_SOA))
-                        {
-                            LogDNSSEC("NSEC3Find: NS bit set without SOA bit in %s, ignoring", CRDisplayString(m, cr));
-                            return mDNSfalse;
-                        }
-                    }
-
-                    LogDNSSEC("NSEC3Find: ClosestEncloser %s found for name %##s", CRDisplayString(m, cr), name->c);
-                    if (closestEncloser)
-                    {
-                        *ce = name;
-                        *closestEncloser = cr;
-                    }
-                    if (val == NSEC3ClosestEncloser)
-                        return mDNStrue;
-                    else
-                        break;
-                }
-            }
-
-            if ((val == NSEC3Covers || val == NSEC3CEProof) && (!closerEncloser || !(*closerEncloser)))
-            {
-                if (NSEC3CoversName(m, cr, hashName, hlen, b32Name, b32len))
-                {
-                    // 2.2) If there is an NSEC3 RR in the response that covers SNAME, set the flag.
-                    if (closerEncloser)
-                        *closerEncloser = cr;
-                    if (val == NSEC3Covers)
-                        return mDNStrue;
-                    else
-                        break;
-                }
-            }
-        }
-        // 2.3) If there is a matching NSEC3 RR in the response and the flag
-        // was set, then the proof is complete, and SNAME is the closest
-        // encloser.
-        if (val == NSEC3CEProof && closestEncloser && *closestEncloser)
-        {
-            if (closerEncloser && *closerEncloser)
-            {
-                LogDNSSEC("NSEC3Find: Found closest and closer encloser");
-                return mDNStrue;
-            }
-            else
-            {
-                // 2.4) If there is a matching NSEC3 RR in the response, but the flag
-                // is not set, then the response is bogus.
-                //
-                // Note: We don't have to wait till we finish trying all the names. If the matchName
-                // happens, we found the closest encloser which means we should have found the closer
-                // encloser before.
-
-                LogDNSSEC("NSEC3Find: Found closest, but not closer encloser");
-                return mDNSfalse;
-            }
-        }
-        // 3.  Truncate SNAME by one label from the left, go to step 2.
-    }
-    LogDNSSEC("NSEC3Find: Cannot find name %##s (%s)", origName->c, DNSTypeName(qtype));
-    return mDNSfalse;
-}
-
-mDNSlocal mDNSBool NSEC3ClosestEncloserProof(mDNS *const m, CacheRecord *ncr, domainname *name, CacheRecord **closestEncloser, CacheRecord **closerEncloser,
-       const domainname **ce, mDNSu16 qtype)
-{
-    if (!NSEC3Find(m, NSEC3CEProof, ncr, name, closestEncloser, closerEncloser, ce, qtype))
-    {
-        LogDNSSEC("NSEC3ClosestEncloserProof: ERROR!! Cannot do closest encloser proof");
-        return mDNSfalse;
-    }
-
-    // Note: It is possible that closestEncloser and closerEncloser are the same.
-    if (!closestEncloser || !closerEncloser || !ce)
-    {
-        LogMsg("NSEC3ClosestEncloserProof: ClosestEncloser %p or CloserEncloser %p ce %p, something is NULL", closestEncloser, closerEncloser, ce);
-        return mDNSfalse;
-    }
-
-    // If the name exists, we should not have gotten the name error
-    if (SameDomainName((*ce), name))
-    {
-        LogMsg("NSEC3ClosestEncloserProof: ClosestEncloser %s same as origName %##s", CRDisplayString(m, *closestEncloser),
-            (*ce)->c);
-        return mDNSfalse;
-    }
-    return mDNStrue;
-}
-
-mDNSlocal mDNSBool VerifyNSEC3(mDNS *const m, DNSSECVerifier *dv, CacheRecord *ncr, CacheRecord *closestEncloser,
-    CacheRecord *closerEncloser, CacheRecord *wildcard, DNSSECVerifierCallback callback)
-{
-    mStatus status;
-    RRVerifier *r;
-
-    // We have three NSEC3s. If any of two are same, we should just prove one of them.
-    // This is just not an optimization; DNSSECNegativeValidationCB does not handle
-    // identical NSEC3s very well.
-
-    if (closestEncloser == closerEncloser)
-        closestEncloser = mDNSNULL;
-    if (closerEncloser == wildcard)
-        closerEncloser = mDNSNULL;
-    if (closestEncloser == wildcard)
-        closestEncloser = mDNSNULL;
-
-    dv->pendingNSEC = mDNSNULL;
-    if (closestEncloser)
-    {
-        r = AllocateRRVerifier(&closestEncloser->resrec, &status);
-        if (!r)
-            return mDNSfalse;
-        r->next = dv->pendingNSEC;
-        dv->pendingNSEC = r;
-    }
-    if (closerEncloser)
-    {
-        r = AllocateRRVerifier(&closerEncloser->resrec, &status);
-        if (!r)
-            return mDNSfalse;
-        r->next = dv->pendingNSEC;
-        dv->pendingNSEC = r;
-    }
-    if (wildcard)
-    {
-        r = AllocateRRVerifier(&wildcard->resrec, &status);
-        if (!r)
-            return mDNSfalse;
-        r->next = dv->pendingNSEC;
-        dv->pendingNSEC = r;
-    }
-    if (!dv->pendingNSEC)
-    {
-        LogMsg("VerifyNSEC3: ERROR!! pending NSEC null");
-        return mDNSfalse;
-    }
-    r = dv->pendingNSEC;
-    dv->pendingNSEC = r->next;
-    r->next = mDNSNULL;
-
-    LogDNSSEC("VerifyNSEC3: Verifying %##s (%s)", r->name.c, DNSTypeName(r->rrtype));
-    if (!dv->pendingNSEC)
-        VerifyNSEC(m, mDNSNULL, r, dv, ncr, mDNSNULL);
-    else
-        VerifyNSEC(m, mDNSNULL, r, dv, ncr, callback);
-    return mDNStrue;
-}
-
-mDNSexport void NSEC3NameErrorProof(mDNS *const m, DNSSECVerifier *dv, CacheRecord *ncr)
-{
-    CacheRecord *closerEncloser;
-    CacheRecord *closestEncloser;
-    CacheRecord *wildcard;
-    const domainname *ce = mDNSNULL;
-    domainname wild;
-
-    if (!NSEC3ClosestEncloserProof(m, ncr, &dv->q.qname, &closestEncloser, &closerEncloser, &ce, dv->q.qtype))
-    {
-        goto error;
-    }
-    LogDNSSEC("NSEC3NameErrorProof: ClosestEncloser %s, ce %##s", CRDisplayString(m, closestEncloser), ce->c);
-    LogDNSSEC("NSEC3NameErrorProof: CloserEncloser %s", CRDisplayString(m, closerEncloser));
-
-    // *.closestEncloser should be covered by some nsec3 which would then prove
-    // that the wildcard does not exist
-    wild.c[0] = 1;
-    wild.c[1] = '*';
-    wild.c[2] = 0;
-    if (!AppendDomainName(&wild, ce))
-    {
-        LogMsg("NSEC3NameErrorProof: Can't append domainname to closest encloser name %##s", ce->c);
-        goto error;
-    }
-    if (!NSEC3Find(m, NSEC3Covers, ncr, &wild, mDNSNULL, &wildcard, mDNSNULL, dv->q.qtype))
-    {
-        LogMsg("NSEC3NameErrorProof: Cannot find encloser for wildcard");
-        goto error;
-    }
-    else
-    {
-        LogDNSSEC("NSEC3NameErrorProof: Wildcard %##s covered by %s", wild.c, CRDisplayString(m, wildcard));
-        if (wildcard == closestEncloser)
-        {
-            LogDNSSEC("NSEC3NameErrorProof: ClosestEncloser matching Wildcard %s", CRDisplayString(m, wildcard));
-        }
-    }
-    if (NSEC3OptOut(closerEncloser))
-    {
-        dv->flags |= NSEC3_OPT_OUT;
-    }
-    if (!VerifyNSEC3(m, dv, ncr, closestEncloser, closerEncloser, wildcard, NameErrorNSECCallback))
-        goto error;
-    else
-        return;
-
-error:
-    dv->DVCallback(m, dv, DNSSEC_Bogus);
-}
-
-// Section 8.5, 8.6 of RFC 5155 first paragraph
-mDNSlocal mDNSBool NSEC3NoDataError(mDNS *const m, CacheRecord *ncr, domainname *name, mDNSu16 qtype, CacheRecord **closestEncloser)
-{
-    const domainname *ce = mDNSNULL;
-
-    *closestEncloser = mDNSNULL;
-    // Note: This also covers ENT in which case the bitmap is empty
-    if (NSEC3Find(m, NSEC3ClosestEncloser, ncr, name, closestEncloser, mDNSNULL, &ce, qtype))
-    {
-        int bmaplen;
-        mDNSu8 *bmap;
-        mDNSBool ns, soa;
-
-        NSEC3Parse(&(*closestEncloser)->resrec, mDNSNULL, mDNSNULL, mDNSNULL, &bmaplen, &bmap);
-        if (BitmapTypeCheck(bmap, bmaplen, qtype) || BitmapTypeCheck(bmap, bmaplen, kDNSType_CNAME))
-        {
-            LogMsg("NSEC3NoDataError: qtype %s exists in %s", DNSTypeName(qtype), CRDisplayString(m, *closestEncloser));
-            return mDNSfalse;
-        }
-        ns = BitmapTypeCheck(bmap, bmaplen, kDNSType_NS);
-        soa = BitmapTypeCheck(bmap, bmaplen, kDNSType_SOA);
-        if (qtype != kDNSType_DS)
-        {
-            // For non-DS type questions, we don't want to use the parent side records to
-            // answer it
-            if (ns && !soa)
-            {
-                LogDNSSEC("NSEC3NoDataError: Parent side NSEC %s, can't use for child qname %##s (%s)",
-                    CRDisplayString(m, *closestEncloser), name->c, DNSTypeName(qtype));
-                return mDNSfalse;
-            }
-        }
-        else
-        {
-            if (soa)
-            {
-                LogDNSSEC("NSEC3NoDataError: Child side NSEC %s, can't use for parent qname %##s (%s)",
-                    CRDisplayString(m, *closestEncloser), name->c, DNSTypeName(qtype));
-                return mDNSfalse;
-            }
-        }
-        LogDNSSEC("NSEC3NoDataError: Name -%##s- exists, but qtype %s does not exist in %s", name->c, DNSTypeName(qtype), CRDisplayString(m, *closestEncloser));
-        return mDNStrue;
-    }
-    return mDNSfalse;
-}
-
-mDNSexport void NSEC3NoDataProof(mDNS *const m, DNSSECVerifier *dv, CacheRecord *ncr)
-{
-    CacheRecord *closerEncloser = mDNSNULL;
-    CacheRecord *closestEncloser = mDNSNULL;
-    CacheRecord *wildcard = mDNSNULL;
-    const domainname *ce = mDNSNULL;
-    domainname wild;
-
-    // Section 8.5, 8.6 of RFC 5155
-    if (NSEC3NoDataError(m, ncr, &dv->q.qname, dv->q.qtype, &closestEncloser))
-    {
-        goto verify;
-    }
-    // Section 8.6, 8.7: if we can't find the NSEC3 RR, verify the closest encloser proof
-    // for QNAME and the "next closer" should have the opt out
-    if (!NSEC3ClosestEncloserProof(m, ncr, &dv->q.qname, &closestEncloser, &closerEncloser, &ce, dv->q.qtype))
-    {
-        goto error;
-    }
-
-    // Section 8.7: find a matching NSEC3 for *.closestEncloser
-    wild.c[0] = 1;
-    wild.c[1] = '*';
-    wild.c[2] = 0;
-    if (!AppendDomainName(&wild, ce))
-    {
-        LogMsg("NSEC3NameErrorProof: Can't append domainname to closest encloser name %##s", ce->c);
-        goto error;
-    }
-    if (!NSEC3Find(m, NSEC3ClosestEncloser, ncr, &wild, &wildcard, mDNSNULL, &ce, dv->q.qtype))
-    {
-        // Not a wild card case. Section 8.6 second para applies.
-        LogDNSSEC("NSEC3NoDataProof: Cannot find encloser for wildcard, perhaps not a wildcard case");
-        if (!NSEC3OptOut(closerEncloser))
-        {
-            LogDNSSEC("NSEC3DataProof: opt out not set for %##s (%s), bogus", dv->q.qname.c, DNSTypeName(dv->q.qtype));
-            goto error;
-        }
-        LogDNSSEC("NSEC3DataProof: opt out set, proof complete for %##s (%s)", dv->q.qname.c, DNSTypeName(dv->q.qtype));
-        dv->flags |= NSEC3_OPT_OUT;
-    }
-    else
-    {
-        int bmaplen;
-        mDNSu8 *bmap;
-        NSEC3Parse(&wildcard->resrec, mDNSNULL, mDNSNULL, mDNSNULL, &bmaplen, &bmap);
-        if (BitmapTypeCheck(bmap, bmaplen, dv->q.qtype) || BitmapTypeCheck(bmap, bmaplen, kDNSType_CNAME))
-        {
-            LogDNSSEC("NSEC3NoDataProof: qtype %s exists in %s", DNSTypeName(dv->q.qtype), CRDisplayString(m, wildcard));
-            goto error;
-        }
-        if (dv->q.qtype == kDNSType_DS && BitmapTypeCheck(bmap, bmaplen, kDNSType_SOA))
-        {
-            LogDNSSEC("NSEC3NoDataProof: Child side wildcard NSEC3 %s, can't use for parent qname %##s (%s)",
-                CRDisplayString(m, wildcard), dv->q.qname.c, DNSTypeName(dv->q.qtype));
-            goto error;
-        }
-        else if (dv->q.qtype != kDNSType_DS && !BitmapTypeCheck(bmap, bmaplen, kDNSType_SOA) &&
-            BitmapTypeCheck(bmap, bmaplen, kDNSType_NS))
-        {
-            // Don't use the parent side record for this
-            LogDNSSEC("NSEC3NoDataProof: Parent side wildcard NSEC3 %s, can't use for child qname %##s (%s)",
-                CRDisplayString(m, wildcard), dv->q.qname.c, DNSTypeName(dv->q.qtype));
-            goto error;
-        }
-        LogDNSSEC("NSEC3NoDataProof: Wildcard %##s matched by %s", wild.c, CRDisplayString(m, wildcard));
-    }
-verify:
-
-    if (!VerifyNSEC3(m, dv, ncr, closestEncloser, closerEncloser, wildcard, NoDataNSECCallback))
-        goto error;
-    else
-        return;
-error:
-    dv->DVCallback(m, dv, DNSSEC_Bogus);
-}
-
-mDNSexport mDNSBool NSEC3WildcardAnswerProof(mDNS *const m, CacheRecord *ncr, DNSSECVerifier *dv)
-{
-    int skip;
-    const domainname *nc;
-    CacheRecord *closerEncloser;
-
-    (void) m;
-
-    // Find the next closer name and prove that it is covered by the NSEC3
-    skip = CountLabels(&dv->origName) - CountLabels(dv->wildcardName) - 1;
-    if (skip)
-        nc = SkipLeadingLabels(&dv->origName, skip);
-    else
-        nc = &dv->origName;
-
-    LogDNSSEC("NSEC3WildcardAnswerProof: wildcard name %##s", nc->c);
-
-    if (!NSEC3Find(m, NSEC3Covers, ncr, (domainname *)nc, mDNSNULL, &closerEncloser, mDNSNULL, dv->q.qtype))
-    {
-        LogMsg("NSEC3WildcardAnswerProof: Cannot find closer encloser");
-        return mDNSfalse;
-    }
-    if (!closerEncloser)
-    {
-        LogMsg("NSEC3WildcardAnswerProof: closerEncloser NULL");
-        return mDNSfalse;
-    }
-    if (NSEC3OptOut(closerEncloser))
-    {
-        dv->flags |= NSEC3_OPT_OUT;
-    }
-    // NSEC3 Verification is done by the caller
-    return mDNStrue;
-}
-
-mDNSexport CacheRecord *NSEC3RecordIsDelegation(mDNS *const m, domainname *name, mDNSu16 qtype)
-{
-    CacheGroup *cg;
-    CacheRecord *cr;
-    CacheRecord *ncr;
-    mDNSu32 namehash;
-
-    namehash = DomainNameHashValue(name);
-
-    cg = CacheGroupForName(m, namehash, name);
-    if (!cg)
-    {
-        LogDNSSEC("NSEC3RecordForName: cg NULL for %##s", name);
-        return mDNSNULL;
-    }
-    for (ncr = cg->members; ncr; ncr = ncr->next)
-    {
-        if (ncr->resrec.RecordType != kDNSRecordTypePacketNegative ||
-            ncr->resrec.rrtype != qtype)
-        {
-            continue;
-        }
-        for (cr = ncr->nsec; cr; cr = cr->next)
-        {
-            int hlen, b32len;
-            const mDNSu8 hashName[NSEC3_MAX_HASH_LEN];
-            const mDNSu8 b32Name[NSEC3_MAX_B32_LEN+1];
-            const RDataBody2 *const rdb = (RDataBody2 *)cr->resrec.rdata->u.data;
-            rdataNSEC3 *nsec3;
-
-            if (cr->resrec.rrtype != kDNSType_NSEC3)
-                continue;
-
-            nsec3 = (rdataNSEC3 *)rdb->data;
-
-            if (!NSEC3HashName(name, nsec3, hashName, &hlen))
-            {
-                LogMsg("NSEC3RecordIsDelegation: NSEC3HashName failed for %##s", name->c);
-                return mDNSNULL;
-            }
-
-            b32len = baseEncode((char *)b32Name, sizeof(b32Name), (mDNSu8 *)hashName, hlen, ENC_BASE32);
-            if (!b32len)
-            {
-                LogMsg("NSEC3RecordIsDelegation: baseEncode of name %##s failed", name->c);
-                return mDNSNULL;
-            }
-            // Section 2.3 of RFC 4035 states that:
-            //
-            // Each owner name in the zone that has authoritative data or a delegation point NS RRset MUST
-            // have an NSEC resource record. 
-            //
-            // This applies to NSEC3 record. So, if we have an NSEC3 record matching the question name with the
-            // NS bit set, then this is a delegation.
-            //
-            if (!NSEC3SameName(&cr->resrec.name->c[1], cr->resrec.name->c[0], (const mDNSu8 *)b32Name, b32len))
-            {
-                int bmaplen;
-                mDNSu8 *bmap;
-                
-                LogDNSSEC("NSEC3RecordIsDelegation: CacheRecord %s matches name %##s, b32name %s", CRDisplayString(m, cr), name->c, b32Name);
-                NSEC3Parse(&cr->resrec, mDNSNULL, mDNSNULL, mDNSNULL, &bmaplen, &bmap);
-
-                // See the Insecure Delegation Proof section in dnssec-bis: DS bit and SOA bit
-                // should be absent
-                if (BitmapTypeCheck(bmap, bmaplen, kDNSType_SOA) ||
-                    BitmapTypeCheck(bmap, bmaplen, kDNSType_DS))
-                {
-                    LogDNSSEC("NSEC3RecordIsDelegation: CacheRecord %s has DS or SOA bit set, ignoring", CRDisplayString(m, cr));
-                    return mDNSNULL;
-                }
-                if (BitmapTypeCheck(bmap, bmaplen, kDNSType_NS))
-                    return cr;
-                else
-                    return mDNSNULL;
-            }
-            // If opt-out is not set, then it does not cover any delegations
-            if (!(nsec3->flags & NSEC3_FLAGS_OPTOUT))
-                continue;
-            // Opt-out allows insecure delegations to exist without the NSEC3 RR at the
-            // hashed owner name (see RFC 5155 section 6.0).
-            if (NSEC3CoversName(m, cr, hashName, hlen, b32Name, b32len))
-            {
-                LogDNSSEC("NSEC3RecordIsDelegation: CacheRecord %s covers name %##s with optout", CRDisplayString(m, cr), name->c);
-                return cr;
-            }
-        }
-    }
-    return mDNSNULL;
-}
-
-#else // !DNSSEC_DISABLED
-
-#endif // !DNSSEC_DISABLED
diff --git a/mDNSCore/nsec3.h b/mDNSCore/nsec3.h
deleted file mode 100644 (file)
index be32b80..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-/* -*- Mode: C; tab-width: 4 -*-
- *
- * Copyright (c) 2011-2012 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __NSEC3_H
-#define __NSEC3_H
-
-#include "dnssec.h"
-
-extern mDNSBool NSEC3WildcardAnswerProof(mDNS *const m, CacheRecord *ncr, DNSSECVerifier *dv);
-extern void NSEC3NameErrorProof(mDNS *const m, DNSSECVerifier *dv, CacheRecord *ncr);
-extern void NSEC3NoDataProof(mDNS *const m, DNSSECVerifier *dv, CacheRecord *ncr);
-extern CacheRecord *NSEC3RecordIsDelegation(mDNS *const m, domainname *name, mDNSu16 qtype);
-
-#endif // __NSEC3_H
index 7a4d9e6c170c2327106c4469f6c5e300a55bd31f..98d8ea09962d4fe05cda15039a828834721e6d89 100755 (executable)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002-2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "SymptomReporter.h"
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+#include "QuerierSupport.h"
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include "dnssec_v2.h"
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
 #if (defined(_MSC_VER))
 // Disable "assignment within conditional expression".
 // Other compilers understand the convention that if you place the assignment expression within an extra pair
@@ -113,6 +121,7 @@ mDNSlocal void SetRecordRetry(mDNS *const m, AuthRecord *rr, mDNSu32 random)
 #pragma mark - Name Server List Management
 #endif
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 mDNSexport DNSServer *mDNS_AddDNSServer(mDNS *const m, const domainname *domain, const mDNSInterfaceID interface,
     const mDNSs32 serviceID, const mDNSAddr *addr, const mDNSIPPort port, ScopeType scopeType, mDNSu32 timeout,
     mDNSBool isCell, mDNSBool isExpensive, mDNSBool isConstrained, mDNSBool isCLAT46, mDNSu32 resGroupID,
@@ -157,7 +166,6 @@ mDNSexport DNSServer *mDNS_AddDNSServer(mDNS *const m, const domainname *domain,
         if (!server->usableA        != !usableA)        continue;
         if (!server->usableAAAA     != !usableAAAA)     continue;
         if (!server->isCell         != !isCell)         continue;
-        if (!server->req_DO         != !reqDO)          continue;
         if (!(server->flags & DNSServerFlag_Delete))
         {
             debugf("Note: DNS Server %#a:%d for domain %##s (%p) registered more than once",
@@ -212,12 +220,6 @@ mDNSexport DNSServer *mDNS_AddDNSServer(mDNS *const m, const domainname *domain,
             server->isExpensive   = isExpensive;
             server->isConstrained = isConstrained;
             server->isCLAT46      = isCLAT46;
-            server->req_DO        = reqDO;
-            // We start off assuming that the DNS server is not DNSSEC aware and
-            // when we receive the first response to a DNSSEC question, we set
-            // it to true.
-            server->DNSSECAware = mDNSfalse;
-            server->retransDO = 0;
             AssignDomainName(&server->domain, domain);
             *p = server; // Append new record at end of list
         }
@@ -245,7 +247,7 @@ mDNSexport void PenalizeDNSServer(mDNS *const m, DNSQuestion *q, mDNSOpaque16 re
 
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
               "PenalizeDNSServer: Penalizing DNS server " PRI_IP_ADDR " question for question %p " PRI_DM_NAME " (" PUB_S ") SuppressUnusable %d",
-              (q->qDNSServer ? &q->qDNSServer->addr : mDNSNULL), q, DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype), q->SuppressUnusable);
+              (q->qDNSServer ? &q->qDNSServer->addr : mDNSNULL), q, DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), q->SuppressUnusable);
 
     // If we get error from any DNS server, remember the error. If all of the servers,
     // return the error, then return the first error. 
@@ -276,14 +278,20 @@ mDNSexport void PenalizeDNSServer(mDNS *const m, DNSQuestion *q, mDNSOpaque16 re
         {
             LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "PenalizeDNSServer: Not Penalizing PTR question");
         }
-        else if ((rcode == kDNSFlag1_RC_FormErr) || (rcode == kDNSFlag1_RC_ServFail) || (rcode == kDNSFlag1_RC_NotImpl) || (rcode == kDNSFlag1_RC_Refused))
+        else if ((rcode == kDNSFlag1_RC_FormErr) || (rcode == kDNSFlag1_RC_ServFail) || (rcode == kDNSFlag1_RC_NotImpl))
         {
             LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                       "PenalizeDNSServer: Not Penalizing DNS Server since it at least responded with rcode %d", rcode);
         }
         else
         {
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "PenalizeDNSServer: Penalizing question type %d", q->qtype);
+            const char *reason = "";
+            if (rcode == kDNSFlag1_RC_Refused)
+            {
+                reason = " because server refused to answer";
+            }
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "PenalizeDNSServer: Penalizing question type %d" PUB_S,
+                      q->qtype, reason);
             q->qDNSServer->penaltyTime = NonZeroTime(m->timenow + DNSSERVER_PENALTY_TIME);
         }
     }
@@ -323,7 +331,7 @@ end:
         {
             LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                       "PenalizeDNSServer: Server for " PRI_DM_NAME " (" PUB_S ") changed to " PRI_IP_ADDR ":%d (" PRI_DM_NAME ")",
-                      DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype), &q->qDNSServer->addr, mDNSVal16(q->qDNSServer->port), DM_NAME_PARAM(q->qDNSServer->domain.c));
+                      DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), &q->qDNSServer->addr, mDNSVal16(q->qDNSServer->port), DM_NAME_PARAM(&q->qDNSServer->domain));
             // We want to try the next server immediately. As the question may already have backed off, reset
             // the interval. We do this only the first time when we try all the DNS servers. Once we reached the end of
             // list and retrying all the servers again e.g., at least one server failed to respond in the previous try, we
@@ -353,12 +361,13 @@ end:
             // we want the normal backoff to work.
             LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                       "PenalizeDNSServer: Server for %p, " PRI_DM_NAME " (" PUB_S ") changed to NULL, Interval %d",
-                      q, DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype), q->ThisQInterval);
+                      q, DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), q->ThisQInterval);
         }
         q->unansweredQueries = 0;
 
     }
 }
+#endif // !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 
 // ***************************************************************************
 #if COMPILER_LIKES_PRAGMA_MARK
@@ -1311,16 +1320,11 @@ mDNSlocal void tcpCallback(TCPSocket *sock, void *context, mDNSBool ConnectionEs
         }
         else if (q)
         {
+            mDNSOpaque16 HeaderFlags = uQueryFlags;
+
             // LLQ Polling mode or non-LLQ uDNS over TCP
-            InitializeDNSMessage(&tcpInfo->request.h, q->TargetQID, (DNSSECQuestion(q) ? DNSSecQFlags : uQueryFlags));
+            InitializeDNSMessage(&tcpInfo->request.h, q->TargetQID, HeaderFlags);
             end = putQuestion(&tcpInfo->request, tcpInfo->request.data, tcpInfo->request.data + AbsoluteMaxDNSMessageData, &q->qname, q->qtype, q->qclass);
-            if (DNSSECQuestion(q) && q->qDNSServer && !q->qDNSServer->isCell)
-            {
-                if (q->ProxyQuestion)
-                    end = DNSProxySetAttributes(q, &tcpInfo->request.h, &tcpInfo->request, end, tcpInfo->request.data + AbsoluteMaxDNSMessageData);
-                else
-                    end = putDNSSECOption(&tcpInfo->request, end, tcpInfo->request.data + AbsoluteMaxDNSMessageData);
-            }
 
             AuthInfo = q->AuthInfo;     // Need to add TSIG to this message
         }
@@ -1557,9 +1561,22 @@ mDNSlocal tcpInfo_t *MakeTCPConn(mDNS *const m, const DNSMessage *const msg, con
     info = (tcpInfo_t *) mDNSPlatformMemAllocateClear(sizeof(*info));
     if (!info) { LogMsg("ERROR: MakeTCP - memallocate failed"); return(mDNSNULL); }
 
+    if (msg)
+    {
+        const mDNSu8 *const start = (const mDNSu8 *)msg;
+        if ((end < start) || ((end - start) > (int)sizeof(info->request)))
+        {
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+                "MakeTCPConn: invalid DNS message pointers -- msg: %p, end: %p", msg, end);
+            mDNSPlatformMemFree(info);
+            return mDNSNULL;
+        }
+        info->requestLen = (int)(end - start);
+        mDNSPlatformMemCopy(&info->request, msg, info->requestLen);
+    }
+
     info->m          = m;
     info->sock       = mDNSPlatformTCPSocket(flags, Addr->type, &srcport, hostname, useBackgroundTrafficClass);
-    info->requestLen = 0;
     info->question   = question;
     info->rr         = rr;
     info->Addr       = *Addr;
@@ -1570,12 +1587,6 @@ mDNSlocal tcpInfo_t *MakeTCPConn(mDNS *const m, const DNSMessage *const msg, con
     info->numReplies = 0;
     info->SrcPort = srcport;
 
-    if (msg)
-    {
-        info->requestLen = (int) (end - ((mDNSu8*)msg));
-        mDNSPlatformMemCopy(&info->request, msg, info->requestLen);
-    }
-
     if (!info->sock) { LogMsg("MakeTCPConn: unable to create TCP socket"); mDNSPlatformMemFree(info); return(mDNSNULL); }
     mDNSPlatformSetSocktOpt(info->sock, mDNSTransport_TCP, Addr->type, question);
     err = mDNSPlatformTCPConnect(info->sock, Addr, Port, (question ? question->InterfaceID : mDNSNULL), tcpCallback, info);
@@ -1884,8 +1895,6 @@ mDNSlocal mStatus GetZoneData_StartQuery(mDNS *const m, ZoneData *zd, mDNSu16 qt
     zd->question.TimeoutQuestion     = 0;
     zd->question.WakeOnResolve       = 0;
     zd->question.UseBackgroundTraffic = mDNSfalse;
-    zd->question.ValidationRequired = 0;
-    zd->question.ValidatingResponse = 0;
     zd->question.ProxyQuestion      = 0;
     zd->question.pid                 = mDNSPlatformGetPID();
     zd->question.euid                = 0;
@@ -2542,8 +2551,6 @@ mDNSlocal void GetStaticHostname(mDNS *m)
     q->TimeoutQuestion  = 0;
     q->WakeOnResolve    = 0;
     q->UseBackgroundTraffic = mDNSfalse;
-    q->ValidationRequired = 0;
-    q->ValidatingResponse = 0;
     q->ProxyQuestion      = 0;
     q->pid              = mDNSPlatformGetPID();
     q->euid             = 0;
@@ -3820,7 +3827,7 @@ mDNSexport void uDNS_ReceiveMsg(mDNS *const m, DNSMessage *const msg, const mDNS
            msg->h.numAnswers,     msg->h.numAnswers     == 1 ? ", "   : "s,",
            msg->h.numAuthorities, msg->h.numAuthorities == 1 ? "y,  " : "ies,",
            msg->h.numAdditionals, msg->h.numAdditionals == 1 ? ""     : "s", end - msg->data);
-#if MDNSRESPONDER_SUPPORTS(APPLE, SYMPTOMS)
+#if MDNSRESPONDER_SUPPORTS(APPLE, SYMPTOMS) && !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     if (NumUnreachableDNSServers > 0)
         SymptomReporterDNSServerReachable(m, srcaddr);
 #endif
@@ -4404,47 +4411,6 @@ unreg_error:
 #pragma mark - Periodic Execution Routines
 #endif
 
-mDNSlocal void handle_unanswered_query(mDNS *const m)
-{
-    DNSQuestion *q = m->CurrentQuestion;
-
-    if (q->unansweredQueries >= MAX_DNSSEC_UNANSWERED_QUERIES && DNSSECOptionalQuestion(q))
-    {
-        // If we are not receiving any responses for DNSSEC question, it could be due to
-        // a broken middlebox or a DNS server that does not understand the EDNS0/DOK option that
-        // silently drops the packets. Also as per RFC 5625 there are certain buggy DNS Proxies
-        // that are known to drop these pkts. To handle this, we turn off sending the EDNS0/DOK
-        // option if we have not received any responses indicating that the server or
-        // the middlebox is DNSSEC aware. If we receive at least one response to a DNSSEC
-        // question, we don't turn off validation. Also, we wait for MAX_DNSSEC_RETRANSMISSIONS
-        // before turning off validation to accomodate packet loss.
-        // 
-        // Note: req_DO affects only DNSSEC_VALIDATION_SECURE_OPTIONAL questions;
-        // DNSSEC_VALIDATION_SECURE questions ignores req_DO.
-
-        if (!q->qDNSServer->DNSSECAware && q->qDNSServer->req_DO)
-        {
-            q->qDNSServer->retransDO++;
-            if (q->qDNSServer->retransDO == MAX_DNSSEC_RETRANSMISSIONS)
-            {
-                LogInfo("handle_unanswered_query: setting req_DO false for %#a", &q->qDNSServer->addr);
-                q->qDNSServer->req_DO = mDNSfalse;
-            }
-        }
-
-        if (!q->qDNSServer->req_DO)
-        {
-            q->ValidationState     = DNSSECValNotRequired;
-            q->ValidationRequired  = DNSSEC_VALIDATION_NONE;
-
-            if (q->ProxyQuestion)
-                q->ProxyDNSSECOK = mDNSfalse;
-            LogInfo("handle_unanswered_query: unanswered query for %##s (%s), so turned off validation for %#a",
-                q->qname.c, DNSTypeName(q->qtype), &q->qDNSServer->addr);
-        }
-    }
-}
-
 mDNSlocal void uDNS_HandleLLQState(mDNS *const m, DNSQuestion *q)
 {
     LogMsg("->uDNS_HandleLLQState: %##s %d", &q->qname, q->state);
@@ -4514,7 +4480,7 @@ mDNSlocal void uDNS_HandleLLQState(mDNS *const m, DNSQuestion *q)
 
 // The question to be checked is not passed in as an explicit parameter;
 // instead it is implicit that the question to be checked is m->CurrentQuestion.
-mDNSexport void uDNS_CheckCurrentQuestion(mDNS *const m)
+mDNSlocal void uDNS_CheckCurrentQuestion(mDNS *const m)
 {
     DNSQuestion *q = m->CurrentQuestion;
     if (m->timenow - NextQSendTime(q) < 0) return;
@@ -4524,7 +4490,9 @@ mDNSexport void uDNS_CheckCurrentQuestion(mDNS *const m)
         uDNS_HandleLLQState(m,q);
     }
 
-    handle_unanswered_query(m);
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    Querier_HandleUnicastQuestion(q);
+#else
     // We repeat the check above (rather than just making this the "else" case) because startLLQHandshake can change q->state to LLQ_Poll
     if (!(q->LongLived && q->state != LLQ_Poll))
     {
@@ -4535,7 +4503,7 @@ mDNSexport void uDNS_CheckCurrentQuestion(mDNS *const m)
             {
                 LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                           "[R%u->Q%u] uDNS_CheckCurrentQuestion: Sent %d unanswered queries for " PRI_DM_NAME " (" PUB_S ") to " PRI_IP_ADDR ":%d (" PRI_DM_NAME ")",
-                          q->request_id, mDNSVal16(q->TargetQID), q->unansweredQueries, DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype), &orig->addr, mDNSVal16(orig->port), DM_NAME_PARAM(orig->domain.c));
+                          q->request_id, mDNSVal16(q->TargetQID), q->unansweredQueries, DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), &orig->addr, mDNSVal16(orig->port), DM_NAME_PARAM(&orig->domain));
             }
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, SYMPTOMS)
@@ -4570,7 +4538,7 @@ mDNSexport void uDNS_CheckCurrentQuestion(mDNS *const m)
             {
                 LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                           "[R%u->Q%u] uDNS_checkCurrentQuestion: Retrying question %p " PRI_DM_NAME " (" PUB_S ") DNS Server " PRI_IP_ADDR ":%d ThisQInterval %d",
-                          q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype), new ? &new->addr : mDNSNULL, mDNSVal16(new ? new->port : zeroIPPort), q->ThisQInterval);
+                          q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), new ? &new->addr : mDNSNULL, mDNSVal16(new ? new->port : zeroIPPort), q->ThisQInterval);
                 DNSServerChangeForQuestion(m, q, new);
             }
             for (qptr = q->next ; qptr; qptr = qptr->next)
@@ -4580,17 +4548,10 @@ mDNSexport void uDNS_CheckCurrentQuestion(mDNS *const m)
         {
             mDNSu8 *end;
             mStatus err = mStatus_NoError;
+            mDNSOpaque16 HeaderFlags = uQueryFlags;
 
-            InitializeDNSMessage(&m->omsg.h, q->TargetQID, (DNSSECQuestion(q) ? DNSSecQFlags : uQueryFlags));
-
+            InitializeDNSMessage(&m->omsg.h, q->TargetQID, HeaderFlags);
             end = putQuestion(&m->omsg, m->omsg.data, m->omsg.data + AbsoluteMaxDNSMessageData, &q->qname, q->qtype, q->qclass);
-            if (DNSSECQuestion(q) && !q->qDNSServer->isCell)
-            {
-                if (q->ProxyQuestion)
-                    end = DNSProxySetAttributes(q, &m->omsg.h, &m->omsg, end, m->omsg.data + AbsoluteMaxDNSMessageData);
-                else
-                    end = putDNSSECOption(&m->omsg, end, m->omsg.data + AbsoluteMaxDNSMessageData);
-            }
 
             if (end > m->omsg.data)
             {
@@ -4620,22 +4581,7 @@ mDNSexport void uDNS_CheckCurrentQuestion(mDNS *const m)
                 if (!q->LocalSocket) err = mStatus_NoMemoryErr; // If failed to make socket (should be very rare), we'll try again next time
                 else
                 {
-#if MDNSRESPONDER_SUPPORTS(APPLE, SUSPICIOUS_REPLY_DEFENSE)
-                    // If we are in suspicious mode, restart question as TCP
-                    mDNSs32  suspiciousTimeout = m->NextSuspiciousTimeout ? m->NextSuspiciousTimeout - m->timenow : 0;
-                    if (suspiciousTimeout > 0 && suspiciousTimeout <= SUSPICIOUS_REPLY_DEFENSE_SECS * mDNSPlatformOneSecond)
-                    {
-                        uDNS_RestartQuestionAsTCP(m, q, &q->qDNSServer->addr, q->qDNSServer->port);
-                        err = mStatus_NoError;
-#if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
-                        q->metrics.dnsOverTCPState = DNSOverTCP_SuspiciousDefense;
-#endif
-                    }
-                    else
-#endif
-                    {
-                        err = mDNSSendDNSMessage(m, &m->omsg, end, q->qDNSServer->interface, mDNSNULL, q->LocalSocket, &q->qDNSServer->addr, q->qDNSServer->port, mDNSNULL, q->UseBackgroundTraffic);
-                    }
+                    err = mDNSSendDNSMessage(m, &m->omsg, end, q->qDNSServer->interface, mDNSNULL, q->LocalSocket, &q->qDNSServer->addr, q->qDNSServer->port, mDNSNULL, q->UseBackgroundTraffic);
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
                     if (!err)
@@ -4648,7 +4594,7 @@ mDNSexport void uDNS_CheckCurrentQuestion(mDNS *const m)
                         }
                         if (q->metrics.querySendCount++ == 0)
                         {
-                            q->metrics.firstQueryTime = m->timenow;
+                            q->metrics.firstQueryTime = NonZeroTime(m->timenow);
                         }
                     }
 #endif
@@ -4661,7 +4607,7 @@ mDNSexport void uDNS_CheckCurrentQuestion(mDNS *const m)
 
                 LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                           "[R%u->Q%u] uDNS_CheckCurrentQuestion: host unreachable error for DNS server " PRI_IP_ADDR " for question [%p] " PRI_DM_NAME " (" PUB_S ")",
-                          q->request_id, mDNSVal16(q->TargetQID), &q->qDNSServer->addr, q, DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype));
+                          q->request_id, mDNSVal16(q->TargetQID), &q->qDNSServer->addr, q, DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype));
 
                 if (!StrictUnicastOrdering)
                 {
@@ -4679,7 +4625,7 @@ mDNSexport void uDNS_CheckCurrentQuestion(mDNS *const m)
                 {
                     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                               "[R%u->Q%u] uDNS_checkCurrentQuestion: Retrying question %p " PRI_DM_NAME " (" PUB_S ") DNS Server " PRI_IP_ADDR ":%u ThisQInterval %d",
-                              q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype),
+                              q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype),
                               newServer ? &newServer->addr : mDNSNULL, mDNSVal16(newServer ? newServer->port : zeroIPPort), q->ThisQInterval);
                     DNSServerChangeForQuestion(m, q, newServer);
                 }
@@ -4740,7 +4686,7 @@ mDNSexport void uDNS_CheckCurrentQuestion(mDNS *const m)
                 if (!mDNSOpaque128IsZero(&q->validDNSServers))
                     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
                               "[R%u->Q%u] uDNS_CheckCurrentQuestion: ERROR!!: valid DNSServer bits not zero 0x%x, 0x%x 0x%x 0x%x for question " PRI_DM_NAME " (" PUB_S ")",
-                              q->request_id, mDNSVal16(q->TargetQID), q->validDNSServers.l[3], q->validDNSServers.l[2], q->validDNSServers.l[1], q->validDNSServers.l[0], DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype));
+                              q->request_id, mDNSVal16(q->TargetQID), q->validDNSServers.l[3], q->validDNSServers.l[2], q->validDNSServers.l[1], q->validDNSServers.l[0], DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype));
                 // If we reached the end of list while picking DNS servers, then we don't want to deactivate the
                 // question. Try after 60 seconds. We find this by looking for valid DNSServers for this question,
                 // if we find any, then we must have tried them before we came here. This avoids maintaining
@@ -4750,7 +4696,7 @@ mDNSexport void uDNS_CheckCurrentQuestion(mDNS *const m)
                 {
                     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                               "[R%u->Q%u] uDNS_CheckCurrentQuestion: no DNS server for " PRI_DM_NAME " (" PUB_S ")",
-                              q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype));
+                              q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype));
                     q->ThisQInterval = 0;
                 }
                 else
@@ -4769,7 +4715,7 @@ mDNSexport void uDNS_CheckCurrentQuestion(mDNS *const m)
                         if (qptr->DuplicateOf == q) { qptr->validDNSServers = q->validDNSServers; qptr->qDNSServer = q->qDNSServer; }
                     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                               "[R%u->Q%u] uDNS_checkCurrentQuestion: Tried all DNS servers, retry question %p SuppressUnusable %d " PRI_DM_NAME " (" PUB_S ") with DNS Server " PRI_IP_ADDR ":%d after 60 seconds, ThisQInterval %d",
-                              q->request_id, mDNSVal16(q->TargetQID), q, q->SuppressUnusable, DM_NAME_PARAM(q->qname.c), DNSTypeName(q->qtype),
+                              q->request_id, mDNSVal16(q->TargetQID), q, q->SuppressUnusable, DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype),
                               q->qDNSServer ? &q->qDNSServer->addr : mDNSNULL, mDNSVal16(q->qDNSServer ? q->qDNSServer->port : zeroIPPort), q->ThisQInterval);
                 }
             }
@@ -4778,7 +4724,7 @@ mDNSexport void uDNS_CheckCurrentQuestion(mDNS *const m)
                 q->ThisQInterval = 0;
                 LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                           "[R%u->Q%u] uDNS_CheckCurrentQuestion DNS server " PRI_IP_ADDR ":%d for " PRI_DM_NAME " is disabled",
-                          q->request_id, mDNSVal16(q->TargetQID), &q->qDNSServer->addr, mDNSVal16(q->qDNSServer->port), DM_NAME_PARAM(q->qname.c));
+                          q->request_id, mDNSVal16(q->TargetQID), &q->qDNSServer->addr, mDNSVal16(q->qDNSServer->port), DM_NAME_PARAM(&q->qname));
             }
 
             if (cg)
@@ -4811,6 +4757,7 @@ mDNSexport void uDNS_CheckCurrentQuestion(mDNS *const m)
             // MUST NOT touch m->CurrentQuestion (or q) after this -- client callback could have deleted it
         }
     }
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 }
 
 mDNSexport void CheckNATMappings(mDNS *m)
@@ -5004,17 +4951,17 @@ mDNSlocal mDNSs32 CheckRecordUpdates(mDNS *m)
 mDNSexport void uDNS_Tasks(mDNS *const m)
 {
     mDNSs32 nexte;
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     DNSServer *d;
-
-#if MDNSRESPONDER_SUPPORTS(APPLE, SUSPICIOUS_REPLY_DEFENSE)
-    if (m->NextSuspiciousTimeout && m->NextSuspiciousTimeout <= m->timenow) m->NextSuspiciousTimeout = 0;
 #endif
+
     m->NextuDNSEvent = m->timenow + FutureTime;
 
     nexte = CheckRecordUpdates(m);
     if (m->NextuDNSEvent - nexte > 0)
         m->NextuDNSEvent = nexte;
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     for (d = m->DNSServers; d; d=d->next)
         if (d->penaltyTime)
         {
@@ -5028,12 +4975,13 @@ mDNSexport void uDNS_Tasks(mDNS *const m)
             if (m->NextuDNSEvent - d->penaltyTime > 0)
                 m->NextuDNSEvent = d->penaltyTime;
         }
+#endif
 
     if (m->CurrentQuestion)
     {
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
                   "uDNS_Tasks ERROR m->CurrentQuestion already set: " PRI_DM_NAME " (" PRI_S ")",
-                  DM_NAME_PARAM(m->CurrentQuestion->qname.c), DNSTypeName(m->CurrentQuestion->qtype));
+                  DM_NAME_PARAM(&m->CurrentQuestion->qname), DNSTypeName(m->CurrentQuestion->qtype));
     }
     m->CurrentQuestion = m->Questions;
     while (m->CurrentQuestion && m->CurrentQuestion != m->NewQuestions)
@@ -5614,11 +5562,6 @@ mDNSexport void uDNS_RestartQuestionAsTCP(mDNS *m, DNSQuestion *const q, const m
     // new DNS server. So, always try to establish a new connection.
     if (q->tcp) { DisposeTCPConn(q->tcp); q->tcp = mDNSNULL; }
     q->tcp = MakeTCPConn(m, mDNSNULL, mDNSNULL, kTCPSocketFlags_Zero, srcaddr, srcport, mDNSNULL, q, mDNSNULL);
-#if MDNSRESPONDER_SUPPORTS(APPLE, SUSPICIOUS_REPLY_DEFENSE)
-    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-        "uDNS_RestartQuestionAsTCP: suspicious timeout %d ticks",
-        m->NextSuspiciousTimeout ? m->NextSuspiciousTimeout - m->timenow : 0);
-#endif
 }
 
 mDNSlocal void FlushAddressCacheRecords(mDNS *const m)
@@ -5686,7 +5629,7 @@ struct CompileTimeAssertionChecks_uDNS
     // other overly-large structures instead of having a pointer to them, can inadvertently
     // cause structure sizes (and therefore memory usage) to balloon unreasonably.
     char sizecheck_tcpInfo_t     [(sizeof(tcpInfo_t)      <=  9056) ? 1 : -1];
-    char sizecheck_SearchListElem[(sizeof(SearchListElem) <=  6136) ? 1 : -1];
+    char sizecheck_SearchListElem[(sizeof(SearchListElem) <=  6381) ? 1 : -1];
 };
 
 #if COMPILER_LIKES_PRAGMA_MARK
@@ -5795,7 +5738,11 @@ mDNSlocal void DNSPushProcessResponse(mDNS *const m, const DNSMessage *const msg
         mrr->rroriginalttl = kLLQ_DefLease /* XXX */;
 
         // Use the DNS Server we remember from the question that created this DNS Push server structure.
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+               mdns_replace(&mrr->dnsservice, server->dnsservice);
+#else
         mrr->rDNSServer = server->qDNSServer;
+#endif
         
         // 2. See if we want to add this packet resource record to our cache
         // We only try to cache answers if we have a cache to put them in
@@ -5804,10 +5751,9 @@ mDNSlocal void DNSPushProcessResponse(mDNS *const m, const DNSMessage *const msg
             const mDNSu32 slot = HashSlotFromNameHash(mrr->namehash);
             CacheGroup *cg = CacheGroupForName(m, mrr->namehash, mrr->name);
             CacheRecord *rr = mDNSNULL;
-            CacheRecord *NSECCachePtr = (CacheRecord *)1;
 
             // 2a. Check if this packet resource record is already in our cache.
-            rr = mDNSCoreReceiveCacheCheck(m, msg, uDNS_LLQ_Events, slot, cg, mDNSNULL, &cfp, &NSECCachePtr, mDNSNULL);
+            rr = mDNSCoreReceiveCacheCheck(m, msg, uDNS_LLQ_Events, slot, cg, &cfp, mDNSNULL);
 
             // If packet resource record not in our cache, add it now
             // (unless it is just a deletion of a record we never had, in which case we don't care)
@@ -6240,7 +6186,11 @@ DNSPushNotificationServer *GetConnectionToDNSPushNotificationServer(mDNS *m, DNS
     newServer->numberOfQuestions = 1;
     AssignDomainName(&newServer->serverName, &q->nta->Host);
     newServer->port = q->nta->Port;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mdns_replace(&newServer->dnsservice, q->dnsservice);
+#else
     newServer->qDNSServer = q->qDNSServer;
+#endif
     ConvertDomainNameToCString(&newServer->serverName, name);
     newServer->connection = dso_create(mDNSfalse, 10, name, DNSPushDSOCallback, newServer, NULL);
     if (newServer->connection == NULL)
index ee8339327d25b40c1b833b6b465530365647d66e..5561599407811ba9ae2d62db198e5ec977e5f105 100755 (executable)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002-2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -38,7 +38,6 @@ extern "C" {
 //#define MAX_UCAST_POLL_INTERVAL (1 * 60 * mDNSPlatformOneSecond)
 #define LLQ_POLL_INTERVAL       (15 * 60 * mDNSPlatformOneSecond) // Polling interval for zones w/ an advertised LLQ port (ie not static zones) if LLQ fails due to NAT, etc.
 #define RESPONSE_WINDOW (60 * mDNSPlatformOneSecond)         // require server responses within one minute of request
-#define MAX_DNSSEC_UNANSWERED_QUERIES 1                      // number of unanswered queries from any one uDNS server before turning off DNSSEC Validation
 #define MAX_UCAST_UNANSWERED_QUERIES 2                       // number of unanswered queries from any one uDNS server before trying another server
 #define DNSSERVER_PENALTY_TIME (60 * mDNSPlatformOneSecond)  // number of seconds for which new questions don't pick this server
 
@@ -72,15 +71,6 @@ extern "C" {
 // then use the default value of 30 seconds
 #define DEFAULT_UDNS_TIMEOUT    30 // in seconds
 
-// For questions that are validating responses (q->ValidatingResponse == 1), use 10 seconds
-// which accomodates two DNS servers and two queries per DNS server.
-#define DEFAULT_UDNSSEC_TIMEOUT    10 // in seconds
-
-// If we are sending queries with EDNS0/DO option and we have no indications that the server
-// is DNSSEC aware and we have already reached MAX_DNSSEC_RETRANSMISSIONS, we disable
-// validation (for optional case only) for any questions that uses this server
-#define MAX_DNSSEC_RETRANSMISSIONS 3
-
 #if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
 // Push notification structures
 struct mDNS_DNSPushNotificationServer
@@ -92,7 +82,11 @@ struct mDNS_DNSPushNotificationServer
     mDNSs32                    lastDisconnect;    // Last time we got a disconnect, used to avoid constant reconnects
     domainname                 serverName;        // The hostname returned by the _dns-push-tls._tcp.<zone> SRV lookup
     mDNSIPPort                 port;              // The port from the SRV lookup
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mdns_dns_service_t         dnsservice;
+#else
     DNSServer                 *qDNSServer;        // DNS server stolen from the question that created this server structure.
+#endif
     mDNS                      *m;
     DNSPushNotificationServer *next;
 } ;
@@ -142,7 +136,6 @@ extern mStatus mDNS_StartNATOperation_internal(mDNS *const m, NATTraversalInfo *
 extern void RecordRegistrationGotZoneData(mDNS *const m, mStatus err, const ZoneData *zoneData);
 extern mStatus uDNS_DeregisterRecord(mDNS *const m, AuthRecord *const rr);
 extern const domainname *GetServiceTarget(mDNS *m, AuthRecord *const rr);
-extern void uDNS_CheckCurrentQuestion(mDNS *const m);
 
 // integer fields of msg header must be in HOST byte order before calling this routine
 extern void uDNS_ReceiveMsg(mDNS *const m, DNSMessage *const msg, const mDNSu8 *const end,
@@ -190,11 +183,10 @@ extern void natTraversalHandlePortMapReply(mDNS *const m, NATTraversalInfo *n, c
 // DNS Push Notification
 extern void SubscribeToDNSPushNotification(mDNS *m, DNSQuestion *q);
 #endif
-    
-extern CacheRecord* mDNSCoreReceiveCacheCheck(mDNS *const m, const DNSMessage *const response, uDNS_LLQType LLQType,
-                                                                                         const mDNSu32 slot, CacheGroup *cg, DNSQuestion *unicastQuestion,
-                                              CacheRecord ***cfp, CacheRecord **NSECCachePtr, mDNSInterfaceID InterfaceID);
 
+extern CacheRecord* mDNSCoreReceiveCacheCheck(mDNS *const m, const DNSMessage *const response, uDNS_LLQType LLQType,
+                                                                                         const mDNSu32 slot, CacheGroup *cg,
+                                              CacheRecord ***cfp, mDNSInterfaceID InterfaceID);
 #ifdef  __cplusplus
 }
 #endif
index a4586af6b64359c8ee9ae51c0a977e5931bef1aa..e65e5a19d39448d9a8863bc2dfc9a42549cd733a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018-2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2018-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
     #define MDNSRESPONDER_SUPPORTS_APPLE_D2D                        1
 #endif
 
+// Feature: DNS64 support for DNS proxy
+// Radar:   <rdar://problem/56505415>
+// Enabled: Yes.
+
+#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_DNS_PROXY_DNS64)
+    #define MDNSRESPONDER_SUPPORTS_APPLE_DNS_PROXY_DNS64            1
+#endif
+
 // Feature: DNS64 IPv6 synthesis.
 // Radar:   <rdar://problem/32297396>
 // Enabled: Yes, but only for iOS and macOS, which support the DNS proxy network extension.
     #define MDNSRESPONDER_SUPPORTS_APPLE_PREALLOCATED_CACHE         0
 #endif
 
+// Feature: Use mdns_querier objects for DNS transports.
+// Radar:   <rdar://problem/55746371>
+// Enabled: Yes.
+
+#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_QUERIER)
+    #define MDNSRESPONDER_SUPPORTS_APPLE_QUERIER                    1
+#endif
+
 // Feature: Randomized AWDL Hostname
 // Radar:   <rdar://problem/47525004>
 // Enabled: Yes.
     #define MDNSRESPONDER_SUPPORTS_APPLE_SLOW_ACTIVATION            0
 #endif
 
-// Feature: Suspicious Reply Defense
-// Radar:   <rdar://problem/50050767>
-// Enabled: Yes.
-
-#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_SUSPICIOUS_REPLY_DEFENSE)
-    #define MDNSRESPONDER_SUPPORTS_APPLE_SUSPICIOUS_REPLY_DEFENSE   1
-#endif
-
-// Feature: Symptoms Reporting
-// Radar:   <rdar://problem/20194922>
-// Enabled: Yes.
-
-#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_SYMPTOMS)
-    #define MDNSRESPONDER_SUPPORTS_APPLE_SYMPTOMS                   1
-#endif
-
 // Feature: Support for performing dot-local queries via mDNS and DNS in parallel.
 // Radar:   <rdar://problem/4786302>
 // Enabled: Yes.
     #endif
 #endif
 
+// Feature: Support for Analytics For Cache
+// Radar:   <rdar://problem/52206048>
+// Enabled: iOS & macOS
+
+#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_CACHE_ANALYTICS)
+       #if !(defined(TARGET_OS_IOS) && defined(TARGET_OS_OSX))
+               #error "Expected TARGET_OS_IOS && TARGET_OS_OSX to be defined."
+       #endif
+       #if (TARGET_OS_IOS || TARGET_OS_OSX)
+               #define MDNSRESPONDER_SUPPORTS_APPLE_CACHE_ANALYTICS        1
+       #else
+               #define MDNSRESPONDER_SUPPORTS_APPLE_CACHE_ANALYTICS        0
+       #endif
+#endif
+
+// Feature: Support for Analytics For WAB (Wide Area Bonjour)
+// Radar:   <rdar://problem/52136688>
+// Enabled: iOS & macOS
+
+#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_WAB_ANALYTICS)
+       #define MDNSRESPONDER_SUPPORTS_APPLE_WAB_ANALYTICS                      0
+#endif
+
+// Feature: Support for Analytics
+// Enabled: If any analytics are enabled (above)
+
+#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_ANALYTICS)
+       #if (MDNSRESPONDER_SUPPORTS_APPLE_CACHE_ANALYTICS || MDNSRESPONDER_SUPPORTS_APPLE_WAB_ANALYTICS)
+               #define MDNSRESPONDER_SUPPORTS_APPLE_ANALYTICS                  1
+       #else
+               #define MDNSRESPONDER_SUPPORTS_APPLE_ANALYTICS                  0
+       #endif
+#endif
+
+// Feature: Add audit token to questions
+// Radar:      <rdar://problem/59042213>
+// Enabled: On all Apple platforms
+
+#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_AUDIT_TOKEN)
+       #define MDNSRESPONDER_SUPPORTS_APPLE_AUDIT_TOKEN                                1
+#endif
+
+// Feature: Enforce entitlements prompts
+// Radar:      <rdar://problem/55922132>
+// Enabled: iOS only (depends on MDNSRESPONDER_SUPPORTS_APPLE_AUDIT_TOKEN)
+
+#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_TRUST_ENFORCEMENT)
+       #if MDNSRESPONDER_SUPPORTS_APPLE_AUDIT_TOKEN
+               #if (TARGET_OS_IOS)
+                       #define MDNSRESPONDER_SUPPORTS_APPLE_TRUST_ENFORCEMENT  1
+               #else
+                       #define MDNSRESPONDER_SUPPORTS_APPLE_TRUST_ENFORCEMENT  0
+               #endif
+       #else
+               #define MDNSRESPONDER_SUPPORTS_APPLE_TRUST_ENFORCEMENT          0
+       #endif
+#endif
+
+// Feature: Symptoms Reporting
+// Radar:   <rdar://problem/20194922>
+// Enabled: Yes. (depends on MDNSRESPONDER_SUPPORTS_APPLE_AUDIT_TOKEN)
+
+#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_SYMPTOMS)
+       #if MDNSRESPONDER_SUPPORTS_APPLE_AUDIT_TOKEN
+               #define MDNSRESPONDER_SUPPORTS_APPLE_SYMPTOMS                   1
+       #else
+               #define MDNSRESPONDER_SUPPORTS_APPLE_SYMPTOMS                   0
+       #endif
+#endif
+
+#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_IPC_TLV)
+    #define MDNSRESPONDER_SUPPORTS_APPLE_IPC_TLV                    1
+#endif
+
+// Feature: DNSSEC support
+// Radar:   <rdar://problem/55275552>
+// Enabled: On all Apple platforms
+// Notes:
+//             MDNSRESPONDER_SUPPORTS(APPLE, QUERIER) should always be true if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+//             is true, except for only one case, the Tests target that runs XCTest. In the XCTest,
+//             MDNSRESPONDER_SUPPORTS_APPLE_DNSSECv2 is predefined in the preprocess which does not check what
+//             MDNSRESPONDER_SUPPORTS checks. In order to test DNSSEC functions in XCtest without querier support, we
+//             will wrap all DNSSEC code that calls querier, since the code will never be excuted in XCTest.
+#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_DNSSECv2)
+       #if MDNSRESPONDER_SUPPORTS_APPLE_QUERIER
+               #define MDNSRESPONDER_SUPPORTS_APPLE_DNSSECv2                   0
+       #else
+               #define MDNSRESPONDER_SUPPORTS_APPLE_DNSSECv2                   0
+       #endif
+#endif
+
 #endif  // __ApplePlatformFeatures_h
diff --git a/mDNSMacOSX/CryptoSupport.c b/mDNSMacOSX/CryptoSupport.c
deleted file mode 100644 (file)
index 5ed3dee..0000000
+++ /dev/null
@@ -1,779 +0,0 @@
-/*
- * Copyright (c) 2011-2019 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// ***************************************************************************
-// CryptoSupport.c
-// Supporting routines for DNSSEC crypto
-// ***************************************************************************
-
-#include "mDNSEmbeddedAPI.h"
-#include <CommonCrypto/CommonDigest.h>  // For Hash algorithms SHA1 etc.
-#include <dispatch/dispatch.h>          // For Base32/Base64 encoding/decoding
-#include <dispatch/private.h>          // dispatch_data_create_with_transform
-#include "CryptoAlg.h"
-#include "CryptoSupport.h"
-#include "dnssec.h"
-#include "DNSSECSupport.h"
-
-#if TARGET_OS_IPHONE
-#include <Security/SecRSAKey.h>                  // For RSA_SHA1 etc. verification
-#else
-#include <Security/Security.h>
-#endif
-
-#if !TARGET_OS_IPHONE
-mDNSlocal SecKeyRef SecKeyCreateRSAPublicKey_OSX(unsigned char *asn1, int length);
-#endif
-
-typedef struct
-{
-    dispatch_data_t encData;
-    dispatch_data_t encMap;
-    dispatch_data_t encNULL;
-}encContext;
-
-mDNSlocal mStatus enc_create(AlgContext *ctx)
-{
-    encContext *ptr;
-
-    switch (ctx->alg)
-    {
-    case ENC_BASE32:
-    case ENC_BASE64:
-        ptr = (encContext *) mDNSPlatformMemAllocateClear(sizeof(*ptr));
-        if (!ptr) return mStatus_NoMemoryErr;
-        break;
-    default:
-        LogMsg("enc_create: Unsupported algorithm %d", ctx->alg);
-        return mStatus_BadParamErr;
-    }
-    ptr->encData = NULL;
-    ptr->encMap = NULL;
-    // The encoded data is not NULL terminated. So, we concatenate a null byte later when we encode and map
-    // the real data.
-    ptr->encNULL = dispatch_data_create("", 1, dispatch_get_global_queue(0, 0), ^{});
-    if (!ptr->encNULL)
-    {
-        mDNSPlatformMemFree(ptr);
-        return mStatus_NoMemoryErr;
-    }
-    ctx->context = ptr;
-    return mStatus_NoError;
-}
-
-mDNSlocal mStatus enc_destroy(AlgContext *ctx)
-{
-    encContext *ptr = (encContext *)ctx->context;
-    if (ptr->encData) dispatch_release(ptr->encData);
-    if (ptr->encMap) dispatch_release(ptr->encMap);
-    if (ptr->encNULL) dispatch_release(ptr->encNULL);
-    mDNSPlatformMemFree(ptr);
-    return mStatus_NoError;
-}
-
-mDNSlocal mStatus enc_add(AlgContext *ctx, const void *data, mDNSu32 len)
-{
-    switch (ctx->alg)
-    {
-    case ENC_BASE32:
-    case ENC_BASE64:
-    {
-        encContext *ptr = (encContext *)ctx->context;
-        dispatch_data_t src_data = dispatch_data_create(data, len, dispatch_get_global_queue(0, 0), ^{});
-        if (!src_data)
-        {
-            LogMsg("enc_add: dispatch_data_create src failed");
-            return mStatus_BadParamErr;
-        }
-        dispatch_data_t dest_data = dispatch_data_create_with_transform(src_data, DISPATCH_DATA_FORMAT_TYPE_NONE,
-                                                                        (ctx->alg == ENC_BASE32 ? DISPATCH_DATA_FORMAT_TYPE_BASE32HEX : DISPATCH_DATA_FORMAT_TYPE_BASE64));
-        dispatch_release(src_data);
-        if (!dest_data)
-        {
-            LogMsg("enc_add: dispatch_data_create dst failed");
-            return mStatus_BadParamErr;
-        }
-        ptr->encData = dest_data;
-
-        return mStatus_NoError;
-    }
-    default:
-        LogMsg("enc_add: Unsupported algorithm %d", ctx->alg);
-        return mStatus_BadParamErr;
-    }
-}
-
-mDNSlocal mDNSu8* enc_encode(AlgContext *ctx)
-{
-    const void *result = NULL;
-
-    switch (ctx->alg)
-    {
-    case ENC_BASE32:
-    case ENC_BASE64:
-    {
-        encContext *ptr = (encContext *)ctx->context;
-        size_t size;
-        dispatch_data_t dest_data = ptr->encData;
-        dispatch_data_t data = dispatch_data_create_concat(dest_data, ptr->encNULL);
-
-        if (!data)
-        {
-            LogMsg("enc_encode: cannot concatenate");
-            return NULL;
-        }
-
-        dispatch_data_t map = dispatch_data_create_map(data, &result, &size);
-        if (!map)
-        {
-            LogMsg("enc_encode: cannot create map %d", ctx->alg);
-            return NULL;
-        }
-        dispatch_release(dest_data);
-        ptr->encData = data;
-        ptr->encMap = map;
-
-        return (mDNSu8 *)result;
-    }
-    default:
-        LogMsg("enc_encode: Unsupported algorithm %d", ctx->alg);
-        return mDNSNULL;
-    }
-}
-
-mDNSlocal mStatus sha_create(AlgContext *ctx)
-{
-    mDNSu8 *ptr;
-    switch (ctx->alg)
-    {
-    case SHA1_DIGEST_TYPE:
-        ptr = (mDNSu8 *) mDNSPlatformMemAllocate(sizeof(CC_SHA1_CTX));
-        if (!ptr) return mStatus_NoMemoryErr;
-        CC_SHA1_Init((CC_SHA1_CTX *)ptr);
-        break;
-    case SHA256_DIGEST_TYPE:
-        ptr = (mDNSu8 *) mDNSPlatformMemAllocate(sizeof(CC_SHA256_CTX));
-        if (!ptr) return mStatus_NoMemoryErr;
-        CC_SHA256_Init((CC_SHA256_CTX *)ptr);
-        break;
-    default:
-        LogMsg("sha_create: Unsupported algorithm %d", ctx->alg);
-        return mStatus_BadParamErr;
-    }
-    ctx->context = ptr;
-    return mStatus_NoError;
-}
-
-mDNSlocal mStatus sha_destroy(AlgContext *ctx)
-{
-    mDNSPlatformMemFree(ctx->context);
-    return mStatus_NoError;
-}
-
-mDNSlocal mDNSu32 sha_len(AlgContext *ctx)
-{
-    switch (ctx->alg)
-    {
-    case SHA1_DIGEST_TYPE:
-        return CC_SHA1_DIGEST_LENGTH;
-    case SHA256_DIGEST_TYPE:
-        return CC_SHA256_DIGEST_LENGTH;
-    default:
-        LogMsg("sha_len: Unsupported algorithm %d", ctx->alg);
-        return mStatus_BadParamErr;
-    }
-}
-
-mDNSlocal mStatus sha_add(AlgContext *ctx, const void *data, mDNSu32 len)
-{
-    switch (ctx->alg)
-    {
-    case SHA1_DIGEST_TYPE:
-        CC_SHA1_Update((CC_SHA1_CTX *)ctx->context, data, len);
-        break;
-    case SHA256_DIGEST_TYPE:
-        CC_SHA256_Update((CC_SHA256_CTX *)ctx->context, data, len);
-        break;
-    default:
-        LogMsg("sha_add: Unsupported algorithm %d", ctx->alg);
-        return mStatus_BadParamErr;
-    }
-    return mStatus_NoError;
-}
-
-mDNSlocal mStatus sha_verify(AlgContext *ctx, mDNSu8 *key, mDNSu32 keylen, mDNSu8 *digestIn, mDNSu32 dlen)
-{
-    mDNSu8 digest[CC_SHA512_DIGEST_LENGTH];
-    mDNSu32 digestLen;
-
-    (void) key;   //unused
-    (void)keylen; //unused
-    switch (ctx->alg)
-    {
-    case SHA1_DIGEST_TYPE:
-        digestLen = CC_SHA1_DIGEST_LENGTH;
-        CC_SHA1_Final(digest, (CC_SHA1_CTX *)ctx->context);
-        break;
-    case SHA256_DIGEST_TYPE:
-        digestLen = CC_SHA256_DIGEST_LENGTH;
-        CC_SHA256_Final(digest, (CC_SHA256_CTX *)ctx->context);
-        break;
-    default:
-        LogMsg("sha_verify: Unsupported algorithm %d", ctx->alg);
-        return mStatus_BadParamErr;
-    }
-    if (dlen != digestLen)
-    {
-        LogMsg("sha_verify(Alg %d): digest len mismatch len %u, expected %u", ctx->alg, (unsigned int)dlen, (unsigned int)digestLen);
-        return mStatus_BadParamErr;
-    }
-    if (!memcmp(digest, digestIn, digestLen))
-        return mStatus_NoError;
-    else
-        return mStatus_NoAuth;
-}
-
-mDNSlocal mStatus sha_final(AlgContext *ctx, void *digestOut, mDNSu32 dlen)
-{
-    mDNSu8 digest[CC_SHA512_DIGEST_LENGTH];
-    mDNSu32 digestLen;
-
-    switch (ctx->alg)
-    {
-    case SHA1_DIGEST_TYPE:
-        digestLen = CC_SHA1_DIGEST_LENGTH;
-        CC_SHA1_Final(digest, (CC_SHA1_CTX *)ctx->context);
-        break;
-    case SHA256_DIGEST_TYPE:
-        digestLen = CC_SHA256_DIGEST_LENGTH;
-        CC_SHA256_Final(digest, (CC_SHA256_CTX *)ctx->context);
-        break;
-    default:
-        LogMsg("sha_final: Unsupported algorithm %d", ctx->alg);
-        return mStatus_BadParamErr;
-    }
-    if (dlen != digestLen)
-    {
-        LogMsg("sha_final(Alg %d): digest len mismatch len %u, expected %u", ctx->alg, (unsigned int)dlen, (unsigned int)digestLen);
-        return mStatus_BadParamErr;
-    }
-    memcpy(digestOut, digest, digestLen);
-    return mStatus_NoError;
-}
-
-mDNSlocal mStatus rsa_sha_create(AlgContext *ctx)
-{
-    mDNSu8 *ptr;
-    switch (ctx->alg)
-    {
-    case CRYPTO_RSA_NSEC3_SHA1:
-    case CRYPTO_RSA_SHA1:
-        ptr = (mDNSu8 *) mDNSPlatformMemAllocate(sizeof(CC_SHA1_CTX));
-        if (!ptr) return mStatus_NoMemoryErr;
-        CC_SHA1_Init((CC_SHA1_CTX *)ptr);
-        break;
-    case CRYPTO_RSA_SHA256:
-        ptr = (mDNSu8 *) mDNSPlatformMemAllocate(sizeof(CC_SHA256_CTX));
-        if (!ptr) return mStatus_NoMemoryErr;
-        CC_SHA256_Init((CC_SHA256_CTX *)ptr);
-        break;
-    case CRYPTO_RSA_SHA512:
-        ptr = (mDNSu8 *) mDNSPlatformMemAllocate(sizeof(CC_SHA512_CTX));
-        if (!ptr) return mStatus_NoMemoryErr;
-        CC_SHA512_Init((CC_SHA512_CTX *)ptr);
-        break;
-    default:
-        LogMsg("rsa_sha_create: Unsupported algorithm %d", ctx->alg);
-        return mStatus_BadParamErr;
-    }
-    ctx->context = ptr;
-    return mStatus_NoError;
-}
-
-mDNSlocal mStatus rsa_sha_destroy(AlgContext *ctx)
-{
-    mDNSPlatformMemFree(ctx->context);
-    return mStatus_NoError;
-}
-
-mDNSlocal mDNSu32 rsa_sha_len(AlgContext *ctx)
-{
-    switch (ctx->alg)
-    {
-    case CRYPTO_RSA_NSEC3_SHA1:
-    case CRYPTO_RSA_SHA1:
-        return CC_SHA1_DIGEST_LENGTH;
-    case CRYPTO_RSA_SHA256:
-        return CC_SHA256_DIGEST_LENGTH;
-    case CRYPTO_RSA_SHA512:
-        return CC_SHA512_DIGEST_LENGTH;
-    default:
-        LogMsg("rsa_sha_len: Unsupported algorithm %d", ctx->alg);
-        return mStatus_BadParamErr;
-    }
-}
-
-mDNSlocal mStatus rsa_sha_add(AlgContext *ctx, const void *data, mDNSu32 len)
-{
-    switch (ctx->alg)
-    {
-    case CRYPTO_RSA_NSEC3_SHA1:
-    case CRYPTO_RSA_SHA1:
-        CC_SHA1_Update((CC_SHA1_CTX *)ctx->context, data, len);
-        break;
-    case CRYPTO_RSA_SHA256:
-        CC_SHA256_Update((CC_SHA256_CTX *)ctx->context, data, len);
-        break;
-    case CRYPTO_RSA_SHA512:
-        CC_SHA512_Update((CC_SHA512_CTX *)ctx->context, data, len);
-        break;
-    default:
-        LogMsg("rsa_sha_add: Unsupported algorithm %d", ctx->alg);
-        return mStatus_BadParamErr;
-    }
-    return mStatus_NoError;
-}
-
-mDNSlocal SecKeyRef rfc3110_import(const mDNSu8 *data, const mDNSu32 len)
-{
-    static const int max_modulus_bytes = 512;    // Modulus is limited to 4096 bits (512 octets) in length.
-    static const int max_exp_bytes = 512;        // Exponent is limited to 4096 bits (512 octets) in length.
-    static const int asn1_type_bytes = 3;        // Since there is an ASN1 SEQ and two INTs.
-    static const int asn1_max_len_bytes = 3 * 3; // Capped at 3 due to max payload size.
-    unsigned char asn1[max_modulus_bytes + 1 + max_exp_bytes + asn1_type_bytes + asn1_max_len_bytes]; // +1 is for leading 0 for non negative asn1 number
-    const mDNSu8 *modulus;
-    unsigned int modulus_length;
-    const mDNSu8 *exponent;
-    unsigned int exp_length;
-    unsigned int num_length_bytes;
-    mDNSu32 index = 0;
-    mDNSu32 asn1_length = 0;
-
-    // Validate Input
-    if (!data)
-        return NULL;
-
-    // we have to have at least 1 byte for the length
-    if (len < 1)
-        return NULL;
-
-    // Parse Modulus and Exponent
-
-    // If the first byte is zero, then the exponent length is in the three-byte format, otherwise the length is in the first byte.
-    if (data[0] == 0)
-    {
-        if (len < 3)
-            return NULL;
-        exp_length = (data[1] << 8) | data[2];
-        num_length_bytes = 3;
-    }
-    else
-    {
-        exp_length = data[0];
-        num_length_bytes = 1;
-    }
-
-    // RFC3110 limits the exponent length to 4096 bits (512 octets).
-    if (exp_length > 512)
-        return NULL;
-
-    // We have to have at least len bytes + size of exponent.
-    if (len < (num_length_bytes + exp_length))
-        return NULL;
-
-    // The modulus is the remaining space.
-    modulus_length = len - (num_length_bytes + exp_length);
-
-    // RFC3110 limits the modulus length to 4096 bits (512 octets).
-    if (modulus_length > 512)
-        return NULL;
-
-    if (modulus_length < 1)
-        return NULL;
-
-    // add 1 to modulus length for pre-ceding 0 t make ASN1 value non-negative
-    ++modulus_length;
-
-    exponent = &data[num_length_bytes];
-    modulus = &data[num_length_bytes + exp_length];
-
-    // 2 bytes for commands since first doesn't count
-    // 2 bytes for min 1 byte length field
-    asn1_length = modulus_length + exp_length + 2 + 2;
-
-    // Account for modulus length causing INT length field to grow.
-    if (modulus_length >= 128)
-    {
-        if (modulus_length > 255)
-            asn1_length += 2;
-        else
-            asn1_length += 1;
-    }
-
-    // Account for exponent length causing INT length field to grow.
-    if (exp_length >= 128)
-    {
-        if (exp_length > 255)
-            asn1_length += 2;
-        else
-            asn1_length += 1;
-    }
-
-    // Construct ASN1 formatted public key
-    // Write ASN1 SEQ byte
-    asn1[index++] = 0x30;
-
-    // Write ASN1 length for SEQ
-    if (asn1_length < 128)
-    {
-        asn1[index++] = asn1_length & 0xFF;
-    }
-    else
-    {
-        asn1[index++] = (0x80 | ((asn1_length > 255) ? 2 : 1));
-        if (asn1_length > 255)
-            asn1[index++] = (asn1_length & 0xFF00) >> 8;
-        asn1[index++] = asn1_length & 0xFF;
-    }
-
-    // Write ASN1 INT for modulus
-    asn1[index++] = 0x02;
-    // Write ASN1 length for INT
-    if (modulus_length < 128)
-    {
-        asn1[index++] = modulus_length & 0xFF;
-    }
-    else
-    {
-        asn1[index++] = 0x80 | ((modulus_length > 255) ? 2 : 1);
-        if (modulus_length > 255)
-            asn1[index++] = (modulus_length & 0xFF00) >> 8;
-        asn1[index++] = modulus_length & 0xFF;
-    }
-
-    // Write preceding 0 so our integer isn't negative
-    asn1[index++] = 0x00;
-    // Write actual modulus (-1 for preceding 0)
-    memcpy(&asn1[index], modulus, modulus_length - 1);
-    index += (modulus_length - 1);
-
-    // Write ASN1 INT for exponent
-    asn1[index++] = 0x02;
-    // Write ASN1 length for INT
-    if (exp_length < 128)
-    {
-        asn1[index++] = exp_length & 0xFF;
-    }
-    else
-    {
-        asn1[index++] = 0x80 | ((exp_length > 255) ? 2 : 1);
-        if (exp_length > 255)
-            asn1[index++] = (exp_length & 0xFF00) >> 8;
-        asn1[index++] = exp_length & 0xFF;
-    }
-    // Write exponent bytes
-    memcpy(&asn1[index], exponent, exp_length);
-    index += exp_length;
-
-#if TARGET_OS_IPHONE
-    // index contains bytes written, use it for length
-    return (SecKeyCreateRSAPublicKey(NULL, asn1, index, kSecKeyEncodingPkcs1));
-#else
-    return (SecKeyCreateRSAPublicKey_OSX(asn1, index));
-#endif
-}
-
-#if TARGET_OS_IPHONE
-mDNSlocal mStatus rsa_sha_verify(AlgContext *ctx, mDNSu8 *key, mDNSu32 keylen, mDNSu8 *signature, mDNSu32 siglen)
-{
-    SecKeyRef keyref;
-    OSStatus result;
-    mDNSu8 digest[CC_SHA512_DIGEST_LENGTH];
-    int digestlen;
-    int cryptoAlg;
-
-    switch (ctx->alg)
-    {
-    case CRYPTO_RSA_NSEC3_SHA1:
-    case CRYPTO_RSA_SHA1:
-        cryptoAlg = kSecPaddingPKCS1SHA1;
-        digestlen = CC_SHA1_DIGEST_LENGTH;
-        CC_SHA1_Final(digest, (CC_SHA1_CTX *)ctx->context);
-        break;
-    case CRYPTO_RSA_SHA256:
-        cryptoAlg = kSecPaddingPKCS1SHA256;
-        digestlen = CC_SHA256_DIGEST_LENGTH;
-        CC_SHA256_Final(digest, (CC_SHA256_CTX *)ctx->context);
-        break;
-    case CRYPTO_RSA_SHA512:
-        cryptoAlg = kSecPaddingPKCS1SHA512;
-        digestlen = CC_SHA512_DIGEST_LENGTH;
-        CC_SHA512_Final(digest, (CC_SHA512_CTX *)ctx->context);
-        break;
-    default:
-        LogMsg("rsa_sha_verify: Unsupported algorithm %d", ctx->alg);
-        return mStatus_BadParamErr;
-    }
-
-    keyref = rfc3110_import(key, keylen);
-    if (!keyref)
-    {
-        LogMsg("rsa_sha_verify: Error decoding rfc3110 key data");
-        return mStatus_NoMemoryErr;
-    }
-    result = SecKeyRawVerify(keyref, cryptoAlg, digest, digestlen, signature, siglen);
-    CFRelease(keyref);
-    if (result != noErr)
-    {
-        LogMsg("rsa_sha_verify: Failed for alg %d", ctx->alg);
-        return mStatus_BadParamErr;
-    }
-    else
-    {
-        LogInfo("rsa_sha_verify: Passed for alg %d", ctx->alg);
-        return mStatus_NoError;
-    }
-}
-#else // TARGET_OS_IPHONE
-
-mDNSlocal SecKeyRef SecKeyCreateRSAPublicKey_OSX(unsigned char *asn1, int length)
-{
-    SecKeyRef result = NULL;
-    
-    SecExternalFormat extFormat = kSecFormatBSAFE;
-    SecExternalItemType itemType = kSecItemTypePublicKey;
-    CFArrayRef outArray = NULL;
-
-    CFDataRef keyData = CFDataCreate(NULL, asn1, length);
-    if (!keyData)
-        return NULL;
-
-    OSStatus err =  SecItemImport(keyData, NULL, &extFormat, &itemType, 0, NULL, NULL, &outArray);
-    
-    CFRelease(keyData);
-    if (noErr != err || outArray == NULL)
-    {
-        if (outArray)
-            CFRelease(outArray);
-        return NULL;
-    }
-    
-    result = (SecKeyRef)CFArrayGetValueAtIndex(outArray, 0);
-    if (result == NULL)
-    {
-        CFRelease(outArray);
-        return NULL;
-    }
-    
-    CFRetain(result);
-    CFRelease(outArray);
-    return result;
-}
-
-mDNSlocal Boolean VerifyData(SecKeyRef key, CFStringRef digestStr, mDNSu8 *digest, int dlen, int digestlenAttr, mDNSu8 *sig, int siglen, CFStringRef digest_type)
-{
-    CFErrorRef error;
-    Boolean ret;
-    
-    CFDataRef signature = CFDataCreate(NULL, sig, siglen);
-    if (!signature)
-        return false;
-    
-    SecTransformRef verifyXForm = SecVerifyTransformCreate(key, signature, &error);
-    CFRelease(signature);
-    if (verifyXForm == NULL)
-    {
-        return false;
-    }
-    
-    // tell the transform what type of data it is geting
-    if (!SecTransformSetAttribute(verifyXForm, kSecInputIsAttributeName, digest_type, &error))
-    {
-        LogMsg("VerifyData: SecTransformSetAttribute digest_type");
-        goto err;
-    }
-    
-    if (!SecTransformSetAttribute(verifyXForm, kSecDigestTypeAttribute, digestStr, &error))
-    {
-        LogMsg("VerifyData: SecTransformSetAttribute digestStr");
-        goto err;
-    }
-    
-    CFNumberRef digestLengthRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &digestlenAttr);
-    if (digestLengthRef == NULL)
-    {
-        LogMsg("VerifyData: CFNumberCreate failed");
-        goto err;
-    }
-    
-    ret = SecTransformSetAttribute(verifyXForm, kSecDigestLengthAttribute, digestLengthRef, &error);
-    CFRelease(digestLengthRef);
-    if (!ret)
-    {
-        LogMsg("VerifyData: SecTransformSetAttribute digestLengthRef");
-        goto err;
-    }
-    
-    CFDataRef dataToSign = CFDataCreate(NULL, digest, dlen);
-    if (dataToSign ==  NULL)
-    {
-        LogMsg("VerifyData: CFDataCreate failed");
-        goto err;
-    }
-
-    ret = SecTransformSetAttribute(verifyXForm, kSecTransformInputAttributeName, dataToSign, &error);
-    CFRelease(dataToSign);
-    if (!ret)
-    {
-        LogMsg("VerifyData: SecTransformSetAttribute TransformAttributeName");
-        goto err;
-    }
-    
-    CFBooleanRef boolRef = SecTransformExecute(verifyXForm, &error);
-    ret = (boolRef != NULL) ? CFBooleanGetValue(boolRef) : false;
-    if (boolRef != NULL) CFRelease(boolRef);
-    CFRelease(verifyXForm);
-
-    if (error != NULL)
-    {
-        CFStringRef errStr = CFErrorCopyDescription(error);
-        char errorbuf[128];
-        errorbuf[0] = 0;
-        if (errStr != NULL)
-        {
-            if (!CFStringGetCString(errStr, errorbuf, sizeof(errorbuf), kCFStringEncodingUTF8))
-            {
-                LogMsg("VerifyData: CFStringGetCString failed");
-            }
-            CFRelease(errStr);
-        }
-        LogMsg("VerifyData: SecTransformExecute failed with %s", errorbuf);
-        return false;
-    }
-    return ret;
-err:
-    CFRelease(verifyXForm);
-    return false;
-}
-
-mDNSlocal mStatus rsa_sha_verify(AlgContext *ctx, mDNSu8 *key, mDNSu32 keylen, mDNSu8 *signature, mDNSu32 siglen)
-{
-    SecKeyRef keyref;
-    mDNSu8 digest[CC_SHA512_DIGEST_LENGTH];
-    int digestlen;
-    int digestlenAttr;
-    CFStringRef digestStr;
-    mDNSBool ret;
-
-    switch (ctx->alg)
-    {
-    case CRYPTO_RSA_NSEC3_SHA1:
-    case CRYPTO_RSA_SHA1:
-        digestStr = kSecDigestSHA1;
-        digestlen = CC_SHA1_DIGEST_LENGTH;
-        digestlenAttr = 0;
-        CC_SHA1_Final(digest, (CC_SHA1_CTX *)ctx->context);
-        break;
-    case CRYPTO_RSA_SHA256:
-        digestStr = kSecDigestSHA2;
-        digestlen = CC_SHA256_DIGEST_LENGTH;
-        digestlenAttr = 256;
-        CC_SHA256_Final(digest, (CC_SHA256_CTX *)ctx->context);
-        break;
-    case CRYPTO_RSA_SHA512:
-        digestStr = kSecDigestSHA2;
-        digestlen = CC_SHA512_DIGEST_LENGTH;
-        digestlenAttr = 512;
-        CC_SHA512_Final(digest, (CC_SHA512_CTX *)ctx->context);
-        break;
-    default:
-        LogMsg("rsa_sha_verify: Unsupported algorithm %d", ctx->alg);
-        return mStatus_BadParamErr;
-    }
-
-    keyref = rfc3110_import(key, keylen);
-    if (!keyref)
-    {
-        LogMsg("rsa_sha_verify: Error decoding rfc3110 key data");
-        return mStatus_NoMemoryErr;
-    }
-    ret = VerifyData(keyref, digestStr, digest, digestlen, digestlenAttr, signature, siglen, kSecInputIsDigest);
-    CFRelease(keyref);
-    if (!ret)
-    {
-        LogMsg("rsa_sha_verify: Failed for alg %d", ctx->alg);
-        return mStatus_BadParamErr;
-    }
-    else
-    {
-        LogInfo("rsa_sha_verify: Passed for alg %d", ctx->alg);
-        return mStatus_NoError;
-    }
-}
-#endif // TARGET_OS_IPHONE
-
-AlgFuncs sha_funcs = {sha_create, sha_destroy, sha_len, sha_add, sha_verify, mDNSNULL, sha_final};
-AlgFuncs rsa_sha_funcs = {rsa_sha_create, rsa_sha_destroy, rsa_sha_len, rsa_sha_add, rsa_sha_verify, mDNSNULL, mDNSNULL};
-AlgFuncs enc_funcs = {enc_create, enc_destroy, mDNSNULL, enc_add, mDNSNULL, enc_encode, mDNSNULL};
-
-#ifndef DNSSEC_DISABLED
-
-mDNSexport mStatus DNSSECCryptoInit(mDNS *const m)
-{
-    mStatus result;
-
-    result = DigestAlgInit(SHA1_DIGEST_TYPE, &sha_funcs);
-    if (result != mStatus_NoError)
-        return result;
-    result = DigestAlgInit(SHA256_DIGEST_TYPE, &sha_funcs);
-    if (result != mStatus_NoError)
-        return result;
-    result = CryptoAlgInit(CRYPTO_RSA_SHA1, &rsa_sha_funcs);
-    if (result != mStatus_NoError)
-        return result;
-    result = CryptoAlgInit(CRYPTO_RSA_NSEC3_SHA1, &rsa_sha_funcs);
-    if (result != mStatus_NoError)
-        return result;
-    result = CryptoAlgInit(CRYPTO_RSA_SHA256, &rsa_sha_funcs);
-    if (result != mStatus_NoError)
-        return result;
-    result = CryptoAlgInit(CRYPTO_RSA_SHA512, &rsa_sha_funcs);
-    if (result != mStatus_NoError)
-        return result;
-    result = EncAlgInit(ENC_BASE32, &enc_funcs);
-    if (result != mStatus_NoError)
-        return result;
-    result = EncAlgInit(ENC_BASE64, &enc_funcs);
-    if (result != mStatus_NoError)
-        return result;
-
-    result = DNSSECPlatformInit(m);
-
-    return result;
-}
-
-#else // !DNSSEC_DISABLED
-
-mDNSexport mStatus DNSSECCryptoInit(mDNS *const m)
-{
-    (void) m;
-    
-    return mStatus_NoError;
-}
-
-#endif // !DNSSEC_DISABLED
-
-
diff --git a/mDNSMacOSX/CryptoSupport.h b/mDNSMacOSX/CryptoSupport.h
deleted file mode 100644 (file)
index 8625ba7..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/* -*- Mode: C; tab-width: 4 -*-
- *
- * Copyright (c) 2011 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __CRYPTO_SUPPORT_H
-#define __CRYPTO_SUPPORT_H
-
-extern mStatus DNSSECCryptoInit(mDNS *const m);
-
-#endif // __CRYPTO_SUPPORT_H
index 4194d6f86ea8c378aa5439e0ff97d764aac556f8..4070f2eaf91c9b3147114554f80cb8fad32f9e02 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002-2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
 #include "dns_sd_internal.h"
 #include "uds_daemon.h"
 #include "BLE.h"
+#include "mdns_powerlog.h"
 
 D2DStatus D2DInitialize(CFRunLoopRef runLoop, D2DServiceCallback serviceCallback, void* userData) __attribute__((weak_import));
 D2DStatus D2DRetain(D2DServiceInstance instanceHandle, D2DTransportType transportType) __attribute__((weak_import));
@@ -47,9 +48,9 @@ mDNSexport void D2D_start_advertising_interface(NetworkInterfaceInfo *interface)
             LogInfo("D2D_start_advertising_interface: %s", interface->ifname);
 
         if (interface->RR_A.resrec.RecordType)
-            external_start_advertising_service(&interface->RR_A.resrec, 0);
+            external_start_advertising_service(&interface->RR_A.resrec, 0, 0);
         if (interface->RR_PTR.resrec.RecordType)
-            external_start_advertising_service(&interface->RR_PTR.resrec, 0);
+            external_start_advertising_service(&interface->RR_PTR.resrec, 0, 0);
     }
 }
 
@@ -62,9 +63,9 @@ mDNSexport void D2D_stop_advertising_interface(NetworkInterfaceInfo *interface)
             LogInfo("D2D_stop_advertising_interface: %s", interface->ifname);
 
         if (interface->RR_A.resrec.RecordType)
-            external_stop_advertising_service(&interface->RR_A.resrec, 0);
+            external_stop_advertising_service(&interface->RR_A.resrec, 0, 0);
         if (interface->RR_PTR.resrec.RecordType)
-            external_stop_advertising_service(&interface->RR_PTR.resrec, 0);
+            external_stop_advertising_service(&interface->RR_PTR.resrec, 0, 0);
     }
 }
 
@@ -74,7 +75,7 @@ mDNSexport void D2D_stop_advertising_record(AuthRecord *ar)
     DNSServiceFlags flags = deriveD2DFlagsFromAuthRecType(ar->ARType);
     if (callExternalHelpers(ar->resrec.InterfaceID, ar->resrec.name, flags))
     {
-        external_stop_advertising_service(&ar->resrec, flags);
+        external_stop_advertising_service(&ar->resrec, flags, 0);
     }
 }
 
@@ -84,7 +85,7 @@ mDNSexport void D2D_start_advertising_record(AuthRecord *ar)
     DNSServiceFlags flags = deriveD2DFlagsFromAuthRecType(ar->ARType);
     if (callExternalHelpers(ar->resrec.InterfaceID, ar->resrec.name, flags))
     {
-        external_start_advertising_service(&ar->resrec, flags);
+        external_start_advertising_service(&ar->resrec, flags, 0);
     }
 }
 
@@ -623,7 +624,7 @@ mDNSlocal D2DTransportType xD2DInterfaceToTransportType(mDNSInterfaceID Interfac
     return D2DTransportMax;
 }
 
-mDNSexport void external_start_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const typeDomain, DNS_TypeValues qtype, DNSServiceFlags flags)
+mDNSexport void external_start_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const typeDomain, DNS_TypeValues qtype, DNSServiceFlags flags, pid_t clientPID)
 {
 #if ENABLE_BLE_TRIGGERED_BONJOUR
     // BLE support currently not handled by a D2D plugin
@@ -638,10 +639,10 @@ mDNSexport void external_start_browsing_for_service(mDNSInterfaceID InterfaceID,
     }
     else
 #endif  // ENABLE_BLE_TRIGGERED_BONJOUR
-        internal_start_browsing_for_service(InterfaceID, typeDomain, qtype, flags);
+        internal_start_browsing_for_service(InterfaceID, typeDomain, qtype, flags, clientPID);
 }
 
-mDNSexport void internal_start_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const typeDomain, DNS_TypeValues qtype, DNSServiceFlags flags)
+mDNSexport void internal_start_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const typeDomain, DNS_TypeValues qtype, DNSServiceFlags flags, pid_t clientPID)
 {
     domainname lower;
 
@@ -662,18 +663,32 @@ mDNSexport void internal_start_browsing_for_service(mDNSInterfaceID InterfaceID,
             for (i = 0; i < D2DTransportMax; i++)
             {
                 if (i == excludedTransport) continue;
-                if (D2DStartBrowsingForKeyOnTransport) D2DStartBrowsingForKeyOnTransport(compression_lhs, end - compression_lhs, i);
+                if (D2DStartBrowsingForKeyOnTransport)
+                {
+                    if (i == D2DAWDLTransport)
+                    {
+                        mdns_powerlog_awdl_browse_start(typeDomain->c, qtype, clientPID);
+                    }
+                    D2DStartBrowsingForKeyOnTransport(compression_lhs, end - compression_lhs, i);
+                }
             }
         }
         else
         {
-            if (D2DStartBrowsingForKeyOnTransport) D2DStartBrowsingForKeyOnTransport(compression_lhs, end - compression_lhs, transportType);
+            if (D2DStartBrowsingForKeyOnTransport)
+            {
+                if (transportType == D2DAWDLTransport)
+                {
+                    mdns_powerlog_awdl_browse_start(typeDomain->c, qtype, clientPID);
+                }
+                D2DStartBrowsingForKeyOnTransport(compression_lhs, end - compression_lhs, transportType);
+            }
         }
     }
     D2DBrowseListRetain(&lower, qtype);
 }
 
-mDNSexport void external_stop_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const typeDomain, DNS_TypeValues qtype, DNSServiceFlags flags)
+mDNSexport void external_stop_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const typeDomain, DNS_TypeValues qtype, DNSServiceFlags flags, pid_t clientPID)
 {
 #if ENABLE_BLE_TRIGGERED_BONJOUR
     // BLE support currently not handled by a D2D plugin
@@ -689,10 +704,10 @@ mDNSexport void external_stop_browsing_for_service(mDNSInterfaceID InterfaceID,
     }
     else
 #endif  // ENABLE_BLE_TRIGGERED_BONJOUR
-        internal_stop_browsing_for_service(InterfaceID, typeDomain, qtype, flags);
+        internal_stop_browsing_for_service(InterfaceID, typeDomain, qtype, flags, clientPID);
 }
 
-mDNSexport void internal_stop_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const typeDomain, DNS_TypeValues qtype, DNSServiceFlags flags)
+mDNSexport void internal_stop_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const typeDomain, DNS_TypeValues qtype, DNSServiceFlags flags, pid_t clientPID)
 {
     domainname lower;
 
@@ -714,12 +729,26 @@ mDNSexport void internal_stop_browsing_for_service(mDNSInterfaceID InterfaceID,
             for (i = 0; i < D2DTransportMax; i++)
             {
                 if (i == excludedTransport) continue;
-                if (D2DStopBrowsingForKeyOnTransport) D2DStopBrowsingForKeyOnTransport(compression_lhs, end - compression_lhs, i);
+                if (D2DStopBrowsingForKeyOnTransport)
+                {
+                    D2DStopBrowsingForKeyOnTransport(compression_lhs, end - compression_lhs, i);
+                    if (i == D2DAWDLTransport)
+                    {
+                        mdns_powerlog_awdl_browse_stop(typeDomain->c, qtype, clientPID);
+                    }
+                }
             }
         }
         else
         {
-            if (D2DStopBrowsingForKeyOnTransport) D2DStopBrowsingForKeyOnTransport(compression_lhs, end - compression_lhs, transportType);
+            if (D2DStopBrowsingForKeyOnTransport)
+            {
+                D2DStopBrowsingForKeyOnTransport(compression_lhs, end - compression_lhs, transportType);
+                if (transportType == D2DAWDLTransport)
+                {
+                    mdns_powerlog_awdl_browse_stop(typeDomain->c, qtype, clientPID);
+                }
+            }
         }
 
         // The D2D driver may not generate the D2DServiceLost event for this key after
@@ -729,7 +758,7 @@ mDNSexport void internal_stop_browsing_for_service(mDNSInterfaceID InterfaceID,
     }
 }
 
-mDNSexport void external_start_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags)
+mDNSexport void external_start_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags, pid_t clientPID)
 {
 #if ENABLE_BLE_TRIGGERED_BONJOUR
     if (applyToBLE(resourceRecord->InterfaceID, flags))
@@ -741,10 +770,10 @@ mDNSexport void external_start_advertising_service(const ResourceRecord *const r
     }
     else
 #endif  // ENABLE_BLE_TRIGGERED_BONJOUR
-        internal_start_advertising_service(resourceRecord, flags);
+        internal_start_advertising_service(resourceRecord, flags, clientPID);
 }
 
-mDNSexport void internal_start_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags)
+mDNSexport void internal_start_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags, pid_t clientPID)
 {
     domainname lower;
     mDNSu8 *rhs = NULL;
@@ -770,16 +799,30 @@ mDNSexport void internal_start_advertising_service(const ResourceRecord *const r
         for (i = 0; i < D2DTransportMax; i++)
         {
             if (i == excludedTransport) continue;
-            if (D2DStartAdvertisingPairOnTransport) D2DStartAdvertisingPairOnTransport(compression_lhs, rhs - compression_lhs, rhs, end - rhs, i);
+            if (D2DStartAdvertisingPairOnTransport)
+            {
+                if (i == D2DAWDLTransport)
+                {
+                    mdns_powerlog_awdl_advertise_start(lower.c, resourceRecord->rrtype, clientPID);
+                }
+                D2DStartAdvertisingPairOnTransport(compression_lhs, rhs - compression_lhs, rhs, end - rhs, i);
+            }
         }
     }
     else
     {
-        if (D2DStartAdvertisingPairOnTransport) D2DStartAdvertisingPairOnTransport(compression_lhs, rhs - compression_lhs, rhs, end - rhs, transportType);
+        if (D2DStartAdvertisingPairOnTransport)
+        {
+            if (transportType == D2DAWDLTransport)
+            {
+                mdns_powerlog_awdl_advertise_start(lower.c, resourceRecord->rrtype, clientPID);
+            }
+            D2DStartAdvertisingPairOnTransport(compression_lhs, rhs - compression_lhs, rhs, end - rhs, transportType);
+        }
     }
 }
 
-mDNSexport void external_stop_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags)
+mDNSexport void external_stop_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags, pid_t clientPID)
 {
 #if ENABLE_BLE_TRIGGERED_BONJOUR
     // BLE support currently not handled by a D2D plugin
@@ -792,10 +835,10 @@ mDNSexport void external_stop_advertising_service(const ResourceRecord *const re
     }
     else
 #endif  // ENABLE_BLE_TRIGGERED_BONJOUR
-        internal_stop_advertising_service(resourceRecord, flags);
+        internal_stop_advertising_service(resourceRecord, flags, clientPID);
 }
 
-mDNSexport void internal_stop_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags)
+mDNSexport void internal_stop_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags, pid_t clientPID)
 {
     domainname lower;
     mDNSu8 *rhs = NULL;
@@ -821,16 +864,30 @@ mDNSexport void internal_stop_advertising_service(const ResourceRecord *const re
         for (i = 0; i < D2DTransportMax; i++)
         {
             if (i == excludedTransport) continue;
-            if (D2DStopAdvertisingPairOnTransport) D2DStopAdvertisingPairOnTransport(compression_lhs, rhs - compression_lhs, rhs, end - rhs, i);
+            if (D2DStopAdvertisingPairOnTransport)
+            {
+                D2DStopAdvertisingPairOnTransport(compression_lhs, rhs - compression_lhs, rhs, end - rhs, i);
+                if (i == D2DAWDLTransport)
+                {
+                    mdns_powerlog_awdl_advertise_stop(lower.c, resourceRecord->rrtype, clientPID);
+                }
+            }
         }
     }
     else
     {
-        if (D2DStopAdvertisingPairOnTransport) D2DStopAdvertisingPairOnTransport(compression_lhs, rhs - compression_lhs, rhs, end - rhs, transportType);
+        if (D2DStopAdvertisingPairOnTransport)
+        {
+            D2DStopAdvertisingPairOnTransport(compression_lhs, rhs - compression_lhs, rhs, end - rhs, transportType);
+            if (transportType == D2DAWDLTransport)
+            {
+                mdns_powerlog_awdl_advertise_stop(lower.c, resourceRecord->rrtype, clientPID);
+            }
+        }
     }
 }
 
-mDNSexport void external_start_resolving_service(mDNSInterfaceID InterfaceID, const domainname *const fqdn, DNSServiceFlags flags)
+mDNSexport void external_start_resolving_service(mDNSInterfaceID InterfaceID, const domainname *const fqdn, DNSServiceFlags flags, pid_t clientPID)
 {
     domainname lower;
     mDNSu8 *rhs = NULL;
@@ -852,8 +909,14 @@ mDNSexport void external_start_resolving_service(mDNSInterfaceID InterfaceID, co
         for (i = 0; i < D2DTransportMax; i++)
         {
             if (i == excludedTransport) continue;
-            if (D2DStartResolvingPairOnTransport) D2DStartResolvingPairOnTransport(compression_lhs, rhs - compression_lhs, rhs, end - rhs, i);
-
+            if (D2DStartResolvingPairOnTransport)
+            {
+                if (i == D2DAWDLTransport)
+                {
+                    mdns_powerlog_awdl_resolve_start(lower.c, kDNSType_PTR, clientPID);
+                }
+                D2DStartResolvingPairOnTransport(compression_lhs, rhs - compression_lhs, rhs, end - rhs, i);
+            }
             if (i == D2DAWDLTransport)
                 AWDL_used = true;
         }
@@ -861,8 +924,14 @@ mDNSexport void external_start_resolving_service(mDNSInterfaceID InterfaceID, co
     else
     {
         // Resolving over one specific transport.
-        if (D2DStartResolvingPairOnTransport) D2DStartResolvingPairOnTransport(compression_lhs, rhs - compression_lhs, rhs, end - rhs, transportType);
-
+        if (D2DStartResolvingPairOnTransport)
+        {
+            if (transportType == D2DAWDLTransport)
+            {
+                mdns_powerlog_awdl_resolve_start(lower.c, kDNSType_PTR, clientPID);
+            }
+            D2DStartResolvingPairOnTransport(compression_lhs, rhs - compression_lhs, rhs, end - rhs, transportType);
+        }
         if (transportType == D2DAWDLTransport)
             AWDL_used = true;
     }
@@ -873,12 +942,12 @@ mDNSexport void external_start_resolving_service(mDNSInterfaceID InterfaceID, co
     if (AWDL_used && AWDLInterfaceID)
     {
         LogInfo("external_start_resolving_service: browse for TXT and SRV over AWDL");
-        external_start_browsing_for_service(AWDLInterfaceID, fqdn, kDNSType_TXT, 0);
-        external_start_browsing_for_service(AWDLInterfaceID, fqdn, kDNSType_SRV, 0);
+        external_start_browsing_for_service(AWDLInterfaceID, fqdn, kDNSType_TXT, 0, clientPID);
+        external_start_browsing_for_service(AWDLInterfaceID, fqdn, kDNSType_SRV, 0, clientPID);
     }
 }
 
-mDNSexport void external_stop_resolving_service(mDNSInterfaceID InterfaceID, const domainname *const fqdn, DNSServiceFlags flags)
+mDNSexport void external_stop_resolving_service(mDNSInterfaceID InterfaceID, const domainname *const fqdn, DNSServiceFlags flags, pid_t clientPID)
 {
     domainname lower;
     mDNSu8 *rhs = NULL;
@@ -899,16 +968,28 @@ mDNSexport void external_stop_resolving_service(mDNSInterfaceID InterfaceID, con
         for (i = 0; i < D2DTransportMax; i++)
         {
             if (i == excludedTransport) continue;
-            if (D2DStopResolvingPairOnTransport) D2DStopResolvingPairOnTransport(compression_lhs, rhs - compression_lhs, rhs, end - rhs, i);
-
+            if (D2DStopResolvingPairOnTransport)
+            {
+                D2DStopResolvingPairOnTransport(compression_lhs, rhs - compression_lhs, rhs, end - rhs, i);
+                if (i == D2DAWDLTransport)
+                {
+                    mdns_powerlog_awdl_resolve_stop(lower.c, kDNSType_PTR, clientPID);
+                }
+            }
             if (i == D2DAWDLTransport)
                 AWDL_used = true;
         }
     }
     else
     {
-        if (D2DStopResolvingPairOnTransport) D2DStopResolvingPairOnTransport(compression_lhs, rhs - compression_lhs, rhs, end - rhs, transportType);
-
+        if (D2DStopResolvingPairOnTransport)
+        {
+            D2DStopResolvingPairOnTransport(compression_lhs, rhs - compression_lhs, rhs, end - rhs, transportType);
+            if (transportType == D2DAWDLTransport)
+            {
+                mdns_powerlog_awdl_resolve_stop(lower.c, kDNSType_PTR, clientPID);
+            }
+        }
         if (transportType == D2DAWDLTransport)
             AWDL_used = true;
     }
@@ -919,8 +1000,8 @@ mDNSexport void external_stop_resolving_service(mDNSInterfaceID InterfaceID, con
     if (AWDL_used && AWDLInterfaceID)
     {
         LogInfo("external_stop_resolving_service: stop browse for TXT and SRV on AWDL");
-        external_stop_browsing_for_service(AWDLInterfaceID, fqdn, kDNSType_TXT, 0);
-        external_stop_browsing_for_service(AWDLInterfaceID, fqdn, kDNSType_SRV, 0);
+        external_stop_browsing_for_service(AWDLInterfaceID, fqdn, kDNSType_TXT, 0, clientPID);
+        external_stop_browsing_for_service(AWDLInterfaceID, fqdn, kDNSType_SRV, 0, clientPID);
     }
 }
 
index be008fd03d8ef825cde51fdb92a0cee382291b85..11cb20719349058566679b8aa005c9204a1291a6 100644 (file)
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2015-2018 Apple Inc. All rights reserved.
+ * Copyright (c) 2015-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "dnssd_ipc.h"
 #include <DeviceToDeviceManager/DeviceToDeviceManager.h>
 
-extern void internal_start_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const type, DNS_TypeValues qtype, DNSServiceFlags flags);
-extern void internal_stop_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const type, DNS_TypeValues qtype, DNSServiceFlags flags);
-extern void internal_start_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags);
-extern void internal_stop_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags);
+extern void internal_start_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const type, DNS_TypeValues qtype, DNSServiceFlags flags, pid_t clientPID);
+extern void internal_stop_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const type, DNS_TypeValues qtype, DNSServiceFlags flags, pid_t clientPID);
+extern void internal_start_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags, pid_t clientPID);
+extern void internal_stop_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags, pid_t clientPID);
 
 void xD2DAddToCache(D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize);
 void xD2DRemoveFromCache(D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize);
 
 extern mDNSBool callExternalHelpers(mDNSInterfaceID InterfaceID, const domainname *const domain, DNSServiceFlags flags);
 extern mDNSu32 deriveD2DFlagsFromAuthRecType(AuthRecType authRecType);
-extern void external_start_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const type, DNS_TypeValues qtype, DNSServiceFlags flags);
-extern void external_stop_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const type, DNS_TypeValues qtype, DNSServiceFlags flags);
-extern void external_start_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags);
-extern void external_stop_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags);
-extern void external_start_resolving_service(mDNSInterfaceID InterfaceID, const domainname *const fqdn, DNSServiceFlags flags);
-extern void external_stop_resolving_service(mDNSInterfaceID InterfaceID, const domainname *const fqdn, DNSServiceFlags flags);
+extern void external_start_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const type, DNS_TypeValues qtype, DNSServiceFlags flags, pid_t clientPID);
+extern void external_stop_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const type, DNS_TypeValues qtype, DNSServiceFlags flags, pid_t clientPID);
+extern void external_start_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags, pid_t clientPID);
+extern void external_stop_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags, pid_t clientPID);
+extern void external_start_resolving_service(mDNSInterfaceID InterfaceID, const domainname *const fqdn, DNSServiceFlags flags, pid_t clientPID);
+extern void external_stop_resolving_service(mDNSInterfaceID InterfaceID, const domainname *const fqdn, DNSServiceFlags flags, pid_t clientPID);
 extern void external_connection_release(const domainname *instance);
 
 extern void D2D_start_advertising_interface(NetworkInterfaceInfo *interface);
index 3aa54c86b6ae9dbe50e92dc4a89f88a05f4bab12..d6bf895fd31dcda89befaca2c7ffc40321528104 100644 (file)
@@ -46,13 +46,28 @@ check_compile_time(sizeof_field(DNS64, qnameStash)  == kDNS64IPv4OnlyFQDNLength)
 //  Local Prototypes
 //===========================================================================================================================
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+mDNSlocal mStatus   _DNS64GetIPv6Addrs(mDNS *m, mdns_dns_service_t inDNSService, struct in6_addr **outAddrs, uint32_t *outAddrCount);
+#else
 mDNSlocal mStatus   _DNS64GetIPv6Addrs(mDNS *m, mDNSu32 inResGroupID, struct in6_addr **outAddrs, uint32_t *outAddrCount);
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+mDNSlocal mStatus   _DNS64GetPrefixes(mDNS *m, mdns_dns_service_t inDNSService, nw_nat64_prefix_t **outPrefixes, uint32_t *outPrefixCount);
+#else
 mDNSlocal mStatus   _DNS64GetPrefixes(mDNS *m, mDNSu32 inResGroupID, nw_nat64_prefix_t **outPrefixes, uint32_t *outPrefixCount);
-mDNSlocal mDNSBool  _DNS64GetReverseIPv6Addr(const domainname *inQName, struct in6_addr *outAddr);
+#endif
 mDNSlocal mDNSu32   _DNS64IPv4OnlyFQDNHash(void);
 mDNSlocal void      _DNS64RestartQuestion(mDNS *m, DNSQuestion *q, DNS64State newState);
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+mDNSlocal mDNSBool  _DNS64InterfaceSupportsNAT64(mdns_dns_service_t inDNSService);
+#else
 mDNSlocal mDNSBool  _DNS64InterfaceSupportsNAT64(uint32_t inIfIndex);
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+mDNSlocal mDNSBool  _DNS64TestIPv6Synthesis(mDNS *m, mdns_dns_service_t inDNSService, const mDNSv4Addr *inV4Addr);
+#else
 mDNSlocal mDNSBool  _DNS64TestIPv6Synthesis(mDNS *m, mDNSu32 inResGroupID, const mDNSv4Addr *inV4Addr);
+#endif
 
 //===========================================================================================================================
 //  DNS64StateMachine
@@ -77,8 +92,12 @@ mDNSexport mDNSBool DNS64StateMachine(mDNS *m, DNSQuestion *inQ, const ResourceR
                 (inRR->rrtype    == kDNSType_AAAA) &&
                 (inRR->rrclass   == kDNSClass_IN) &&
                 ((inQ->qnamehash != _DNS64IPv4OnlyFQDNHash()) || !SameDomainName(&inQ->qname, kDNS64IPv4OnlyFQDN)) &&
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                inQ->dnsservice && _DNS64InterfaceSupportsNAT64(inQ->dnsservice))
+#else
                 inQ->qDNSServer &&
                 _DNS64InterfaceSupportsNAT64((uint32_t)((uintptr_t)inQ->qDNSServer->interface)))
+#endif
             {
                 _DNS64RestartQuestion(m, inQ, kDNS64State_PrefixDiscovery);
                 return (mDNStrue);
@@ -86,9 +105,13 @@ mDNSexport mDNSBool DNS64StateMachine(mDNS *m, DNSQuestion *inQ, const ResourceR
             else if ((inQ->qtype == kDNSType_PTR) &&
                 (inRR->rrtype    == kDNSType_PTR) &&
                 (inRR->rrclass   == kDNSClass_IN) &&
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                inQ->dnsservice && _DNS64InterfaceSupportsNAT64(inQ->dnsservice) &&
+#else
                 inQ->qDNSServer &&
                 _DNS64InterfaceSupportsNAT64((uint32_t)((uintptr_t)inQ->qDNSServer->interface)) &&
-                _DNS64GetReverseIPv6Addr(&inQ->qname, NULL))
+#endif
+                GetReverseIPv6Addr(&inQ->qname, NULL))
             {
                 _DNS64RestartQuestion(m, inQ, kDNS64State_PrefixDiscoveryPTR);
                 return (mDNStrue);
@@ -107,14 +130,12 @@ mDNSexport mDNSBool DNS64StateMachine(mDNS *m, DNSQuestion *inQ, const ResourceR
             (inRR->rrclass    == kDNSClass_IN))
         {
             _DNS64RestartQuestion(m, inQ, kDNS64State_QueryA);
-            return (mDNStrue);
         }
         else
         {
             _DNS64RestartQuestion(m, inQ, kDNS64State_QueryAAAA);
-            return (mDNStrue);
         }
-        break;
+        return (mDNStrue);
 
     // The "ipv4only.arpa." question is going to be answered. Restart the question now. DNS64HandleNewQuestion() will decide
     // whether or not to change it to a reverse IPv4 question.
@@ -122,7 +143,6 @@ mDNSexport mDNSBool DNS64StateMachine(mDNS *m, DNSQuestion *inQ, const ResourceR
     case kDNS64State_PrefixDiscoveryPTR:
         _DNS64RestartQuestion(m, inQ, kDNS64State_QueryPTR);
         return (mDNStrue);
-        break;
 
     // If this question is going to be answered with a CNAME, then do nothing.
     // If this question is going to be answered with a positive A record that's synthesizable, then set the state to
@@ -136,8 +156,13 @@ mDNSexport mDNSBool DNS64StateMachine(mDNS *m, DNSQuestion *inQ, const ResourceR
                 (inResult         == QC_add) &&
                 (inRR->rrtype     == kDNSType_A) &&
                 (inRR->rrclass    == kDNSClass_IN) &&
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                inQ->dnsservice &&
+                _DNS64TestIPv6Synthesis(m, inQ->dnsservice, &inRR->rdata->u.ipv4))
+#else
                 inQ->qDNSServer &&
                 _DNS64TestIPv6Synthesis(m, inQ->qDNSServer->resGroupID, &inRR->rdata->u.ipv4))
+#endif
             {
                 inQ->dns64.state = kDNS64State_QueryA2;
             }
@@ -182,9 +207,17 @@ mDNSexport mStatus DNS64AnswerCurrentQuestion(mDNS *m, const ResourceRecord *inR
     struct in6_addr         synthV6;
     DNSQuestion * const     q = m->CurrentQuestion;
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    require_action_quiet(q->dnsservice, exit, err = mStatus_BadParamErr);
+#else
     require_action_quiet(q->qDNSServer, exit, err = mStatus_BadParamErr);
+#endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    err = _DNS64GetPrefixes(m, q->dnsservice, &prefixes, &prefixCount);
+#else
     err = _DNS64GetPrefixes(m, q->qDNSServer->resGroupID, &prefixes, &prefixCount);
+#endif
     require_noerr_quiet(err, exit);
 
     newRR               = *inRR;
@@ -221,7 +254,12 @@ mDNSexport void DNS64HandleNewQuestion(mDNS *m, DNSQuestion *inQ)
         struct in6_addr     v6Addr;
 
         inQ->dns64.state = kDNS64State_ReverseIPv6;
-        if (inQ->qDNSServer && _DNS64GetReverseIPv6Addr(&inQ->qname, &v6Addr))
+        memset(&v6Addr, 0, sizeof(v6Addr));
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        if (inQ->dnsservice && GetReverseIPv6Addr(&inQ->qname, v6Addr.s6_addr))
+#else
+        if (inQ->qDNSServer && GetReverseIPv6Addr(&inQ->qname, v6Addr.s6_addr))
+#endif
         {
             mStatus                 err;
             nw_nat64_prefix_t *     prefixes;
@@ -230,7 +268,11 @@ mDNSexport void DNS64HandleNewQuestion(mDNS *m, DNSQuestion *inQ)
             struct in_addr          v4Addr;
             char                    qnameStr[MAX_REVERSE_MAPPING_NAME_V4];
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            err = _DNS64GetPrefixes(m, inQ->dnsservice, &prefixes, &prefixCount);
+#else
             err = _DNS64GetPrefixes(m, inQ->qDNSServer->resGroupID, &prefixes, &prefixCount);
+#endif
             require_noerr_quiet(err, exit);
 
             for (i = 0; i < prefixCount; i++)
@@ -297,6 +339,7 @@ mDNSexport void DNS64ResetState(DNSQuestion *inQ)
 //  DNS64RestartQuestions
 //===========================================================================================================================
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 mDNSexport void DNS64RestartQuestions(mDNS *m)
 {
     DNSQuestion *       q;
@@ -337,19 +380,32 @@ mDNSexport void DNS64RestartQuestions(mDNS *m)
         mDNS_StartQuery_internal(m, q);
     }
 }
+#endif
 
 //===========================================================================================================================
 //  _DNS64GetIPv6Addrs
 //===========================================================================================================================
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+#define IsPositiveAAAAFromDNSService(RR, DNS_SERVICE)       \
+    (((RR)->dnsservice == (DNS_SERVICE))  &&                \
+    ((RR)->rrtype == kDNSType_AAAA) &&                      \
+    ((RR)->RecordType != kDNSRecordTypePacketNegative) &&   \
+    !(RR)->InterfaceID)
+#else
 #define IsPositiveAAAAFromResGroup(RR, RES_GROUP_ID)        \
     ((RR)->rDNSServer &&                                    \
     ((RR)->rDNSServer->resGroupID == RES_GROUP_ID) &&       \
     ((RR)->rrtype == kDNSType_AAAA) &&                      \
     ((RR)->RecordType != kDNSRecordTypePacketNegative) &&   \
     !(RR)->InterfaceID)
+#endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+mDNSlocal mStatus _DNS64GetIPv6Addrs(mDNS *m, mdns_dns_service_t inDNSService, struct in6_addr **outAddrs, uint32_t *outAddrCount)
+#else
 mDNSlocal mStatus _DNS64GetIPv6Addrs(mDNS *m, const mDNSu32 inResGroupID, struct in6_addr **outAddrs, uint32_t *outAddrCount)
+#endif
 {
     mStatus                 err;
     const CacheGroup *      cg;
@@ -364,7 +420,11 @@ mDNSlocal mStatus _DNS64GetIPv6Addrs(mDNS *m, const mDNSu32 inResGroupID, struct
     recordCount = 0;
     for (cr = cg->members; cr; cr = cr->next)
     {
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        if (IsPositiveAAAAFromDNSService(&cr->resrec, inDNSService))
+#else
         if (IsPositiveAAAAFromResGroup(&cr->resrec, inResGroupID))
+#endif
         {
             recordCount++;
         }
@@ -377,7 +437,11 @@ mDNSlocal mStatus _DNS64GetIPv6Addrs(mDNS *m, const mDNSu32 inResGroupID, struct
     addrCount = 0;
     for (cr = cg->members; cr && (addrCount < recordCount); cr = cr->next)
     {
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        if (IsPositiveAAAAFromDNSService(&cr->resrec, inDNSService))
+#else
         if (IsPositiveAAAAFromResGroup(&cr->resrec, inResGroupID))
+#endif
         {
             memcpy(addrs[addrCount].s6_addr, cr->resrec.rdata->u.ipv6.b, 16);
             addrCount++;
@@ -398,7 +462,11 @@ exit:
 //  _DNS64GetPrefixes
 //===========================================================================================================================
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+mDNSlocal mStatus _DNS64GetPrefixes(mDNS *m, mdns_dns_service_t inDNSService, nw_nat64_prefix_t **outPrefixes, uint32_t *outPrefixCount)
+#else
 mDNSlocal mStatus _DNS64GetPrefixes(mDNS *m, mDNSu32 inResGroupID, nw_nat64_prefix_t **outPrefixes, uint32_t *outPrefixCount)
+#endif
 {
     mStatus                 err;
     struct in6_addr *       v6Addrs;
@@ -406,7 +474,11 @@ mDNSlocal mStatus _DNS64GetPrefixes(mDNS *m, mDNSu32 inResGroupID, nw_nat64_pref
     nw_nat64_prefix_t *     prefixes;
     int32_t                 prefixCount;
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    err = _DNS64GetIPv6Addrs(m, inDNSService, &v6Addrs, &v6AddrCount);
+#else
     err = _DNS64GetIPv6Addrs(m, inResGroupID, &v6Addrs, &v6AddrCount);
+#endif
     require_noerr_quiet(err, exit);
 
     prefixCount = nw_nat64_copy_prefixes_from_ipv4only_records(v6Addrs, v6AddrCount, &prefixes);
@@ -420,51 +492,6 @@ exit:
     return (err);
 }
 
-//===========================================================================================================================
-//  _DNS64GetReverseIPv6Addr
-//===========================================================================================================================
-
-#define kReverseIPv6Domain  ((const domainname *) "\x3" "ip6" "\x4" "arpa")
-
-mDNSlocal mDNSBool _DNS64GetReverseIPv6Addr(const domainname *inQName, struct in6_addr *outAddr)
-{
-    const mDNSu8 *      ptr;
-    int                 i;
-    unsigned int        c;
-    unsigned int        nl;
-    unsigned int        nu;
-
-    // If the name is of the form "x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.ip6.arpa.", where each x
-    // is a hex digit, then the sequence of 32 hex digit labels represents the nibbles of an IPv6 address in reverse order.
-    // See <https://tools.ietf.org/html/rfc3596#section-2.5>.
-
-    ptr = (const mDNSu8 *)inQName;
-    for (i = 0; i < 16; i++)
-    {
-        if (*ptr++ != 1) return (mDNSfalse);                    // If this label's length is not 1, then fail.
-        c = *ptr++;                                             // Get label byte.
-        if (     (c >= '0') && (c <= '9')) nl =  c - '0';       // If it's a hex digit, get its numeric value.
-        else if ((c >= 'a') && (c <= 'f')) nl = (c - 'a') + 10;
-        else if ((c >= 'A') && (c <= 'F')) nl = (c - 'A') + 10;
-        else                               return (mDNSfalse);  // Otherwise, fail.
-
-        if (*ptr++ != 1) return (mDNSfalse);                    // If this label's length is not 1, then fail.
-        c = *ptr++;                                             // Get label byte.
-        if (     (c >= '0') && (c <= '9')) nu =  c - '0';       // If it's a hex digit, get its numeric value.
-        else if ((c >= 'a') && (c <= 'f')) nu = (c - 'a') + 10;
-        else if ((c >= 'A') && (c <= 'F')) nu = (c - 'A') + 10;
-        else                               return (mDNSfalse);  // Otherwise, fail.
-
-        if (outAddr) outAddr->s6_addr[15 - i] = (mDNSu8)((nu << 4) | nl);
-    }
-
-    // The rest of the name needs to be "ip6.arpa.". If it isn't, fail.
-
-    if (!SameDomainName((const domainname *)ptr, kReverseIPv6Domain)) return (mDNSfalse);
-
-    return (mDNStrue);
-}
-
 //===========================================================================================================================
 //  _DNS64IPv4OnlyFQDNHash
 //===========================================================================================================================
@@ -530,6 +557,20 @@ mDNSlocal void _DNS64RestartQuestion(mDNS *const m, DNSQuestion *inQ, DNS64State
 //  _DNS64InterfaceSupportsNAT64
 //===========================================================================================================================
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+mDNSlocal mDNSBool _DNS64InterfaceSupportsNAT64(mdns_dns_service_t inDNSService)
+{
+    if (!mdns_dns_service_interface_has_ipv4_connectivity(inDNSService) &&
+        mdns_dns_service_interface_has_ipv6_connectivity(inDNSService))
+    {
+        return (mDNStrue);
+    }
+    else
+    {
+        return (mDNSfalse);
+    }
+}
+#else
 mDNSlocal mDNSBool _DNS64InterfaceSupportsNAT64(uint32_t inIfIndex)
 {
     mdns_interface_monitor_t monitor = GetInterfaceMonitorForIndex(inIfIndex);
@@ -540,12 +581,17 @@ mDNSlocal mDNSBool _DNS64InterfaceSupportsNAT64(uint32_t inIfIndex)
     }
     return (mDNSfalse);
 }
+#endif
 
 //===========================================================================================================================
 //  _DNS64TestIPv6Synthesis
 //===========================================================================================================================
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+mDNSlocal mDNSBool _DNS64TestIPv6Synthesis(mDNS *m, mdns_dns_service_t inDNSService, const mDNSv4Addr *inV4Addr)
+#else
 mDNSlocal mDNSBool _DNS64TestIPv6Synthesis(mDNS *m, mDNSu32 inResGroupID, const mDNSv4Addr *inV4Addr)
+#endif
 {
     mStatus                 err;
     nw_nat64_prefix_t *     prefixes    = NULL;
@@ -555,7 +601,11 @@ mDNSlocal mDNSBool _DNS64TestIPv6Synthesis(mDNS *m, mDNSu32 inResGroupID, const
     struct in6_addr         synthV6;
     mDNSBool                result      = mDNSfalse;
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    err = _DNS64GetPrefixes(m, inDNSService, &prefixes, &prefixCount);
+#else
     err = _DNS64GetPrefixes(m, inResGroupID, &prefixes, &prefixCount);
+#endif
     require_noerr_quiet(err, exit);
 
     memcpy(&v4Addr.s_addr, inV4Addr->b, 4);
index dc07dcc1acd351798815f92c0a38240c4afe3e1b..2e21613d467a91230746e8563473e4f1fdadd094 100644 (file)
@@ -30,7 +30,9 @@ mDNSexport mDNSBool DNS64StateMachine(mDNS *m, DNSQuestion *inQ, const ResourceR
 mDNSexport mStatus  DNS64AnswerCurrentQuestion(mDNS *m, const ResourceRecord *inRR, QC_result inResult);
 mDNSexport void     DNS64HandleNewQuestion(mDNS *m, DNSQuestion *inQ);
 mDNSexport void     DNS64ResetState(DNSQuestion *inQ);
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 mDNSexport void     DNS64RestartQuestions(mDNS *m);
+#endif
 
 #ifdef  __cplusplus
 }
diff --git a/mDNSMacOSX/DNSHeuristics.h b/mDNSMacOSX/DNSHeuristics.h
new file mode 100644 (file)
index 0000000..4ef3013
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __DNSHeuristics_h
+#define __DNSHeuristics_h
+
+#import <Foundation/Foundation.h>
+
+void dns_heuristics_report_resolution_failure(NSURL *url, bool is_timeout);
+
+void dns_heuristics_report_resolution_success(void);
+
+#endif // __DNSHeuristics_h
diff --git a/mDNSMacOSX/DNSHeuristics.m b/mDNSMacOSX/DNSHeuristics.m
new file mode 100644 (file)
index 0000000..449d29d
--- /dev/null
@@ -0,0 +1,318 @@
+/*
+* Copyright (c) 2020 Apple Inc. All rights reserved.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#import "DNSHeuristicsInternal.h"
+#import "DNSHeuristics.h"
+#import "mdns_symptoms.h"
+#import "mdns_helpers.h"
+#import <os/log.h>
+
+#if (TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST)
+
+MDNS_LOG_CATEGORY_DEFINE(heuristics, "heuristics");
+
+#endif
+
+#define DNS_MIN(A, B) ((A) < (B) ? (A) : (B))
+
+/*
+ * Persisted heuristic data model:
+ *
+ * "DNSFailures": {
+ *    "LastFailureTimestamp": <>,
+ *    "LongCount": <>,
+ *    "BurstCount": <>,
+ * },
+ * "FilteredNetwork": <>
+ */
+const NSString *DNSFailureStateKey = @"DNSFailures";
+const NSString *DNSHeuristicsLastFailureTimestamp = @"LastFailureTimestamp";
+const NSString *DNSHeuristicsLongCounterKey = @"LongCount";
+const NSString *DNSHeuristicsBurstCounterKey = @"BurstCount";
+const NSString *DNSHeuristicsFilterFlagKey = @"FilteredNetwork";
+
+/*
+ * DNS resolution failures are tracked using two counters:
+ *
+ * 1. A "long" counter, tracking the total number of failures. If the total number of failures exceeds DNSHeuristicDefaultLongCounterThreshold
+ *    the network is marked as an active filterer. After a cooldown period of DNSHeuristicDefaultLongCounterTimeWindow seconds
+ *    since the last failure the network stops being marked as such and the counter is reset to zero.
+ * 2. A "burst" counter, implemented as a token bucket. The token bucket is replenished every two minutes. Each failure
+ *    removes a token from the bucket. If there are ever no tokens available, the network is marked as an active filterer.
+ */
+NSUInteger DNSHeuristicDefaultLongCounterThreshold = 10; // long counter
+NSUInteger DNSHeuristicDefaultLongCounterTimeWindow = 24*60*60*1000; // one day
+NSUInteger DNSHeuristicsDefaultBurstTokenBucketCapacity = 10; // token bucket
+NSUInteger DNSHeuristicsDefaultBurstTokenBucketRefillTime = 2*60*1000; // refill every two minutes
+NSUInteger DNSHeuristicsDefaultBurstTokenBucketRefillCount = 1; // put one token back in every epoch
+NSUInteger DNSHeuristicsDefaultMultipleTimeoutWindow = 30*1000; // only penalize for one timeout every thirty seconds
+
+#if (TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST)
+#import <Apple80211/Apple80211API.h>
+#import <Kernel/IOKit/apple80211/apple80211_var.h>
+#import <Kernel/IOKit/apple80211/apple80211_ioctl.h>
+#import <MobileWiFi/WiFiTypes.h>
+#import <MobileWiFi/WiFiManagerClient.h>
+#import <MobileWiFi/WiFiDeviceClient.h>
+#import <MobileWiFi/WiFiKeys.h>
+#import <MobileWiFi/MobileWiFi.h>
+
+static WiFiManagerClientRef
+getNetworkManager(void)
+{
+    static WiFiManagerClientRef manager = NULL;
+    if (manager == NULL) {
+        manager = WiFiManagerClientCreate(kCFAllocatorDefault, kWiFiClientTypeNormal);
+    }
+    return manager;
+}
+
+static WiFiNetworkRef
+copyCurrentWiFiNetwork(WiFiManagerClientRef manager)
+{
+    if (manager == NULL) {
+        return NULL;
+    }
+
+    NSArray *interfaces = (__bridge_transfer NSArray *)WiFiManagerClientCopyInterfaces(manager);
+
+    for (id interface in interfaces) {
+        if (WiFiDeviceClientGetInterfaceRoleIndex((__bridge WiFiDeviceClientRef)interface) == WIFI_MANAGER_MAIN_INTERFACE_ROLE) {
+            WiFiDeviceClientRef device = (__bridge WiFiDeviceClientRef)interface;
+            WiFiNetworkRef network = WiFiDeviceClientCopyCurrentNetwork(device);
+            if (network != NULL) {
+                return network;
+            }
+        }
+    }
+
+    return WiFiManagerClientCopyCurrentSessionBasedNetwork(manager);
+}
+
+#endif /* TARGET_OS_IPHONE */
+
+static dispatch_queue_t
+copyHeuristicsQueue(void)
+{
+    static dispatch_queue_t queue = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        queue = dispatch_queue_create("DNSHeuristicsQueue", NULL);
+    });
+    return queue;
+}
+
+@implementation DNSHeuristics
+
+#if (TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST)
+
++ (NSDictionary *)copyNetworkSettings:(WiFiNetworkRef)network NS_RETURNS_RETAINED
+{
+    if (network == NULL) {
+        return nil;
+    }
+
+    NSDictionary *networkFailures = (__bridge NSDictionary *)WiFiNetworkGetProperty(network, (__bridge CFStringRef)DNSFailureStateKey);
+    return [networkFailures copy];
+}
+
++ (BOOL)setNetworkSettings:(WiFiManagerClientRef)manager
+                   network:(WiFiNetworkRef)network
+                     value:(NSDictionary *)value
+{
+
+    if (manager == NULL || network == NULL) {
+        return NO;
+    }
+
+    return (BOOL)WiFiManagerClientSetNetworkProperty(manager, network, (__bridge CFStringRef)DNSFailureStateKey, (__bridge CFDictionaryRef)value);
+}
+
++ (BOOL)getNetworkFilteredFlag:(WiFiNetworkRef)network
+{
+    if (network == NULL) {
+        return NO;
+    }
+    CFBooleanRef value = WiFiNetworkGetProperty(network, (__bridge CFStringRef)DNSHeuristicsFilterFlagKey);
+    return value == kCFBooleanTrue ? YES : NO;
+}
+
++ (BOOL)setNetworkAsFiltered:(WiFiManagerClientRef)manager
+                     network:(WiFiNetworkRef)network
+{
+    if (manager == NULL || network == NULL) {
+        return NO;
+    }
+    return (BOOL)WiFiManagerClientSetNetworkProperty(manager, network, (__bridge CFStringRef)DNSHeuristicsFilterFlagKey, kCFBooleanTrue);
+}
+
++ (BOOL)clearNetworkAsFiltered:(WiFiManagerClientRef)manager
+                       network:(WiFiNetworkRef)network
+{
+    if (manager == NULL || network == NULL) {
+        return NO;
+    }
+    return (BOOL)WiFiManagerClientSetNetworkProperty(manager, network, (__bridge CFStringRef)DNSHeuristicsFilterFlagKey, kCFBooleanFalse);
+}
+
++ (BOOL)setNetworkAsFiltered:(WiFiManagerClientRef)manager
+                     network:(WiFiNetworkRef)network
+                    filtered:(BOOL)filtered
+{
+    if (filtered) {
+        return [DNSHeuristics setNetworkAsFiltered:manager network:network];
+    } else {
+        return [DNSHeuristics clearNetworkAsFiltered:manager network:network];
+    }
+}
+
+#endif // #if TARGET_OS_IPHONE
+
++ (BOOL)countersExceedThreshold:(NSUInteger)dailyCounter
+                   burstCounter:(NSUInteger)burstCounter
+{
+    return (dailyCounter > DNSHeuristicDefaultLongCounterThreshold || burstCounter == 0);
+}
+
++ (NSUInteger)currentTimeMs
+{
+    return (NSUInteger)([[NSDate date] timeIntervalSince1970] * 1000);
+}
+
++ (NSDictionary *)copyEmptyHeuristicState NS_RETURNS_RETAINED
+{
+    return @{
+        DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:0],
+        DNSHeuristicsLongCounterKey: [NSNumber numberWithUnsignedInteger:0],
+        DNSHeuristicsBurstCounterKey: [NSNumber numberWithUnsignedInteger:DNSHeuristicsDefaultBurstTokenBucketCapacity],
+    };
+}
+
++ (BOOL)updateHeuristicState:(BOOL)resolutionSuccess
+                                  isTimeout:(BOOL)isTimeout
+{
+    BOOL result = YES;
+
+#if (TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST)
+
+    WiFiManagerClientRef manager = getNetworkManager();
+    WiFiNetworkRef network = copyCurrentWiFiNetwork(manager);
+    NSDictionary *heuristicState = [DNSHeuristics copyNetworkSettings:network];
+    if (!heuristicState) {
+        heuristicState = @{}; // Empty dictionary to start
+    }
+    if (![heuristicState objectForKey:DNSHeuristicsLastFailureTimestamp]) {
+        heuristicState = [DNSHeuristics copyEmptyHeuristicState];
+    }
+
+    NSUInteger now = [DNSHeuristics currentTimeMs];
+    NSUInteger lastFailureTimestamp = [(NSNumber *)heuristicState[DNSHeuristicsLastFailureTimestamp] unsignedIntegerValue];
+    NSUInteger longCounter = [(NSNumber *)heuristicState[DNSHeuristicsLongCounterKey] unsignedIntegerValue];
+    NSUInteger burstCounter = [(NSNumber *)heuristicState[DNSHeuristicsBurstCounterKey] unsignedIntegerValue];
+    BOOL filteredFlag = [DNSHeuristics getNetworkFilteredFlag:network];
+
+    if (resolutionSuccess) {
+        // Check to see if the network can be forgiven, i.e., if we've gone over a day since the last failure.
+               if (filteredFlag) {
+                       if (lastFailureTimestamp + DNSHeuristicDefaultLongCounterTimeWindow < now) {
+                               const uint64_t delta = (now - lastFailureTimestamp);
+                               os_log(_mdns_heuristics_log(), "Logging DoH success after %llums, clearing filtered state", delta);
+                               result &= [DNSHeuristics setNetworkSettings:manager network:network value:[DNSHeuristics copyEmptyHeuristicState]];
+                               result &= [DNSHeuristics setNetworkAsFiltered:manager network:network filtered:NO];
+                       } else if (lastFailureTimestamp < now) {
+                               const uint64_t delta = (now - lastFailureTimestamp);
+                               os_log_info(_mdns_heuristics_log(), "Logging DoH success after %llums, keeping filtered state", delta);
+                       } else {
+                               os_log(_mdns_heuristics_log(), "Logging DoH success, invalid last failure, clearing filtered state");
+                               result &= [DNSHeuristics setNetworkSettings:manager network:network value:[DNSHeuristics copyEmptyHeuristicState]];
+                               result &= [DNSHeuristics setNetworkAsFiltered:manager network:network filtered:NO];
+                       }
+               }
+    } else if (isTimeout && lastFailureTimestamp < now &&
+                          lastFailureTimestamp + DNSHeuristicsDefaultMultipleTimeoutWindow > now) {
+               const uint64_t delta = (now - lastFailureTimestamp);
+               os_log_info(_mdns_heuristics_log(), "Logging DoH timeout failure after only %llums, not incrementing failure counter", delta);
+       } else {
+        // The long counter always increases upon each failure.
+        NSUInteger newLongCounter = (longCounter + 1);
+
+        // Replenish the burst token bucket, and then compute the new bucket value.
+        NSUInteger refillCount = (now - lastFailureTimestamp) / DNSHeuristicsDefaultBurstTokenBucketRefillTime;
+        NSUInteger refillAmount = refillCount * DNSHeuristicsDefaultBurstTokenBucketRefillCount;
+        NSUInteger refilledBucketValue = DNS_MIN(DNSHeuristicsDefaultBurstTokenBucketCapacity, burstCounter + refillAmount);
+        NSUInteger newBucketValue = (refilledBucketValue > 0) ? (refilledBucketValue - 1) : 0;
+
+        BOOL newFilteredFlag = filteredFlag || [DNSHeuristics countersExceedThreshold:newLongCounter burstCounter:newBucketValue];
+
+               if (!filteredFlag && newFilteredFlag) {
+                       os_log(_mdns_heuristics_log(), "Logging DoH %sfailure %llu (bucket %llu), moving into filtered state",
+                                  isTimeout ? "timeout " : "", (uint64_t)newLongCounter, (uint64_t)newBucketValue);
+               } else if (filteredFlag) {
+                       os_log_info(_mdns_heuristics_log(), "Logging DoH %sfailure %llu (bucket %llu), keeping filtered state",
+                                               isTimeout ? "timeout " : "", (uint64_t)newLongCounter, (uint64_t)newBucketValue);
+               } else {
+                       os_log_info(_mdns_heuristics_log(), "Logging DoH %sfailure %llu (bucket %llu), keeping unfiltered state",
+                                               isTimeout ? "timeout " : "", (uint64_t)newLongCounter, (uint64_t)newBucketValue);
+               }
+
+        NSDictionary *newState = @{
+            DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:now],
+            DNSHeuristicsLongCounterKey: [NSNumber numberWithUnsignedInteger:newLongCounter],
+            DNSHeuristicsBurstCounterKey: [NSNumber numberWithUnsignedInteger:newBucketValue],
+        };
+
+        result &= [DNSHeuristics setNetworkSettings:manager network:network value:newState];
+        result &= [DNSHeuristics setNetworkAsFiltered:manager network:network filtered:newFilteredFlag];
+    }
+
+    if (network) {
+        CFRelease(network);
+    }
+
+#endif
+
+    return result;
+}
+
++ (BOOL)reportResolutionFailure:(NSURL *)url
+                                         isTimeout:(BOOL)isTimeout
+{
+#ifndef DNS_XCTEST // Skip this symptoms report in the XCTests
+    NSString *urlHostname = [url host];
+    const char *hostname = urlHostname ? [urlHostname UTF8String] : "";
+    mdns_symptoms_report_encrypted_dns_connection_failure(hostname);
+#endif // DNS_XCTEST
+
+       return [DNSHeuristics updateHeuristicState:NO isTimeout:isTimeout];
+}
+
+@end
+
+void
+dns_heuristics_report_resolution_failure(NSURL *url, bool is_timeout)
+{
+    dispatch_async(copyHeuristicsQueue(), ^{
+        [DNSHeuristics reportResolutionFailure:url isTimeout:!!is_timeout];
+    });
+}
+
+void
+dns_heuristics_report_resolution_success(void)
+{
+    dispatch_async(copyHeuristicsQueue(), ^{
+               [DNSHeuristics updateHeuristicState:YES isTimeout:NO];
+    });
+}
diff --git a/mDNSMacOSX/DNSHeuristicsInternal.h b/mDNSMacOSX/DNSHeuristicsInternal.h
new file mode 100644 (file)
index 0000000..91b6196
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+* Copyright (c) 2020 Apple Inc. All rights reserved.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#ifndef __DNSHeuristicsInternal_h
+#define __DNSHeuristicsInternal_h
+
+#import "DNSHeuristics.h"
+#import <Foundation/Foundation.h>
+
+#if (TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST)
+#import <MobileWiFi/WiFiTypes.h>
+#import <MobileWiFi/WiFiManagerClient.h>
+#import <MobileWiFi/WiFiDeviceClient.h>
+#import <MobileWiFi/WiFiKeys.h>
+#import <MobileWiFi/MobileWiFi.h>
+#endif // TARGET_OS_IPHONE
+
+extern const NSString *DNSFailureStateKey;
+extern const NSString *DNSHeuristicsLastFailureTimestamp;
+extern const NSString *DNSHeuristicsFilterFlagKey;
+extern const NSString *DNSHeuristicsLongCounterKey;
+extern const NSString *DNSHeuristicsBurstCounterKey;
+
+extern NSUInteger DNSHeuristicDefaultLongCounterThreshold;
+extern NSUInteger DNSHeuristicDefaultLongCounterTimeWindow;
+extern NSUInteger DNSHeuristicsDefaultBurstTokenBucketCapacity;
+extern NSUInteger DNSHeuristicsDefaultBurstTokenBucketRefillTime;
+extern NSUInteger DNSHeuristicsDefaultBurstTokenBucketRefillCount;
+
+@interface DNSHeuristics : NSObject
+#if (TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST)
++ (BOOL)getNetworkFilteredFlag:(WiFiNetworkRef)network;
++ (NSDictionary *)copyNetworkSettings:(WiFiNetworkRef)network NS_RETURNS_RETAINED;
++ (BOOL)setNetworkSettings:(WiFiManagerClientRef)manager
+                   network:(WiFiNetworkRef)network
+                     value:(NSDictionary *)value;
++ (BOOL)setNetworkAsFiltered:(WiFiManagerClientRef)manager
+                     network:(WiFiNetworkRef)network;
++ (BOOL)clearNetworkAsFiltered:(WiFiManagerClientRef)manager
+                       network:(WiFiNetworkRef)network;
+#endif // TARGET_OS_IPHONE
++ (BOOL)updateHeuristicState:(BOOL)success isTimeout:(BOOL)isTimeout;
++ (BOOL)reportResolutionFailure:(NSURL *)url isTimeout:(BOOL)isTimeout;
++ (NSUInteger)currentTimeMs;
+@end
+
+#endif /* DNSHeuristicsInternal_h */
index 5e9f005d3fae58bc7a46608044b93b6da4314783..1b66863f767e0d94d0e1cad5366368fe81524587 100644 (file)
@@ -369,19 +369,27 @@ mDNSlocal mStatus SetupTCPProxySocket(int skt, TCPSocket *sock, u_short sa_famil
     return mStatus_NoError;
 }
 
-mDNSlocal void BindDPSocket(int fd, int sa_family)
+mDNSlocal void BindDPSocket(int fd, int sa_family, int type)
 {
     int err;
     const int on = 1;
 
+    if (type == SOCK_STREAM)
+    {
+        err = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+        if (err != 0)
+        {
+            const int setsockopt_errno = errno;
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+                "BindDPSocket: setsockopt SO_REUSEADDR failed for " PUB_S " %d errno %d (" PUB_S ")",
+                (sa_family == AF_INET) ? "IPv4" : "IPv6", fd, setsockopt_errno, strerror(setsockopt_errno));
+            return;
+        }
+    }
     if (sa_family == AF_INET)
     {
         struct sockaddr_in addr;
 
-        err = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));
-        if (err < 0) 
-            LogMsg("BindDPSocket: setsockopt SO_REUSEPORT failed for IPv4 %d errno %d (%s)", fd, errno, strerror(errno));
-
         memset(&addr, 0, sizeof(addr));
         addr.sin_family = AF_INET;
         addr.sin_port = htons(53);
@@ -405,10 +413,6 @@ mDNSlocal void BindDPSocket(int fd, int sa_family)
             LogMsg("DPFBindSocket: setsockopt IPV6_V6ONLY %d errno %d (%s)", fd, errno, strerror(errno));
             return;
         }
-        err = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));
-        if (err < 0)
-            LogMsg("BindDPSocket: setsockopt SO_REUSEPORT failed for V6 %d errno %d (%s)", fd, errno, strerror(errno));
-
         memset(&addr6, 0, sizeof(addr6));
         addr6.sin6_family = AF_INET6;
         addr6.sin6_port = htons(53);
@@ -488,10 +492,10 @@ mDNSexport void mDNSPlatformInitDNSProxySkts(ProxyCallback UDPCallback, ProxyCal
             close(dpskt[3]);
     }
 
-    BindDPSocket(dpskt[0], AF_INET);
-    BindDPSocket(dpskt[1], AF_INET6);
-    BindDPSocket(dpskt[2], AF_INET);
-    BindDPSocket(dpskt[3], AF_INET6);
+    BindDPSocket(dpskt[0], AF_INET, SOCK_DGRAM);
+    BindDPSocket(dpskt[1], AF_INET6, SOCK_DGRAM);
+    BindDPSocket(dpskt[2], AF_INET, SOCK_STREAM);
+    BindDPSocket(dpskt[3], AF_INET6, SOCK_STREAM);
 
     LogInfo("mDNSPlatformInitDNSProxySkts: Opened Listener Sockets for DNS Proxy : %d, %d, %d, %d", 
              dpskt[0], dpskt[1], dpskt[2], dpskt[3]);
diff --git a/mDNSMacOSX/DNSSECSupport.c b/mDNSMacOSX/DNSSECSupport.c
deleted file mode 100644 (file)
index acaff4e..0000000
+++ /dev/null
@@ -1,650 +0,0 @@
-/*
- * Copyright (c) 2012-2019 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// ***************************************************************************
-// DNSSECSupport.c: Platform specific support for DNSSEC like fetching root
-// trust anchor and dnssec probes etc.
-// ***************************************************************************
-
-#include "mDNSEmbeddedAPI.h"
-#include "DNSCommon.h"                  // For mDNS_Lock, mDNS_Random
-#include "dnssec.h"
-#include "DNSSECSupport.h"
-
-#include <CommonCrypto/CommonDigest.h>  // For Hash algorithms SHA1 etc.
-
-// Following are needed for fetching the root trust anchor dynamically
-#include <CoreFoundation/CoreFoundation.h>
-#include <libxml/parser.h>
-#include <libxml/tree.h>
-#include <libxml/xmlmemory.h>
-#include <notify.h>
-
-// 30 days
-#define ROOT_TA_UPDATE_INTERVAL  (30 * 24 * 3600)   // seconds
-
-// After 100 days, the test anchors are not valid. Just an arbitrary number
-// to configure validUntil. 
-#define TEST_TA_EXPIRE_INTERVAL  (100 * 24 * 4600)
-
-// When we can't fetch the root TA due to network errors etc., we start off a timer
-// to fire at 60 seconds and then keep doubling it till we fetch it
-#define InitialTAFetchInterval 60
-#define DNSSECProbePercentage 1
-
-
-#if !TARGET_OS_IPHONE
-DNSQuestion DNSSECProbeQuestion;
-#endif
-
-mDNSlocal int RegisterNotification(mDNS *const m, unsigned int interval);
-
-mDNSlocal void LinkTrustAnchor(mDNS *const m, TrustAnchor *ta)
-{
-    int length = 0;
-    int i;
-    mDNSu8 *p;
-    TrustAnchor **t = &m->TrustAnchors;
-    char buffer[256];
-
-    while (*t)
-        t = &((*t)->next);
-    *t = ta;
-
-    buffer[0] = 0;
-    p = ta->rds.digest;
-    for (i = 0; i < ta->digestLen; i++)
-    {
-        length += mDNS_snprintf(buffer+length, sizeof(buffer)-1-length, "%x", p[i]);
-    }
-    LogInfo("LinkTrustAnchor: Zone %##s, keytag %d, alg %d, digestType %d, digestLen %d, digest %s", ta->zone.c, ta->rds.keyTag,
-        ta->rds.alg, ta->rds.digestType, ta->digestLen, buffer);
-}
-
-mDNSlocal void DelTrustAnchor(mDNS *const m, const domainname *zone)
-{
-    TrustAnchor **ta = &m->TrustAnchors;
-    TrustAnchor *tmp;
-
-    while (*ta && !SameDomainName(&(*ta)->zone, zone))
-        ta = &(*ta)->next;
-
-    // First time, we won't find the TrustAnchor in the list as it has
-    // not been added.
-    if (!(*ta))
-        return;
-
-    tmp = *ta;
-    *ta = (*ta)->next;                  // Cut this record from the list
-    tmp->next = mDNSNULL;
-    if (tmp->rds.digest)
-        mDNSPlatformMemFree(tmp->rds.digest);
-    mDNSPlatformMemFree(tmp);
-}
-
-mDNSlocal void AddTrustAnchor(mDNS *const m, const domainname *zone, mDNSu16 keytag, mDNSu8 alg, mDNSu8 digestType, int diglen,
-    mDNSu8 *digest)
-{
-    TrustAnchor *ta, *tmp;
-    mDNSu32 t = (mDNSu32) time(NULL); 
-
-    // Check for duplicates
-    tmp = m->TrustAnchors;
-    while (tmp)
-    {
-        if (SameDomainName(zone, &tmp->zone) && tmp->rds.keyTag == keytag && tmp->rds.alg == alg && tmp->rds.digestType == digestType &&
-            !memcmp(tmp->rds.digest, digest, diglen))
-        {
-            LogMsg("AddTrustAnchors: Found a duplicate");
-            return;
-        }
-        tmp = tmp->next;
-    }
-
-    ta = (TrustAnchor *) mDNSPlatformMemAllocateClear(sizeof(*ta));
-    if (!ta)
-    {
-        LogMsg("AddTrustAnchor: malloc failure ta");
-        return;
-    }
-    ta->rds.keyTag = keytag;
-    ta->rds.alg = alg;
-    ta->rds.digestType = digestType;
-    ta->rds.digest = digest;
-    ta->digestLen = diglen;
-    ta->validFrom = t;
-    ta->validUntil = t + TEST_TA_EXPIRE_INTERVAL;
-    AssignDomainName(&ta->zone, zone);
-    ta->next = mDNSNULL;
-
-    LinkTrustAnchor(m, ta);
-}
-
-#define HexVal(X) ( ((X) >= '0' && (X) <= '9') ? ((X) - '0'     ) :   \
-                    ((X) >= 'A' && (X) <= 'F') ? ((X) - 'A' + 10) :   \
-                    ((X) >= 'a' && (X) <= 'f') ? ((X) - 'a' + 10) : -1)
-
-mDNSlocal mDNSu8 *ConvertDigest(char *digest, int digestType, int *diglen)
-{
-    int i, j;
-    mDNSu8 *dig;
-
-    switch (digestType)
-    {
-    case SHA1_DIGEST_TYPE:
-        *diglen = CC_SHA1_DIGEST_LENGTH;
-        break;
-    case SHA256_DIGEST_TYPE:
-        *diglen = CC_SHA256_DIGEST_LENGTH;
-        break;
-    default:
-        LogMsg("ConvertDigest: digest type %d not supported", digestType);
-        return mDNSNULL;
-    }
-    dig = (mDNSu8 *) mDNSPlatformMemAllocate(*diglen);
-    if (!dig)
-    {
-        LogMsg("ConvertDigest: malloc failure");
-        return mDNSNULL;
-    }
-
-    for (j=0,i=0; i<*diglen*2; i+=2)
-    {
-        int l, h;
-        l = HexVal(digest[i]);
-        h = HexVal(digest[i+1]);
-        if (l<0 || h<0) { LogMsg("ConvertDigest: Cannot convert digest"); mDNSPlatformMemFree(dig); return NULL;}
-        dig[j++] = (mDNSu8)((l << 4) | h);
-    }
-    return dig;
-}
-
-// All the children are in a linked list
-//
-// <TrustAnchor> has two children: <Zone> and <KeyDigest>
-// <KeyDigest> has four children <KeyTag> <Algorithm> <DigestType> <Digest>
-//
-// Returns false if failed to parse the element i.e., malformed xml document.
-// Validity of the actual values itself is done outside the function.
-mDNSlocal mDNSBool ParseElementChildren(xmlDocPtr tadoc, xmlNode *node, TrustAnchor *ta)
-{
-    xmlNode *cur_node;
-    xmlChar *val1, *val2, *val;
-    char *invalid = NULL;
-
-    val = val1 = val2 = NULL;
-
-    for (cur_node = node; cur_node; cur_node = cur_node->next)
-    {
-        invalid = NULL;
-        val1 = val2 = NULL;
-        
-        val = xmlNodeListGetString(tadoc, cur_node->xmlChildrenNode, 1);
-        if (!val)
-        {
-           LogInfo("ParseElementChildren: NULL value for %s", cur_node->name);
-           continue; 
-        }
-        if (!xmlStrcmp(cur_node->name, (const xmlChar *)"Zone"))
-        {
-            // MaeDomainNameFromDNSNameString does not work for "."
-            if (!xmlStrcmp(val, (const xmlChar *)"."))
-            {
-                ta->zone.c[0] = 0;
-            }
-            else if (!MakeDomainNameFromDNSNameString(&ta->zone, (char *)val))
-            {
-                LogMsg("ParseElementChildren: Cannot parse Zone %s", val);
-                goto error;
-            }
-            else
-            {
-                LogInfo("ParseElementChildren: Element %s, value %##s", cur_node->name, ta->zone.c);
-            }
-        }
-        else if (!xmlStrcmp(cur_node->name, (const xmlChar *)"KeyTag"))
-        {
-            ta->rds.keyTag = strtol((const char *)val, &invalid, 10);
-            if (*invalid != '\0')
-            {
-                LogMsg("ParseElementChildren: KeyTag invalid character %d", *invalid);
-                goto error;
-            }
-            else
-            {
-                LogInfo("ParseElementChildren: Element %s, value %d", cur_node->name, ta->rds.keyTag);
-            }
-        }
-        else if (!xmlStrcmp(cur_node->name, (const xmlChar *)"Algorithm"))
-        {
-            ta->rds.alg = strtol((const char *)val, &invalid, 10);
-            if (*invalid != '\0')
-            {
-                LogMsg("ParseElementChildren: Algorithm invalid character %c", *invalid);
-                goto error;
-            }
-            else
-            {
-                LogInfo("ParseElementChildren: Element %s, value %d", cur_node->name, ta->rds.alg);
-            }
-        }
-        else if (!xmlStrcmp(cur_node->name, (const xmlChar *)"DigestType"))
-        {
-            ta->rds.digestType = strtol((const char *)val, &invalid, 10);
-            if (*invalid != '\0')
-            {
-                LogMsg("ParseElementChildren: Algorithm invalid character %c", *invalid);
-                goto error;
-            }
-            else
-            {
-                LogInfo("ParseElementChildren: Element %s, value %d", cur_node->name, ta->rds.digestType);
-            }
-        }
-        else if (!xmlStrcmp(cur_node->name, (const xmlChar *)"Digest"))
-        {
-            int diglen;
-            mDNSu8 *dig = ConvertDigest((char *)val, ta->rds.digestType, &diglen);
-            if (dig)
-            { 
-                LogInfo("ParseElementChildren: Element %s, digest %s", cur_node->name, val);
-                ta->digestLen = diglen;
-                ta->rds.digest = dig;
-            }
-            else
-            {
-                LogMsg("ParseElementChildren: Element %s, NULL digest", cur_node->name);
-                goto error;
-            }
-        }
-        else if (!xmlStrcmp(cur_node->name, (const xmlChar *)"KeyDigest"))
-        {
-            struct tm tm;
-            val1 = xmlGetProp(cur_node, (const xmlChar *)"validFrom");
-            if (val1)
-            {
-                char *s = strptime((const char *)val1, "%Y-%m-%dT%H:%M:%S", &tm);
-                if (!s)
-                {
-                    LogMsg("ParseElementChildren: Parsing ValidFrom failed %s", val1);
-                    goto error;
-                }
-                else
-                {
-                    ta->validFrom = (mDNSu32)timegm(&tm);
-                }
-            }
-            val2 = xmlGetProp(cur_node, (const xmlChar *)"validUntil");
-            if (val2)
-            {
-                char *s = strptime((const char *)val2, "%Y-%m-%dT%H:%M:%S", &tm);
-                if (!s)
-                {
-                    LogMsg("ParseElementChildren: Parsing ValidFrom failed %s", val2);
-                    goto error;
-                }
-                else
-                {
-                    ta->validUntil = (mDNSu32)timegm(&tm);
-                }
-            }
-            else
-            {
-                // If there is no validUntil, set it to the next probing interval
-                mDNSu32 t = (mDNSu32) time(NULL); 
-                ta->validUntil = t + ROOT_TA_UPDATE_INTERVAL;
-            }
-            LogInfo("ParseElementChildren: ValidFrom time %u, validUntil %u", (unsigned)ta->validFrom, (unsigned)ta->validUntil);
-        }
-        if (val1)
-            xmlFree(val1);
-        if (val2)
-            xmlFree(val2);
-        if (val)
-            xmlFree(val);
-    }
-    return mDNStrue;
-error:
-    if (val1)
-        xmlFree(val1);
-    if (val2)
-        xmlFree(val2);
-    if (val)
-        xmlFree(val);
-    return mDNSfalse;
-}
-
-mDNSlocal mDNSBool ValidateTrustAnchor(TrustAnchor *ta)
-{
-    time_t currTime = time(NULL);
-
-    // Currently only support trust anchor for root.
-    if (!SameDomainName(&ta->zone, (const domainname *)"\000"))
-    {
-        LogInfo("ParseElementChildren: Zone %##s not root", ta->zone.c);
-        return mDNSfalse;
-    }
-
-    switch (ta->rds.digestType)
-    {
-    case SHA1_DIGEST_TYPE:
-        if (ta->digestLen != CC_SHA1_DIGEST_LENGTH) 
-        {
-            LogMsg("ValidateTrustAnchor: Invalid digest len %d for SHA1", ta->digestLen);
-            return mDNSfalse;
-        }
-        break;
-    case SHA256_DIGEST_TYPE:
-        if (ta->digestLen != CC_SHA256_DIGEST_LENGTH) 
-        {
-            LogMsg("ValidateTrustAnchor: Invalid digest len %d for SHA256", ta->digestLen);
-            return mDNSfalse;
-        }
-        break;
-    default:
-        LogMsg("ValidateTrustAnchor: digest type %d not supported", ta->rds.digestType);
-        return mDNSfalse;
-    }
-    if (!ta->rds.digest)
-    {
-        LogMsg("ValidateTrustAnchor: digest NULL for %d", ta->rds.digestType);
-        return mDNSfalse;
-    }
-    switch (ta->rds.alg)
-    {
-    case CRYPTO_RSA_SHA512:
-    case CRYPTO_RSA_SHA256:
-    case CRYPTO_RSA_NSEC3_SHA1:
-    case CRYPTO_RSA_SHA1:
-        break;
-    default:
-        LogMsg("ValidateTrustAnchor: Algorithm %d not supported", ta->rds.alg);
-        return mDNSfalse;
-    }
-    
-    if (DNS_SERIAL_LT(currTime, ta->validFrom))
-    {
-        LogMsg("ValidateTrustAnchor: Invalid ValidFrom time %u, currtime %u", (unsigned)ta->validFrom, (unsigned)currTime);
-        return mDNSfalse;
-    }
-    if (DNS_SERIAL_LT(ta->validUntil, currTime))
-    {
-        LogMsg("ValidateTrustAnchor: Invalid ValidUntil time %u, currtime %u", (unsigned)ta->validUntil, (unsigned)currTime);
-        return mDNSfalse;
-    }
-    return mDNStrue;
-}
-
-mDNSlocal mDNSBool ParseElement(xmlDocPtr tadoc, xmlNode * a_node, TrustAnchor *ta)
-{
-    xmlNode *cur_node = NULL;
-
-    for (cur_node = a_node; cur_node; cur_node = cur_node->next)
-    {
-        if (cur_node->type == XML_ELEMENT_NODE)
-        {
-            // There could be multiple KeyDigests per TrustAnchor. We keep parsing till we
-            // reach the last one or we encounter an error in parsing the document.
-            if (!xmlStrcmp(cur_node->name, (const xmlChar *)"KeyDigest"))
-            {
-                if (ta->rds.digest)
-                    mDNSPlatformMemFree(ta->rds.digest);
-                ta->rds.digestType = 0;
-                ta->digestLen = 0;
-            }
-            if (!ParseElementChildren(tadoc, cur_node->children, ta))
-                return mDNSfalse;
-            if (!ParseElement(tadoc, cur_node->children, ta))
-                return mDNSfalse;
-        }
-    }
-    return mDNStrue;
-}
-
-mDNSlocal void TAComplete(mDNS *const m, void *context)
-{
-    TrustAnchor *ta = (TrustAnchor *)context;
-
-    DelTrustAnchor(m, &ta->zone);
-    LinkTrustAnchor(m, ta);
-}
-
-mDNSlocal void FetchRootTA(mDNS *const m)
-{
-    CFStringRef urlString = CFSTR("https://data.iana.org/root-anchors/root-anchors.xml");
-    CFDataRef xmlData;
-    CFStringRef fileRef = NULL;
-    const char *xmlFileName = NULL;
-    char buf[512];
-    CFURLRef url = NULL;
-    static unsigned int RootTAFetchInterval = InitialTAFetchInterval;
-
-    (void) m;
-
-    TrustAnchor *ta = (TrustAnchor *) mDNSPlatformMemAllocateClear(sizeof(*ta));
-    if (!ta)
-    {
-        LogMsg("FetchRootTA: TrustAnchor alloc failed");
-        return;
-    }
-
-    url = CFURLCreateWithString(NULL, urlString, NULL);
-    if (!url)
-    {
-        LogMsg("FetchRootTA: CFURLCreateWithString error");
-        mDNSPlatformMemFree(ta);
-        return;
-    }
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-    // If we can't fetch the XML file e.g., network problems, trigger a timer. All other failures
-    // should hardly happen in practice for which schedule the normal interval to refetch the TA.
-    Boolean success = CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, url, &xmlData, NULL, NULL, NULL);
-#pragma clang diagnostic pop
-    if (!success)
-    {
-        LogInfo("FetchRootTA: CFURLCreateDataAndPropertiesFromResource error");
-        CFRelease(url);
-        mDNSPlatformMemFree(ta);
-        RegisterNotification(m, RootTAFetchInterval);
-        RootTAFetchInterval *= 2 + 1;
-        return;
-    }
-
-    // get the name of the last component from the url, libxml will use it if
-    // it has to report an error
-    fileRef = CFURLCopyLastPathComponent(url);
-    if (fileRef)
-    {
-        xmlFileName = CFStringGetCStringPtr(fileRef, kCFStringEncodingUTF8);
-        if (!xmlFileName)
-        {
-            if (!CFStringGetCString(fileRef, buf, sizeof(buf), kCFStringEncodingUTF8) )
-                strlcpy(buf, "nofile.xml", sizeof(buf));
-            xmlFileName = (const char *)buf;
-        }
-    }
-
-    // Parse the XML and get the CFXMLTree.
-    xmlDocPtr tadoc = xmlReadMemory((const char*)CFDataGetBytePtr(xmlData),
-        (int)CFDataGetLength(xmlData), xmlFileName, NULL, 0);        
-
-    if (fileRef)
-        CFRelease(fileRef);
-    CFRelease(url);
-    CFRelease(xmlData);
-
-    if (!tadoc)
-    {
-        LogMsg("FetchRootTA: xmlReadMemory failed");
-        goto done;
-    }
-
-    xmlNodePtr root = xmlDocGetRootElement(tadoc);
-    if (!root)
-    {
-        LogMsg("FetchRootTA: Cannot get root element");
-        goto done;
-    }
-
-    if (ParseElement(tadoc, root, ta) && ValidateTrustAnchor(ta))
-    {
-        // Do the actual addition of TA on the main queue.
-        mDNSPlatformDispatchAsync(m, ta, TAComplete);
-    }
-    else
-    {
-        if (ta->rds.digest)
-            mDNSPlatformMemFree(ta->rds.digest);
-        mDNSPlatformMemFree(ta);
-    }
-done:
-    if (tadoc)
-        xmlFreeDoc(tadoc);
-    RegisterNotification(m, ROOT_TA_UPDATE_INTERVAL);
-    RootTAFetchInterval = InitialTAFetchInterval;
-    return;
-}
-
-
-#if APPLE_OSX_mDNSResponder && !TARGET_OS_IPHONE
-mDNSlocal void DNSSECProbeCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord)
-{
-    if (!AddRecord)
-        return;
-
-    mDNS_Lock(m);
-    if ((m->timenow - question->StopTime) >= 0)
-    {
-        mDNS_Unlock(m);
-        LogDNSSEC("DNSSECProbeCallback: Question %##s (%s) timed out", question->qname.c, DNSTypeName(question->qtype));
-        mDNS_StopQuery(m, question);
-        return;
-    }
-    mDNS_Unlock(m);
-
-    // Wait till we get the DNSSEC results. If we get a negative response e.g., no DNS servers, the
-    // question will be restarted by the core and we should have the DNSSEC results eventually.
-    if (AddRecord != QC_dnssec)
-    {
-        LogDNSSEC("DNSSECProbeCallback: Question %##s (%s)", question->qname.c, DNSTypeName(question->qtype), RRDisplayString(m, answer));
-        return;
-    }
-
-    LogDNSSEC("DNSSECProbeCallback: Question %##s (%s), DNSSEC status %s", question->qname.c, DNSTypeName(question->qtype),
-            DNSSECStatusName(question->ValidationStatus));
-
-    mDNS_StopQuery(m, question);
-}
-
-// Send a DNSSEC probe just for the sake of collecting DNSSEC statistics.
-mDNSexport void DNSSECProbe(mDNS *const m)
-{
-    mDNSu32 rand;
-
-    if (DNSSECProbeQuestion.ThisQInterval != -1)
-        return;
-    
-    rand = mDNSRandom(FutureTime) % 100;
-    // Probe 1% of the time
-    if (rand >= DNSSECProbePercentage)
-        return;
-    
-    mDNS_DropLockBeforeCallback();
-    InitializeQuestion(m, &DNSSECProbeQuestion, mDNSInterface_Any, (const domainname *)"\003com", kDNSType_DS, DNSSECProbeCallback, mDNSNULL);
-    DNSSECProbeQuestion.ValidatingResponse = 0;
-    DNSSECProbeQuestion.ValidationRequired = DNSSEC_VALIDATION_SECURE;
-
-    BumpDNSSECStats(m, kStatsActionIncrement, kStatsTypeProbe, 1);
-    mDNS_StartQuery(m, &DNSSECProbeQuestion);
-    mDNS_ReclaimLockAfterCallback(); 
-}
-#endif // APPLE_OSX_mDNSResponder && !TARGET_OS_IPHONE
-
-// For now we fetch the root trust anchor and update the local copy
-mDNSexport void UpdateTrustAnchors(mDNS *const m)
-{
-    // Register for a notification to fire immediately which in turn will update
-    // the trust anchor
-    if (RegisterNotification(m, 1))
-    {
-        LogMsg("UpdateTrustAnchors: ERROR!! failed to register for notification");
-    }
-}
-
-mDNSlocal int RegisterNotification(mDNS *const m, unsigned int interval)
-{
-    int len = strlen("com.apple.system.notify.service.timer:+") + 21; // 21 bytes to accomodate the interval
-    char buffer[len];
-    unsigned int blen;
-    int status;
-
-    // Starting "interval" second from now (+ below indicates relative) register for a notification
-    blen = mDNS_snprintf(buffer, sizeof(buffer), "com.apple.system.notify.service.timer:+%us", interval);
-    if (blen >= sizeof(buffer))
-    {
-        LogMsg("RegisterNotification: Buffer too small blen %d, buffer size %d", blen, sizeof(buffer));
-        return -1;
-    } 
-    LogInfo("RegisterNotification: buffer %s", buffer);
-    if (m->notifyToken)
-    {
-        notify_cancel(m->notifyToken);
-        m->notifyToken = 0;
-    }
-    status = notify_register_dispatch(buffer, &m->notifyToken,
-                dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
-                ^(int t) { (void) t; FetchRootTA(m); });
-
-    if (status != NOTIFY_STATUS_OK)
-    {
-        LogMsg("RegisterNotification: notify_register_dispatch failed");
-        return -1;
-    }
-    return 0;
-}
-
-mDNSexport mStatus DNSSECPlatformInit(mDNS *const m)
-{
-    int diglen;
-
-    m->TrustAnchors = mDNSNULL;
-    m->notifyToken  = 0;
-
-    // Add a couple of trust anchors for testing purposes.
-    mDNSlocal const domainname *testZone  = (const domainname*)"\007example";
-
-    char *digest = "F122E47B5B7D2B6A5CC0A21EADA11D96BB9CC927";
-    mDNSu8 *dig = ConvertDigest(digest, 1, &diglen);
-    if (dig) AddTrustAnchor(m, testZone, 23044, 5, 1, diglen, dig);
-
-    char *digest1 = "D795AE5E1AFB200C6139474199B70EAD3F3484553FD97BE5A43704B8A4791F21";
-    dig = ConvertDigest(digest1, 2, &diglen);
-    if (dig) AddTrustAnchor(m, testZone, 23044, 5, 2, diglen, dig);
-
-    // Add the TA for root zone manually here. We will dynamically fetch the root TA and
-    // update it shortly. If that fails e.g., disconnected from the network, we still
-    // have something to work with.
-    char *digest2 = "49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5";
-    dig = ConvertDigest(digest2, 2, &diglen);
-    if (dig) AddTrustAnchor(m, (const domainname *)"\000", 19036, 8, 2, diglen, dig);
-
-#if !TARGET_OS_IPHONE
-    DNSSECProbeQuestion.ThisQInterval = -1;
-#endif
-    return mStatus_NoError;
-}
diff --git a/mDNSMacOSX/DNSSECSupport.h b/mDNSMacOSX/DNSSECSupport.h
deleted file mode 100644 (file)
index eaaf36e..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/* -*- Mode: C; tab-width: 4 -*-
- *
- * Copyright (c) 2012-2013 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __DNSSEC_SUPPORT_H
-#define __DNSSEC_SUPPORT_H
-
-extern mStatus DNSSECPlatformInit(mDNS *const m);
-extern void UpdateTrustAnchors(mDNS *const m);
-
-#endif // __DNSSEC_SUPPORT_H
diff --git a/mDNSMacOSX/FeatureFlags/mDNSResponder.plist b/mDNSMacOSX/FeatureFlags/mDNSResponder.plist
new file mode 100644 (file)
index 0000000..1d7c9e1
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>bonjour_privacy</key>
+       <dict>
+               <key>Enabled</key>
+               <true/>
+               <key>DisplayName</key>
+               <string>Require Entitlements For Bonjour</string>
+               <key>Description</key>
+               <string>Restrict bonjour browsing without proper entitlments</string>
+               <key>Radar</key>
+               <integer>59374417</integer>
+       </dict>
+</dict>
+</plist>
diff --git a/mDNSMacOSX/HTTPUtilities.h b/mDNSMacOSX/HTTPUtilities.h
new file mode 100644 (file)
index 0000000..b024c8e
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __HTTPUtilities_h
+#define __HTTPUtilities_h
+
+#include <dispatch/dispatch.h>
+#include <nw/private.h>
+
+CFStringRef
+create_base64_string(dispatch_data_t message);
+
+void
+http_set_resolver_queue(dispatch_queue_t queue);
+
+typedef void (^http_task_dns_query_response_handler_t)(dispatch_data_t data, CFErrorRef error);
+
+void *
+http_task_create_dns_query(nw_endpoint_t endpoint,
+                                                  const char *url_string,
+                                                  dispatch_data_t message,
+                           uint16_t query_type,
+                                                  bool use_post,
+                                                  http_task_dns_query_response_handler_t response_handler);
+
+void *
+http_task_create_pvd_query(dispatch_queue_t queue,
+                                                  const char *host,
+                                                  const char *path,
+                                                  void (^response_handler)(xpc_object_t json_object));
+
+void
+http_task_start(void *task);
+
+void
+http_task_cancel(void *task);
+
+#endif // __HTTPUtilities_h
diff --git a/mDNSMacOSX/HTTPUtilities.m b/mDNSMacOSX/HTTPUtilities.m
new file mode 100644 (file)
index 0000000..ec86d8a
--- /dev/null
@@ -0,0 +1,293 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <TargetConditionals.h>
+
+// _createDispatchData in NSData_Private.h requires either
+// DEPLOYMENT_TARGET_EMBEDDED or DEPLOYMENT_TARGET_MACOSX.
+// We define them here, rather than in our project file.
+
+#if TARGET_OS_IPHONE
+// DEPLOYMENT_TARGET_EMBEDDED covers both embedded and simulator
+#  ifndef DEPLOYMENT_TARGET_EMBEDDED
+#    define DEPLOYMENT_TARGET_EMBEDDED 1
+#  endif // DEPLOYMENT_TARGET_EMBEDDED
+#else // TARGET_OS_IPHONE
+#  ifndef DEPLOYMENT_TARGET_MACOSX
+#    define DEPLOYMENT_TARGET_MACOSX 1
+#  endif // DEPLOYMENT_TARGET_MACOSX
+#endif // TARGET_OS_IPHONE
+
+#import <Foundation/NSData_Private.h>
+#import <CFNetwork/CFNSURLConnection.h>
+#import <nw/private.h>
+
+#import "mdns_symptoms.h"
+#import "DNSMessage.h"
+#import "HTTPUtilities.h"
+#import <CoreFoundation/CFXPCBridge.h>
+#import "DNSHeuristics.h"
+#import <Foundation/Foundation.h>
+#import <os/log.h>
+
+static NSURLSession *
+shared_session(dispatch_queue_t queue)
+{
+       static NSURLSession *session = nil;
+       static dispatch_once_t onceToken;
+       dispatch_once(&onceToken, ^{
+               @autoreleasepool {
+                       // Disable AppSSO (process-wide) before any use of URLSession
+                       [NSURLSession _disableAppSSO];
+
+                       // Create (and configure) the NSURLSessionConfiguration
+                       NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
+
+                       // Disable HTTP Cookies
+                       configuration.HTTPCookieStorage = nil;
+
+                       // Disable HTTP Cache
+                       configuration.URLCache = nil;
+
+                       // Disable Credential Storage
+                       configuration.URLCredentialStorage = nil;
+
+                       if (@available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *)) {
+                               // Disable ATS (process-wide) before any use of URLSession
+                               [NSURLSession _disableATS];
+
+                               // Disable reachability lookups
+                               configuration._allowsReachabilityCheck = NO;
+                       }
+
+                       configuration._suppressedAutoAddedHTTPHeaders = [NSSet setWithObjects:@"User-Agent", nil];
+                       configuration._allowsTLSSessionTickets = YES;
+                       configuration._allowsTCPFastOpen = YES;
+
+                       NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
+                       operationQueue.underlyingQueue = queue;
+                       session = [NSURLSession sessionWithConfiguration:configuration
+                                                                                                       delegate:nil
+                                                                                          delegateQueue:operationQueue];
+               }
+       });
+       return session;
+}
+
+CFStringRef
+create_base64_string(dispatch_data_t message)
+{
+       @autoreleasepool {
+               NSString *base64String = [((NSData *)message) base64EncodedStringWithOptions:0];
+               base64String = [base64String stringByReplacingOccurrencesOfString:@"/"
+                                                                                                                               withString:@"_"];
+               base64String = [base64String stringByReplacingOccurrencesOfString:@"+"
+                                                                                                                          withString:@"-"];
+               return (__bridge_retained CFStringRef)base64String;
+       }
+}
+
+void
+http_set_resolver_queue(dispatch_queue_t queue)
+{
+       @autoreleasepool {
+               // Set up session
+               (void)shared_session(queue);
+       }
+}
+
+void *
+http_task_create_dns_query(nw_endpoint_t endpoint, const char *urlString, dispatch_data_t message, uint16_t query_type, bool use_post, http_task_dns_query_response_handler_t response_handler)
+{
+       @autoreleasepool {
+               NSURLSession *session = shared_session(nil);
+               NSMutableURLRequest *request = nil;
+               if (use_post) {
+                       request = [[NSMutableURLRequest alloc] initWithURL:(NSURL *)[[NSURL alloc] initWithString:(NSString *)@(urlString)]];
+                       request.HTTPMethod = @"POST";
+                       request.HTTPBody = (NSData *)message;
+               } else {
+                       NSString *base64String = [((NSData *)message) base64EncodedStringWithOptions:0];
+                       base64String = [base64String stringByReplacingOccurrencesOfString:@"/"
+                                                                                                                                       withString:@"_"];
+                       base64String = [base64String stringByReplacingOccurrencesOfString:@"+"
+                                                                                                                                  withString:@"-"];
+                       base64String = [base64String stringByReplacingOccurrencesOfString:@"="
+                                                                                                                                  withString:@""];
+                       NSString *urlWithQuery = [NSString stringWithFormat:@"%s?dns=%@", urlString, base64String];
+                       request = [[NSMutableURLRequest alloc] initWithURL:(NSURL *)[[NSURL alloc] initWithString:urlWithQuery]];
+                       request.HTTPMethod = @"GET";
+               }
+               [request setValue:@"application/dns-message" forHTTPHeaderField:@"accept"];
+               [request setValue:@"application/dns-message" forHTTPHeaderField:@"content-type"];
+
+               __block nw_activity_t activity = nil;
+               switch (query_type) {
+                       case kDNSRecordType_A: {
+                               activity = nw_activity_create(kDNSActivityDomain, kDNSActivityLabelUnicastAQuery);
+                               break;
+                       }
+                       case kDNSRecordType_AAAA: {
+                               activity = nw_activity_create(kDNSActivityDomain, kDNSActivityLabelUnicastAAAAQuery);
+                               break;
+                       }
+                       default: {
+                               // Don't mark any activity for non-address queries.
+                               break;
+                       }
+               }
+
+               if (activity != nil) {
+                       nw_activity_activate(activity);
+               }
+
+               NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
+               completionHandler:^(NSData *data,
+                                                       __unused NSURLResponse *response,
+                                                       NSError *error) {
+                       if (activity != nil) {
+                               if (error != nil) {
+                                       nw_activity_complete_with_reason(activity, nw_activity_completion_reason_failure);
+                               } else {
+                                       nw_activity_complete_with_reason(activity, nw_activity_completion_reason_success);
+                               }
+                       }
+
+                       if (error != nil) {
+                               // If we did not receive a response from the server, report a failure.
+                               // Any HTTP response, even one with a 50x failure, will yield a nil error.
+                               // Any other error, such as a TCP-level or TLS-level failure, will manifest
+                               // in a non-nil error. We only care about non-HTTP errors here.
+
+                               // Some NSURLSession errors are also ignored here, such as when a task
+                               // is cancelled (a common occurrence due to the client behavior) or the
+                               // connection failed due to no network.
+                               const bool errorIsWhitelisted = ([error.domain isEqualToString:NSURLErrorDomain] &&
+                                                                                                (error.code == NSURLErrorCancelled ||
+                                                                                                 error.code == NSURLErrorNotConnectedToInternet));
+                               const bool errorIsTimeout = ([error.domain isEqualToString:NSURLErrorDomain] &&
+                                                                                        error.code == NSURLErrorTimedOut);
+                               if (!errorIsWhitelisted) {
+                                       dns_heuristics_report_resolution_failure([request URL], errorIsTimeout);
+                               }
+                       } else {
+                               dns_heuristics_report_resolution_success();
+                       }
+
+                       dispatch_data_t dispatch_data = [data _createDispatchData];
+                       response_handler(dispatch_data, (__bridge CFErrorRef)error);
+               }];
+
+               if (@available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *)) {
+                       dataTask._hostOverride = endpoint;
+               }
+
+               if (dataTask && activity != nil) {
+                       dataTask._nw_activity = activity;
+               }
+
+               return (__bridge_retained void *)dataTask;
+       }
+}
+
+void *
+http_task_create_pvd_query(dispatch_queue_t queue, const char *host, const char *path, void (^response_handler)(xpc_object_t json_object))
+{
+       @autoreleasepool {
+               NSURLSession *session = shared_session(nil);
+               NSString *pvdURL = [NSString stringWithFormat:@"https://%s/.well-known/pvd%s", host, path];
+               NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:(NSURL *)[[NSURL alloc] initWithString:pvdURL]];
+               request.HTTPMethod = @"GET";
+               [request setValue:@"application/pvd+json" forHTTPHeaderField:@"accept"];
+               [request setValue:@"application/pvd+json" forHTTPHeaderField:@"content-type"];
+
+               __block nw_activity_t activity = nw_activity_create(kDNSActivityDomain, kDNSActivityLabelProvisioningRequest);
+               if (activity != nil) {
+                       nw_activity_activate(activity);
+               }
+
+               NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
+               completionHandler:^(NSData *data,
+                                                       __unused NSURLResponse *response,
+                                                       __unused NSError *error) {
+                       dispatch_async(queue, ^{
+                               if (data == nil) {
+                                       nw_activity_complete_with_reason(activity, nw_activity_completion_reason_failure);
+                                       response_handler(nil);
+                               } else {
+                                       NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
+                                       if ([dictionary isKindOfClass:[NSDictionary class]]) {
+                                               xpc_object_t xpc_dictionary = _CFXPCCreateXPCObjectFromCFObject((__bridge CFDictionaryRef)dictionary);
+
+                                               // Convert "expires" to "seconds-remaining"
+                                               NSString *expires = dictionary[@"expires"];
+                                               NSNumber *secondsRemaining = dictionary[@"seconds-remaining"];
+
+                                               if (xpc_dictionary != nil &&
+                                                       [expires isKindOfClass:[NSString class]] && secondsRemaining == nil) {
+                                                       NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
+                                                       [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
+                                                       [dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
+                                                       [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"];
+                                                       [dateFormatter setFormatterBehavior:NSDateFormatterBehaviorDefault];
+
+                                                       NSDate *date = [dateFormatter dateFromString:expires];
+
+                                                       NSTimeInterval secondsFromNowFloat = date.timeIntervalSinceNow;
+                                                       uint64_t secondsFromNow = (uint64_t)secondsFromNowFloat;
+
+                                                       xpc_dictionary_set_uint64(xpc_dictionary, "seconds-remaining", secondsFromNow);
+                                               } else if (xpc_dictionary != nil && secondsRemaining != nil) {
+                                                       uint64_t secondsFromNow = (uint64_t)secondsRemaining.unsignedLongLongValue;
+                                                       xpc_dictionary_set_uint64(xpc_dictionary, "seconds-remaining", secondsFromNow);
+                                               }
+
+                                               nw_activity_complete_with_reason(activity, nw_activity_completion_reason_success);
+                                               response_handler(xpc_dictionary);
+                                       } else {
+                                               nw_activity_complete_with_reason(activity, nw_activity_completion_reason_failure);
+                                               response_handler(nil);
+                                       }
+                               }
+                       });
+               }];
+
+               if (dataTask && activity != nil) {
+                       dataTask._nw_activity = nw_activity_create(kDNSActivityDomain, kDNSActivityLabelProvisioningRequest);
+               }
+
+               return (__bridge_retained void *)dataTask;
+       }
+}
+
+void
+http_task_start(void *task)
+{
+       @autoreleasepool {
+               NSURLSessionDataTask *dataTask = (__bridge NSURLSessionDataTask *)task;
+               [dataTask resume];
+       }
+}
+
+void
+http_task_cancel(void *task)
+{
+       @autoreleasepool {
+               NSURLSessionDataTask *dataTask = (__bridge_transfer NSURLSessionDataTask *)task;
+               [dataTask cancel];
+               dataTask = nil;
+       }
+}
diff --git a/mDNSMacOSX/LoggingProfiles/AppleInternal/com.apple.mdns.plist b/mDNSMacOSX/LoggingProfiles/AppleInternal/com.apple.mdns.plist
new file mode 100644 (file)
index 0000000..83ab7ee
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>DEFAULT-OPTIONS</key>
+       <dict>
+               <key>Level</key>
+               <dict>
+                       <key>Persist</key>
+                       <string>Info</string>
+               </dict>
+       </dict>
+       <key>resolver</key>
+       <dict>
+               <key>Enable-Oversize-Messages</key>
+               <true/>
+       </dict>
+</dict>
+</plist>
diff --git a/mDNSMacOSX/LoggingProfiles/AppleInternal/com.apple.srp-mdns-proxy.plist b/mDNSMacOSX/LoggingProfiles/AppleInternal/com.apple.srp-mdns-proxy.plist
new file mode 100644 (file)
index 0000000..2deb6cb
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>DEFAULT-OPTIONS</key>
+       <dict>
+               <key>Level</key>
+               <dict>
+                       <key>Persist</key>
+                       <string>Debug</string>
+               </dict>
+       </dict>
+</dict>
+</plist>
index 1eba185d5000ca6c0573de4e24b0a5f484bd5674..680d65e49229043e29c70776c520a20b850fffc8 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
        <key>DEFAULT-OPTIONS</key>
                        <string>Inherit</string>
                </dict>
        </dict>
+       <key>Default</key>
+       <dict>
+               <key>Enable-Oversize-Messages</key>
+               <true/>
+       </dict>
 </dict>
 </plist>
diff --git a/mDNSMacOSX/LoggingProfiles/com.apple.srp-mdns-proxy.plist b/mDNSMacOSX/LoggingProfiles/com.apple.srp-mdns-proxy.plist
new file mode 100644 (file)
index 0000000..1eba185
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>DEFAULT-OPTIONS</key>
+       <dict>
+               <key>Level</key>
+               <dict>
+                       <key>Persist</key>
+                       <string>Inherit</string>
+                       <key>Enable</key>
+                       <string>Inherit</string>
+               </dict>
+       </dict>
+</dict>
+</plist>
diff --git a/mDNSMacOSX/Private/advertising_proxy_services.c b/mDNSMacOSX/Private/advertising_proxy_services.c
new file mode 100644 (file)
index 0000000..4a21fa8
--- /dev/null
@@ -0,0 +1,299 @@
+/* advertising_proxy_services.h
+ *
+ * Copyright (c) 2020 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the implementation of the SRP Advertising Proxy management
+ * API on MacOS, which is private API used to control and manage the advertising
+ * proxy.
+ */
+
+
+#include <Block.h>
+#include <os/log.h>
+#include "xpc_clients.h"
+#include "advertising_proxy_services.h"
+#include "srp.h"
+
+#if defined(TARGET_OS_TV)
+
+//*************************************************************************************************************
+// Globals
+
+typedef struct _advertising_proxy_conn_ref_t
+{
+    int                        ref_count;     // This structure is refcounted
+    xpc_connection_t           connection;    // xpc_connection between client and daemon
+    advertising_proxy_reply    app_callback;  // Callback function ptr for Client
+    dispatch_queue_t           client_queue;  // Queue specified by client for scheduling its Callback
+    const char                *command_name;  // The advertising proxy command we've been given
+} adv_conn_ref_t;
+
+static void
+advertising_proxy_ref_finalize(adv_conn_ref_t *conn_ref)
+{
+    free(conn_ref);
+}
+
+void advertising_proxy_ref_dealloc(adv_conn_ref_t *conn_ref)
+{
+    if (conn_ref == NULL)
+    {
+        os_log(OS_LOG_DEFAULT, "dns_services: advertising_proxy_ref_dealloc called with NULL advertising_proxy_conn_ref");
+        return;
+    }
+    conn_ref->app_callback = NULL;
+    if (conn_ref->connection != NULL) {
+        xpc_connection_cancel(conn_ref->connection);
+    }
+
+    // This is releasing the caller's reference. We may still have an internal reference.
+    RELEASE_HERE(conn_ref, advertising_proxy_ref_finalize);
+    os_log(OS_LOG_DEFAULT, "dns_services: advertising_proxy_ref_dealloc successfully released conn_ref");
+}
+
+static void
+adv_connection_finalize(void *v_conn_ref)
+{
+    adv_conn_ref_t *conn_ref = v_conn_ref;
+    os_log(OS_LOG_DEFAULT, "adv_connection_finalize: releasing conn_ref at %d", conn_ref->ref_count);
+    RELEASE_HERE(conn_ref, advertising_proxy_ref_finalize);
+}
+
+// Called for errors. The cancel argument is set to true if we need the callback to cancel the connection, as will be
+// true for errors other than XPC_ERROR_CONNECTION_INVALID.  If the callback doesn't call advertising_proxy_ref_dealloc,
+// which will cancel the connection, then adv_connection_call_callback cancels it.
+static void
+adv_connection_call_callback(adv_conn_ref_t *conn_ref, xpc_object_t *event, int status, bool cancel)
+{
+    int ref_count = conn_ref->ref_count;
+    conn_ref->app_callback(conn_ref, event, status);
+    if (conn_ref->ref_count > 1 && conn_ref->ref_count == ref_count) {
+#ifndef __clang_analyzer__
+        RELEASE_HERE(conn_ref, advertising_proxy_ref_finalize);
+#endif
+        if (cancel) {
+            xpc_connection_cancel(conn_ref->connection);
+        }
+    }
+
+    // We can never call the callback again after reporting an error.
+    conn_ref->app_callback = NULL;
+}
+
+static void
+adv_event_handler(xpc_object_t event, adv_conn_ref_t *conn_ref)
+{
+    if (event == XPC_ERROR_CONNECTION_INVALID) {
+        os_log(OS_LOG_DEFAULT, "adv_event_handler (%s): cleanup %p %p", conn_ref->command_name, conn_ref, conn_ref->connection);
+        if (conn_ref->app_callback != NULL) {
+            adv_connection_call_callback(conn_ref, event, kDNSSDAdvertisingProxyStatus_Disconnected, false);
+        } else {
+            os_log(OS_LOG_DEFAULT, "No callback");
+        }
+        if (conn_ref->connection != NULL) {
+            xpc_release(conn_ref->connection);
+            conn_ref->connection = NULL;
+        } else {
+            ERROR("adv_event_handler(%s): cleanup: conn_ref->connection is NULL when it shouldn't be!",
+                  conn_ref->command_name);
+        }
+    } else if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) {
+        if (conn_ref->app_callback != NULL) {
+            conn_ref->app_callback(conn_ref, event, kDNSSDAdvertisingProxyStatus_NoError);
+        } else {
+            os_log(OS_LOG_DEFAULT, "adv_event_handler (%s): no callback", conn_ref->command_name);
+        }
+    } else {
+        os_log(OS_LOG_DEFAULT, "adv_event_handler: Unexpected Connection Error [%s]",
+               xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
+        if (conn_ref->app_callback) {
+            adv_connection_call_callback(conn_ref, event, kDNSSDAdvertisingProxyStatus_DaemonNotRunning, true);
+        } else {
+            os_log(OS_LOG_DEFAULT, "adv_event_handler: no callback");
+            xpc_connection_cancel(conn_ref->connection);
+        }
+    }
+}
+
+// Creates a new advertising_proxy_ Connection Reference(advertising_proxy_conn_ref)
+static advertising_proxy_error_type
+adv_init_connection(adv_conn_ref_t **ref, const char *servname, xpc_object_t dict,
+                const char *command_name, advertising_proxy_reply app_callback, dispatch_queue_t client_queue,
+                const char *file, int line)
+{
+    // Use an adv_conn_ref_t *on the stack to be captured in the blocks below, rather than
+    // capturing the advertising_proxy_conn_ref* owned by the client
+    adv_conn_ref_t *conn_ref = calloc(1, sizeof(adv_conn_ref_t));
+    if (conn_ref == NULL) {
+        os_log(OS_LOG_DEFAULT, "dns_services: init_connection() No memory to allocate!");
+        return kDNSSDAdvertisingProxyStatus_NoMemory;
+   }
+
+    // Initialize the advertising_proxy_conn_ref
+    dispatch_retain(client_queue);
+    conn_ref->command_name = command_name;
+    conn_ref->client_queue = client_queue;
+    conn_ref->app_callback = app_callback;
+    conn_ref->connection = xpc_connection_create_mach_service(servname, conn_ref->client_queue,
+                                                              XPC_CONNECTION_MACH_SERVICE_PRIVILEGED);
+
+    if (conn_ref->connection == NULL)
+    {
+        os_log(OS_LOG_DEFAULT, "dns_services: init_connection() conn_ref/lib_q is NULL");
+        if (conn_ref != NULL) {
+            free(conn_ref);
+        }
+        return kDNSSDAdvertisingProxyStatus_NoMemory;
+    }
+
+    RETAIN_HERE(conn_ref); // For the event handler.
+    xpc_connection_set_event_handler(conn_ref->connection, ^(xpc_object_t event) { adv_event_handler(event, conn_ref); });
+    xpc_connection_set_finalizer_f(conn_ref->connection, adv_connection_finalize);
+    xpc_connection_set_context(conn_ref->connection, conn_ref);
+    xpc_connection_resume(conn_ref->connection);
+
+    xpc_connection_send_message_with_reply(conn_ref->connection, dict, conn_ref->client_queue,
+                                           ^(xpc_object_t event) { adv_event_handler(event, conn_ref); });
+
+    if (ref) {
+        *ref = conn_ref;
+        // For the caller
+        RETAIN(conn_ref);
+    }
+    return kDNSSDAdvertisingProxyStatus_NoError;
+}
+
+#define adv_send_command(ref, client_queue, command_name, dict, command, app_callback)  \
+    adv_send_command_(ref, client_queue, command_name, dict, command, app_callback, __FILE__, __LINE__)
+static advertising_proxy_error_type
+adv_send_command_(adv_conn_ref_t **ref, dispatch_queue_t client_queue, const char *command_name,
+                  xpc_object_t dict, const char *command, advertising_proxy_reply app_callback, const char *file, int line)
+{
+    advertising_proxy_error_type errx = kDNSSDAdvertisingProxyStatus_NoError;
+
+    if (dict == NULL) {
+        os_log(OS_LOG_DEFAULT, "adv_send_command(%s): no memory for command dictionary.", command_name);
+        return kDNSSDAdvertisingProxyStatus_NoMemory;
+    }
+
+    // Sanity Checks
+    if (app_callback == NULL || client_queue == NULL)
+    {
+        os_log(OS_LOG_DEFAULT, "%s: NULL cti_connection_t OR Callback OR Client_Queue parameter",
+               command_name);
+        return kDNSSDAdvertisingProxyStatus_BadParam;
+    }
+
+    xpc_dictionary_set_string(dict, kDNSAdvertisingProxyCommand, command);
+
+    errx = adv_init_connection(ref, kDNSAdvertisingProxyService, dict, command_name, app_callback, client_queue, file, line);
+    if (errx) // On error init_connection() leaves *conn_ref set to NULL
+    {
+        os_log(OS_LOG_DEFAULT,
+               "%s: Since init_connection() returned %d error returning w/o sending msg",
+               command_name, errx);
+        return errx;
+    }
+
+    return errx;
+}
+
+
+advertising_proxy_error_type
+advertising_proxy_enable(adv_conn_ref_t **conn_ref, dispatch_queue_t client_queue, advertising_proxy_reply callback)
+{
+    advertising_proxy_error_type errx;
+    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_enable",
+                            dict, kDNSAdvertisingProxyEnable, callback);
+    xpc_release(dict);
+    return errx;
+}
+
+advertising_proxy_error_type
+advertising_proxy_flush_entries(adv_conn_ref_t **conn_ref,
+                                dispatch_queue_t client_queue, advertising_proxy_reply callback)
+{
+    advertising_proxy_error_type errx;
+    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_flush_entries",
+                            dict, kDNSAdvertisingProxyFlushEntries, callback);
+    xpc_release(dict);
+    return errx;
+}
+
+advertising_proxy_error_type
+advertising_proxy_get_service_list(adv_conn_ref_t **conn_ref,
+                                   dispatch_queue_t client_queue, advertising_proxy_reply callback)
+{
+    advertising_proxy_error_type errx;
+    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_get_service_list",
+                            dict, kDNSAdvertisingProxyListServices, callback);
+    xpc_release(dict);
+    return errx;
+}
+
+advertising_proxy_error_type
+advertising_proxy_block_service(adv_conn_ref_t **conn_ref,
+                                dispatch_queue_t client_queue, advertising_proxy_reply callback)
+{
+    advertising_proxy_error_type errx;
+    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_block_service",
+                            dict, kDNSAdvertisingProxyBlockService, callback);
+    xpc_release(dict);
+    return errx;
+}
+
+advertising_proxy_error_type
+advertising_proxy_unblock_service(adv_conn_ref_t **conn_ref,
+                                  dispatch_queue_t client_queue, advertising_proxy_reply callback)
+{
+    advertising_proxy_error_type errx;
+    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_unblock_service",
+                            dict, kDNSAdvertisingProxyUnblockService, callback);
+    xpc_release(dict);
+    return errx;
+}
+
+advertising_proxy_error_type
+advertising_proxy_regenerate_ula(adv_conn_ref_t **conn_ref,
+                                 dispatch_queue_t client_queue, advertising_proxy_reply callback)
+{
+    advertising_proxy_error_type errx;
+    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_regenerate_ula",
+                            dict, kDNSAdvertisingProxyRegenerateULA, callback);
+    xpc_release(dict);
+    return errx;
+}
+#endif // defined(TARGET_OS_TV)
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 120
+// indent-tabs-mode: nil
+// End:
diff --git a/mDNSMacOSX/Private/advertising_proxy_services.h b/mDNSMacOSX/Private/advertising_proxy_services.h
new file mode 100644 (file)
index 0000000..9fdd77f
--- /dev/null
@@ -0,0 +1,286 @@
+/* advertising_proxy_services.h
+ *
+ * Copyright (c) 2020 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains definitions for the SRP Advertising Proxy management
+ * API on MacOS, which is private API used to control and manage the advertising
+ * proxy.
+ */
+
+#ifndef DNSSD_PROXY_SERVICES_H
+#define DNSSD_PROXY_SERVICES_H
+
+#if defined(TARGET_OS_TV)
+
+#include <dispatch/dispatch.h>
+#include <xpc/xpc.h>
+
+#if (defined(__GNUC__) && (__GNUC__ >= 4))
+#define DNS_SERVICES_EXPORT __attribute__((visibility("default")))
+#else
+#define DNS_SERVICES_EXPORT
+#endif
+
+// advertising_proxy_conn_ref: Opaque internal data type
+typedef struct _advertising_proxy_conn_ref_t *advertising_proxy_conn_ref;
+
+typedef enum
+{
+    kDNSSDAdvertisingProxyStatus_NoError                   =  0,
+    kDNSSDAdvertisingProxyStatus_UnknownErr                = -65537,   /* 0xFFFE FFFF */
+    kDNSSDAdvertisingProxyStatus_NoMemory                  = -65539,   /* No Memory   */
+    kDNSSDAdvertisingProxyStatus_BadParam                  = -65540,   /* Client passed invalid arg */
+    kDNSSDAdvertisingProxyStatus_DaemonNotRunning          = -65563,   /* Daemon not running */
+    kDNSSDAdvertisingProxyStatus_Disconnected              = -65569    /* Daemon disconnected */
+} advertising_proxy_error_type;
+
+/*********************************************************************************************
+ *
+ *  DNSSD Advertising Proxy control/management library functions
+ *
+ *********************************************************************************************/
+
+/* advertising_proxy_reply: Callback from all DNSSD Advertising proxy library functions
+ *
+ * advertising_proxy_reply() parameters:
+ *
+ * conn_ref:                   The advertising_proxy_conn_ref initialized by the library function.  Call advertising_proxy_
+ *
+ * response:                   Any data returned by the advertising proxy in response to the request.
+ *
+ * errCode:                    Will be kDNSSDAdvertisingProxy_NoError on success, otherwise will indicate the
+ *                             failure that occurred.
+ *
+ */
+
+typedef void (*advertising_proxy_reply)
+(
+    advertising_proxy_conn_ref  conn_ref,
+    xpc_object_t                response,
+    advertising_proxy_error_type errCode
+);
+
+/* advertising_proxy_enable
+ *
+ * enables the DNSSD Advertising Proxy functionality which will remain ON until the client explicitly turns it OFF
+ * by passing the returned advertising_proxy_conn_ref to advertising_proxy_ref_dealloc(), or the client exits or crashes.
+ *
+ * advertising_proxy_enable() Parameters:
+ *
+ * conn_ref:                   A pointer to advertising_proxy_conn_ref that is initialized to NULL.
+ *                            If the call succeeds it will be initialized to a non-NULL value.
+ *                            Client terminates the DNSSD Advertising Proxy by passing this advertising_proxy_conn_ref to advertising_proxy_ref_dealloc().
+ *
+ * clientq:                   Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * callback:                  CallBack function for the client that indicates success or failure.
+ *                            Note: callback may be invoked more than once, For e.g. if enabling DNSSD Advertising Proxy
+ *                            first succeeds and the daemon possibly crashes sometime later.
+ *
+ * return value:              Returns kDNSSDAdvertisingProxy_NoError when no error otherwise returns an error code indicating
+ *                            the error that occurred. Note: A return value of kDNSSDAdvertisingProxy_NoError does not mean
+ *                            that DNSSD Advertising Proxy was successfully enabled. The callback may asynchronously
+ *                            return an error (such as kDNSSDAdvertisingProxy_DaemonNotRunning)
+ *
+ */
+
+DNS_SERVICES_EXPORT
+advertising_proxy_error_type advertising_proxy_enable
+(
+    advertising_proxy_conn_ref *conn_ref,
+    dispatch_queue_t            clientq,
+    advertising_proxy_reply     callback
+);
+
+/* advertising_proxy_flush_entries
+ *
+ * Flushes any host entries that have been registered with the advertising proxy.   For testing only:
+ * this is never the right thing to do in production.
+ *
+ * advertising_proxy_flush_entries() Parameters:
+ *
+ * conn_ref:                  A pointer to advertising_proxy_conn_ref that is initialized to NULL.
+ *                            If the call succeeds it will be initialized to a non-NULL value.
+ *                            The same conn_ref can be used for more than one call.
+ *
+ * clientq:                   Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * callback:                  CallBack function for the client that indicates success or failure.
+ *                            Callback is not called until either the command has failed, or has completed.
+ *
+ * return value:              Returns kDNSSDAdvertisingProxy_NoError when no error otherwise returns an
+ *                            error code indicating the error that occurred. Note: A return value of
+ *                            kDNSSDAdvertisingProxy_NoError does not mean that DNSSD Advertising Proxy host
+ *                            table was successfully flushed. The callback may asynchronously return an
+ *                            error (such as kDNSSDAdvertisingProxy_DaemonNotRunning)
+ *
+ */
+
+DNS_SERVICES_EXPORT
+advertising_proxy_error_type advertising_proxy_flush_entries
+(
+    advertising_proxy_conn_ref *conn_ref,
+    dispatch_queue_t            clientq,
+    advertising_proxy_reply     callback
+);
+
+/* advertising_proxy_get_service_list
+ *
+ * Returns a list of registered services on the advertising proxy.
+ *
+ * advertising_proxy_get_service_list() Parameters:
+ *
+ * conn_ref:                  A pointer to advertising_proxy_conn_ref that is initialized to NULL.
+ *                            If the call succeeds it will be initialized to a non-NULL value.
+ *                            The same conn_ref can be used for more than one call.
+ *
+ * clientq:                   Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * callback:                  CallBack function for the client that indicates success or failure.
+ *                            Callback is not called until either the command has failed, or has completed.
+ *
+ * return value:              Returns kDNSSDAdvertisingProxy_NoError when no error otherwise returns an
+ *                            error code indicating the error that occurred. Note: A return value of
+ *                            kDNSSDAdvertisingProxy_NoError does not mean that DNSSD Advertising Proxy host
+ *                            table was successfully flushed. The callback may asynchronously return an
+ *                            error (such as kDNSSDAdvertisingProxy_DaemonNotRunning)
+ *
+ */
+
+DNS_SERVICES_EXPORT
+advertising_proxy_error_type advertising_proxy_get_service_list
+(
+    advertising_proxy_conn_ref *conn_ref,
+    dispatch_queue_t            clientq,
+    advertising_proxy_reply     callback
+);
+
+/* advertising_proxy_block_service
+ *
+ * For testing, block advertisement of SRP service on the thread network.
+ *
+ * advertising_proxy_block_service() Parameters:
+ *
+ * conn_ref:                  A pointer to advertising_proxy_conn_ref that is initialized to NULL.
+ *                            If the call succeeds it will be initialized to a non-NULL value.
+ *                            The same conn_ref can be used for more than one call.
+ *
+ * clientq:                   Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * callback:                  CallBack function for the client that indicates success or failure.
+ *                            Callback is not called until either the command has failed, or has completed.
+ *
+ * return value:              Returns kDNSSDAdvertisingProxy_NoError when no error otherwise returns an
+ *                            error code indicating the error that occurred. Note: A return value of
+ *                            kDNSSDAdvertisingProxy_NoError does not mean that DNSSD Advertising Proxy host
+ *                            table was successfully flushed. The callback may asynchronously return an
+ *                            error (such as kDNSSDAdvertisingProxy_DaemonNotRunning)
+ *
+ */
+
+DNS_SERVICES_EXPORT
+advertising_proxy_error_type advertising_proxy_block_service
+(
+    advertising_proxy_conn_ref *conn_ref,
+    dispatch_queue_t            clientq,
+    advertising_proxy_reply     callback
+);
+
+/* advertising_proxy_unblock_service
+ *
+ * For testing, unblock advertisement of SRP service on the thread network.
+ *
+ * advertising_proxy_unblock_service() Parameters:
+ *
+ * conn_ref:                  A pointer to advertising_proxy_conn_ref that is initialized to NULL.
+ *                            If the call succeeds it will be initialized to a non-NULL value.
+ *                            The same conn_ref can be used for more than one call.
+ *
+ * clientq:                   Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * callback:                  CallBack function for the client that indicates success or failure.
+ *                            Callback is not called until either the command has failed, or has completed.
+ *
+ * return value:              Returns kDNSSDAdvertisingProxy_NoError when no error otherwise returns an
+ *                            error code indicating the error that occurred. Note: A return value of
+ *                            kDNSSDAdvertisingProxy_NoError does not mean that DNSSD Advertising Proxy host
+ *                            table was successfully flushed. The callback may asynchronously return an
+ *                            error (such as kDNSSDAdvertisingProxy_DaemonNotRunning)
+ *
+ */
+
+DNS_SERVICES_EXPORT
+advertising_proxy_error_type advertising_proxy_unblock_service
+(
+    advertising_proxy_conn_ref *conn_ref,
+    dispatch_queue_t            clientq,
+    advertising_proxy_reply     callback
+);
+
+/* advertising_proxy_regenerate_ula
+ *
+ * For testing, generate a new ULA prefix
+ *
+ * advertising_proxy_regenerate_ula() Parameters:
+ *
+ * conn_ref:                  A pointer to advertising_proxy_conn_ref that is initialized to NULL.
+ *                            If the call succeeds it will be initialized to a non-NULL value.
+ *                            The same conn_ref can be used for more than one call.
+ *
+ * clientq:                   Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * callback:                  CallBack function for the client that indicates success or failure.
+ *                            Callback is not called until either the command has failed, or has completed.
+ *
+ * return value:              Returns kDNSSDAdvertisingProxy_NoError when no error otherwise returns an
+ *                            error code indicating the error that occurred. Note: A return value of
+ *                            kDNSSDAdvertisingProxy_NoError does not mean that DNSSD Advertising Proxy host
+ *                            table was successfully flushed. The callback may asynchronously return an
+ *                            error (such as kDNSSDAdvertisingProxy_DaemonNotRunning)
+ *
+ */
+
+DNS_SERVICES_EXPORT
+advertising_proxy_error_type advertising_proxy_regenerate_ula
+(
+    advertising_proxy_conn_ref *conn_ref,
+    dispatch_queue_t            clientq,
+    advertising_proxy_reply     callback
+);
+
+/* advertising_proxy_ref_dealloc()
+ *
+ * Terminate a connection with the daemon and free memory associated with the advertising_proxy_conn_ref.
+ * When used on a advertising_proxy_conn_ref returned by advertising_proxy_enable, terminates the advertising
+ * proxy.  When used on a call that subscribes to notifications about objects managed by the advertising proxy,
+ * discontinues those notifications.
+ *
+ * conn_ref:        A advertising_proxy_conn_ref initialized by any of the advertising_proxy_*() calls.
+ *
+ */
+DNS_SERVICES_EXPORT
+void advertising_proxy_ref_dealloc(advertising_proxy_conn_ref conn_ref);
+#endif /* DNSSD_PROXY_SERVICES_H */
+#endif // defined(TARGET_OS_TV)
+
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/mDNSMacOSX/Private/cti-services.c b/mDNSMacOSX/Private/cti-services.c
new file mode 100644 (file)
index 0000000..3493c16
--- /dev/null
@@ -0,0 +1,1519 @@
+/* cti_services.c
+ *
+ * Copyright (c) 2020 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the implementation of the SRP Advertising Proxy management
+ * API on MacOS, which is private API used to control and manage the advertising
+ * proxy.
+ */
+
+
+#include <Block.h>
+#include <os/log.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <netinet6/in6_var.h>
+#include <netinet/icmp6.h>
+#include <netinet6/nd6.h>
+#include "xpc_clients.h"
+
+#define THREAD_SERVICE_SEND_BOTH 1
+
+#include "cti-services.h"
+
+//*************************************************************************************************************
+// Globals
+
+static int client_serial_number;
+
+typedef union {
+    cti_reply_t reply;
+    cti_tunnel_reply_t tunnel_reply;
+    cti_service_reply_t service_reply;
+    cti_prefix_reply_t prefix_reply;
+    cti_state_reply_t state_reply;
+    cti_partition_id_reply_t partition_id_reply;
+    cti_network_node_type_reply_t network_node_type_reply;
+} cti_callback_t;
+
+typedef void
+(*cti_internal_callback_t)(cti_connection_t NONNULL conn_ref, xpc_object_t reply, cti_status_t status);
+
+struct _cti_connection_t
+{
+    int ref_count;
+
+    // xpc_connection between client and daemon
+    xpc_connection_t NULLABLE connection;
+
+    // Callback function ptr for Client
+    cti_callback_t callback;
+
+    // Before we can send commands, we have to check in, so when starting up, we stash the initial command
+    // here until we get an acknowledgment for the checkin.
+    xpc_object_t *first_command;
+
+    // For commands that fetch properties and also track properties, this will contain the name of the property
+    // for which events are requested.
+    const char *property_name;
+
+    cti_internal_callback_t NONNULL internal_callback;
+
+    // Queue specified by client for scheduling its Callback
+    dispatch_queue_t NONNULL client_queue;
+
+    // Client context
+    void *NULLABLE context;
+
+    // Printed when debugging the event handler
+    const char *NONNULL command_name;
+
+    // True if we've gotten a response to the check-in message.
+    bool checked_in;
+};
+
+//*************************************************************************************************************
+// Utility Functions
+
+static void
+cti_connection_finalize(cti_connection_t ref)
+{
+    free(ref);
+}
+
+#define cti_connection_release(ref) cti_connection_release_(ref, __FILE__, __LINE__)
+static void
+cti_connection_release_(cti_connection_t ref, const char *file, int line)
+{
+    ref->callback.reply = NULL;
+    if (ref->connection != NULL) {
+        xpc_connection_cancel(ref->connection);
+    }
+    RELEASE(ref, cti_connection_finalize);
+}
+
+static void
+cti_xpc_connection_finalize(void *context)
+{
+    cti_connection_release(context);
+}
+
+static char *
+cti_xpc_copy_description(xpc_object_t object)
+{
+    xpc_type_t type = xpc_get_type(object);
+    if (type == XPC_TYPE_UINT64) {
+        uint64_t num = xpc_uint64_get_value(object);
+        char buf[23];
+        snprintf(buf, sizeof buf, "%llu", num);
+        return strdup(buf);
+    } else if (type == XPC_TYPE_INT64) {
+        int64_t num = xpc_int64_get_value(object);
+        char buf[23];
+        snprintf(buf, sizeof buf, "%lld", num);
+        return strdup(buf);
+    } else if (type == XPC_TYPE_STRING) {
+        const char *str = xpc_string_get_string_ptr(object);
+        size_t len = xpc_string_get_length(object);
+        char *ret = malloc(len + 3);
+        if (ret != NULL) {
+            *ret = '"';
+            strlcpy(ret + 1, str, len + 1);
+            ret[len + 1] = '"';
+            ret[len + 2] = 0;
+            return ret;
+        }
+    } else if (type == XPC_TYPE_DATA) {
+        const uint8_t *data = xpc_data_get_bytes_ptr(object);
+        size_t i, len = xpc_data_get_length(object);
+        char *ret = malloc(len * 2 + 3);
+        if (ret != NULL) {
+            ret[0] = '0';
+            ret[1] = 'x';
+            for (i = 0; i < len; i++) {
+                snprintf(ret + i * 2, 3, "%02x", data[i]);
+            }
+            return ret;
+        }
+    } else if (type == XPC_TYPE_BOOL) {
+        bool flag = xpc_bool_get_value(object);
+        if (flag) {
+            return strdup("true");
+        } else {
+            return strdup("false");
+        }
+    } else if (type == XPC_TYPE_ARRAY) {
+        size_t avail, vlen, len = 0, i, count = xpc_array_get_count(object);
+        char **values = malloc(count * sizeof(*values));
+        char *ret, *p_ret;
+        if (values == NULL) {
+            return NULL;
+        }
+        xpc_array_apply(object, ^bool (size_t index, xpc_object_t value) {
+                values[index] = cti_xpc_copy_description(value);
+                return true;
+            });
+        for (i = 0; i < count; i++) {
+            if (values[i] == NULL) {
+                len += 6;
+            } else {
+                len += strlen(values[i]) + 2;
+            }
+        }
+        ret = malloc(len + 3);
+        p_ret = ret;
+        avail = len + 1;
+        *p_ret++ = '[';
+        --avail;
+        for (i = 0; i < count; i++) {
+            if (p_ret != NULL) {
+                snprintf(p_ret, avail, "%s%s%s", i == 0 ? "" : " ", values[i] != NULL ? values[i] : "NULL", (i + 1 == count) ? "" : ",");
+                vlen = strlen(p_ret);
+                p_ret += vlen;
+                avail -= vlen;
+            }
+            if (values[i] != NULL) {
+                free(values[i]);
+            }
+        }
+        *p_ret++ = ']';
+        *p_ret++ = 0;
+        free(values);
+        return ret;
+    }
+    return xpc_copy_description(object);
+}
+
+static void
+cti_log_object(const char *context, const char *command, const char *preamble, const char *divide, xpc_object_t *object, char *indent)
+{
+    xpc_type_t type = xpc_get_type(object);
+    static char no_indent[] = "";
+    if (indent == NULL) {
+        indent = no_indent;
+    }
+    char *new_indent;
+    size_t depth;
+    char *desc;
+    char *compound_begin = "";
+    char *compound_end = "";
+
+    if (type == XPC_TYPE_DICTIONARY || type == XPC_TYPE_ARRAY) {
+        bool compact = true;
+        bool *p_compact = &compact;
+        if (type == XPC_TYPE_DICTIONARY) {
+            compound_begin = "{";
+            compound_end = "}";
+            xpc_dictionary_apply(object, ^bool (const char *__unused key, xpc_object_t value) {
+                    xpc_type_t sub_type = xpc_get_type(value);
+                    if (sub_type == XPC_TYPE_DICTIONARY) {
+                        *p_compact = false;
+                    } else if (sub_type == XPC_TYPE_ARRAY) {
+                        xpc_array_apply(value, ^bool (size_t __unused index, xpc_object_t sub_value) {
+                                xpc_type_t sub_sub_type = xpc_get_type(sub_value);
+                                if (sub_sub_type == XPC_TYPE_DICTIONARY || sub_sub_type == XPC_TYPE_ARRAY) {
+                                    *p_compact = false;
+                                }
+                                return true;
+                            });
+                    }
+                    return true;
+                });
+        } else {
+            compound_begin = "[";
+            compound_end = "]";
+            xpc_array_apply(object, ^bool (size_t __unused index, xpc_object_t value) {
+                    xpc_type_t sub_type = xpc_get_type(value);
+                    if (sub_type == XPC_TYPE_DICTIONARY || sub_type == XPC_TYPE_ARRAY) {
+                        *p_compact = false;
+                    }
+                    return true;
+                });
+        }
+        if (compact) {
+            size_t i, count;
+            const char **keys = NULL;
+            char **values;
+            char linebuf[160], *p_space;
+            size_t space_avail = sizeof(linebuf);
+            bool first = true;
+
+            if (type == XPC_TYPE_DICTIONARY) {
+                count = xpc_dictionary_get_count(object);
+            } else {
+                count = xpc_array_get_count(object);
+            }
+
+            values = malloc(count * sizeof(*values));
+            if (values == NULL) {
+                INFO("cti_log_object: no memory");
+                return;
+            }
+            if (type == XPC_TYPE_DICTIONARY) {
+                int index = 0, *p_index = &index;
+                keys = malloc(count * sizeof(*keys));
+                if (keys == NULL) {
+                    free(values);
+                    INFO("cti_log_object: no memory");
+                }
+                xpc_dictionary_apply(object, ^bool (const char *key, xpc_object_t value) {
+                        values[*p_index] = cti_xpc_copy_description(value);
+                        keys[*p_index] = key;
+                        (*p_index)++;
+                        return true;
+                    });
+            } else {
+                xpc_array_apply(object, ^bool (size_t index, xpc_object_t value) {
+                        values[index] = cti_xpc_copy_description(value);
+                        return true;
+                    });
+            }
+            p_space = linebuf;
+            for (i = 0; i < count; i++) {
+                char *str = values[i];
+                size_t len;
+                char *eol = "";
+                bool emitted = false;
+                if (str == NULL) {
+                    str = "NULL";
+                    len = 6;
+                } else {
+                    len = strlen(str) + 2;
+                }
+                if (type == XPC_TYPE_DICTIONARY) {
+#ifdef __clang_analyzer__
+                    len = 2;
+#else
+                    len += strlen(keys[i]) + 2; // "key: "
+#endif
+                }
+                if (len + 1 > space_avail) {
+                    if (i + 1 == count) {
+                        eol = compound_end;
+                    }
+                    if (space_avail != sizeof(linebuf)) {
+                        if (first) {
+                            INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP PUB_S_SRP PUB_S_SRP,
+                                 context, command, indent, preamble, divide, compound_begin, linebuf, eol);
+                            first = false;
+                        } else {
+                            INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " +" PUB_S_SRP PUB_S_SRP,
+                                 context, command, indent, preamble, divide, linebuf, eol);
+                        }
+                        space_avail = sizeof linebuf;
+                        p_space = linebuf;
+                    }
+                    if (len + 1 > space_avail) {
+                        if (type == XPC_TYPE_DICTIONARY) {
+#ifndef __clang_analyzer__
+                            if (first) {
+                                INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP PUB_S_SRP ": " PUB_S_SRP PUB_S_SRP,
+                                     context, command, indent, preamble, divide, compound_begin, keys[i], str, eol);
+                                first = false;
+                            } else {
+                                INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " +" PUB_S_SRP ": " PUB_S_SRP PUB_S_SRP,
+                                     context, command, indent, preamble, divide, keys[i], str, eol);
+                            }
+#endif
+                        } else {
+                            if (first) {
+                                INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP PUB_S_SRP PUB_S_SRP,
+                                     context, command, indent, preamble, divide, compound_begin, str, eol);
+                                first = false;
+                            } else {
+                                INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " +" PUB_S_SRP PUB_S_SRP,
+                                     context, command, indent, preamble, divide, str, eol);
+                            }
+                        }
+                        emitted = true;
+                    }
+                }
+                if (!emitted) {
+                    if (type == XPC_TYPE_DICTIONARY) {
+#ifndef __clang_analyzer__
+                        snprintf(p_space, space_avail, "%s%s: %s%s", i == 0 ? "" : " ", keys[i], str, i + 1 == count ? "" : ",");
+#endif
+                    } else {
+                        snprintf(p_space, space_avail, "%s%s%s", i == 0 ? "" : " ", str, i + 1 == count ? "" : ",");
+                    }
+                    len = strlen(p_space);
+                    p_space += len;
+                    space_avail -= len;
+                }
+                if (values[i] != NULL) {
+                    free(values[i]);
+                    values[i] = NULL;
+                }
+            }
+            if (linebuf != p_space) {
+                if (first) {
+                    INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP PUB_S_SRP PUB_S_SRP,
+                         context, command, indent, preamble, divide, compound_begin, linebuf, compound_end);
+                } else {
+                    INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " + " PUB_S_SRP PUB_S_SRP,
+                         context, command, indent, preamble, divide, linebuf, compound_end);
+                }
+            }
+            free(values);
+            if (keys != NULL) {
+                free(keys);
+            }
+        } else {
+            depth = strlen(indent);
+            new_indent = malloc(depth + 3);
+            if (new_indent == NULL) {
+                new_indent = indent;
+            } else {
+                memset(new_indent, ' ', depth + 2);
+                new_indent[depth + 2] = 0;
+            }
+            if (type == XPC_TYPE_DICTIONARY) {
+                xpc_dictionary_apply(object, ^bool (const char *key, xpc_object_t value) {
+                    cti_log_object(context, command, key, ": ", value, new_indent);
+                    return true;
+                });
+            } else {
+                xpc_array_apply(object, ^bool (size_t index, xpc_object_t value) {
+                    char numbuf[23];
+                    snprintf(numbuf, sizeof(numbuf), "%zd", index);
+                    cti_log_object(context, command, numbuf, ": ", value, new_indent);
+                    return true;
+                });
+            }
+            if (new_indent != indent) {
+                free(new_indent);
+            }
+        }
+    } else {
+        desc = cti_xpc_copy_description(object);
+        INFO(PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP,
+             context, command, indent, preamble, divide, desc);
+        free(desc);
+    }
+}
+
+static void
+cti_event_handler(xpc_object_t event, cti_connection_t conn_ref)
+{
+    if (event == XPC_ERROR_CONNECTION_INVALID) {
+        INFO("cti_event_handler (" PUB_S_SRP "): cleanup", conn_ref->command_name);
+        if (conn_ref->callback.reply != NULL) {
+            conn_ref->internal_callback(conn_ref, event, kCTIStatus_Disconnected);
+        } else {
+            INFO("No callback");
+        }
+        if (conn_ref->connection != NULL) {
+            xpc_release(conn_ref->connection);
+            conn_ref->connection = NULL;
+        }
+    } else if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) {
+        cti_log_object("cti_event_handler", conn_ref->command_name, "", "", event, "");
+        if (!conn_ref->checked_in) {
+            xpc_object_t command_result = xpc_dictionary_get_value(event, "commandResult");
+            int status = 0;
+            if (command_result != NULL) {
+                status = (int)xpc_int64_get_value(command_result);
+                if (status == 0) {
+                    xpc_object_t command_data = xpc_dictionary_get_value(event, "commandData");
+                    if (command_data == NULL) {
+                        status = 0;
+                    } else {
+                        xpc_object_t ret_value = xpc_dictionary_get_value(command_data, "ret");
+                        if (ret_value == NULL) {
+                            status = 0;
+                        } else {
+                            status = (int)xpc_int64_get_value(ret_value);
+                        }
+                    }
+                }
+            }
+
+            if (status != 0) {
+                conn_ref->internal_callback(conn_ref, event, kCTIStatus_UnknownError);
+                xpc_connection_cancel(conn_ref->connection);
+            } else if (conn_ref->property_name != NULL) {
+                // We're meant to both get the property and subscribe to events on it.
+                xpc_object_t *dict = xpc_dictionary_create(NULL, NULL, 0);
+                if (dict == NULL) {
+                    ERROR("cti_event_handler(" PUB_S_SRP "): no memory.", conn_ref->command_name);
+                    xpc_connection_cancel(conn_ref->connection);
+                } else {
+                    xpc_object_t *array = xpc_array_create(NULL, 0);
+                    if (array == NULL) {
+                        ERROR("cti_event_handler(" PUB_S_SRP "): no memory.", conn_ref->command_name);
+                        xpc_connection_cancel(conn_ref->connection);
+                    } else {
+                        xpc_dictionary_set_string(dict, "command", "eventsOn");
+                        xpc_dictionary_set_string(dict, "clientName", "wpanctl");
+                        xpc_dictionary_set_value(dict, "eventList", array);
+                        xpc_array_set_string(array, XPC_ARRAY_APPEND, conn_ref->property_name);
+                        conn_ref->property_name = NULL;
+                        cti_log_object("cti_event_handler/events on", conn_ref->command_name, "", "", dict, "");
+                        xpc_connection_send_message_with_reply(conn_ref->connection, dict, conn_ref->client_queue,
+                                                               ^(xpc_object_t in_event) {
+                                                                   cti_event_handler(in_event, conn_ref);
+                                                               });
+                        xpc_release(array);
+                    }
+                    xpc_release(dict);
+                }
+            } else {
+                xpc_object_t *message = conn_ref->first_command;
+                conn_ref->first_command = NULL;
+                cti_log_object("cti_event_handler/command is", conn_ref->command_name, "", "", message, "");
+                conn_ref->checked_in = true;
+
+                xpc_connection_send_message_with_reply(conn_ref->connection, message, conn_ref->client_queue,
+                                                       ^(xpc_object_t in_event) {
+                                                           cti_event_handler(in_event, conn_ref);
+                                                       });
+                xpc_release(message);
+            }
+        } else {
+            conn_ref->internal_callback(conn_ref, event, kCTIStatus_NoError);
+        }
+    } else {
+        cti_log_object("cti_event_handler/other", conn_ref->command_name, "", "", event, "");
+        ERROR("cti_event_handler: Unexpected Connection Error [" PUB_S_SRP "]",
+              xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
+        conn_ref->internal_callback(conn_ref, NULL, kCTIStatus_DaemonNotRunning);
+        if (event != XPC_ERROR_CONNECTION_INTERRUPTED) {
+            xpc_connection_cancel(conn_ref->connection);
+        }
+    }
+}
+
+// Creates a new cti_ Connection Reference(cti_connection_t)
+static cti_status_t
+init_connection(cti_connection_t *ref, const char *servname, xpc_object_t *dict, const char *command_name,
+                const char *property_name, void *context, cti_callback_t app_callback,
+                cti_internal_callback_t internal_callback, dispatch_queue_t client_queue, const char *file, int line)
+{
+    // Use an cti_connection_t on the stack to be captured in the blocks below, rather than
+    // capturing the cti_connection_t* owned by the client
+    cti_connection_t conn_ref = calloc(1, sizeof(struct _cti_connection_t));
+    if (conn_ref == NULL) {
+        ERROR("dns_services: init_connection() No memory to allocate!");
+        return kCTIStatus_NoMemory;
+   }
+
+    // Initialize the cti_connection_t
+    dispatch_retain(client_queue);
+    conn_ref->command_name = command_name;
+    conn_ref->property_name = property_name;
+    conn_ref->context = context;
+    conn_ref->client_queue = client_queue;
+    conn_ref->callback = app_callback;
+    conn_ref->internal_callback = internal_callback;
+    conn_ref->connection = xpc_connection_create_mach_service(servname, conn_ref->client_queue,
+                                                              XPC_CONNECTION_MACH_SERVICE_PRIVILEGED);
+    conn_ref->first_command = dict;
+    xpc_retain(dict);
+
+    cti_log_object("init_connection/command", conn_ref->command_name, "", "", dict, "");
+
+    if (conn_ref->connection == NULL)
+    {
+        ERROR("dns_services: init_connection() conn_ref/lib_q is NULL");
+        if (conn_ref != NULL) {
+            free(conn_ref);
+        }
+        return kCTIStatus_NoMemory;
+    }
+
+    RETAIN_HERE(conn_ref); // For the event handler.
+    xpc_connection_set_event_handler(conn_ref->connection, ^(xpc_object_t event) { cti_event_handler(event, conn_ref); });
+    xpc_connection_set_finalizer_f(conn_ref->connection, cti_xpc_connection_finalize);
+    xpc_connection_set_context(conn_ref->connection, conn_ref);
+    xpc_connection_resume(conn_ref->connection);
+
+    char srp_name[] = "srp-mdns-proxyd";
+    char client_name[sizeof(srp_name) + 20];
+    snprintf(client_name, sizeof client_name, "%s-%d", srp_name, client_serial_number);
+    client_serial_number++;
+
+    xpc_object_t checkin_command = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_string(checkin_command, "command", "checkIn");
+    xpc_dictionary_set_string(checkin_command, "clientName", client_name);
+
+    cti_log_object("init_connection/checkin", conn_ref->command_name, "", "", checkin_command, "");
+    xpc_connection_send_message_with_reply(conn_ref->connection, checkin_command, conn_ref->client_queue,
+                                           ^(xpc_object_t event) { cti_event_handler(event, conn_ref); });
+
+    xpc_release(checkin_command);
+    if (ref) {
+        *ref = conn_ref;
+    }
+    // We always retain a reference for the caller, even if the caller doesn't actually hold the reference.
+    // Calls that do not result in repeated callbacks release this reference after calling the callback.
+    // Such calls do not return a reference to the caller, so there is no chance of a double release.
+    // Calls that result in repeated callbacks have to release the reference by calling cti_events_discontinue.
+    // If this isn't done, the reference will never be released.
+    RETAIN(conn_ref);
+
+    return kCTIStatus_NoError;
+}
+
+#define setup_for_command(ref, client_queue, command_name, property_name, dict, command, \
+                          context, app_callback, internal_callback)                      \
+    setup_for_command_(ref, client_queue, command_name, property_name, dict, command,    \
+                       context, app_callback, internal_callback, __FILE__, __LINE__)
+static cti_status_t
+setup_for_command_(cti_connection_t *ref, dispatch_queue_t client_queue, const char *command_name,
+                   const char *property_name, xpc_object_t dict, const char *command, void *context,
+                   cti_callback_t app_callback, cti_internal_callback_t internal_callback, const char *file, int line)
+{
+    cti_status_t errx = kCTIStatus_NoError;
+
+    // Sanity Checks
+    if (app_callback.reply == NULL || internal_callback == NULL || client_queue == NULL)
+    {
+        ERROR(PUB_S_SRP ": NULL cti_connection_t OR Callback OR Client_Queue parameter", command_name);
+        return kCTIStatus_BadParam;
+    }
+
+    // Get conn_ref from init_connection()
+    xpc_dictionary_set_string(dict, "command", command);
+
+    errx = init_connection(ref, "com.apple.wpantund.xpc", dict, command_name, property_name,
+                           context, app_callback, internal_callback, client_queue, file, line);
+    if (errx) // On error init_connection() leaves *conn_ref set to NULL
+    {
+        ERROR(PUB_S_SRP ": Since init_connection() returned %d error returning w/o sending msg", command_name, errx);
+        return errx;
+    }
+
+    return errx;
+}
+
+static void
+cti_internal_reply_callback(cti_connection_t NONNULL conn_ref, xpc_object_t __unused reply, cti_status_t status)
+{
+    cti_reply_t callback;
+    INFO("cti_internal_reply_callback: conn_ref = %p", conn_ref);
+    callback = conn_ref->callback.reply;
+    if (callback != NULL) {
+        callback(conn_ref->context, status);
+    }
+    cti_connection_release(conn_ref);
+}
+
+cti_status_t
+cti_add_service(void *context, cti_reply_t callback, dispatch_queue_t client_queue,
+                uint32_t enterprise_number, const uint8_t *NONNULL service_data, size_t service_data_length,
+                const uint8_t *NONNULL server_data, size_t server_data_length)
+{
+    cti_status_t errx;
+    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    cti_callback_t app_callback;
+    app_callback.reply = callback;
+
+    xpc_dictionary_set_data(dict, "service_data", service_data, service_data_length);
+    xpc_dictionary_set_data(dict, "server_data", server_data, server_data_length);
+    xpc_dictionary_set_uint64(dict, "enterprise_number", enterprise_number);
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "ServiceAdd");
+    xpc_dictionary_set_bool(dict, "stable", true);
+
+    errx = setup_for_command(NULL, client_queue, "add_service", NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_reply_callback);
+    xpc_release(dict);
+
+    return errx;
+}
+
+cti_status_t
+cti_remove_service(void *context, cti_reply_t callback, dispatch_queue_t client_queue,
+                   uint32_t enterprise_number, const uint8_t *NONNULL service_data, size_t service_data_length)
+{
+    cti_status_t errx;
+    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    cti_callback_t app_callback;
+    app_callback.reply = callback;
+
+    xpc_dictionary_set_data(dict, "service_data", service_data, service_data_length);
+    xpc_dictionary_set_uint64(dict, "enterprise_number", enterprise_number);
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "ServiceRemove");
+
+    errx = setup_for_command(NULL, client_queue, "remove_service", NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_reply_callback);
+    xpc_release(dict);
+
+    return errx;
+}
+
+static cti_status_t
+cti_do_prefix(void *context, cti_reply_t callback, dispatch_queue_t client_queue,
+              struct in6_addr *prefix, int prefix_length, bool on_mesh, bool preferred, bool slaac, bool stable, bool adding)
+{
+    cti_status_t errx;
+    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    cti_callback_t app_callback;
+    app_callback.reply = callback;
+
+    if (dict == NULL) {
+        ERROR("cti_do_prefix: no memory for command dictionary.");
+        return kCTIStatus_NoMemory;
+    }
+    xpc_dictionary_set_bool(dict, "preferred", preferred);
+    if (adding) {
+        xpc_dictionary_set_uint64(dict, "preferredLifetime", ND6_INFINITE_LIFETIME);
+        xpc_dictionary_set_uint64(dict, "validLifetime", ND6_INFINITE_LIFETIME);
+    } else {
+        xpc_dictionary_set_uint64(dict, "preferredLifetime", 0);
+        xpc_dictionary_set_uint64(dict, "validLifetime", 0);
+    }
+    xpc_dictionary_set_int64(dict, "prefix_length", 16);
+    xpc_dictionary_set_bool(dict, "dhcp", false);
+    xpc_dictionary_set_data(dict, "prefix", prefix, sizeof(*prefix));
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_uint64(dict, "prefix_len_in_bits", prefix_length);
+    xpc_dictionary_set_bool(dict, "slaac", slaac);
+    xpc_dictionary_set_bool(dict, "onMesh", on_mesh);
+    xpc_dictionary_set_bool(dict, "configure", false);
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "ConfigGateway");
+    xpc_dictionary_set_bool(dict, "stable", stable);
+    xpc_dictionary_set_bool(dict, "defaultRoute", adding);
+    xpc_dictionary_set_int64(dict, "priority", 0);
+
+    errx = setup_for_command(NULL, client_queue, "add_prefix", NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_reply_callback);
+    xpc_release(dict);
+
+    return errx;
+}
+
+cti_status_t
+cti_add_prefix(void *context, cti_reply_t callback, dispatch_queue_t client_queue,
+               struct in6_addr *prefix, int prefix_length, bool on_mesh, bool preferred, bool slaac, bool stable)
+{
+    return cti_do_prefix(context, callback, client_queue, prefix, prefix_length, on_mesh, preferred, slaac, stable, true);
+}
+
+cti_status_t
+cti_remove_prefix(void *NULLABLE context, cti_reply_t NONNULL callback, dispatch_queue_t NONNULL client_queue,
+                  struct in6_addr *NONNULL prefix, int prefix_length)
+
+{
+    return cti_do_prefix(context, callback, client_queue, prefix, prefix_length, false, false, false, false, false);
+}
+
+static void
+cti_internal_tunnel_reply_callback(cti_connection_t NONNULL conn_ref, xpc_object_t reply, cti_status_t status_in)
+{
+    cti_tunnel_reply_t callback = conn_ref->callback.tunnel_reply;
+    xpc_retain(reply);
+    cti_status_t status = status_in;
+    const char *tunnel_name = NULL;
+    uint64_t command_result = xpc_dictionary_get_int64(reply, "commandResult");
+    if (command_result != 0) {
+        ERROR("cti_internal_tunnel_reply_callback: nonzero result %llu", command_result);
+        status = kCTIStatus_UnknownError;
+    } else {
+        xpc_object_t result_dictionary = xpc_dictionary_get_dictionary(reply, "commandData");
+        if (status == kCTIStatus_NoError) {
+            if (result_dictionary != NULL) {
+                const char *property_name = xpc_dictionary_get_string(result_dictionary, "property_name");
+                if (property_name == NULL || strcmp(property_name, "Config:TUN:InterfaceName")) {
+                    status = kCTIStatus_UnknownError;
+                } else {
+                    tunnel_name = xpc_dictionary_get_string(result_dictionary, "value");
+                    if (tunnel_name == NULL) {
+                        status = kCTIStatus_UnknownError;
+                    }
+                }
+            } else {
+                status = kCTIStatus_UnknownError;
+            }
+        }
+    }
+    if (callback != NULL) {
+        callback(conn_ref->context, tunnel_name, status);
+    }
+    xpc_release(reply);
+    conn_ref->callback.reply = NULL;
+    if (conn_ref->connection != NULL) {
+        xpc_connection_cancel(conn_ref->connection);
+    }
+}
+
+cti_status_t
+cti_get_tunnel_name(void *NULLABLE context, cti_tunnel_reply_t NONNULL callback, dispatch_queue_t NONNULL client_queue)
+{
+    cti_status_t errx;
+    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    cti_callback_t app_callback;
+    app_callback.tunnel_reply = callback;
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "Config:TUN:InterfaceName");
+
+    errx = setup_for_command(NULL, client_queue, "get_tunnel_name", NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_tunnel_reply_callback);
+    xpc_release(dict);
+
+    return errx;
+}
+
+static cti_status_t
+cti_event_or_response_extract(xpc_object_t *reply, xpc_object_t *result_dictionary)
+{
+    xpc_object_t *result = xpc_dictionary_get_dictionary(reply, "commandData");
+    if (result == NULL) {
+        result = xpc_dictionary_get_dictionary(reply, "eventData");
+    } else {
+        int command_status = (int)xpc_dictionary_get_int64(reply, "commandResult");
+        if (command_status != 0) {
+            INFO("cti_event_or_response_extract: nonzero status %d", command_status);
+            return kCTIStatus_UnknownError;
+        }
+    }
+    if (result != NULL) {
+        *result_dictionary = result;
+        return kCTIStatus_NoError;
+    }
+    INFO("cti_event_or_response_extract: null result");
+    return kCTIStatus_UnknownError;
+}
+
+static void
+cti_internal_state_reply_callback(cti_connection_t NONNULL conn_ref, xpc_object_t reply, cti_status_t status_in)
+{
+    cti_state_reply_t callback = conn_ref->callback.state_reply;
+    cti_network_state_t state = kCTI_NCPState_Unknown;
+    cti_status_t status = status_in;
+    if (status == kCTIStatus_NoError) {
+        xpc_object_t result_dictionary = NULL;
+        status = cti_event_or_response_extract(reply, &result_dictionary);
+        if (status == kCTIStatus_NoError) {
+            const char *state_name = xpc_dictionary_get_string(result_dictionary, "value");
+            if (state_name == NULL) {
+                status = kCTIStatus_UnknownError;
+            } else if (!strcmp(state_name, "uninitialized")) {
+                state = kCTI_NCPState_Uninitialized;
+            } else if (!strcmp(state_name, "uninitialized:fault")) {
+                state = kCTI_NCPState_Fault;
+            } else if (!strcmp(state_name, "uninitialized:upgrading")) {
+                state = kCTI_NCPState_Upgrading;
+            } else if (!strcmp(state_name, "offline:deep-sleep")) {
+                state = kCTI_NCPState_DeepSleep;
+            } else if (!strcmp(state_name, "offline")) {
+                state = kCTI_NCPState_Offline;
+            } else if (!strcmp(state_name, "offline:commissioned")) {
+                state = kCTI_NCPState_Commissioned;
+            } else if (!strcmp(state_name, "associating")) {
+                state = kCTI_NCPState_Associating;
+            } else if (!strcmp(state_name, "associating:credentials-needed")) {
+                state = kCTI_NCPState_CredentialsNeeded;
+            } else if (!strcmp(state_name, "associated")) {
+                state = kCTI_NCPState_Associated;
+            } else if (!strcmp(state_name, "associated:no-parent")) {
+                state = kCTI_NCPState_Isolated;
+            } else if (!strcmp(state_name, "associated:netwake-asleep")) {
+                state = kCTI_NCPState_NetWake_Asleep;
+            } else if (!strcmp(state_name, "associated:netwake-waking")) {
+                state = kCTI_NCPState_NetWake_Waking;
+            }
+        }
+    }
+    if (callback != NULL) {
+        callback(conn_ref->context, state, status);
+    }
+}
+
+cti_status_t
+cti_get_state(cti_connection_t *ref, void *NULLABLE context, cti_state_reply_t NONNULL callback,
+              dispatch_queue_t NONNULL client_queue)
+{
+    cti_status_t errx;
+    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    cti_callback_t app_callback;
+    app_callback.state_reply = callback;
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "NCP:State");
+
+    errx = setup_for_command(ref, client_queue, "get_state", "NCP:State", dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_state_reply_callback);
+    xpc_release(dict);
+
+    return errx;
+}
+
+static void
+cti_internal_partition_id_callback(cti_connection_t NONNULL conn_ref, xpc_object_t reply, cti_status_t status_in)
+{
+    cti_partition_id_reply_t callback = conn_ref->callback.partition_id_reply;
+    int32_t partition_id = -1;
+    cti_status_t status = status_in;
+    if (status == kCTIStatus_NoError) {
+        xpc_object_t result_dictionary = NULL;
+        status = cti_event_or_response_extract(reply, &result_dictionary);
+        if (status == kCTIStatus_NoError) {
+            xpc_object_t value = xpc_dictionary_get_value(result_dictionary, "value");
+            if (value == NULL) {
+                ERROR("cti_internal_partition_id_callback: No partition ID returned.");
+            } else if (xpc_get_type(value) != XPC_TYPE_UINT64) {
+                char *value_string = xpc_copy_description(value);
+                ERROR("cti_internal_partition_id_callback: Partition ID is " PUB_S_SRP " instead if uint64_t.",
+                      value_string);
+                free(value_string);
+            } else {
+                partition_id = (int32_t)xpc_dictionary_get_uint64(result_dictionary, "value");
+            }
+        }
+    }
+    if (callback != NULL) {
+        callback(conn_ref->context, partition_id, status);
+    }
+}
+
+cti_status_t
+cti_get_partition_id(cti_connection_t *ref, void *NULLABLE context, cti_partition_id_reply_t NONNULL callback,
+                     dispatch_queue_t NONNULL client_queue)
+{
+    cti_status_t errx;
+    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    cti_callback_t app_callback;
+    app_callback.partition_id_reply = callback;
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "Network:PartitionId");
+
+    errx = setup_for_command(ref, client_queue, "get_partition_id", "Network:PartitionId", dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_partition_id_callback);
+    xpc_release(dict);
+
+    return errx;
+}
+
+static void
+cti_internal_network_node_type_callback(cti_connection_t NONNULL conn_ref, xpc_object_t reply, cti_status_t status_in)
+{
+    cti_network_node_type_reply_t callback = conn_ref->callback.network_node_type_reply;
+    cti_network_node_type_t network_node_type = kCTI_NetworkNodeType_Unknown;
+    cti_status_t status = status_in;
+    if (status == kCTIStatus_NoError) {
+        xpc_object_t result_dictionary = NULL;
+        status = cti_event_or_response_extract(reply, &result_dictionary);
+        if (status == kCTIStatus_NoError) {
+            xpc_object_t value = xpc_dictionary_get_value(result_dictionary, "value");
+            if (value == NULL) {
+                ERROR("cti_internal_network_node_type_callback: No node type returned.");
+            } else if (xpc_get_type(value) != XPC_TYPE_STRING) {
+                char *value_string = xpc_copy_description(value);
+                ERROR("cti_internal_network_node_type_callback: node type type is " PUB_S_SRP " instead of string.",
+                      value_string);
+                free(value_string);
+            } else {
+                const char *node_type_name = xpc_dictionary_get_string(result_dictionary, "value");
+                if (!strcmp(node_type_name, "unknown")) {
+                    network_node_type = kCTI_NetworkNodeType_Unknown;
+                } else if (!strcmp(node_type_name, "router")) {
+                        network_node_type = kCTI_NetworkNodeType_Router;
+                } else if (!strcmp(node_type_name, "end-device")) {
+                    network_node_type = kCTI_NetworkNodeType_EndDevice;
+                } else if (!strcmp(node_type_name, "sleepy-end-device")) {
+                    network_node_type = kCTI_NetworkNodeType_SleepyEndDevice;
+                } else if (!strcmp(node_type_name, "nl-lurker")) {
+                    network_node_type = kCTI_NetworkNodeType_NestLurker;
+                } else if (!strcmp(node_type_name, "commissioner")) {
+                    network_node_type = kCTI_NetworkNodeType_Commissioner;
+                } else if (!strcmp(node_type_name, "leader")) {
+                    network_node_type = kCTI_NetworkNodeType_Leader;
+                }
+            }
+        }
+    }
+    if (callback != NULL) {
+        callback(conn_ref->context, network_node_type, status);
+    }
+}
+
+cti_status_t
+cti_get_network_node_type(cti_connection_t *ref, void *NULLABLE context, cti_network_node_type_reply_t NONNULL callback,
+                     dispatch_queue_t NONNULL client_queue)
+{
+    cti_status_t errx;
+    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    cti_callback_t app_callback;
+    app_callback.network_node_type_reply = callback;
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "Network:NodeType");
+
+    errx = setup_for_command(ref, client_queue, "get_network_node_type", "Network:NodeType", dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_network_node_type_callback);
+    xpc_release(dict);
+
+    return errx;
+}
+
+static void
+cti_service_finalize(cti_service_t *service)
+{
+    if (service->server != NULL) {
+        free(service->server);
+    }
+    free(service);
+}
+
+static void
+cti_service_vec_finalize(cti_service_vec_t *services)
+{
+    size_t i;
+
+    if (services->services != NULL) {
+        for (i = 0; i < services->num; i++) {
+            if (services->services[i] != NULL) {
+                RELEASE_HERE(services->services[i], cti_service_finalize);
+            }
+        }
+        free(services->services);
+    }
+    free(services);
+}
+
+cti_service_vec_t *
+cti_service_vec_create_(size_t num_services, const char *file, int line)
+{
+    cti_service_vec_t *services = calloc(1, sizeof(*services));
+    if (services != NULL) {
+        if (num_services != 0) {
+            services->services = calloc(num_services, sizeof(cti_service_t *));
+            if (services->services == NULL) {
+                free(services);
+                return NULL;
+            }
+        }
+        services->num = num_services;
+        RETAIN(services);
+    }
+    return services;
+}
+
+void
+cti_service_vec_release_(cti_service_vec_t *services, const char *file, int line)
+{
+    RELEASE(services, cti_service_vec_finalize);
+}
+
+cti_service_t *
+cti_service_create_(uint64_t enterprise_number, uint16_t service_type, uint16_t service_version,
+                    uint8_t *server, size_t server_length, int flags, const char *file, int line)
+{
+    cti_service_t *service = calloc(1, sizeof(*service));
+    if (service != NULL) {
+        service->enterprise_number = enterprise_number;
+        service->service_type = service_type;
+        service->service_version = service_version;
+        service->server = server;
+        service->server_length = server_length;
+        service->flags = flags;
+        RETAIN(service);
+    }
+    return service;
+}
+
+void
+cti_service_release_(cti_service_t *service, const char *file, int line)
+{
+    RELEASE(service, cti_service_finalize);
+}
+
+static uint8_t *
+cti_array_to_bytes(xpc_object_t array, size_t *length_ret, const char *log_name)
+{
+    size_t length = xpc_array_get_count(array);
+    size_t i;
+    uint8_t *ret;
+
+    ret = malloc(length);
+    if (ret == NULL) {
+        ERROR(PUB_S_SRP ": no memory for return buffer", log_name);
+        return NULL;
+    }
+
+    for (i = 0; i < length; i++) {
+        uint64_t v = xpc_array_get_uint64(array, i);
+        ret[i] = v;
+    }
+    *length_ret = length;
+    return ret;
+}
+
+static cti_status_t
+cti_parse_services_array(cti_service_vec_t **services, xpc_object_t services_array)
+{
+    size_t services_array_length = xpc_array_get_count(services_array);
+    size_t i, j;
+    cti_service_vec_t *service_vec;
+    cti_service_t *service;
+    cti_status_t status = kCTIStatus_NoError;
+
+    service_vec = cti_service_vec_create(services_array_length);
+    if (service_vec == NULL) {
+        return kCTIStatus_NoMemory;
+    }
+
+    // Array of arrays
+    for (i = 0; i < services_array_length; i++) {
+        xpc_object_t service_array = xpc_array_get_value(services_array, i);
+        int match_count = 0;
+        bool matched[5] = { false, false, false, false, false};
+        uint64_t enterprise_number = 0;
+        uint8_t *server_data = NULL;
+        size_t server_data_length = 0;
+        uint8_t *service_data = NULL;
+        size_t service_data_length = 0;
+        int flags = 0;
+
+        if (service_array == NULL) {
+            ERROR("Unable to get service array %zd", i);
+        } else {
+            size_t service_array_length = xpc_array_get_count(service_array);
+            for (j = 0; j < service_array_length; j++) {
+                xpc_object_t *array_sub_dict = xpc_array_get_value(service_array, j);
+                if (array_sub_dict == NULL) {
+                    ERROR("can't get service_array %zd subdictionary %zd", i, j);
+                    goto service_array_element_failed;
+                } else {
+                    const char *key = xpc_dictionary_get_string(array_sub_dict, "key");
+                    if (key == NULL) {
+                        ERROR("Invalid services array %zd subdictionary %zd: no key", i, j);
+                        goto service_array_element_failed;
+                    } else if (!strcmp(key, "EnterpriseNumber")) {
+                        if (matched[0]) {
+                            ERROR("services array %zd: Enterprise number appears twice.", i);
+                            goto service_array_element_failed;
+                        }
+                        enterprise_number = xpc_dictionary_get_uint64(array_sub_dict, "value");
+                        matched[0] = true;
+                    } else if (!strcmp(key, "Origin")) {
+                        if (matched[1]) {
+                            ERROR("Services array %zd: Origin appears twice.", i);
+                            goto service_array_element_failed;
+                        }
+                        const char *origin_string = xpc_dictionary_get_string(array_sub_dict, "value");
+                        if (origin_string == NULL) {
+                            ERROR("Unable to get origin string from services array %zd", i);
+                            goto service_array_element_failed;
+                        } else if (!strcmp(origin_string, "user")) {
+                            // Not NCP
+                        } else if (!strcmp(origin_string, "ncp")) {
+                            flags |= kCTIFlag_NCP;
+                        } else {
+                            ERROR("unknown origin " PUB_S_SRP, origin_string);
+                            goto service_array_element_failed;
+                        }
+                        matched[1] = true;
+                    } else if (!strcmp(key, "ServerData")) {
+                        if (matched[2]) {
+                            ERROR("Services array %zd: Server data appears twice.", i);
+                            goto service_array_element_failed;
+                        }
+                        server_data = cti_array_to_bytes(xpc_dictionary_get_array(array_sub_dict, "value"),
+                                                         &server_data_length, "Server data");
+                        if (server_data == NULL) {
+                            goto service_array_element_failed;
+                        }
+                        matched[2] = true;
+                    } else if (!strcmp(key, "ServiceData")) {
+                        if (matched[3]) {
+                            ERROR("Services array %zd: Service data appears twice.", i);
+                            goto service_array_element_failed;
+                        }
+                        service_data = cti_array_to_bytes(xpc_dictionary_get_array(array_sub_dict, "value"),
+                                                          &service_data_length, "Service data");
+                        if (service_data == NULL) {
+                            goto service_array_element_failed;
+                        }
+                        matched[3] = true;
+                    } else if (!strcmp(key, "Stable")) {
+                        if (matched[4]) {
+                            ERROR("Services array %zd: Stable state appears twice.", i);
+                            goto service_array_element_failed;
+                        }
+                        if (xpc_dictionary_get_bool(array_sub_dict, "value")) {
+                            flags |= kCTIFlag_Stable;
+                        }
+                        matched[4] = true;
+                    } else {
+                        ERROR("Unknown key in service array %zd subdictionary %zd: " PUB_S_SRP, i, j, key);
+                        goto service_array_element_failed;
+                    }
+                    match_count++;
+                }
+            }
+            if (match_count != 5) {
+                ERROR("expecting %d sub-dictionaries to service array %zd, but got %d.",
+                      5, i, match_count);
+                goto service_array_element_failed;
+            }
+            uint16_t service_type, service_version;
+            if (enterprise_number == THREAD_ENTERPRISE_NUMBER) {
+                if (service_data_length != 1) {
+                    INFO("Invalid service data: length = %zd", service_data_length);
+                    goto service_array_element_failed;
+                }
+                service_type = service_data[0];
+                service_version = 1;
+            } else {
+                // We don't support any other enterprise numbers.
+                service_type = service_version = 0;
+            }
+
+            service = cti_service_create(enterprise_number, service_type, service_version,
+                                         server_data, server_data_length, flags);
+            if (service == NULL) {
+                ERROR("Unable to store service %lld %d %d: out of memory.", enterprise_number,
+                      service_type, service_version);
+            } else {
+                server_data = NULL;
+                service_vec->services[i] = service;
+            }
+            goto done_with_service_array;
+        service_array_element_failed:
+            if (status == kCTIStatus_NoError) {
+                status = kCTIStatus_UnknownError;
+            }
+        done_with_service_array:
+            if (server_data != NULL) {
+                free(server_data);
+            }
+            if (service_data != NULL) {
+                free(service_data);
+            }
+        }
+    }
+    if (status == kCTIStatus_NoError) {
+        *services = service_vec;
+    } else {
+        if (service_vec != NULL) {
+            RELEASE_HERE(service_vec, cti_service_vec_finalize);
+        }
+    }
+    return status;
+}
+
+static void
+cti_internal_service_reply_callback(cti_connection_t NONNULL conn_ref, xpc_object_t reply, cti_status_t status_in)
+{
+    cti_service_reply_t callback = conn_ref->callback.service_reply;
+    cti_service_vec_t *vec = NULL;
+    cti_status_t status = status_in;
+    if (status == kCTIStatus_NoError) {
+        xpc_object_t result_dictionary = NULL;
+        status = cti_event_or_response_extract(reply, &result_dictionary);
+        if (status == kCTIStatus_NoError) {
+            xpc_object_t *value = xpc_dictionary_get_array(result_dictionary, "value");
+            if (value == NULL) {
+                INFO("cti_internal_service_reply_callback: services array not present in Thread:Services event.");
+            } else {
+                status = cti_parse_services_array(&vec, value);
+            }
+        }
+    }
+    if (callback != NULL) {
+        callback(conn_ref->context, vec, status);
+    }
+    if (vec != NULL) {
+        RELEASE_HERE(vec, cti_service_vec_finalize);
+    }
+}
+
+cti_status_t
+cti_get_service_list(cti_connection_t *ref, void *NULLABLE context, cti_service_reply_t NONNULL callback,
+                     dispatch_queue_t NONNULL client_queue)
+{
+    cti_status_t errx;
+    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    cti_callback_t app_callback;
+    app_callback.service_reply = callback;
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "Thread:Services");
+
+    errx = setup_for_command(ref, client_queue, "get_service_list", "Thread:Services", dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_service_reply_callback);
+    xpc_release(dict);
+
+    return errx;
+}
+
+static void
+cti_prefix_finalize(cti_prefix_t *prefix)
+{
+    free(prefix);
+}
+
+static void
+cti_prefix_vec_finalize(cti_prefix_vec_t *prefixes)
+{
+    size_t i;
+
+    if (prefixes->prefixes != NULL) {
+        for (i = 0; i < prefixes->num; i++) {
+            if (prefixes->prefixes[i] != NULL) {
+                RELEASE_HERE(prefixes->prefixes[i], cti_prefix_finalize);
+            }
+        }
+        free(prefixes->prefixes);
+    }
+    free(prefixes);
+}
+
+cti_prefix_vec_t *
+cti_prefix_vec_create_(size_t num_prefixes, const char *file, int line)
+{
+    cti_prefix_vec_t *prefixes = calloc(1, sizeof(*prefixes));
+    if (prefixes != NULL) {
+        if (num_prefixes != 0) {
+            prefixes->prefixes = calloc(num_prefixes, sizeof(cti_prefix_t *));
+            if (prefixes->prefixes == NULL) {
+                free(prefixes);
+                return NULL;
+            }
+        }
+        prefixes->num = num_prefixes;
+        RETAIN(prefixes);
+    }
+    return prefixes;
+}
+
+void
+cti_prefix_vec_release_(cti_prefix_vec_t *prefixes, const char *file, int line)
+{
+    RELEASE(prefixes, cti_prefix_vec_finalize);
+}
+
+cti_prefix_t *
+cti_prefix_create_(struct in6_addr *prefix, int prefix_length, int metric, int flags, const char *file, int line)
+{
+    cti_prefix_t *prefix_ret = calloc(1, sizeof(*prefix_ret));
+    if (prefix != NULL) {
+        prefix_ret->prefix = *prefix;
+        prefix_ret->prefix_length = prefix_length;
+        prefix_ret->metric = metric;
+        prefix_ret->flags = flags;
+        RETAIN(prefix_ret);
+    }
+    return prefix_ret;
+}
+
+void
+cti_prefix_release_(cti_prefix_t *prefix, const char *file, int line)
+{
+    RELEASE(prefix, cti_prefix_finalize);
+}
+
+static cti_status_t
+cti_parse_prefixes_array(cti_prefix_vec_t **vec_ret, xpc_object_t prefixes_array)
+{
+    size_t prefixes_array_length = xpc_array_get_count(prefixes_array);
+    size_t i, j;
+    cti_prefix_vec_t *prefixes = cti_prefix_vec_create(prefixes_array_length);
+    cti_status_t status = kCTIStatus_NoError;
+
+    if (prefixes == NULL) {
+        INFO("cti_parse_prefixes_array: no memory.");
+        status = kCTIStatus_NoMemory;
+    } else {
+        // Array of arrays
+        for (i = 0; i < prefixes_array_length; i++) {
+            xpc_object_t prefix_array = xpc_array_get_value(prefixes_array, i);
+            int match_count = 0;
+            bool matched[5] = { false, false};
+            const char *destination = NULL;
+            int metric = 0;
+            struct in6_addr prefix_addr;
+
+            if (prefix_array == NULL) {
+                ERROR("Unable to get prefix array %zu", i);
+            } else {
+                size_t prefix_array_length = xpc_array_get_count(prefix_array);
+                for (j = 0; j < prefix_array_length; j++) {
+                    xpc_object_t *array_sub_dict = xpc_array_get_value(prefix_array, j);
+                    if (array_sub_dict == NULL) {
+                        ERROR("can't get prefix_array %zu subdictionary %zu", i, j);
+                        goto done_with_prefix_array;
+                    } else {
+                        const char *key = xpc_dictionary_get_string(array_sub_dict, "key");
+                        if (key == NULL) {
+                            ERROR("Invalid prefixes array %zu subdictionary %zu: no key", i, j);
+                            goto done_with_prefix_array;
+                        }
+                        // Fix me: when <rdar://problem/59371674> is fixed, remove Addreess key test.
+                        else if (!strcmp(key, "Addreess") || !strcmp(key, "Address")) {
+                            if (matched[0]) {
+                                ERROR("prefixes array %zu: Address appears twice.", i);
+                                goto done_with_prefix_array;
+                            }
+                            destination = xpc_dictionary_get_string(array_sub_dict, "value");
+                            if (destination == NULL) {
+                                INFO("process_prefixes_array: null address");
+                                goto done_with_prefix_array;
+                            }
+                            matched[0] = true;
+                        } else if (!strcmp(key, "Metric")) {
+                            if (matched[1]) {
+                                ERROR("prefixes array %zu: Metric appears twice.", i);
+                                goto done_with_prefix_array;
+                            }
+                            metric = (int)xpc_dictionary_get_uint64(array_sub_dict, "value");
+                        } else {
+                            ERROR("Unknown key in prefix array %zu subdictionary %zu: " PUB_S_SRP, i, j, key);
+                            goto done_with_prefix_array;
+                        }
+                        match_count++;
+                    }
+                }
+                if (match_count != 2) {
+                    ERROR("expecting %d sub-dictionaries to prefix array %zu, but got %d.",
+                          2, i, match_count);
+                    goto done_with_prefix_array;
+                }
+
+                // The prefix is in IPv6 address presentation form, so convert it to bits.
+                char prefix_buffer[INET6_ADDRSTRLEN];
+                const char *slash = strchr(destination, '/');
+                size_t prefix_pres_len = slash - destination;
+                if (prefix_pres_len >= INET6_ADDRSTRLEN - 1) {
+                    ERROR("prefixes array %zu: destination is longer than maximum IPv6 address string: " PUB_S_SRP,
+                          j, destination);
+                    goto done_with_prefix_array;
+                }
+#ifndef __clang_analyzer__ // destination is never null at this point
+                memcpy(prefix_buffer, destination, prefix_pres_len);
+#endif
+                prefix_buffer[prefix_pres_len] = 0;
+                inet_pton(AF_INET6, prefix_buffer, &prefix_addr);
+
+                // Also convert the prefix.
+                char *endptr = NULL;
+                int prefix_len = (int)strtol(slash + 1, &endptr, 10);
+                if (endptr == slash + 1 || *endptr != 0 || prefix_len != 64) {
+                    INFO("bogus prefix length provided by thread: " PUB_S_SRP, destination);
+                    prefix_len = 64;
+                }
+
+                cti_prefix_t *prefix = cti_prefix_create(&prefix_addr, prefix_len, metric, 0);
+                if (prefix != NULL) {
+                    prefixes->prefixes[i] = prefix;
+                }
+                continue;
+            done_with_prefix_array:
+                status = kCTIStatus_UnknownError;
+            }
+        }
+    }
+    if (status == kCTIStatus_NoError) {
+        *vec_ret = prefixes;
+    } else {
+        if (prefixes != NULL) {
+            RELEASE_HERE(prefixes, cti_prefix_vec_finalize);
+        }
+    }
+    return status;
+}
+
+static void
+cti_internal_prefix_reply_callback(cti_connection_t NONNULL conn_ref, xpc_object_t reply, cti_status_t status_in)
+{
+    cti_prefix_reply_t callback = conn_ref->callback.prefix_reply;
+    cti_status_t status = status_in;
+    cti_prefix_vec_t *vec = NULL;
+    xpc_object_t result_dictionary = NULL;
+    if (status == kCTIStatus_NoError) {
+        status = cti_event_or_response_extract(reply, &result_dictionary);
+        if (status == kCTIStatus_NoError) {
+            xpc_object_t *value = xpc_dictionary_get_array(result_dictionary, "value");
+            if (value == NULL) {
+                INFO("cti_internal_prefix_reply_callback: prefixes array not present in IPv6:Routes event.");
+            } else {
+                status = cti_parse_prefixes_array(&vec, value);
+            }
+        }
+    }
+    if (callback != NULL) {
+        callback(conn_ref->context, vec, status);
+    } else {
+        INFO("Not calling callback.");
+    }
+    if (vec != NULL) {
+        RELEASE_HERE(vec, cti_prefix_vec_finalize);
+    }
+}
+
+cti_status_t
+cti_get_prefix_list(cti_connection_t *ref, void *NULLABLE context, cti_prefix_reply_t NONNULL callback,
+                     dispatch_queue_t NONNULL client_queue)
+{
+    cti_status_t errx;
+    xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    cti_callback_t app_callback;
+    app_callback.prefix_reply = callback;
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "IPv6:Routes");
+
+    errx = setup_for_command(ref, client_queue, "get_prefix_list", "IPv6:Routes", dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_prefix_reply_callback);
+    xpc_release(dict);
+
+    return errx;
+}
+
+cti_status_t
+cti_events_discontinue(cti_connection_t ref)
+{
+    cti_connection_release(ref);
+    return kCTIStatus_NoError;
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 120
+// indent-tabs-mode: nil
+// End:
diff --git a/mDNSMacOSX/Private/cti-services.h b/mDNSMacOSX/Private/cti-services.h
new file mode 100644 (file)
index 0000000..f632e6f
--- /dev/null
@@ -0,0 +1,755 @@
+/* cti_services.h
+ *
+ * Copyright (c) 2020 Apple Computer, Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains definitions for the SRP Advertising Proxy CoreThreadRadio
+ * API on MacOS, which is private API used to control and manage the wpantund
+ * and thereby manage the thread network.
+ */
+
+#ifndef CTI_SERVICES_H
+#define CTI_SERVICES_H
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <dispatch/dispatch.h>
+#include <xpc/xpc.h>
+#include "srp.h"
+
+#if (defined(__GNUC__) && (__GNUC__ >= 4))
+#define DNS_SERVICES_EXPORT __attribute__((visibility("default")))
+#else
+#define DNS_SERVICES_EXPORT
+#endif
+
+// cti_connection: Opaque internal data type
+typedef struct _cti_connection_t *cti_connection_t;
+
+typedef enum
+{
+    kCTIStatus_NoError                   =  0,
+    kCTIStatus_UnknownError              = -65537,   /* 0xFFFE FFFF */
+    kCTIStatus_NoMemory                  = -65539,   /* No Memory   */
+    kCTIStatus_BadParam                  = -65540,   /* Client passed invalid arg */
+    kCTIStatus_DaemonNotRunning          = -65563,   /* Daemon not running */
+    kCTIStatus_Disconnected              = -65569    /* XPC connection disconnected. */
+} cti_status_t;
+
+// Enum values for kWPANTUNDStateXXX (see wpan-properties.h)
+typedef enum {
+    kCTI_NCPState_Uninitialized,
+    kCTI_NCPState_Fault,
+    kCTI_NCPState_Upgrading,
+    kCTI_NCPState_DeepSleep,
+    kCTI_NCPState_Offline,
+    kCTI_NCPState_Commissioned,
+    kCTI_NCPState_Associating,
+    kCTI_NCPState_CredentialsNeeded,
+    kCTI_NCPState_Associated,
+    kCTI_NCPState_Isolated,
+    kCTI_NCPState_NetWake_Asleep,
+    kCTI_NCPState_NetWake_Waking,
+    kCTI_NCPState_Unknown
+} cti_network_state_t;
+
+typedef enum {
+    kCTI_NetworkNodeType_Unknown,
+    kCTI_NetworkNodeType_Router,
+    kCTI_NetworkNodeType_EndDevice,
+    kCTI_NetworkNodeType_SleepyEndDevice,
+    kCTI_NetworkNodeType_NestLurker,
+    kCTI_NetworkNodeType_Commissioner,
+    kCTI_NetworkNodeType_Leader,
+} cti_network_node_type_t;
+
+typedef struct _cti_service {
+    uint64_t enterprise_number;
+    uint16_t service_type;
+    uint16_t service_version;
+    uint8_t *NONNULL server;
+    size_t server_length;
+    int ref_count;
+    int flags;      // E.g., kCTIFlag_NCP
+} cti_service_t;
+
+typedef struct _cti_service_vec {
+    size_t num;
+    int ref_count;
+    cti_service_t *NULLABLE *NONNULL services;
+} cti_service_vec_t;
+
+typedef struct _cti_prefix {
+    struct in6_addr prefix;
+    int prefix_length;
+    int metric;
+    int flags;
+    int ref_count;
+} cti_prefix_t;
+
+typedef struct _cti_prefix_vec {
+    size_t num;
+    int ref_count;
+    cti_prefix_t *NULLABLE *NONNULL prefixes;
+} cti_prefix_vec_t;
+
+// CTI flags.
+#define kCTIFlag_Stable                1
+#define kCTIFlag_NCP                   2
+
+/*********************************************************************************************
+ *
+ *  wpantund private SPI for use in SRP Advertising Proxy
+ *
+ *********************************************************************************************/
+
+/* cti_reply:
+ *
+ * A general reply mechanism to indicate success or failure for a cti call that doesn't
+ * return any data.
+ *
+ * cti_reply parameters:
+ *
+ * context:    The context that was passed to the cti service call to which this is a callback.
+ *
+ * status:        Will be kCTIStatus_NoError on success, otherwise will indicate the
+ *                        failure that occurred.
+ *
+ */
+
+typedef void
+(*cti_reply_t)(void *NULLABLE context, cti_status_t status);
+
+/* cti_tunnel_reply: Callback for cti_get_tunnel_name()
+ *
+ * Called exactly once in response to a cti_get_tunnel_name() call, either with an error or with
+ * the name of the tunnel that wpantund is using as the Thread network interface.   The invoking
+ * program is responsible for releasing the connection state either during or after the callback.
+ *
+ * cti_reply parameters:
+ *
+ * context:       The context that was passed to the cti service call to which this is a callback.
+ *
+ * tunnel_name:   If error is kCTIStatus_NoError, this dictionary contains either the response to
+ *                           a request, or else the content of a CTI event if this is an event callback.
+ *
+ * status:           Will be kCTIStatus_NoError on success, otherwise will indicate the
+ *                           failure that occurred.
+ *
+ */
+
+typedef void
+(*cti_tunnel_reply_t)(void *NULLABLE context, const char *NONNULL tunnel_name,
+                      cti_status_t status);
+
+/* cti_get_tunnel_name
+ *
+ * Get the name of the tunnel that wpantund is presenting as the Thread network interface.
+ * The tunnel name is passed to the reply callback if the request succeeds; otherwise an error
+ * is either returned immediately or returned to the callback.
+ *
+ * context:        An anonymous pointer that will be passed along to the callback when
+ *                 an event occurs.
+ *
+ * client_queueq:  Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * callback:       CallBack function for the client that indicates success or failure.
+ *
+ * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating the
+ *                 error that occurred. Note: A return value of kCTIStatus_NoError does not mean that the
+ *                 request succeeded, merely that it was successfully started.
+ *
+ */
+
+DNS_SERVICES_EXPORT cti_status_t
+cti_get_tunnel_name(void *NULLABLE context, cti_tunnel_reply_t NONNULL callback, dispatch_queue_t NONNULL client_queue);
+
+/*
+ * cti_service_vec_create
+ *
+ * creates a service array vector of specified length
+ *
+ * num_services:     Number of service slots available in the service vector.
+ *
+ * return value:     NULL, if the call failed; otherwise a service vector capable of containing the requested number of
+ *                   services.
+ */
+cti_service_vec_t *NULLABLE
+cti_service_vec_create_(size_t num_services, const char *NONNULL file, int line);
+#define cti_service_vec_create(num_services) cti_service_vec_create_(num_services, __FILE__, __LINE__)
+
+/*
+ * cti_service_vec_release
+ *
+ * decrements the reference count on the provided service vector and, if it reaches zero, finalizes the service vector,
+ * which calls cti_service_release on each service in the vector, potentially also finalizing them.
+ *
+ * num_services:     Number of service slots available in the service vector.
+ *
+ * return value:     NULL, if the call failed; otherwise a service vector capable of containing the requested number of
+ *                   services.
+ */
+
+void
+cti_service_vec_release_(cti_service_vec_t *NONNULL services, const char *NONNULL file, int line);
+#define cti_service_vec_release(services) cti_service_vec_release_(services, __FILE__, __LINE__)
+
+/*
+ * cti_service_create
+ *
+ * Creates a service containing the specified information.   service and server are retained, and will be
+ * freed using free() when the service object is finalized.   Caller must not retain or free these values, and
+ * they must be allocated on the malloc heap.
+ *
+ * enterprise_number: The enterprise number for this service.
+ *
+ * service_type:      The service type, from the service data.
+ *
+ * service_version:   The service version, from the service data.
+ *
+ * server:            Server information for this service, stored in network byte order.   Format depends on service type.
+ *
+ * server_length:     Length of server information in bytes.
+ *
+ * flags:             Thread network status flags, e.g. NCP versue User
+ *
+ * return value:     NULL, if the call failed; otherwise a service object containing the specified state.
+ */
+
+cti_service_t *NULLABLE
+cti_service_create_(uint64_t enterprise_number, uint16_t service_type, uint16_t service_version,
+                    uint8_t *NONNULL server, size_t server_length, int flags,
+                    const char *NONNULL file, int line);
+#define cti_service_create(enterprise_number, service_type, service_version, server, server_length, flags) \
+    cti_service_create_(enterprise_number, service_type, service_version, server, server_length, flags, \
+                        __FILE__, __LINE__)
+
+/*
+ * cti_service_vec_release
+ *
+ * decrements the reference count on the provided service vector and, if it reaches zero, finalizes the service vector,
+ * which calls cti_service_release on each service in the vector, potentially also finalizing them.
+ *
+ * services:         The service vector to release
+ *
+ * return value:     NULL, if the call failed; otherwise a service vector capable of containing the requested number of
+ *                   services.
+ */
+
+void
+cti_service_release_(cti_service_t *NONNULL service, const char *NONNULL file, int line);
+#define cti_service_release(services) cti_service_release(service, __FILE__, __LINE__)
+
+/* cti_service_reply: Callback from cti_get_service_list()
+ *
+ * Called when an error occurs during processing of the cti_get_service_list call, or when data
+ * is available in response to the call.
+ *
+ * In the case of an error, the callback will not be called again, and the caller is responsible for
+ * releasing the connection state and restarting if needed.
+ *
+ * The callback will be called once immediately upon success, and once again each time the service list
+ * is updated.   If the callback wishes to retain either the cti_service_vec_t or any of the elements
+ * of that vector, the appropriate retain function should be called; when the object is no longer needed,
+ * the corresponding release call must be called.
+ *
+ * cti_reply parameters:
+ *
+ * context:           The context that was passed to the cti service call to which this is a callback.
+ *
+ * services:          If status is kCTIStatus_NoError, a cti_service_vec_t containing the list of services
+ *                    provided in the update.
+ *
+ * status:               Will be kCTIStatus_NoError if the service list request is successful, or
+ *                    will indicate the failure that occurred.
+ *
+ */
+
+typedef void
+(*cti_service_reply_t)(void *NULLABLE context, cti_service_vec_t *NULLABLE services, cti_status_t status);
+
+/* cti_get_service_list
+ *
+ * Requests wpantund to immediately send the current list of services published in the Thread network data.
+ * Whenever the service list is updated, the callback will be called again with the new information.  A
+ * return value of kCTIStatus_NoError means that the caller can expect the reply callback to be called at least
+ * once.  Any other error status means that the request could not be sent, and the callback will never be
+ * called.
+ *
+ * To discontinue receiving service add/remove callbacks, the calling program should call
+ * cti_connection_ref_deallocate on the conn_ref returned by a successful call to get_service_list();
+ *
+ * ref:            A pointer to a reference to the connection is stored through ref if ref is not NULL.
+ *                 When events are no longer needed, call cti_discontinue_events() on the returned pointer.
+ *
+ * context:        An anonymous pointer that will be passed along to the callback when
+ *                 an event occurs.
+ *
+ * client_queue:   Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * callback:       CallBack function for the client that indicates success or failure.
+ *                 If the get_services call fails, response will be NULL and status
+ *                 will indicate what went wrong.  No further callbacks can be expected
+ *                 after this.   If the request succeeds, then the callback will be called
+ *                 once immediately with the current service list, and then again whenever
+ *                 the service list is updated.
+ *
+ * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
+ *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
+ *                 that the request succeeded, merely that it was successfully started.
+ *
+ */
+
+DNS_SERVICES_EXPORT cti_status_t
+cti_get_service_list(cti_connection_t NULLABLE *NULLABLE ref, void *NULLABLE context, cti_service_reply_t NONNULL callback,
+                     dispatch_queue_t NONNULL client_queue);
+
+/*
+ * cti_service_vec_create
+ *
+ * creates a service array vector of specified length
+ *
+ * num_prefixes:     Number of prefix slots available in the prefix vector.
+ *
+ * return value:     NULL, if the call failed; otherwise a prefix vector capable of containing the requested number of
+ *                   prefixes.
+ */
+cti_prefix_vec_t *NULLABLE
+cti_prefix_vec_create_(size_t num_prefixes, const char *NONNULL file, int line);
+#define cti_prefix_vec_create(num_prefixes) cti_prefix_vec_create_(num_prefixes, __FILE__, __LINE__)
+
+/*
+ * cti_prefix_vec_release
+ *
+ * decrements the reference count on the provided prefix vector and, if it reaches zero, finalizes the prefix vector,
+ * which calls cti_prefix_release on each prefix in the vector, potentially also finalizing them.
+ *
+ * num_prefixes:     Number of prefix slots available in the prefix vector.
+ *
+ * return value:     NULL, if the call failed; otherwise a prefix vector capable of containing the requested number of
+ *                   prefixes.
+ */
+
+void
+cti_prefix_vec_release_(cti_prefix_vec_t *NONNULL prefixes, const char *NONNULL file, int line);
+#define cti_prefix_vec_release(prefixes) cti_prefix_vec_release(prefixes, __FILE__, __LINE__)
+
+/*
+ * cti_prefix_create
+ *
+ * Creates a prefix containing the specified information.   prefix and server are retained, and will be
+ * freed using free() when the prefix object is finalized.   Caller must not retain or free these values, and
+ * they must be allocated on the malloc heap.
+ *
+ * enterprise_number: The enterprise number for this prefix.
+ *
+ * prefix_type:      The prefix type, from the prefix data.
+ *
+ * prefix_version:   The prefix version, from the prefix data.
+ *
+ * server:            Server information for this prefix, stored in network byte order.   Format depends on prefix type.
+ *
+ * server_length:     Length of server information in bytes.
+ *
+ * flags:             Thread network status flags, e.g. NCP versue User
+ *
+ * return value:     NULL, if the call failed; otherwise a prefix object containing the specified state.
+ */
+
+cti_prefix_t *NULLABLE
+cti_prefix_create_(struct in6_addr *NONNULL prefix, int prefix_length, int metric, int flags,
+                   const char *NONNULL file, int line);
+#define cti_prefix_create(prefix, prefix_length, metric, flags) \
+    cti_prefix_create_(prefix, prefix_length, metric, flags, __FILE__, __LINE__)
+
+/*
+ * cti_prefix_vec_release
+ *
+ * decrements the reference count on the provided prefix vector and, if it reaches zero, finalizes the prefix vector,
+ * which calls cti_prefix_release on each prefix in the vector, potentially also finalizing them.
+ *
+ * prefixes:         The prefix vector to release.
+ *
+ * return value:     NULL, if the call failed; otherwise a prefix vector capable of containing the requested number of
+ *                   prefixes.
+ */
+
+void
+cti_prefix_release_(cti_prefix_t *NONNULL prefix, const char *NONNULL file, int line);
+#define cti_prefix_release(prefixes) cti_prefix_release(prefix, __FILE__, __LINE__)
+
+/* cti_prefix_reply: Callback from cti_get_prefix_list()
+ *
+ * Called when an error occurs during processing of the cti_get_prefix_list call, or when a prefix
+ * is added or removed.
+ *
+ * In the case of an error, the callback will not be called again, and the caller is responsible for
+ * releasing the connection state and restarting if needed.
+ *
+ * The callback will be called once for each prefix present on the Thread network at the time
+ * get_prefix_list() is first called, and then again whenever a prefix is added or removed.
+ *
+ * cti_reply parameters:
+ *
+ * context:           The context that was passed to the cti prefix call to which this is a callback.
+ *
+ * prefix_vec:        If status is kCTIStatus_NoError, a vector containing all of the prefixes that were reported in
+ *                    this event.
+ *
+ * status:               Will be kCTIStatus_NoError if the prefix list request is successful, or
+ *                    will indicate the failure that occurred.
+ *
+ */
+
+typedef void
+(*cti_prefix_reply_t)(void *NULLABLE context, cti_prefix_vec_t *NONNULL prefixes, cti_status_t status);
+
+/* cti_get_prefix_list
+ *
+ * Requests wpantund to immediately send the current list of off-mesh prefixes configured in the Thread
+ * network data.  Whenever the prefix list is updated, the callback will be called again with the new
+ * information.  A return value of kCTIStatus_NoError means that the caller can expect the reply callback to be
+ * called at least once.  Any other error means that the request could not be sent, and the callback will
+ * never be called.
+ *
+ * To discontinue receiving prefix add/remove callbacks, the calling program should call
+ * cti_connection_ref_deallocate on the conn_ref returned by a successful call to get_prefix_list();
+ *
+ * ref:            A pointer to a reference to the connection is stored through ref if ref is not NULL.
+ *                 When events are no longer needed, call cti_discontinue_events() on the returned pointer.
+ *
+ * context:        An anonymous pointer that will be passed along to the callback when
+ *                 an event occurs.
+ *
+ * client_queue:   Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * callback:       CallBack function for the client that indicates success or failure.
+ *
+ * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
+ *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
+ *                 that the request succeeded, merely that it was successfully started.
+ *
+ */
+
+DNS_SERVICES_EXPORT cti_status_t
+cti_get_prefix_list(cti_connection_t NULLABLE *NULLABLE ref, void *NULLABLE context, cti_prefix_reply_t NONNULL callback,
+                    dispatch_queue_t NONNULL client_queue);
+
+/* cti_state_reply: Callback from cti_get_state()
+ *
+ * Called when an error occurs during processing of the cti_get_state call, or when network state
+ * information is available.
+ *
+ * In the case of an error, the callback will not be called again, and the caller is responsible for
+ * releasing the connection state and restarting if needed.
+ *
+ * The callback will be called initially to report the network state, and subsequently whenever the
+ * network state changes.
+ *
+ * cti_reply parameters:
+ *
+ * context:           The context that was passed to the cti state call to which this is a callback.
+ *
+ * state:             The network state.
+ *
+ * status:               Will be kCTIStatus_NoError if the prefix list request is successful, or
+ *                    will indicate the failure that occurred.
+ *
+ */
+
+typedef void
+(*cti_state_reply_t)(void *NULLABLE context, cti_network_state_t state, cti_status_t status);
+
+/* cti_get_state
+ *
+ * Requests wpantund to immediately send the current state of the thread network.  Whenever the thread
+ * network state changes, the callback will be called again with the new state.  A return value of
+ * kCTIStatus_NoError means that the caller can expect the reply callback to be called at least once.  Any
+ * other error means that the request could not be sent, and the callback will never be called.
+ *
+ * To discontinue receiving state change callbacks, the calling program should call
+ * cti_connection_ref_deallocate on the conn_ref returned by a successful call to cti_get_state();
+ *
+ * ref:            A pointer to a reference to the connection is stored through ref if ref is not NULL.
+ *                 When events are no longer needed, call cti_discontinue_events() on the returned pointer.
+ *
+ * context:        An anonymous pointer that will be passed along to the callback when
+ *                 an event occurs.
+ * client_queue:   Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * callback:       CallBack function for the client that indicates success or failure.
+ *
+ * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
+ *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
+ *                 that the request succeeded, merely that it was successfully started.
+ *
+ */
+
+DNS_SERVICES_EXPORT cti_status_t
+cti_get_state(cti_connection_t NULLABLE *NULLABLE ref, void *NULLABLE context, cti_state_reply_t NONNULL callback,
+              dispatch_queue_t NONNULL client_queue);
+
+/* cti_partition_id_reply: Callback from cti_get_partition_id()
+ *
+ * Called when an error occurs during processing of the cti_get_partition_id call, or when network partition ID
+ * information is available.
+ *
+ * In the case of an error, the callback will not be called again, and the caller is responsible for
+ * releasing the connection partition_id and restarting if needed.
+ *
+ * The callback will be called initially to report the network partition ID, and subsequently whenever the
+ * network partition ID changes.
+ *
+ * cti_reply parameters:
+ *
+ * context:           The context that was passed to the cti prefix call to which this is a callback.
+ *
+ * partition_id:      The network partition ID, or -1 if it is not known.
+ *
+ * status:               Will be kCTIStatus_NoError if the partition ID request is successful, or will indicate the failure
+ *                    that occurred.
+ *
+ */
+
+typedef void
+(*cti_partition_id_reply_t)(void *NULLABLE context, int32_t partition_id, cti_status_t status);
+
+/* cti_get_partition_id
+ *
+ * Requests wpantund to immediately send the current partition_id of the thread network.  Whenever the thread
+ * network partition_id changes, the callback will be called again with the new partition_id.  A return value of
+ * kCTIStatus_NoError means that the caller can expect the reply callback to be called at least once.  Any
+ * other error means that the request could not be sent, and the callback will never be called.
+ *
+ * To discontinue receiving partition_id change callbacks, the calling program should call
+ * cti_connection_ref_deallocate on the conn_ref returned by a successful call to cti_get_partition_id();
+ *
+ * ref:            A pointer to a reference to the connection is stored through ref if ref is not NULL.
+ *                 When events are no longer needed, call cti_discontinue_events() on the returned pointer.
+ *
+ * context:        An anonymous pointer that will be passed along to the callback when
+ *                 an event occurs.
+ * client_queue:   Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * callback:       CallBack function for the client that indicates success or failure.
+ *
+ * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
+ *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
+ *                 that the request succeeded, merely that it was successfully started.
+ *
+ */
+
+DNS_SERVICES_EXPORT cti_status_t
+cti_get_partition_id(cti_connection_t NULLABLE *NULLABLE ref, void *NULLABLE context,
+                     cti_partition_id_reply_t NONNULL callback, dispatch_queue_t NONNULL client_queue);
+
+/* cti_network_node_type_reply: Callback from cti_get_network_node_type()
+ *
+ * Called when an error occurs during processing of the cti_get_network_node_type call, or when network partition ID
+ * information is available.
+ *
+ * In the case of an error, the callback will not be called again, and the caller is responsible for releasing the
+ * connection network_node_type and restarting if needed.
+ *
+ * The callback will be called initially to report the network partition ID, and subsequently whenever the network
+ * partition ID changes.
+ *
+ * cti_reply parameters:
+ *
+ * context:           The context that was passed to the cti prefix call to which this is a callback.
+ *
+ * network_node_type: The network node type, kCTI_NetworkNodeType_Unknown if it is not known.
+ *
+ * status:               Will be kCTIStatus_NoError if the partition ID request is successful, or will indicate the failure
+ *                    that occurred.
+ *
+ */
+
+typedef void
+(*cti_network_node_type_reply_t)(void *NULLABLE context, cti_network_node_type_t network_node_type, cti_status_t status);
+
+/* cti_get_network_node_type
+ *
+ * Requests wpantund to immediately send the current network_node_type of the thread network.  Whenever the thread
+ * network network_node_type changes, the callback will be called again with the new network_node_type.  A return value of
+ * kCTIStatus_NoError means that the caller can expect the reply callback to be called at least once.  Any
+ * other error means that the request could not be sent, and the callback will never be called.
+ *
+ * To discontinue receiving network_node_type change callbacks, the calling program should call
+ * cti_connection_ref_deallocate on the conn_ref returned by a successful call to cti_get_network_node_type();
+ *
+ * ref:            A pointer to a reference to the connection is stored through ref if ref is not NULL.
+ *                 When events are no longer needed, call cti_discontinue_events() on the returned pointer.
+ *
+ * context:        An anonymous pointer that will be passed along to the callback when
+ *                 an event occurs.
+ * client_queue:   Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * callback:       CallBack function for the client that indicates success or failure.
+ *
+ * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
+ *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
+ *                 that the request succeeded, merely that it was successfully started.
+ *
+ */
+
+DNS_SERVICES_EXPORT cti_status_t
+cti_get_network_node_type(cti_connection_t NULLABLE *NULLABLE ref, void *NULLABLE context,
+                          cti_network_node_type_reply_t NONNULL callback, dispatch_queue_t NONNULL client_queue);
+
+/* cti_add_service
+ *
+ * Requests wpantund to add the specified service to the thread network data.  A return value of
+ * kCTIStatus_NoError means that the caller can expect the reply callback to be called with a success or fail
+ * status exactly one time.  Any other error means that the request could not be sent, and the callback will
+ * never be called.
+ *
+ * context:           An anonymous pointer that will be passed along to the callback when
+ *                    an event occurs.
+ *
+ * client_queue:      Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * callback:          CallBack function for the client that indicates success or failure.
+ *
+ * enterprise_number: Contains the enterprise number of the service.
+ *
+ * service_data:      Typically four bytes, in network byte order, the first two bytes indicate
+ *                    the type of service within the enterprise' number space, and the second
+ *                    two bytes indicate the version number.
+ *
+ * service_data_len:  The length of the service data in bytes.
+ *
+ * server_data:       Typically four bytes, in network byte order, the first two bytes indicate
+ *                    the type of service within the enterprise' number space, and the second
+ *                    two bytes indicate the version number.
+ *
+ * server_data_len:   The length of the service data in bytes.
+ *
+ * return value:      Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
+ *                    the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
+ *                    that the request succeeded, merely that it was successfully started.
+ *
+ */
+
+DNS_SERVICES_EXPORT cti_status_t
+cti_add_service(void *NULLABLE context, cti_reply_t NONNULL callback, dispatch_queue_t NONNULL client_queue,
+                uint32_t enterprise_number, const uint8_t *NONNULL service_data, size_t service_data_length,
+                const uint8_t *NONNULL server_data, size_t server_data_length);
+
+/* cti_remove_service
+ *
+ * Requests wpantund to remove the specified service from the thread network data.  A return value of
+ * kCTIStatus_NoError means that the caller can expect the reply callback to be called with a success or fail
+ * status exactly one time.  Any other error means that the request could not be sent, and the callback will
+ * never be called.
+ *
+ * context:           An anonymous pointer that will be passed along to the callback when
+ *                    an event occurs.
+ *
+ * client_queue:      Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * enterprise_number: Contains the enterprise number of the service.
+ *
+ * service_data:      Typically four bytes, in network byte order, the first two bytes indicate
+ *                    the type of service within the enterprise' number space, and the second
+ *                    two bytes indicate the version number.
+ *
+ * service_data_len:  The length of the service data in bytes.
+ *
+ * callback:          callback function for the client that indicates success or failure.
+ *
+ * return value:      Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
+ *                    the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
+ *                    that the request succeeded, merely that it was successfully started.
+ */
+
+DNS_SERVICES_EXPORT cti_status_t
+cti_remove_service(void *NULLABLE context, cti_reply_t NONNULL callback, dispatch_queue_t NONNULL client_queue,
+                   uint32_t enterprise_number, const uint8_t *NONNULL service_data,
+                   size_t service_data_length);
+
+/* cti_add_prefix
+ *
+ * Requests wpantund to add the specified prefix to the set of off-mesh prefixes configured on the thread
+ * network.  A return value of kCTIStatus_NoError means that the caller can expect the reply callback to be
+ * called with a success or fail status exactly one time.  Any other error means that the request could not
+ * be sent, and the callback will never be called.
+ *
+ * context:        An anonymous pointer that will be passed along to the callback when
+ *                 an event occurs.
+ * client_queue:   Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * prefix:         A pointer to a struct in6_addr.  Must not be reatained by the callback.
+ *
+ * prefix_len:     The length of the prefix in bits.
+ *
+ * callback:       CallBack function for the client that indicates success or failure.
+ *
+ * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
+ *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
+ *                 that the request succeeded, merely that it was successfully started.
+ *
+ */
+
+DNS_SERVICES_EXPORT cti_status_t
+cti_add_prefix(void *NULLABLE context, cti_reply_t NONNULL callback, dispatch_queue_t NONNULL client_queue,
+               struct in6_addr *NONNULL prefix, int prefix_length, bool on_mesh, bool preferred, bool slaac,
+               bool stable);
+
+/* cti_remove_prefix
+ *
+ * Requests wpantund to remove the specified prefix from the set of off-mesh prefixes configured on the thread network.
+ * A return value of kCTIStatus_NoError means that the caller can expect the reply callback to be called with a success
+ * or fail status exactly one time.  Any other error means that the request could not be sent, and the callback will
+ * never be called.
+ *
+ * context:        An anonymous pointer that will be passed along to the callback when
+ *                 an event occurs.
+ *
+ * client_queue:   Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * prefix:         A pointer to a struct in6_addr.  Must not be reatained by the callback.
+ *
+ * prefix_len:     The length of the prefix in bits.
+ *
+ * callback:       CallBack function for the client that indicates success or failure.
+ *
+ * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
+ *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
+ *                 that the request succeeded, merely that it was successfully started.
+ *
+ */
+
+DNS_SERVICES_EXPORT cti_status_t
+cti_remove_prefix(void *NULLABLE context, cti_reply_t NONNULL callback, dispatch_queue_t NONNULL client_queue,
+                  struct in6_addr *NONNULL prefix, int prefix_length);
+
+/* cti_events_discontinue
+ *
+ * Requests that the CTI library stop delivering events on the specified connection.   The connection will have
+ * been returned by a CTI library call that subscribes to events.
+ */
+DNS_SERVICES_EXPORT cti_status_t
+cti_events_discontinue(cti_connection_t NONNULL ref);
+
+#endif /* CTI_SERVICES_H */
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 120
+// indent-tabs-mode: nil
+// End:
index 69f2f02237c56341ce32776a7c86472d0b988839..e7a15a5139abd92244f7011430fa493cafebe8c5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012-2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2012-2020 Apple Inc. All rights reserved.
  *
  * PRIVATE DNSX CLIENT LIBRARY --FOR Apple Platforms ONLY OSX/iOS--
  * Resides in /usr/lib/libdns_services.dylib
@@ -9,6 +9,7 @@
 #include <xpc/xpc.h>
 #include <Block.h>
 #include <os/log.h>
+#include <stdatomic.h>
 #include "xpc_clients.h"
 
 //*************************************************************************************************************
@@ -22,6 +23,7 @@ struct _DNSXConnRef_t
     dispatch_queue_t      lib_q;         // internal queue created in library itself
     DNSXEnableProxyReply  AppCallBack;   // Callback function ptr for Client
     dispatch_queue_t      client_q;      // Queue specified by client for scheduling its Callback
+    _Atomic(int32_t)      refCount;      // Reference count for this object.
 };
 
 //*************************************************************************************************************
@@ -42,6 +44,19 @@ static void LogDebug(const char *prefix, xpc_object_t o)
     free(desc);
 }
 
+static void _DNSXConnRefRetain(DNSXConnRef connRef)
+{
+    atomic_fetch_add(&connRef->refCount, 1);
+}
+
+static void _DNSXConnRefRelease(DNSXConnRef connRef)
+{
+    if (atomic_fetch_add(&connRef->refCount, -1) == 1)
+    {
+        free(connRef);
+    }
+}
+
 //**************************************************************************************************************
 
 void DNSXRefDeAlloc(DNSXConnRef connRef)
@@ -65,8 +80,8 @@ void DNSXRefDeAlloc(DNSXConnRef connRef)
         dispatch_async((connRef)->client_q, ^{
             dispatch_release(connRef->client_q);
             connRef->client_q = NULL;
-            free(connRef);
-            os_log_info(OS_LOG_DEFAULT, "dns_services: DNSXRefDeAlloc successfully DeAllocated client_q & freed connRef");
+            _DNSXConnRefRelease(connRef);
+            os_log_info(OS_LOG_DEFAULT, "dns_services: DNSXRefDeAlloc successfully DeAllocated client_q & released connRef");
         });
     });
     
@@ -82,6 +97,7 @@ static DNSXErrorType SendMsgToServer(DNSXConnRef connRef, xpc_object_t msg)
     
     LogDebug("dns_services: SendMsgToServer Sending msg to Daemon", msg);
     
+    _DNSXConnRefRetain(connRef);
     xpc_connection_send_message_with_reply((connRef)->conn_ref, msg, (connRef)->lib_q, ^(xpc_object_t recv_msg)
     {
         xpc_type_t type = xpc_get_type(recv_msg);
@@ -131,6 +147,7 @@ static DNSXErrorType SendMsgToServer(DNSXConnRef connRef, xpc_object_t msg)
                                 xpc_dictionary_get_string(recv_msg, XPC_ERROR_KEY_DESCRIPTION));
             LogDebug("dns_services: SendMsgToServer Unexpected Reply contents", recv_msg);
         }
+        _DNSXConnRefRelease(connRef);
     });
     
     return errx;
@@ -146,7 +163,7 @@ static DNSXErrorType InitConnection(DNSXConnRef *connRefOut, const char *servnam
     }
     
     // Use a DNSXConnRef on the stack to be captured in the blocks below, rather than capturing the DNSXConnRef* owned by the client
-    DNSXConnRef connRef = malloc(sizeof(struct _DNSXConnRef_t));
+    DNSXConnRef connRef = (DNSXConnRef)calloc(1, sizeof(*connRef));
     if (connRef == NULL)
     {
         os_log(OS_LOG_DEFAULT, "dns_services: InitConnection() No memory to allocate!");
@@ -154,6 +171,7 @@ static DNSXErrorType InitConnection(DNSXConnRef *connRefOut, const char *servnam
     }
     
     // Initialize the DNSXConnRef
+    atomic_init(&connRef->refCount, 1);
     dispatch_retain(clientq);
     connRef->client_q     = clientq;
     connRef->AppCallBack  = AppCallBack;
@@ -164,7 +182,9 @@ static DNSXErrorType InitConnection(DNSXConnRef *connRefOut, const char *servnam
     {
         os_log(OS_LOG_DEFAULT, "dns_services: InitConnection() conn_ref/lib_q is NULL");
         if (connRef != NULL)
-            free(connRef);
+        {
+            _DNSXConnRefRelease(connRef);
+        }
         return kDNSX_NoMem;
     }
     
@@ -194,16 +214,18 @@ static DNSXErrorType InitConnection(DNSXConnRef *connRefOut, const char *servnam
     return kDNSX_NoError;
 }
 
-DNSXErrorType DNSXEnableProxy(DNSXConnRef *connRef, DNSProxyParameters proxyparam, IfIndex inIfindexArr[MaxInputIf],
-                              IfIndex outIfindex, dispatch_queue_t clientq, DNSXEnableProxyReply callBack)
+#define kDNSXProxyRecognizedFlags (kDNSXProxyFlagForceAAAASynthesis)
+
+static DNSXErrorType _DNSXEnableProxy(DNSXConnRef *connRef, DNSProxyParameters proxyparam, IfIndex inIfindexArr[MaxInputIf],
+                                      IfIndex outIfindex, uint8_t ipv6Prefix[16], int ipv6PrefixBitLen,
+                                      DNSXProxyFlags flags, dispatch_queue_t clientq, DNSXEnableProxyReply callBack)
 {
-    
     DNSXErrorType errx = kDNSX_NoError;
     
     // Sanity Checks
     if (connRef == NULL || callBack == NULL || clientq == NULL)
     {
-        os_log(OS_LOG_DEFAULT, "dns_services: DNSXEnableProxy called with NULL DNSXConnRef OR Callback OR ClientQ parameter");
+        os_log(OS_LOG_DEFAULT, "dns_services: _DNSXEnableProxy called with NULL DNSXConnRef OR Callback OR ClientQ parameter");
         return kDNSX_BadParam;
     }
     
@@ -223,11 +245,17 @@ DNSXErrorType DNSXEnableProxy(DNSXConnRef *connRef, DNSProxyParameters proxypara
         return kDNSX_BadParam;
     }
     
+    if (flags & ~kDNSXProxyRecognizedFlags)
+    {
+        os_log_error(OS_LOG_DEFAULT, "dns_services: Use of unrecognized flags 0x%08X", flags & ~kDNSXProxyRecognizedFlags);
+        return kDNSX_BadParam;
+    }
+
     // Create Dictionary To Send
     xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
     if (dict == NULL)
     {
-        os_log(OS_LOG_DEFAULT, "dns_services: DNSXEnableProxy could not create the Msg Dict To Send!");
+        os_log(OS_LOG_DEFAULT, "dns_services: _DNSXEnableProxy could not create the Msg Dict To Send!");
         DNSXRefDeAlloc(*connRef);
         return kDNSX_NoMem;
     }
@@ -242,6 +270,13 @@ DNSXErrorType DNSXEnableProxy(DNSXConnRef *connRef, DNSProxyParameters proxypara
     
     xpc_dictionary_set_uint64(dict, kDNSOutIfindex,      outIfindex);
     
+    if (ipv6Prefix)
+    {
+        xpc_dictionary_set_data(dict, kDNSProxyDNS64IPv6Prefix, ipv6Prefix, 16);
+        xpc_dictionary_set_int64(dict, kDNSProxyDNS64IPv6PrefixBitLen, ipv6PrefixBitLen);
+        const bool forceAAAASynthesis = (flags & kDNSXProxyFlagForceAAAASynthesis) ? true : false;
+        xpc_dictionary_set_bool(dict, kDNSProxyDNS64ForceAAAASynthesis, forceAAAASynthesis);
+    }
     errx = SendMsgToServer(*connRef, dict);
     xpc_release(dict);
     dict = NULL;
@@ -249,3 +284,16 @@ DNSXErrorType DNSXEnableProxy(DNSXConnRef *connRef, DNSProxyParameters proxypara
     return errx;
 }
 
+DNSXErrorType DNSXEnableProxy(DNSXConnRef *connRef, DNSProxyParameters proxyparam, IfIndex inIfindexArr[MaxInputIf],
+                              IfIndex outIfindex, dispatch_queue_t clientq, DNSXEnableProxyReply callBack)
+{
+    return _DNSXEnableProxy(connRef, proxyparam, inIfindexArr, outIfindex, NULL, 0, kDNSXProxyFlagNull, clientq, callBack);
+}
+
+DNSXErrorType DNSXEnableProxy64(DNSXConnRef *connRef, DNSProxyParameters proxyparam, IfIndex inIfindexArr[MaxInputIf],
+                                IfIndex outIfindex, uint8_t ipv6Prefix[16], int ipv6PrefixBitLen, DNSXProxyFlags flags,
+                                dispatch_queue_t clientq, DNSXEnableProxyReply callBack)
+{
+    return _DNSXEnableProxy(connRef, proxyparam, inIfindexArr, outIfindex, ipv6Prefix, ipv6PrefixBitLen, flags, clientq,
+        callBack);
+}
index 4c3544fc7fdcc2fc5c0e74b09e594e0625bf7077..975cd599f19f227e122ed48c97d022d348b6cf63 100644 (file)
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2012-2018 Apple Inc. All rights reserved.
+ * Copyright (c) 2012-2020 Apple Inc. All rights reserved.
  *
  *
  * @header      Interface to DNSX SPI
@@ -118,6 +118,70 @@ DNSXErrorType DNSXEnableProxy
     DNSXEnableProxyReply     callBack
  );
 
+typedef uint32_t DNSXProxyFlags;
+
+#define kDNSXProxyFlagNull               0         // No flags.
+#define kDNSXProxyFlagForceAAAASynthesis (1U << 1) // Force AAAA synthesis during DNS64.
+
+/* DNSXEnableProxy64
+ *
+ * Enables the DNS Proxy functionality which will remain ON until the client explicitly turns it OFF
+ * by passing the returned DNSXConnRef to DNSXRefDeAlloc(), or the client exits or crashes.
+ *
+ * DNSXEnableProxy64() Parameters:
+ *
+ * connRef:                   A pointer to DNSXConnRef that is initialized to NULL.
+ *                            If the call succeeds it will be initialized to a non-NULL value.
+ *                            Client terminates the DNS Proxy by passing this DNSXConnRef to DNSXRefDeAlloc().
+ *
+ * proxyparam:                Enable DNS Proxy functionality with parameters that are described in
+ *                            DNSProxyParameters above.
+ *
+ * inIfindexArr[MaxInputIf]:  List of input interfaces from which the DNS queries will be accepted and
+ *                            forwarded to the output interface specified below. The daemon processes
+ *                            MaxInputIf entries in the list. For eg. if one has less than MaxInputIfs
+ *                            values, just initialize the other values to be 0. Note: This field needs to
+ *                            be initialized by the client.
+ *
+ * outIfindex:                Output interface on which the query will be forwarded.
+ *                            Passing kDNSIfindexAny causes DNS Queries to be sent on the primary interface.
+ *
+ *                            Note: It is the responsibility of the client to ensure the input/output interface
+ *                            indexes are valid.
+ *
+ * ipv6Prefix:                IPv6 prefix to use for DNS64.
+ *
+ * ipv6PrefixBitLength:       Bit length of IPv6 prefix to use for DNS64.
+ *
+ * flags:                     Flags to specify additional behavioral aspects of the proxy.
+ *
+ * clientq:                   Queue the client wants to schedule the callBack on (Note: Must not be NULL)
+ *
+ * callBack:                  CallBack function for the client that indicates success or failure.
+ *                            Note: callback may be invoked more than once, For e.g. if enabling DNS Proxy
+ *                            first succeeds and the daemon possibly crashes sometime later.
+ *
+ * return value:              Returns kDNSX_NoError when no error otherwise returns an error code indicating
+ *                            the error that occurred. Note: A return value of kDNSX_NoError does not mean
+ *                            that DNS Proxy was successfully enabled. The callBack may asynchronously
+ *                            return an error (such as kDNSX_DaemonNotRunning)
+ *
+ */
+
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+DNSXErrorType DNSXEnableProxy64
+(
+    DNSXConnRef              *connRef,
+    DNSProxyParameters       proxyparam,
+    IfIndex                  inIfindexArr[MaxInputIf],
+    IfIndex                  outIfindex,
+    uint8_t                  ipv6Prefix[16],
+    int                      ipv6PrefixBitLength,
+    DNSXProxyFlags           flags,
+    dispatch_queue_t         clientq,
+    DNSXEnableProxyReply     callBack
+);
+
 /* DNSXRefDeAlloc()
  *
  * Terminate a connection with the daemon and free memory associated with the DNSXConnRef.
diff --git a/mDNSMacOSX/QuerierSupport.c b/mDNSMacOSX/QuerierSupport.c
new file mode 100644 (file)
index 0000000..00c8da0
--- /dev/null
@@ -0,0 +1,845 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "QuerierSupport.h"
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+#include "DebugServices.h"
+#include "dns_sd_internal.h"
+#include "mDNSMacOSX.h"
+#include "mdns_xpc.h"
+#include "uDNS.h"
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
+#include "Metrics.h"
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include "dnssec_v2.h"
+#endif
+
+#include <libproc.h>
+#include <mach/mach_time.h>
+#include "mdns_helpers.h"
+
+int PQWorkaroundThreshold = 0;
+
+extern mDNS mDNSStorage;
+
+mDNSlocal void _Querier_LogDNSServices(const mdns_dns_service_manager_t manager)
+{
+    __block mDNSu32 count = 0;
+    const mDNSu32 total = (mDNSu32)mdns_dns_service_manager_get_count(manager);
+    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "Updated DNS services (%u)", total);
+    mdns_dns_service_manager_iterate(manager,
+    ^ bool (const mdns_dns_service_t service)
+    {
+        count++;
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "DNS service (%u/%u) -- %@", count, total, service);
+        return false;
+    });
+}
+
+mDNSexport mdns_dns_service_manager_t Querier_GetDNSServiceManager(void)
+{
+    mDNS *const m = &mDNSStorage;
+    static mdns_dns_service_manager_t sDNSServiceManager = NULL;
+    if (sDNSServiceManager)
+    {
+        return sDNSServiceManager;
+    }
+    const mdns_dns_service_manager_t manager = mdns_dns_service_manager_create(dispatch_get_main_queue(), NULL);
+    if (!manager)
+    {
+        return NULL;
+    }
+    mdns_dns_service_manager_set_report_symptoms(manager, true);
+    mdns_dns_service_manager_enable_problematic_qtype_workaround(manager, PQWorkaroundThreshold);
+    mdns_dns_service_manager_set_event_handler(manager,
+    ^(mdns_event_t event, __unused OSStatus error)
+    {
+        KQueueLock();
+        switch (event)
+        {
+            case mdns_event_error:
+                mdns_dns_service_manager_invalidate(manager);
+                if (sDNSServiceManager == manager)
+                {
+                    mdns_forget(&sDNSServiceManager);
+                }
+                break;
+
+            case mdns_event_update:
+                mdns_dns_service_manager_apply_pending_updates(manager);
+                mDNS_Lock(m);
+                Querier_ProcessDNSServiceChanges();
+                _Querier_LogDNSServices(manager);
+                mDNS_Unlock(m);
+                break;
+
+            case mdns_event_invalidated:
+                mdns_release(manager);
+                break;
+
+            default:
+                break;
+        }
+        KQueueUnlock("DNS Service Manager event handler");
+    });
+    sDNSServiceManager = manager;
+    mdns_retain(sDNSServiceManager);
+    mdns_dns_service_manager_activate(sDNSServiceManager);
+    return sDNSServiceManager;
+}
+
+mDNSlocal mdns_dns_service_t _Querier_GetDNSService(const DNSQuestion *q)
+{
+    mdns_dns_service_t service;
+    const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
+    if (!manager)
+    {
+        return NULL;
+    }
+    if (!uuid_is_null(q->ResolverUUID))
+    {
+        service = mdns_dns_service_manager_get_uuid_scoped_service(manager, q->ResolverUUID);
+    }
+    else if (q->InterfaceID)
+    {
+        const uint32_t ifIndex = (uint32_t)((uintptr_t)q->InterfaceID);
+        service = mdns_dns_service_manager_get_interface_scoped_service(manager, q->qname.c, ifIndex);
+    }
+    else if (q->ServiceID >= 0)
+    {
+        service = mdns_dns_service_manager_get_service_scoped_service(manager, q->qname.c, (uint32_t)q->ServiceID);
+    }
+    else
+    {
+        // Check for a matching discovered resolver for unscoped queries
+        uuid_t discoveredResolverUUID = {};
+        if (mdns_dns_service_manager_fillout_discovered_service_for_name(manager, q->qname.c, discoveredResolverUUID))
+        {
+            service = mdns_dns_service_manager_get_uuid_scoped_service(manager, discoveredResolverUUID);
+        }
+        else
+        {
+            service = mdns_dns_service_manager_get_unscoped_service(manager, q->qname.c);
+        }
+    }
+    if (service && !mdns_dns_service_interface_is_vpn(service))
+    {
+        // Check for encryption, and if the service isn't encrypted, fallback or fail
+        const mDNSBool lacksRequiredEncryption = q->RequireEncryption && !mdns_dns_service_is_encrypted(service);
+        if (lacksRequiredEncryption || mdns_dns_service_has_connection_problems(service))
+        {
+            if (lacksRequiredEncryption)
+            {
+                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+                    "[R%u->Q%u] DNS service %llu lacks required encryption",
+                     q->request_id, mDNSVal16(q->TargetQID), mdns_dns_service_get_id(service));
+                service = NULL;
+            }
+            // Check for a fallback service
+            if (q->CustomID != 0)
+            {
+                service = mdns_dns_service_manager_get_custom_service(manager, q->CustomID);
+            }
+        }
+    }
+    return service;
+}
+
+mDNSlocal pid_t _Querier_GetMyPID(void)
+{
+    static dispatch_once_t sOnce = 0;
+    static pid_t sPID = 0;
+    dispatch_once(&sOnce,
+    ^{
+        sPID = getpid();
+    });
+    return sPID;
+}
+
+mDNSlocal const mDNSu8 *_Querier_GetMyUUID(void)
+{
+    static dispatch_once_t sOnce = 0;
+    static mDNSu8 sUUID[16];
+    dispatch_once(&sOnce,
+    ^{
+        uuid_clear(sUUID);
+        struct proc_uniqidentifierinfo info;
+        const int n = proc_pidinfo(_Querier_GetMyPID(), PROC_PIDUNIQIDENTIFIERINFO, 1, &info, sizeof(info));
+        if (n == (int)sizeof(info))
+        {
+            uuid_copy(sUUID, info.p_uuid);
+        }
+    });
+    return sUUID;
+}
+
+mDNSlocal mDNSBool _Querier_QuestionBelongsToSelf(const DNSQuestion *q)
+{
+    if (q->pid != 0)
+    {
+        return ((q->pid == _Querier_GetMyPID()) ? mDNStrue : mDNSfalse);
+    }
+    else
+    {
+        return ((uuid_compare(q->uuid, _Querier_GetMyUUID()) == 0) ? mDNStrue : mDNSfalse);
+    }
+}
+
+mDNSlocal mDNSBool _Querier_DNSServiceIsUnscopedAndLacksPrivacy(const mdns_dns_service_t service)
+{
+    if ((mdns_dns_service_get_scope(service) == mdns_dns_service_scope_none) &&
+        !mdns_dns_service_is_encrypted(service) && !mdns_dns_service_interface_is_vpn(service))
+    {
+        return mDNStrue;
+    }
+    else
+    {
+        return mDNSfalse;
+    }
+}
+
+#define kQuerierLogFullDNSServicePeriodSecs 30
+
+mDNSlocal mDNSBool _Querier_ShouldLogFullDNSService(const mdns_dns_service_t service)
+{
+    uint64_t *lastFullLogTicks = (uint64_t *)mdns_dns_service_get_context(service);
+    if (lastFullLogTicks)
+    {
+        const uint64_t nowTicks = mach_continuous_time();
+        const uint64_t diffTicks = nowTicks - *lastFullLogTicks;
+        if ((diffTicks / mdns_mach_ticks_per_second()) < kQuerierLogFullDNSServicePeriodSecs)
+        {
+            return mDNSfalse;
+        }
+        *lastFullLogTicks = nowTicks;
+    }
+    else
+    {
+        lastFullLogTicks = (uint64_t *)malloc(sizeof(*lastFullLogTicks));
+        if (lastFullLogTicks)
+        {
+            *lastFullLogTicks = mach_continuous_time();
+            mdns_dns_service_set_context(service, lastFullLogTicks);
+            mdns_dns_service_set_context_finalizer(service, free);
+        }
+    }
+    return mDNStrue;
+}
+
+mDNSexport void Querier_SetDNSServiceForQuestion(DNSQuestion *q)
+{
+    // Thus far, UUID-scoped DNS services may be specified without any server IP addresses, just a hostname. In such a
+    // case, the underlying nw_connection will need to resolve the DNS service's hostname. To avoid potential dependency
+    // cycles because of mDNSResponder issuing GetAddrInfo requests to itself, we simply prevent DNSQuestions with
+    // mDNSResponder's PID or Mach-O UUID from using UUID-scoped DNS services.
+    if (!uuid_is_null(q->ResolverUUID) && _Querier_QuestionBelongsToSelf(q))
+    {
+        uuid_clear(q->ResolverUUID);
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+            "[R%u->Q%u] Cleared resolver UUID for mDNSResponder's own question: " PRI_DM_NAME " (" PUB_S ")",
+            q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype));
+    }
+    mdns_forget(&q->dnsservice);
+    mDNSBool retryPathEval = mDNSfalse;
+    mdns_dns_service_t service = _Querier_GetDNSService(q);
+    if (service)
+    {
+        // If path evaluation for the original QNAME was done by the client, but a CNAME restart has lead us to use a
+        // DNS service that isn't identical to the previous DNS service, and the DNS service is unscoped and lacks
+        // privacy, then retry path evaluation. A path evaluation with the new QNAME may result in using a DNS service
+        // that offers privacy.
+        if ((q->flags & kDNSServiceFlagsPathEvaluationDone) &&
+            (q->lastDNSServiceID != 0) && (mdns_dns_service_get_id(service) != q->lastDNSServiceID) &&
+            _Querier_DNSServiceIsUnscopedAndLacksPrivacy(service))
+        {
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
+                "[R%u->Q%u] Retrying path evaluation for " PRI_DM_NAME " (" PUB_S ") to avoid non-private DNS service",
+                q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype));
+            retryPathEval = mDNStrue;
+        }
+    }
+    else if (!uuid_is_null(q->ResolverUUID))
+    {
+        // If the ResolverUUID is not null, but we didn't get a DNS service, then the ResolverUUID may be stale, i.e.,
+        // the resolver configuration with that UUID may have been deleted, so retry path evaluation.
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+            "[R%u->Q%u] Retrying path evaluation for " PRI_DM_NAME " (" PUB_S ") because ResolverUUID may be stale",
+            q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype));
+        retryPathEval = mDNStrue;
+    }
+    if (retryPathEval)
+    {
+        mDNSPlatformGetDNSRoutePolicy(q);
+        service = _Querier_GetDNSService(q);
+    }
+    q->dnsservice = service;
+    mdns_retain_null_safe(q->dnsservice);
+    if (!q->dnsservice || _Querier_ShouldLogFullDNSService(q->dnsservice))
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+            "[R%u->Q%u] Question for " PRI_DM_NAME " (" PUB_S ") assigned DNS service -- %@",
+            q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), q->dnsservice);
+    }
+    else
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+            "[R%u->Q%u] Question for " PRI_DM_NAME " (" PUB_S ") assigned DNS service %llu",
+            q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype),
+            mdns_dns_service_get_id(q->dnsservice));
+    }
+}
+
+mDNSexport void Querier_RegisterPathResolver(const uuid_t resolverUUID)
+{
+    const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
+    if (manager)
+    {
+        mdns_dns_service_manager_register_path_resolver(manager, resolverUUID);
+    }
+}
+
+mDNSexport mdns_dns_service_id_t Querier_RegisterCustomDNSService(const xpc_object_t resolverConfigDict)
+{
+    mdns_dns_service_id_t ident = 0;
+    const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
+    if (manager)
+    {
+        ident = mdns_dns_service_manager_register_custom_service(manager, resolverConfigDict);
+    }
+    return ident;
+}
+
+mDNSexport mdns_dns_service_id_t Querier_RegisterCustomDNSServiceWithPListData(const uint8_t *dataPtr, size_t dataLen)
+{
+    mdns_dns_service_id_t ident = 0;
+    const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
+    if (manager)
+    {
+        xpc_object_t resolverConfigDict = mdns_xpc_create_dictionary_from_plist_data(dataPtr, dataLen, NULL);
+        if (resolverConfigDict)
+        {
+            ident = mdns_dns_service_manager_register_custom_service(manager, resolverConfigDict);
+            xpc_release(resolverConfigDict);
+        }
+    }
+    return ident;
+}
+
+mDNSexport void Querier_DeregisterCustomDNSService(const mdns_dns_service_id_t ident)
+{
+    const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
+    if (manager)
+    {
+        mdns_dns_service_manager_deregister_custom_service(manager, ident);
+    }
+}
+
+mDNSexport void Querier_RegisterDoHURI(const char *doh_uri, const char *domain)
+{
+    const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
+    if (manager)
+    {
+        mdns_dns_service_manager_register_doh_uri(manager, doh_uri, domain);
+    }
+}
+
+mDNSexport void Querier_ApplyDNSConfig(const dns_config_t *config)
+{
+    const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
+    if (manager)
+    {
+        mdns_dns_service_manager_apply_dns_config(manager, config);
+        _Querier_LogDNSServices(manager);
+    }
+}
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
+mDNSlocal void _Querier_UpdateQuestionMetrics(DNSQuestion *const q)
+{
+    if (q->querier && (mdns_querier_get_resolver_type(q->querier) == mdns_resolver_type_normal))
+    {
+        q->metrics.querySendCount += mdns_querier_get_send_count(q->querier);
+        if (q->metrics.dnsOverTCPState == DNSOverTCP_None)
+        {
+            switch (mdns_querier_get_over_tcp_reason(q->querier))
+            {
+                case mdns_query_over_tcp_reason_truncation:
+                    q->metrics.dnsOverTCPState = DNSOverTCP_Truncated;
+                    break;
+
+                case mdns_query_over_tcp_reason_got_suspicious_reply:
+                    q->metrics.dnsOverTCPState = DNSOverTCP_Suspicious;
+                    break;
+
+                case mdns_query_over_tcp_reason_in_suspicious_mode:
+                    q->metrics.dnsOverTCPState = DNSOverTCP_SuspiciousDefense;
+                    break;
+
+                default:
+                    break;
+            }
+        }
+    }
+}
+
+mDNSlocal void _Querier_UpdateDNSMessageSizeMetrics(const mdns_querier_t querier)
+{
+    if (mdns_querier_get_resolver_type(querier) == mdns_resolver_type_normal)
+    {
+        if (mdns_querier_get_send_count(querier) > 0)
+        {
+            const mDNSu32 len = mdns_querier_get_query_length(querier);
+            if (len > 0)
+            {
+                MetricsUpdateDNSQuerySize(len);
+            }
+        }
+        if ((mdns_querier_get_result_type(querier) == mdns_querier_result_type_response) &&
+            !mdns_querier_response_is_fabricated(querier))
+        {
+            const mDNSu32 len = mdns_querier_get_response_length(querier);
+            if (len > 0)
+            {
+                MetricsUpdateDNSResponseSize(len);
+            }
+        }
+    }
+}
+#endif
+
+mDNSlocal mdns_set_t _Querier_GetOrphanedQuerierSet(void)
+{
+    static mdns_set_t sOrphanedQuerierSet = NULL;
+    if (!sOrphanedQuerierSet)
+    {
+        sOrphanedQuerierSet = mdns_set_create();
+    }
+    return sOrphanedQuerierSet;
+}
+
+mDNSlocal void _Querier_HandleQuerierResponse(const mdns_querier_t querier, const mdns_dns_service_t dnsservice)
+{
+    KQueueLock();
+    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+        "[Q%u] Handling concluded querier: %@", mdns_querier_get_user_id(querier), querier);
+#if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
+    _Querier_UpdateDNSMessageSizeMetrics(querier);
+#endif
+    const mdns_querier_result_type_t resultType = mdns_querier_get_result_type(querier);
+    if (resultType == mdns_querier_result_type_response)
+    {
+        mDNS *const m = &mDNSStorage;
+        if (!mdns_dns_service_is_defunct(dnsservice))
+        {
+            size_t copyLen = mdns_querier_get_response_length(querier);
+            if (copyLen > sizeof(m->imsg.m))
+            {
+                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+                    "[Q%u] Large %lu-byte response will be truncated to fit mDNSCore's %lu-byte message buffer",
+                    mdns_querier_get_user_id(querier), (unsigned long)copyLen, (unsigned long)sizeof(m->imsg.m));
+                copyLen = sizeof(m->imsg.m);
+            }
+            memcpy(&m->imsg.m, mdns_querier_get_response_ptr(querier), copyLen);
+            const mDNSu8 *const end = ((mDNSu8 *)&m->imsg.m) + copyLen;
+            mDNSCoreReceiveForQuerier(m, &m->imsg.m, end, querier, dnsservice);
+        }
+    }
+    const mdns_set_t set = _Querier_GetOrphanedQuerierSet();
+    if (set)
+    {
+        mdns_set_remove(set, (uintptr_t)dnsservice, querier);
+    }
+    DNSQuestion *const q = Querier_GetDNSQuestion(querier);
+    if (q)
+    {
+#if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
+        _Querier_UpdateQuestionMetrics(q);
+#endif
+        mdns_forget(&q->querier);
+        // If the querier timed out, then the DNSQuestion was using an orphaned querier.
+        // Querier_HandleUnicastQuestion() will attempt to give it a new querier.
+        if (resultType == mdns_querier_result_type_timeout)
+        {
+            Querier_HandleUnicastQuestion(q);
+        }
+        else if (resultType == mdns_querier_result_type_error)
+        {
+            // The querier encountered a fatal error, which should be rare. There's nothing we can do but try again.
+            // This usually happens if there's resource exhaustion, so be conservative and wait five seconds before
+            // trying again.
+            mDNS *const m = &mDNSStorage;
+            q->ThisQInterval = 5 * mDNSPlatformOneSecond;
+            q->LastQTime = m->timenow;
+            SetNextQueryTime(m, q);
+        }
+    }
+    KQueueUnlock("_Querier_HandleQuerierResponse");
+}
+
+mDNSexport void Querier_HandleUnicastQuestion(DNSQuestion *q)
+{
+    mDNS *const m = &mDNSStorage;
+    mdns_querier_t querier = NULL;
+    if (!q->dnsservice || q->querier) goto exit;
+
+    const mdns_set_t set = _Querier_GetOrphanedQuerierSet();
+    if (set)
+    {
+        __block mdns_querier_t orphan = NULL;
+        mdns_set_iterate(set, (uintptr_t)q->dnsservice,
+        ^ bool (mdns_object_t _Nonnull object)
+        {
+            const mdns_querier_t candidate = (mdns_querier_t)object;
+            if (mdns_querier_match(candidate, q->qname.c, q->qtype, q->qclass))
+            {
+                orphan = candidate;
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        });
+        if (orphan)
+        {
+            q->querier = orphan;
+            mdns_retain(q->querier);
+            mdns_set_remove(set, (uintptr_t)q->dnsservice, q->querier);
+            mdns_querier_set_time_limit_ms(q->querier, 0);
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+                "[Q%u->Q%u] Adopted orphaned querier", mDNSVal16(q->TargetQID), mdns_querier_get_user_id(q->querier));
+        }
+    }
+    if (!q->querier)
+    {
+        querier = mdns_dns_service_create_querier(q->dnsservice, NULL);
+        require_quiet(querier, exit);
+
+        const OSStatus err = mdns_querier_set_query(querier, q->qname.c, q->qtype, q->qclass);
+        require_noerr_quiet(err, exit);
+
+        q->querier = querier;
+        mdns_retain(q->querier);
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+        if (q->DNSSECStatus.enable_dnssec)
+        {
+            mdns_querier_set_dnssec_ok(querier, true);
+            mdns_querier_set_checking_disabled(querier, true);
+        }
+#endif
+
+        if (q->pid != 0)
+        {
+            mdns_querier_set_delegator_pid(q->querier, q->pid);
+        }
+        else
+        {
+            mdns_querier_set_delegator_uuid(q->querier, q->uuid);
+        }
+        mdns_querier_set_queue(querier, dispatch_get_main_queue());
+        mdns_retain(querier);
+        const mdns_dns_service_t dnsservice = q->dnsservice;
+        mdns_retain(dnsservice);
+        mdns_querier_set_result_handler(querier,
+        ^{
+            _Querier_HandleQuerierResponse(querier, dnsservice);
+            mdns_release(querier);
+            mdns_release(dnsservice);
+        });
+        mdns_querier_set_log_label(querier, "Q%u", mDNSVal16(q->TargetQID));
+        mdns_querier_set_user_id(querier, mDNSVal16(q->TargetQID));
+        mdns_querier_activate(querier);
+    }
+#if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
+    if (mdns_querier_get_resolver_type(q->querier) == mdns_resolver_type_normal)
+    {
+        if (q->metrics.answered)
+        {
+            uDNSMetricsClear(&q->metrics);
+        }
+        if (q->metrics.firstQueryTime == 0)
+        {
+            q->metrics.firstQueryTime = NonZeroTime(m->timenow);
+        }
+    }
+    else
+    {
+        q->metrics.firstQueryTime = 0;
+    }
+#endif
+
+exit:
+    q->ThisQInterval = q->querier ? MaxQuestionInterval : mDNSPlatformOneSecond;
+    q->LastQTime = m->timenow;
+    SetNextQueryTime(m, q);
+    mdns_release_null_safe(querier);
+}
+
+mDNSexport void Querier_ProcessDNSServiceChanges(void)
+{
+    mDNS *const m = &mDNSStorage;
+    DNSQuestion *q;
+    DNSQuestion *restartList = NULL;
+    DNSQuestion **ptr = &restartList;
+    mDNSu32 slot;
+    CacheGroup *cg;
+    CacheRecord *cr;
+#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
+    DNSPushNotificationServer **psp;
+#endif
+
+    m->RestartQuestion = m->Questions;
+    while ((q = m->RestartQuestion) != mDNSNULL)
+    {
+        if (mDNSOpaque16IsZero(q->TargetQID))
+        {
+            m->RestartQuestion = q->next;
+            continue;
+        }
+        mdns_dns_service_t newService = _Querier_GetDNSService(q);
+        mDNSBool forcePathEval = mDNSfalse;
+        if (q->dnsservice != newService)
+        {
+            // If the DNS service would change, but there is no new DNS service or it's unscoped and lacks privacy,
+            // force a path evaluation when the DNSQuestion restarts to determine if there's a DNS service that offers
+            // privacy that should be used. This DNSQuestion might have been unscoped so that it can use a VPN DNS
+            // service, but that service may be defunct now.
+            if (!newService || _Querier_DNSServiceIsUnscopedAndLacksPrivacy(newService))
+            {
+                forcePathEval = mDNStrue;
+            }
+        }
+        else
+        {
+            // If the DNS service wouldn't change and the DNS service is UUID-scoped, perform a path evaluation now to
+            // see if a DNS service change occurs. This might happen if a DNSQuestion was UUID-scoped to a DoH or DoT
+            // service, but there's a new VPN DNS service that handles the DNSQuestion's QNAME.
+            if (q->dnsservice && (mdns_dns_service_get_scope(q->dnsservice) == mdns_dns_service_scope_uuid))
+            {
+                mDNSPlatformGetDNSRoutePolicy(q);
+                newService = _Querier_GetDNSService(q);
+            }
+        }
+        mDNSBool restart = mDNSfalse;
+        if (q->dnsservice != newService)
+        {
+#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
+            // If this question had a DNS Push server associated with it, substitute the new server for the
+            // old one.   If there is no new server, then we'll clean up the push server later.
+            if (!q->DuplicateOf && q->dnsPushServer)
+            {
+                if (q->dnsPushServer->dnsservice == q->dnsservice)
+                {
+                    mdns_replace(&q->dnsPushServer->dnsservice, newService);
+                }
+                // If it is null, do the accounting and drop the push server.
+                if (!q->dnsPushServer->dnsservice)
+                {
+                    DNSPushReconcileConnection(m, q);
+                }
+            }
+#endif
+            restart = mDNStrue;
+        }
+        else
+        {
+            mDNSBool newSuppressed = ShouldSuppressUnicastQuery(q, newService);
+            if (!q->Suppressed != !newSuppressed) restart = mDNStrue;
+        }
+        if (restart)
+        {
+            if (!q->Suppressed)
+            {
+                CacheRecordRmvEventsForQuestion(m, q);
+                if (m->RestartQuestion == q) LocalRecordRmvEventsForQuestion(m, q);
+            }
+            if (m->RestartQuestion == q)
+            {
+                mDNS_StopQuery_internal(m, q);
+                q->ForcePathEval = forcePathEval;
+                q->next = mDNSNULL;
+                *ptr = q;
+                ptr = &q->next;
+            }
+        }
+        if (m->RestartQuestion == q) m->RestartQuestion = q->next;
+    }
+    while ((q = restartList) != mDNSNULL)
+    {
+        restartList = q->next;
+        q->next = mDNSNULL;
+        mDNS_StartQuery_internal(m, q);
+    }
+#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
+    // The above code may have found some DNS Push servers that are no longer valid.   Now that we
+    // are done running through the code, we need to drop our connections to those servers.
+    // When we get here, any such servers should have zero questions associated with them.
+    for (psp = &m->DNSPushServers; *psp != mDNSNULL; )
+    {
+        DNSPushNotificationServer *server = *psp;
+
+        // It's possible that a push server whose DNS server has been deleted could be still connected but
+        // not referenced by any questions.  In this case, we just delete the push server rather than trying
+        // to figure out with which DNS server (if any) to associate it.
+        if (server->dnsservice && mdns_dns_service_is_defunct(server->dnsservice))
+        {
+            mdns_forget(&server->dnsservice);
+        }
+        if (!server->dnsservice)
+        {
+            // This would be a programming error, so should never happen.
+            if (server->numberOfQuestions != 0)
+            {
+                LogInfo("uDNS_SetupDNSConfig: deleting push server %##s that has questions.", &server->serverName);
+            }
+            DNSPushServerDrop(server);
+            *psp = server->next;
+            mDNSPlatformMemFree(server);
+        }
+        else
+        {
+            psp = &(*psp)->next;
+        }
+    }
+#endif
+    FORALL_CACHERECORDS(slot, cg, cr)
+    {
+        if (cr->resrec.InterfaceID) continue;
+        if (!cr->resrec.dnsservice || mdns_dns_service_is_defunct(cr->resrec.dnsservice))
+        {
+            mdns_forget(&cr->resrec.dnsservice);
+            mDNS_PurgeCacheResourceRecord(m, cr);
+        }
+    }
+}
+
+mDNSexport DNSQuestion *Querier_GetDNSQuestion(const mdns_querier_t querier)
+{
+    DNSQuestion *q;
+    for (q = mDNSStorage.Questions; q; q = q->next)
+    {
+        if (q->querier == querier)
+        {
+            return q;
+        }
+    }
+    return mDNSNULL;
+}
+
+mDNSexport mDNSBool Querier_ResourceRecordIsAnswer(const ResourceRecord * const rr, const mdns_querier_t querier)
+{
+    const mDNSu16 qtype = mdns_querier_get_qtype(querier);
+    const mDNSu8 *const qname = mdns_querier_get_qname(querier);
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    const mDNSBool enableDNSSEC = mdns_querier_get_dnssec_ok(querier);
+#endif
+
+
+    if ((RRTypeAnswersQuestionType(rr, qtype)
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+         || (enableDNSSEC && record_type_answers_dnssec_question(rr, qtype))
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+         )
+        && (rr->rrclass == mdns_querier_get_qclass(querier)) &&
+        qname && SameDomainName(rr->name, (const domainname *)qname))
+    {
+        return mDNStrue;
+    }
+    else
+    {
+        return mDNSfalse;
+    }
+}
+
+mDNSexport mDNSBool Querier_SameNameCacheRecordIsAnswer(const CacheRecord *const cr, const mdns_querier_t querier)
+{
+    const ResourceRecord *const rr = &cr->resrec;
+    const mDNSu16 qtype = mdns_querier_get_qtype(querier);
+    if (RRTypeAnswersQuestionType(rr, qtype) && (rr->rrclass == mdns_querier_get_qclass(querier)))
+    {
+        return mDNStrue;
+    }
+    else
+    {
+        return mDNSfalse;
+    }
+}
+
+#define kOrphanedQuerierTimeLimitSecs       5
+#define kOrphanedQuerierSubsetCountLimit    10
+
+mDNSexport void Querier_HandleStoppedDNSQuestion(DNSQuestion *q)
+{
+    if (q->querier && !mdns_querier_has_concluded(q->querier) &&
+        q->dnsservice && !mdns_dns_service_is_defunct(q->dnsservice))
+    {
+        const mdns_set_t set = _Querier_GetOrphanedQuerierSet();
+        const uintptr_t subsetID = (uintptr_t)q->dnsservice;
+        if (set && (mdns_set_get_count(set, subsetID) < kOrphanedQuerierSubsetCountLimit))
+        {
+            const OSStatus err = mdns_set_add(set, subsetID, q->querier);
+            if (!err)
+            {
+                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+                    "[Q%u] Keeping orphaned querier for up to " StringifyExpansion(kOrphanedQuerierTimeLimitSecs) " seconds",
+                    mdns_querier_get_user_id(q->querier));
+                mdns_querier_set_time_limit_ms(q->querier, kOrphanedQuerierTimeLimitSecs * 1000);
+                mdns_forget(&q->querier);
+            }
+        }
+    }
+    mdns_querier_forget(&q->querier);
+    mdns_forget(&q->dnsservice);
+}
+
+mDNSexport void Querier_PrepareQuestionForCNAMERestart(DNSQuestion *const q)
+{
+    q->lastDNSServiceID = q->dnsservice ? mdns_dns_service_get_id(q->dnsservice) : MDNS_DNS_SERVICE_MAX_ID;
+#if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
+    _Querier_UpdateQuestionMetrics(q);
+#endif
+}
+
+mDNSexport void Querier_PrepareQuestionForUnwindRestart(DNSQuestion *const q)
+{
+    q->lastDNSServiceID = 0;
+    q->ForcePathEval    = mDNStrue;
+}
+
+mDNSexport void Querier_HandleSleep(void)
+{
+    const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
+    if (manager)
+    {
+        mdns_dns_service_manager_handle_sleep(manager);
+    }
+}
+
+mDNSexport void Querier_HandleWake(void)
+{
+    const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
+    if (manager)
+    {
+        mdns_dns_service_manager_handle_wake(manager);
+    }
+}
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
diff --git a/mDNSMacOSX/QuerierSupport.h b/mDNSMacOSX/QuerierSupport.h
new file mode 100644 (file)
index 0000000..6c0c7ae
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __QUERIER_SUPPORT_H__
+#define __QUERIER_SUPPORT_H__
+
+#include "mDNSEmbeddedAPI.h"
+#include "mdns_private.h"
+
+// Threshold value for problematic QTYPE workaround.
+extern int PQWorkaroundThreshold;
+
+extern mdns_dns_service_manager_t Querier_GetDNSServiceManager(void);
+extern void Querier_SetDNSServiceForQuestion(DNSQuestion *q);
+extern void Querier_ApplyDNSConfig(const dns_config_t *config);
+extern void Querier_HandleUnicastQuestion(DNSQuestion *q);
+extern void Querier_ProcessDNSServiceChanges(void);
+extern void Querier_RegisterPathResolver(const uuid_t resolverUUID);
+extern mdns_dns_service_id_t Querier_RegisterCustomDNSService(xpc_object_t resolverConfigDict);
+extern mdns_dns_service_id_t Querier_RegisterCustomDNSServiceWithPListData(const uint8_t *dataPtr, size_t dataLen);
+extern void Querier_DeregisterCustomDNSService(mdns_dns_service_id_t ident);
+extern DNSQuestion *Querier_GetDNSQuestion(mdns_querier_t querier);
+extern mDNSBool Querier_ResourceRecordIsAnswer(const ResourceRecord *rr, mdns_querier_t querier);
+extern mDNSBool Querier_SameNameCacheRecordIsAnswer(const CacheRecord *cr, mdns_querier_t querier);
+extern void Querier_HandleStoppedDNSQuestion(DNSQuestion *q);
+extern void Querier_RegisterDoHURI(const char *doh_uri, const char *domain);
+extern void Querier_PrepareQuestionForCNAMERestart(DNSQuestion *q);
+extern void Querier_PrepareQuestionForUnwindRestart(DNSQuestion *q);
+extern void Querier_HandleSleep(void);
+extern void Querier_HandleWake(void);
+
+#endif  // __QUERIER_SUPPORT_H__
index af226760f9bd49387bc448a4c970e6b09b4ce6ca..eefffabeb8418eea55df62ecb38c1e51f2e64913 100755 (executable)
@@ -1,11 +1,11 @@
 #! /bin/bash
 #
-#      Copyright (c) 2017-2019 Apple Inc. All rights reserved.
+#      Copyright (c) 2017-2020 Apple Inc. All rights reserved.
 #
 #      This script is currently for Apple Internal use only.
 #
 
-declare -r version=1.6
+declare -r version=1.8
 declare -r script=${BASH_SOURCE[0]}
 declare -r dnssdutil=${dnssdutil:-dnssdutil}
 
@@ -244,7 +244,7 @@ RunInterfaceMulticastTests()
 RunMulticastTests()
 {
        local -r interfaces=( $( ifconfig -l -u ) )
-       local -r skipPrefixes=( ap awdl bridge ipsec llw p2p pdp_ip pktap UDC utun )
+       local -r skipPrefixes=( ap awdl bridge ipsec llw nan p2p pdp_ip pktap UDC utun )
        local -a pids
        local ifname
        local skip
@@ -303,30 +303,24 @@ RunBrowseTest()
        "${dnssdutil}" browseAll -A -d local -b 10 -c 10 &> "${workPath}/browseAll.txt"
 }
 
-#============================================================================================================================
-#      IsMacOS
-#============================================================================================================================
-
-IsMacOS()
-{
-       [[ $( sw_vers -productName ) =~ ^Mac\ OS ]]
-}
-
 #============================================================================================================================
 #      ArchiveLogs
 #============================================================================================================================
 
 ArchiveLogs()
 {
+       local parentDir=''
+       # First, check for the non-macOS sysdiagnose archive path, then check for the macOS sysdiagnose archive path.
+       for dir in '/var/mobile/Library/Logs/CrashReporter' '/var/tmp'; do
+               if [ -w "${dir}" ]; then
+                       parentDir="${dir}"
+                       break
+               fi
+       done
+       # If a writable path wasn't available, just use /tmp.
+       [ -n "${parentDir}" ] || parentDir='/tmp'
        local -r workdir=$( basename "${workPath}" )
-       local parentDir
-       if IsMacOS; then
-               parentDir=/var/tmp
-       else
-               parentDir=/var/mobile/Library/Logs/CrashReporter
-       fi
        local -r archivePath="${parentDir}/${workdir}.tar.gz"
-       
        LogMsg "Archiving logs."
        echo "---"
        tar -C "${tempPath}" -czf "${archivePath}" "${workdir}"
@@ -334,7 +328,7 @@ ArchiveLogs()
                echo "Created log archive at ${archivePath}"
                echo "*** Please run sysdiagnose NOW. ***"
                echo "Attach both the log archive and the sysdiagnose archive to the radar."
-               if IsMacOS; then
+               if command -v open 2>&1 > /dev/null; then
                        open "${parentDir}"
                fi
        else
@@ -355,13 +349,13 @@ CreateWorkDirName()
                suffix+="_${productName}"
        fi
        
-       local model
-       if IsMacOS; then
-               model=$( sysctl -n hw.model )
-               model=${model//,/-}
+       local model=''
+       if command -v gestalt_query 2>&1 > /dev/null; then
+               model=$( gestalt_query -undecorated ProductType )
        else
-               model=$( gestalt_query -undecorated DeviceName )
+               model=$( sysctl -n hw.model )
        fi
+       model=${model//,/-}
        if [ -n "${model}" ]; then
                suffix+="_${model}"
        fi
@@ -411,13 +405,13 @@ main()
        
        [ "${OPTIND}" -gt "$#" ] || ErrQuit "unexpected argument \"${!OPTIND}\"."
        
-       if IsMacOS; then
-               if [ "${EUID}" -ne 0 ]; then
+       if [ "${EUID}" -ne 0 ]; then
+               if command -v sudo 2>&1 > /dev/null; then
                        echo "Re-launching with sudo"
                        exec sudo "${script}" "$@"
+               else
+                       ErrQuit "$( basename "${script}" ) needs to be run as root."
                fi
-       else
-               [ "${EUID}" -eq 0 ] || ErrQuit "$( basename "${script}" ) needs to be run as root."
        fi
        
        tempPath=$( mktemp -d -q ) || ErrQuit "Failed to make temp directory."
index 9cb08e54022790bc57a8a8c2cb5b6d11ba0e791f..7cd8a3e3b1a82c577d2b709cfc806226006b0431 100644 (file)
@@ -16,6 +16,7 @@
 
 #include "SymptomReporter.h"
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, SYMPTOMS) && !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 #include <arpa/inet.h>
 #include <dlfcn.h>
 #include <stddef.h>
@@ -175,3 +176,4 @@ mDNSexport mStatus SymptomReporterDNSServerUnreachable(DNSServer *s)
 exit:
     return err;
 }
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, SYMPTOMS)
index 5cde3282ccf8f4f62a192096355c47c5399fb938..75c3bdc067868c36ea87d095ad2dc5c4c221e556 100644 (file)
@@ -19,6 +19,7 @@
 
 #include "mDNSEmbeddedAPI.h"
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, SYMPTOMS) && !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 extern mDNSu32 NumUnreachableDNSServers;
 
 #ifdef  __cplusplus
@@ -32,4 +33,5 @@ extern mStatus SymptomReporterDNSServerUnreachable(DNSServer *s);
 }
 #endif
 
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, SYMPTOMS)
 #endif // __SymptomReporter_h
index f96d4adb1b9a8a0ec67497640f8381bccee59cfa..e5b5dc46d5c43df135bcac838d1c0bdc903ae24b 100644 (file)
@@ -175,12 +175,8 @@ static const mDNSv4Addr dns_response_ipv4 = {{ 10, 100, 0, 1 }};
     XCTAssertEqual(q->TimeoutQuestion, 0);
     XCTAssertEqual(q->WakeOnResolve, 0);
     XCTAssertEqual(q->UseBackgroundTraffic, 0);
-    XCTAssertEqual(q->ValidationRequired, 0);
-    XCTAssertEqual(q->ValidatingResponse, 0);
     XCTAssertEqual(q->ProxyQuestion, 0);
     XCTAssertNotEqual((void*)q->QuestionCallback, (void*)mDNSNULL);
-    XCTAssertNil((__bridge id)q->DNSSECAuthInfo);
-    XCTAssertNil((__bridge id)(void*)q->DAIFreeCallback);
     XCTAssertEqual(q->AppendSearchDomains, 0);
     XCTAssertNil((__bridge id)q->DuplicateOf);
     
index facd79b6baac9b39bf37e5db6cc4dc6fc3fdbcfd..50ddaa01b4a04a84df4751f604029c4c75e10c76 100644 (file)
@@ -208,12 +208,8 @@ char test_order_domainname_cstr[] = "web.mydomain.test.";
     XCTAssertEqual(q->TimeoutQuestion, 0);
     XCTAssertEqual(q->WakeOnResolve, 0);
     XCTAssertEqual(q->UseBackgroundTraffic, 0);
-    XCTAssertEqual(q->ValidationRequired, 0);
-    XCTAssertEqual(q->ValidatingResponse, 0);
     XCTAssertEqual(q->ProxyQuestion, 0);
     XCTAssertNotEqual((void*)q->QuestionCallback, (void*)mDNSNULL);
-    XCTAssertNil((__bridge id)q->DNSSECAuthInfo);
-    XCTAssertNil((__bridge id)(void*)q->DAIFreeCallback);
     XCTAssertEqual(q->AppendSearchDomains, 0);
     XCTAssertNil((__bridge id)q->DuplicateOf);
     
diff --git a/mDNSMacOSX/Tests/Unit Tests/DNSHeuristicsTest.m b/mDNSMacOSX/Tests/Unit Tests/DNSHeuristicsTest.m
new file mode 100644 (file)
index 0000000..8af2071
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "unittest_common.h"
+#import "DNSHeuristicsInternal.h"
+#import <XCTest/XCTest.h>
+#import <OCMock/OCMock.h>
+
+@interface DNSHeuristicsTest : XCTestCase
+@end
+
+@implementation DNSHeuristicsTest
+
+#if (TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST)
+- (void)testEmptyStateFailure {
+    id mockHeuristics = OCMClassMock([DNSHeuristics class]);
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(@{});
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+
+    NSURL *url = [NSURL URLWithString:@"https://example.com"];
+    XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
+    OCMVerify(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+}
+
+- (void)testStateFailureUnderThreshold {
+    id mockHeuristics = OCMClassMock([DNSHeuristics class]);
+    NSUInteger now = [DNSHeuristics currentTimeMs];
+    NSURL *url = [NSURL URLWithString:@"https://example.com"];
+    NSDictionary *existingState = @{
+        DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:now],
+        DNSHeuristicsLongCounterKey: [NSNumber numberWithInt:1],
+        DNSHeuristicsBurstCounterKey: [NSNumber numberWithInt:DNSHeuristicsDefaultBurstTokenBucketCapacity],
+        DNSHeuristicsFilterFlagKey: @(NO),
+    };
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(existingState);
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+
+    XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
+    OCMVerify(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+}
+
+- (void)testStateFailureOverThreshold {
+    id mockHeuristics = OCMClassMock([DNSHeuristics class]);
+    NSUInteger now = [DNSHeuristics currentTimeMs];
+    NSURL *url = [NSURL URLWithString:@"https://example.com"];
+    NSDictionary *existingState = @{
+        DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:now],
+        DNSHeuristicsLongCounterKey: [NSNumber numberWithInt:DNSHeuristicDefaultLongCounterThreshold], // reporting an error will cause this count to exceed the threshold
+        DNSHeuristicsBurstCounterKey: [NSNumber numberWithInt:DNSHeuristicsDefaultBurstTokenBucketCapacity],
+        DNSHeuristicsFilterFlagKey: @(NO),
+    };
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(existingState);
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+
+    XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
+    OCMVerify(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+}
+
+- (void)testStateFailureUnderThreshold_StickAfterFailure {
+    id mockHeuristics = OCMClassMock([DNSHeuristics class]);
+    NSUInteger now = [DNSHeuristics currentTimeMs];
+    NSURL *url = [NSURL URLWithString:@"https://example.com"];
+    NSDictionary *existingState = @{
+        DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:now],
+        DNSHeuristicsLongCounterKey: [NSNumber numberWithInt:DNSHeuristicDefaultLongCounterThreshold],
+        DNSHeuristicsBurstCounterKey: [NSNumber numberWithInt:DNSHeuristicsDefaultBurstTokenBucketCapacity],
+        DNSHeuristicsFilterFlagKey: @(YES),
+    };
+    OCMStub(ClassMethod([mockHeuristics getNetworkFilteredFlag:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(existingState);
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + DNSHeuristicDefaultLongCounterTimeWindow * 2); // two days pass, we should reset
+
+    XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
+    OCMReject(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+}
+
+- (void)testStateFailureUnderThreshold_ResetAfterSuccess {
+    id mockHeuristics = OCMClassMock([DNSHeuristics class]);
+    NSUInteger now = [DNSHeuristics currentTimeMs];
+    NSDictionary *existingState = @{
+        DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:now],
+        DNSHeuristicsLongCounterKey: [NSNumber numberWithInt:DNSHeuristicDefaultLongCounterThreshold],
+        DNSHeuristicsBurstCounterKey: [NSNumber numberWithInt:DNSHeuristicsDefaultBurstTokenBucketCapacity],
+        DNSHeuristicsFilterFlagKey: @(YES),
+    };
+    OCMStub(ClassMethod([mockHeuristics getNetworkFilteredFlag:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(existingState);
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + DNSHeuristicDefaultLongCounterTimeWindow * 2); // two days pass, we should reset
+
+    XCTAssertTrue([DNSHeuristics updateHeuristicState:YES isTimeout:NO]);
+    OCMVerify(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+}
+
+- (void)testStateFailureDrainTokenBucket_NoReset {
+    id mockHeuristics = OCMClassMock([DNSHeuristics class]);
+    NSUInteger now = [DNSHeuristics currentTimeMs];
+    NSURL *url = [NSURL URLWithString:@"https://example.com"];
+    NSDictionary *existingState = @{
+        DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:now],
+        DNSHeuristicsLongCounterKey: [NSNumber numberWithInt:0],
+        DNSHeuristicsBurstCounterKey: [NSNumber numberWithInt:1],
+        DNSHeuristicsFilterFlagKey: @(NO),
+    };
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(existingState);
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + 1); // within the same epoch -- overflow
+
+    XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
+    OCMReject(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+}
+
+- (void)testStateFailureDrainTokenBucket_Reset {
+    id mockHeuristics = OCMClassMock([DNSHeuristics class]);
+    NSUInteger now = [DNSHeuristics currentTimeMs];
+    NSURL *url = [NSURL URLWithString:@"https://example.com"];
+    NSDictionary *existingState = @{
+        DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:now],
+        DNSHeuristicsLongCounterKey: [NSNumber numberWithInt:0],
+        DNSHeuristicsBurstCounterKey: [NSNumber numberWithInt:1],
+        DNSHeuristicsFilterFlagKey: @(NO),
+    };
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(existingState);
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + DNSHeuristicsDefaultBurstTokenBucketRefillTime + 1); // allow the bucket to replenish
+
+    XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
+    OCMReject(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+}
+
+- (void)testStateFailureFilteredThenSuccessBeforeWindow {
+    id mockHeuristics = OCMClassMock([DNSHeuristics class]);
+    NSUInteger now = [DNSHeuristics currentTimeMs];
+    NSDictionary *existingState = @{
+        DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:now],
+        DNSHeuristicsLongCounterKey: [NSNumber numberWithInt:0],
+        DNSHeuristicsBurstCounterKey: [NSNumber numberWithInt:DNSHeuristicsDefaultBurstTokenBucketCapacity],
+        DNSHeuristicsFilterFlagKey: @(YES),
+    };
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(existingState);
+    OCMStub(ClassMethod([mockHeuristics getNetworkFilteredFlag:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + DNSHeuristicsDefaultBurstTokenBucketRefillTime + 1); // allow the bucket to replenish
+
+    XCTAssertTrue([DNSHeuristics updateHeuristicState:YES isTimeout:NO]);
+    OCMReject(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+}
+
+- (void)testStateFailureFilteredThenSuccessAfterWindow {
+    id mockHeuristics = OCMClassMock([DNSHeuristics class]);
+    NSUInteger now = [DNSHeuristics currentTimeMs];
+    NSDictionary *existingState = @{
+        DNSHeuristicsLastFailureTimestamp: [NSNumber numberWithUnsignedInteger:(now - DNSHeuristicDefaultLongCounterTimeWindow)],
+        DNSHeuristicsLongCounterKey: [NSNumber numberWithInt:0],
+        DNSHeuristicsBurstCounterKey: [NSNumber numberWithInt:DNSHeuristicsDefaultBurstTokenBucketCapacity],
+        DNSHeuristicsFilterFlagKey: @(YES),
+    };
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(existingState);
+    OCMStub(ClassMethod([mockHeuristics getNetworkFilteredFlag:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + DNSHeuristicsDefaultBurstTokenBucketRefillTime + 1); // allow the bucket to replenish
+
+    XCTAssertTrue([DNSHeuristics updateHeuristicState:YES isTimeout:NO]);
+    OCMReject(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+}
+
+#endif // TARGET_OS_IPHONE
+
+@end
diff --git a/mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Crypto/CanonicalMethodsTest.m b/mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Crypto/CanonicalMethodsTest.m
new file mode 100644 (file)
index 0000000..f1bd475
--- /dev/null
@@ -0,0 +1,79 @@
+//
+//     ValidationMethodsTest.m
+//     Tests
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#import <XCTest/XCTest.h>
+#include "dnssec_v2_crypto.h"
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
+@interface CanonicalMethodsTest : XCTestCase
+
+@end
+
+@implementation CanonicalMethodsTest
+
+- (void)test_copy_canonical_name {
+       mDNSu8 canonical_name[MAX_DOMAIN_NAME];
+       mDNSu8 name_length;
+
+       mDNSu8 name_1[MAX_DOMAIN_NAME] = {
+               3,
+               'w', 'W', 'w',
+               5,
+               'a', 'p', 'p', 'l', 'e',
+               3,
+               'c', 'O', 'm',
+               0
+       };
+       mDNSu8 name_1_after_conversion[MAX_DOMAIN_NAME] = {
+               3,
+               'w', 'w', 'w',
+               5,
+               'a', 'p', 'p', 'l', 'e',
+               3,
+               'c', 'o', 'm',
+               0
+       };
+
+       for (unsigned long i = 15; i < sizeof(name_1); i++) {
+               name_1[i] = 255;
+       }
+
+       // basic test
+       name_length = copy_canonical_name_ut(canonical_name, name_1);
+       XCTAssertEqual(name_length, DomainNameLength((domainname *)name_1_after_conversion));
+       XCTAssert(memcmp(canonical_name, name_1_after_conversion, name_length) == 0);
+}
+
+- (void)test_compare_canonical_dns_name {
+       const unsigned char inputs[][256] = {
+               {7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0},                                                                      // example.
+               {1, 'a', 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0},                                                      // a.example.
+               {8, 'y', 'l', 'j', 'k', 'j', 'l', 'j', 'k', 1, 'a', 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0},   // yljkjljk.a.example.
+               {1, 'Z', 1, 'a', 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0},                                      // Z.a.example.
+               {4, 'z', 'A', 'B', 'C', 1, 'a', 7, 'E', 'X', 'A', 'M', 'P', 'L', 'E', 0},       // zABC.a.EXAMPLE.
+               {1, 'z', 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0},                                                      // z.example.
+               {1,     1 , 1, 'z', 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0},                                   // \001.z.example.
+               {1, '*', 1, 'z', 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0},                                      // *.z.example
+               {1, 200, 1, 'z', 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0}                                       // \200.z.example.
+       };
+
+       for (size_t i = 0; i < sizeof(inputs) / 256; i++) {
+               for (size_t j = 0; j < sizeof(inputs) / 256; j++) {
+                       if (i < j) {
+                               XCTAssertTrue(compare_canonical_dns_name(inputs[i], inputs[j]) < 0);
+                       } else if (i > j) {
+                               XCTAssertTrue(compare_canonical_dns_name(inputs[i], inputs[j]) > 0);
+                       } else { // i == j
+                               XCTAssertTrue(compare_canonical_dns_name(inputs[i], inputs[j]) == 0);
+                       }
+               }
+       }
+}
+
+@end
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
diff --git a/mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Crypto/DigestCalculationTest.m b/mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Crypto/DigestCalculationTest.m
new file mode 100644 (file)
index 0000000..93dfdae
--- /dev/null
@@ -0,0 +1,146 @@
+//
+//     DigestCalculationTest.m
+//     Tests
+//
+//     Created by Joey Deng on 1/28/20.
+//
+
+#import <XCTest/XCTest.h>
+#import "dnssec_v2_crypto.h"
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
+@interface DigestCalculationTest : XCTestCase
+
+@end
+
+@implementation DigestCalculationTest
+
+- (void)testSHA1 {
+       char * const inputs[] = {
+               "abc",
+               "",
+               "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+               "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+               "one million repetitions of 'a'"
+       };
+
+       unsigned char outputs[][SHA1_OUTPUT_SIZE] = {
+               {0xa9, 0x99, 0x3e, 0x36, 0x47, 0x06, 0x81, 0x6a, 0xba, 0x3e, 0x25, 0x71, 0x78, 0x50, 0xc2, 0x6c, 0x9c, 0xd0, 0xd8, 0x9d},
+               {0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09},
+               {0x84, 0x98, 0x3e, 0x44, 0x1c, 0x3b, 0xd2, 0x6e, 0xba, 0xae, 0x4a, 0xa1, 0xf9, 0x51, 0x29, 0xe5, 0xe5, 0x46, 0x70, 0xf1},
+               {0xa4, 0x9b, 0x24, 0x46, 0xa0, 0x2c, 0x64, 0x5b, 0xf4, 0x19, 0xf9, 0x95, 0xb6, 0x70, 0x91, 0x25, 0x3a, 0x04, 0xa2, 0x59},
+               {0x34, 0xaa, 0x97, 0x3c, 0xd4, 0xc4, 0xda, 0xa4, 0xf6, 0x1e, 0xeb, 0x2b, 0xdb, 0xad, 0x27, 0x31, 0x65, 0x34, 0x01, 0x6f}
+       };
+       unsigned char   actual_output[SHA1_OUTPUT_SIZE];
+
+       XCTAssertEqual(sizeof(inputs) / sizeof(char *), sizeof(outputs) / SHA1_OUTPUT_SIZE);
+
+       for (int i = 0, limit = sizeof(inputs) / sizeof(char *); i < limit; i++) {
+               char *                                          input;
+               const unsigned char * const expected_output = outputs[i];
+
+               if (i < 4) {
+                       input = inputs[i];
+               } else if (i == 4) {
+                       input = malloc(1000001);
+                       memset(input, 'a', 1000000);
+                       input[1000000] = '\0';
+               } else {
+                       XCTAssertTrue(0);
+               }
+
+               uint8_t calculated = calculate_digest_for_data((const unsigned char *)input, strlen(input), DIGEST_SHA_1,
+                       actual_output, sizeof(actual_output));
+               XCTAssertTrue(calculated);
+
+               XCTAssertTrue(memcmp(actual_output, expected_output, SHA1_OUTPUT_SIZE) == 0);
+       }
+}
+
+- (void)testSHA256 {
+       char * const inputs[] = {
+               "abc",
+               "",
+               "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+               "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+               "one million repetitions of 'a'"
+       };
+
+       unsigned char outputs[][SHA256_OUTPUT_SIZE] = {
+               {0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad},
+               {0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55},
+               {0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8, 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39, 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67, 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1},
+               {0xcf, 0x5b, 0x16, 0xa7, 0x78, 0xaf, 0x83, 0x80, 0x03, 0x6c, 0xe5, 0x9e, 0x7b, 0x04, 0x92, 0x37, 0x0b, 0x24, 0x9b, 0x11, 0xe8, 0xf0, 0x7a, 0x51, 0xaf, 0xac, 0x45, 0x03, 0x7a, 0xfe, 0xe9, 0xd1},
+               {0xcd, 0xc7, 0x6e, 0x5c, 0x99, 0x14, 0xfb, 0x92, 0x81, 0xa1, 0xc7, 0xe2, 0x84, 0xd7, 0x3e, 0x67, 0xf1, 0x80, 0x9a, 0x48, 0xa4, 0x97, 0x20, 0x0e, 0x04, 0x6d, 0x39, 0xcc, 0xc7, 0x11, 0x2c, 0xd0}
+       };
+       unsigned char   actual_output[SHA256_OUTPUT_SIZE];
+
+       XCTAssertEqual(sizeof(inputs) / sizeof(char *), sizeof(outputs) / SHA256_OUTPUT_SIZE);
+
+       for (int i = 0, limit = sizeof(inputs) / sizeof(char *); i < limit; i++) {
+               char *                                          input;
+               const unsigned char * const expected_output = outputs[i];
+
+               if (i < 4) {
+                       input = inputs[i];
+               } else if (i == 4) {
+                       input = malloc(1000001);
+                       memset(input, 'a', 1000000);
+                       input[1000000] = '\0';
+               } else {
+                       XCTAssertTrue(0);
+               }
+
+               uint8_t caculated = calculate_digest_for_data((const unsigned char *)input, strlen(input), DIGEST_SHA_256,
+                                                                       actual_output, sizeof(actual_output));
+               XCTAssertTrue(caculated);
+
+               XCTAssertTrue(memcmp(actual_output, expected_output, SHA256_OUTPUT_SIZE) == 0);
+       }
+}
+
+- (void)testSHA384 {
+       char * const inputs[] = {
+               "abc",
+               "",
+               "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+               "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+               "one million repetitions of 'a'"
+       };
+
+       unsigned char outputs[][SHA384_OUTPUT_SIZE] = {
+               {0xcb, 0x00, 0x75, 0x3f, 0x45, 0xa3, 0x5e, 0x8b, 0xb5, 0xa0, 0x3d, 0x69, 0x9a, 0xc6, 0x50, 0x07, 0x27, 0x2c, 0x32, 0xab, 0x0e, 0xde, 0xd1, 0x63, 0x1a, 0x8b, 0x60, 0x5a, 0x43, 0xff, 0x5b, 0xed, 0x80, 0x86, 0x07, 0x2b, 0xa1, 0xe7, 0xcc, 0x23, 0x58, 0xba, 0xec, 0xa1, 0x34, 0xc8, 0x25, 0xa7},
+               {0x38, 0xb0, 0x60, 0xa7, 0x51, 0xac, 0x96, 0x38, 0x4c, 0xd9, 0x32, 0x7e, 0xb1, 0xb1, 0xe3, 0x6a, 0x21, 0xfd, 0xb7, 0x11, 0x14, 0xbe, 0x07, 0x43, 0x4c, 0x0c, 0xc7, 0xbf, 0x63, 0xf6, 0xe1, 0xda, 0x27, 0x4e, 0xde, 0xbf, 0xe7, 0x6f, 0x65, 0xfb, 0xd5, 0x1a, 0xd2, 0xf1, 0x48, 0x98, 0xb9, 0x5b},
+               {0x33, 0x91, 0xfd, 0xdd, 0xfc, 0x8d, 0xc7, 0x39, 0x37, 0x07, 0xa6, 0x5b, 0x1b, 0x47, 0x09, 0x39, 0x7c, 0xf8, 0xb1, 0xd1, 0x62, 0xaf, 0x05, 0xab, 0xfe, 0x8f, 0x45, 0x0d, 0xe5, 0xf3, 0x6b, 0xc6, 0xb0, 0x45, 0x5a, 0x85, 0x20, 0xbc, 0x4e, 0x6f, 0x5f, 0xe9, 0x5b, 0x1f, 0xe3, 0xc8, 0x45, 0x2b},
+               {0x09, 0x33, 0x0c, 0x33, 0xf7, 0x11, 0x47, 0xe8, 0x3d, 0x19, 0x2f, 0xc7, 0x82, 0xcd, 0x1b, 0x47, 0x53, 0x11, 0x1b, 0x17, 0x3b, 0x3b, 0x05, 0xd2, 0x2f, 0xa0, 0x80, 0x86, 0xe3, 0xb0, 0xf7, 0x12, 0xfc, 0xc7, 0xc7, 0x1a, 0x55, 0x7e, 0x2d, 0xb9, 0x66, 0xc3, 0xe9, 0xfa, 0x91, 0x74, 0x60, 0x39},
+               {0x9d, 0x0e, 0x18, 0x09, 0x71, 0x64, 0x74, 0xcb, 0x08, 0x6e, 0x83, 0x4e, 0x31, 0x0a, 0x4a, 0x1c, 0xed, 0x14, 0x9e, 0x9c, 0x00, 0xf2, 0x48, 0x52, 0x79, 0x72, 0xce, 0xc5, 0x70, 0x4c, 0x2a, 0x5b, 0x07, 0xb8, 0xb3, 0xdc, 0x38, 0xec, 0xc4, 0xeb, 0xae, 0x97, 0xdd, 0xd8, 0x7f, 0x3d, 0x89, 0x85}
+       };
+       unsigned char   actual_output[SHA384_OUTPUT_SIZE];
+
+       XCTAssertEqual(sizeof(inputs) / sizeof(char *), sizeof(outputs) / SHA384_OUTPUT_SIZE);
+
+       for (int i = 0, limit = sizeof(inputs) / sizeof(char *); i < limit; i++) {
+               char *                                          input;
+               const unsigned char * const expected_output = outputs[i];
+
+               if (i < 4) {
+                       input = inputs[i];
+               } else if (i == 4) {
+                       input = malloc(1000001);
+                       memset(input, 'a', 1000000);
+                       input[1000000] = '\0';
+               } else {
+                       XCTAssertTrue(0);
+               }
+
+               uint8_t calculated =    calculate_digest_for_data((const unsigned char *)input, strlen(input), DIGEST_SHA_384,
+                       actual_output, sizeof(actual_output));
+               XCTAssertTrue(calculated);
+
+               XCTAssertTrue(memcmp(actual_output, expected_output, SHA384_OUTPUT_SIZE) == 0);
+       }
+}
+
+@end
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
diff --git a/mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Crypto/NSEC3HashTest.m b/mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Crypto/NSEC3HashTest.m
new file mode 100644 (file)
index 0000000..09b58a4
--- /dev/null
@@ -0,0 +1,137 @@
+//
+//     NSEC3HashTest.m
+//     Tests
+//
+//     Created by Joey Deng on 1/29/20.
+//
+
+#import <XCTest/XCTest.h>
+#import "dnssec_v2_crypto.h"
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
+
+@interface NSEC3HashTest : XCTestCase
+
+@end
+
+@implementation NSEC3HashTest
+
+- (void)setUp {
+       // Put setup code here. This method is called before the invocation of each test method in the class.
+}
+
+- (void)tearDown {
+       // Put teardown code here. This method is called after the invocation of each test method in the class.
+}
+
+- (void)testNSEC3Hash {
+       const char * inputs[] = {
+               "f", "fo", "foo", "foob", "fooba", "foobar"
+       };
+       mDNSu32 iterations[] = {
+               150, 500, 2500
+       };
+       const char * salts[] = {
+               "", "s", "salt"
+       };
+       const mDNSu8 outputs[][7][20] = {
+               {
+                       {0xd9, 0xcc, 0x35, 0x41, 0xe6, 0x9c, 0x3b, 0xb5, 0x27, 0x10, 0x3d, 0xa9, 0xad, 0x25, 0xdf, 0x9e, 0x64, 0x9e, 0x97, 0x24},
+                       {0x85, 0x7f, 0x81, 0x5d, 0xe5, 0xe3, 0xd3, 0xfc, 0x0f, 0xe0, 0xca, 0x0d, 0xb1, 0xae, 0xa1, 0xad, 0x79, 0x48, 0x66, 0x49},
+                       {0xff, 0x84, 0x51, 0x8d, 0x22, 0x44, 0x52, 0x93, 0x02, 0x80, 0x62, 0x4f, 0x58, 0x3f, 0xd3, 0x79, 0x41, 0x1f, 0x8d, 0xed},
+                       {0xfb, 0x8a, 0xb8, 0x70, 0x23, 0xa6, 0x49, 0x49, 0x0f, 0xb6, 0x1a, 0x78, 0x8a, 0xb0, 0x4b, 0xf4, 0x08, 0x9a, 0x37, 0x47},
+                       {0x8a, 0xe3, 0xeb, 0x10, 0x3c, 0xc3, 0x8d, 0x79, 0x42, 0x92, 0x66, 0x7c, 0x65, 0x89, 0xf1, 0x33, 0x6b, 0x36, 0x00, 0x96},
+                       {0x34, 0xb0, 0xbc, 0xb7, 0xd3, 0xbb, 0x17, 0x8d, 0xd9, 0x08, 0x62, 0x22, 0x37, 0xcd, 0x1c, 0x6c, 0xb6, 0xac, 0x0d, 0x8b},
+               },
+               {
+                       {0xa5, 0xda, 0x58, 0x38, 0xc6, 0x5e, 0x37, 0xc9, 0x3c, 0xd7, 0x01, 0xf9, 0xaa, 0xd3, 0x5d, 0x93, 0x15, 0x41, 0x25, 0x71},
+                       {0xf2, 0x2a, 0xae, 0x68, 0x4c, 0xbb, 0x53, 0xfc, 0xd8, 0x4a, 0x37, 0x9a, 0xc7, 0xac, 0x1a, 0x8d, 0x0e, 0x43, 0xa6, 0x0e},
+                       {0x86, 0xc4, 0x41, 0x10, 0x8d, 0xd7, 0xfb, 0x91, 0xe7, 0x7f, 0xfe, 0xda, 0xcc, 0x05, 0x00, 0x24, 0x8d, 0xdb, 0xed, 0xcd},
+                       {0x99, 0x88, 0x17, 0x3c, 0xaf, 0x86, 0x15, 0xcb, 0x1c, 0x11, 0xec, 0xe7, 0xa4, 0xa6, 0x27, 0x33, 0xb7, 0x77, 0x7b, 0x89},
+                       {0x74, 0x10, 0xdd, 0x9f, 0xc0, 0x3c, 0x56, 0x5e, 0x14, 0x41, 0x3e, 0xa5, 0xa7, 0x7b, 0x18, 0xee, 0xd1, 0x11, 0x77, 0x46},
+                       {0x62, 0xd1, 0xea, 0x1d, 0x7a, 0x71, 0x33, 0xb3, 0x24, 0xd5, 0x16, 0xbd, 0x96, 0xac, 0xab, 0xcd, 0x5b, 0x7e, 0x6a, 0x5e},
+               },
+               {
+                       {0x8f, 0xd5, 0x78, 0xd2, 0x2c, 0x0f, 0x86, 0xa3, 0x68, 0x05, 0x73, 0x38, 0xbd, 0xa4, 0xc7, 0xf8, 0x8e, 0x27, 0xd7, 0xe8},
+                       {0x27, 0xcc, 0x83, 0xc5, 0xde, 0x5f, 0xe2, 0x5a, 0xe4, 0xdb, 0x74, 0xf7, 0xf4, 0x07, 0xea, 0x17, 0xea, 0x39, 0x32, 0x39},
+                       {0x0f, 0x1b, 0xa1, 0x22, 0xbd, 0x1f, 0xcf, 0x41, 0xc1, 0xa5, 0x97, 0x84, 0xf4, 0xce, 0x9a, 0x4f, 0x7b, 0xc4, 0x04, 0x58},
+                       {0x3b, 0x3d, 0x66, 0x87, 0x35, 0x81, 0x3e, 0x86, 0x97, 0xc3, 0xb5, 0xfe, 0xf4, 0x2b, 0x4f, 0xb7, 0x8d, 0x90, 0xd9, 0x82},
+                       {0x53, 0x73, 0x30, 0x60, 0xb7, 0xd0, 0x78, 0xe2, 0x48, 0xb4, 0xe0, 0xeb, 0xc4, 0x43, 0xbe, 0x51, 0x6d, 0x98, 0xa5, 0x2c},
+                       {0x55, 0x25, 0xca, 0xc2, 0xcb, 0x96, 0x8b, 0xe5, 0x97, 0xac, 0x80, 0xc4, 0x08, 0x63, 0xfd, 0xb8, 0x48, 0xfe, 0xbc, 0x8f},
+               },
+               {
+                       {0x7c, 0x64, 0x25, 0x7f, 0xd1, 0xb9, 0x2f, 0x78, 0xcc, 0xdf, 0x3e, 0x8d, 0xbe, 0xff, 0x5f, 0x01, 0x11, 0xa7, 0xa3, 0x33},
+                       {0x1b, 0x81, 0x56, 0x38, 0x9f, 0xa1, 0xf5, 0x4a, 0xcb, 0xf6, 0xb7, 0x6b, 0x26, 0x73, 0x35, 0x0a, 0x78, 0x18, 0xb6, 0x93},
+                       {0x2f, 0x39, 0xdd, 0x4e, 0xb5, 0x24, 0x01, 0xc7, 0xb4, 0x10, 0xcd, 0x0e, 0x6a, 0xa1, 0x06, 0xd0, 0x66, 0xea, 0xb1, 0xbf},
+                       {0xcb, 0x37, 0xe4, 0x88, 0xc9, 0x29, 0x3e, 0x4d, 0x76, 0xfd, 0xd2, 0x44, 0x86, 0xbf, 0xc8, 0xf5, 0xc1, 0xfb, 0xfa, 0x13},
+                       {0x1e, 0xca, 0x0a, 0xa3, 0x9d, 0x2b, 0xb5, 0x0f, 0xdd, 0x9b, 0x8c, 0x4b, 0x23, 0x04, 0xe5, 0xc8, 0x08, 0xdf, 0x6c, 0x5a},
+                       {0x97, 0x41, 0x02, 0x5a, 0xf8, 0x70, 0x6e, 0xc6, 0xc7, 0x98, 0xf0, 0xb4, 0x6c, 0x37, 0x64, 0xea, 0x79, 0x67, 0x3c, 0x84},
+               },
+               {
+                       {0xa6, 0x89, 0x2f, 0x06, 0xba, 0xbb, 0x9f, 0x2d, 0x59, 0xb5, 0x29, 0x66, 0x41, 0xf8, 0xa0, 0x2c, 0x9e, 0x16, 0x3b, 0x02},
+                       {0x78, 0xe2, 0x40, 0x66, 0xdc, 0xfe, 0x85, 0x05, 0xa7, 0xd4, 0x9e, 0xa2, 0x92, 0x56, 0x4a, 0xab, 0x50, 0x0a, 0x1e, 0x97},
+                       {0xdb, 0x45, 0x13, 0x7a, 0xf1, 0x1f, 0x03, 0xc8, 0x2f, 0x8a, 0x5c, 0xe4, 0x34, 0xf0, 0x5b, 0x83, 0xd2, 0xa9, 0x4e, 0xa6},
+                       {0x0b, 0x35, 0x30, 0x8f, 0xef, 0x8e, 0x94, 0x5f, 0xbe, 0x96, 0xf9, 0xbb, 0x8c, 0x44, 0x5f, 0x5e, 0x59, 0x69, 0xfb, 0x6f},
+                       {0x38, 0xd5, 0x6f, 0x4e, 0x50, 0x01, 0x4b, 0x5b, 0x37, 0x91, 0xd4, 0x79, 0xa0, 0x81, 0xee, 0x86, 0x4f, 0x84, 0xa8, 0x0f},
+                       {0xd2, 0x68, 0xc7, 0xf0, 0xb6, 0x6c, 0x93, 0x31, 0x7b, 0x3a, 0x77, 0x6a, 0x0a, 0xfe, 0xcc, 0x93, 0xd0, 0x8f, 0x99, 0xc6},
+               },
+               {
+                       {0x7c, 0x51, 0x00, 0xf6, 0x97, 0xd2, 0xff, 0x32, 0x4e, 0xbe, 0x49, 0xa5, 0x97, 0xec, 0xc9, 0xc9, 0x32, 0x2c, 0x65, 0xc2},
+                       {0xbe, 0x73, 0x19, 0x16, 0x7f, 0x91, 0x19, 0xb7, 0x65, 0x6d, 0x6d, 0x72, 0x96, 0x29, 0xba, 0x88, 0xe1, 0x42, 0xa0, 0x17},
+                       {0x56, 0x14, 0xba, 0xa0, 0x4b, 0xac, 0xbc, 0xca, 0xc7, 0xbf, 0x2d, 0x02, 0x9b, 0x67, 0xea, 0x50, 0x58, 0xbf, 0xf5, 0x7c},
+                       {0xab, 0xda, 0x1c, 0x5c, 0x7f, 0xdd, 0xa4, 0x6f, 0xc4, 0x87, 0x0f, 0xe6, 0x43, 0x6f, 0x83, 0x9a, 0x08, 0x0b, 0xd5, 0xe1},
+                       {0x6a, 0xe1, 0x4d, 0x10, 0xe8, 0xca, 0xe4, 0x31, 0x09, 0xb5, 0xae, 0x52, 0xe5, 0x33, 0xbd, 0x77, 0x8c, 0x22, 0x23, 0x11},
+                       {0x87, 0x85, 0x5c, 0x6b, 0x29, 0x81, 0x77, 0x2d, 0x8b, 0x50, 0xab, 0x5f, 0x56, 0x6a, 0x7b, 0xe0, 0x7d, 0x6d, 0xbd, 0x55},
+               },
+               {
+                       {0xd1, 0xb1, 0x24, 0xc3, 0xf1, 0x99, 0x41, 0x67, 0x38, 0xd8, 0x9e, 0xfc, 0xa5, 0x54, 0x57, 0xf4, 0x02, 0xb9, 0x6c, 0xf4},
+                       {0xd3, 0xd8, 0xce, 0xea, 0x8c, 0xf9, 0x6e, 0x60, 0xa5, 0x3c, 0x7e, 0x9e, 0xee, 0xbf, 0xd9, 0xe3, 0x18, 0xb7, 0x89, 0x61},
+                       {0x78, 0x78, 0xea, 0x90, 0x57, 0x8b, 0x9c, 0x8c, 0xa0, 0x9d, 0xe4, 0x03, 0xcb, 0xd6, 0x24, 0xf5, 0x12, 0x5b, 0x76, 0xda},
+                       {0xd5, 0xf1, 0x94, 0x18, 0xb0, 0x28, 0x83, 0x86, 0x8f, 0xc9, 0x7d, 0x08, 0x55, 0x66, 0x00, 0x42, 0x49, 0x4d, 0x79, 0x06},
+                       {0xd8, 0x72, 0x25, 0xd0, 0x6f, 0x9e, 0x30, 0xb4, 0x09, 0x2e, 0xa9, 0x64, 0xa7, 0xf6, 0x92, 0x38, 0x67, 0xac, 0x1b, 0x06},
+                       {0x13, 0x1e, 0xbd, 0x61, 0x18, 0x6d, 0xbe, 0xce, 0x98, 0xee, 0x99, 0x8e, 0x8b, 0x40, 0x50, 0xd6, 0xe8, 0xdf, 0x6c, 0x51},
+               },
+               {
+                       {0xe4, 0xb4, 0xa4, 0xac, 0x1a, 0xc1, 0xee, 0x96, 0xab, 0xe0, 0x91, 0xf5, 0x05, 0xdf, 0x7d, 0xf4, 0x00, 0xfd, 0x83, 0x94},
+                       {0x59, 0xcb, 0x96, 0x67, 0x39, 0x2c, 0x55, 0x20, 0x10, 0x87, 0xe5, 0xf6, 0x35, 0x31, 0x34, 0xee, 0xb0, 0x6d, 0x9d, 0xec},
+                       {0x1e, 0x4e, 0xde, 0x2e, 0xaa, 0xd1, 0x3a, 0x8e, 0xf8, 0x17, 0xc1, 0x59, 0x88, 0xf1, 0x02, 0x95, 0x06, 0x53, 0xb5, 0x30},
+                       {0x49, 0xe1, 0x74, 0x16, 0x25, 0x1b, 0xa7, 0xd7, 0x3c, 0x7f, 0x8b, 0x73, 0x57, 0x95, 0x7e, 0x60, 0x1f, 0xfd, 0x9c, 0xe6},
+                       {0x3c, 0x03, 0xae, 0xac, 0xb6, 0xa5, 0x34, 0xe8, 0xf1, 0x91, 0x20, 0x13, 0xfd, 0xe3, 0xc0, 0x60, 0xed, 0x42, 0x8c, 0xf5},
+                       {0xc5, 0x26, 0xc7, 0xe1, 0x85, 0x20, 0xc2, 0x64, 0x36, 0xc9, 0xfe, 0x55, 0x8f, 0xb4, 0x25, 0x72, 0x62, 0x21, 0x80, 0xb6},
+               },
+               {
+                       {0x8f, 0xc7, 0xd6, 0x0c, 0x93, 0xf6, 0x31, 0x49, 0x1a, 0x73, 0xe2, 0x6f, 0xe3, 0xd0, 0xc4, 0x2c, 0x40, 0x54, 0x70, 0x6e},
+                       {0xe1, 0x20, 0xb9, 0xc2, 0xab, 0xeb, 0xcb, 0xb5, 0xea, 0x33, 0x8d, 0x93, 0x84, 0x46, 0xcf, 0xa0, 0xa8, 0x0e, 0x45, 0xed},
+                       {0xdc, 0x09, 0x03, 0xd3, 0x46, 0x37, 0x57, 0xab, 0x33, 0xe2, 0xbe, 0xd6, 0x01, 0x3d, 0x8f, 0x93, 0xaf, 0xa3, 0x70, 0xe5},
+                       {0x5a, 0x93, 0xf1, 0xa9, 0xf9, 0x7a, 0xb2, 0xf1, 0x05, 0xca, 0x9f, 0x8a, 0x2b, 0x7f, 0x9e, 0x6b, 0xa0, 0xf4, 0x85, 0x18},
+                       {0xb1, 0xc2, 0xca, 0x06, 0x6c, 0x5b, 0x5b, 0x9c, 0xd3, 0x50, 0xca, 0x17, 0xf5, 0x8e, 0x4e, 0xcd, 0x82, 0x5a, 0x1b, 0x57},
+                       {0x67, 0x8d, 0x0d, 0x8a, 0xa3, 0xf5, 0xa6, 0x64, 0x01, 0x7d, 0xf0, 0x43, 0xb1, 0x3c, 0xbb, 0xe8, 0x04, 0x53, 0x15, 0x0f},
+               }
+       };
+
+       mDNSu32 c = 0;
+       mDNSu8 digest_output[SHA1_OUTPUT_SIZE];
+
+       for (size_t i = 0; i < sizeof(iterations) / sizeof(mDNSs32); i++) {
+               mDNSu32 iteration = iterations[i];
+               for (size_t j = 0; j < sizeof(salts) / sizeof(char *); j++) {
+                       const char * const salt = salts[j];
+                       for (size_t k = 0; k < sizeof(inputs) / sizeof(char *); k++) {
+                               const char * input = inputs[k];
+
+                               mDNSBool calculated = calculate_hash_for_nsec3(digest_output, SHA1_OUTPUT_SIZE, NSEC3_HASH_ALGORITHM_SHA_1,
+                                       (unsigned char *)input, strlen(input), (unsigned char *)salt, strlen(salt), iteration);
+                               XCTAssertTrue(calculated);
+
+                               mDNSBool hash_matches = (memcmp(digest_output, outputs[c][k], SHA1_OUTPUT_SIZE) == 0);
+
+                               XCTAssertTrue(hash_matches, "c: %u, iteration: %u, salt: %s, input: %s", c, iteration, salt, input);
+                       }
+                       c++;
+               }
+       }
+}
+
+@end
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
diff --git a/mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Utility/BaseNEncodingDecodingTest.m b/mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Utility/BaseNEncodingDecodingTest.m
new file mode 100644 (file)
index 0000000..1b35019
--- /dev/null
@@ -0,0 +1,88 @@
+//
+//     BaseNEncodingDecodingTest.m
+//     Tests
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#import <XCTest/XCTest.h>
+#include "base_n.h"
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
+@interface BaseNEncodingDecodingTest : XCTestCase
+
+@end
+
+@implementation BaseNEncodingDecodingTest
+
+char * encoded_str = NULL;
+
+- (void) testBase64Encoding {
+       unsigned char   data_input[1024];
+       char *                  test_case_ptr;
+       char *                  answer_ptr;
+       char *                  test_cases[] = {
+               "", "f", "fo", "foo", "foob", "fooba", "foobar"
+       };
+       char *                  answers[] = {
+               "", "Zg==", "Zm8=", "Zm9v", "Zm9vYg==", "Zm9vYmE=", "Zm9vYmFy"
+       };
+
+       XCTAssertEqual(sizeof(test_cases), sizeof(answers));
+
+       for (int i = 0, limit = sizeof(answers) / sizeof(char *); i < limit; i++) {
+               test_case_ptr   = test_cases[i];
+               answer_ptr              = answers[i];
+               strlcpy((char *)data_input, test_case_ptr, sizeof(data_input));
+
+               XCTAssertEqual(strlen(answer_ptr), get_base_n_encoded_str_length(DNSSEC_BASE_64, strlen(test_case_ptr)));
+
+               encoded_str = base_n_encode(DNSSEC_BASE_64, data_input, strlen(test_case_ptr));
+               XCTAssertTrue(encoded_str != NULL);
+               XCTAssertTrue(strcmp(encoded_str, answer_ptr) == 0, "i: %d, input: %s, encoded_str: %s, answer_ptr: %s",
+                       i, test_case_ptr, encoded_str, answer_ptr);
+
+               free(encoded_str);
+               encoded_str = NULL;
+       }
+}
+
+- (void) testBase32HexEncoding {
+       unsigned char   data_input[1024];
+       char *                  test_case_ptr;
+       char *                  answer_ptr;
+       char *                  test_cases[] = {
+               "", "f", "fo", "foo", "foob", "fooba", "foobar"
+       };
+       char *                  answers[] = {
+               "", "CO======", "CPNG====", "CPNMU===", "CPNMUOG=", "CPNMUOJ1", "CPNMUOJ1E8======"
+       };
+
+       XCTAssertEqual(sizeof(test_cases), sizeof(answers));
+
+       for (int i = 0, limit = sizeof(answers) / sizeof(char *); i < limit; i++) {
+               test_case_ptr                   = test_cases[i];
+               answer_ptr                              = answers[i];
+               strlcpy((char *)data_input, test_case_ptr, sizeof(data_input));
+
+               XCTAssertEqual(strlen(answer_ptr), get_base_n_encoded_str_length(DNSSEC_BASE_32_HEX, strlen(test_case_ptr)));
+
+               encoded_str = base_n_encode(DNSSEC_BASE_32_HEX, data_input, strlen(test_case_ptr));
+               XCTAssertTrue(encoded_str != NULL);
+               XCTAssertTrue(strcmp(encoded_str, answer_ptr) == 0, "i: %d, input: %s, encoded_str: %s, answer_ptr: %s",
+                       i, test_case_ptr, encoded_str, answer_ptr);
+
+               free(encoded_str);
+               encoded_str = NULL;
+       }
+}
+
+- (void) tearDown {
+       if (encoded_str != NULL) {
+               free(encoded_str);
+       }
+}
+
+@end
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
diff --git a/mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Utility/ListTMethodsTest.m b/mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Utility/ListTMethodsTest.m
new file mode 100644 (file)
index 0000000..7454aa7
--- /dev/null
@@ -0,0 +1,210 @@
+//
+//     ListTMethodsTest.m
+//     Tests
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#import <XCTest/XCTest.h>
+#include "list.h"
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
+typedef struct test_struct_t {
+       mDNSu8  field_one;
+       mDNSu32 field_two;
+} test_struct_t;
+
+@interface ListTMethodsTest : XCTestCase
+
+@end
+
+@implementation ListTMethodsTest
+
+- (void)testListInitAndUninit{
+       list_t list;
+
+       list_init(&list, sizeof(test_struct_t));
+       XCTAssertEqual(list.head_ptr,           &list.head,                             @"list.head_ptr should point to list.head");
+       XCTAssertEqual(list.tail_ptr,           &list.tail,                             @"list.tail_ptr should point to list.tail");
+       XCTAssertEqual(list.head_ptr->next, list.tail_ptr,                      @"The next node of head should be tail");
+       XCTAssertEqual(list.tail_ptr->prev, list.head_ptr,                      @"The previous node of tail should be head");
+       XCTAssertEqual(list.data_size,          sizeof(test_struct_t),  @"The data size should be sizeof(test_struct_t)");
+
+       list_uninit(&list);
+       XCTAssertEqual(list.data_size, 0,                       @"The data size should be 0");
+       XCTAssertNil((__bridge id)list.head.next,       @"The next node of head should be NULL");
+       XCTAssertNil((__bridge id)list.tail.prev,       @"The previous node of tail should be NULL");
+       XCTAssertNil((__bridge id)list.head_ptr,        @"The head pointer should point to NULL");
+       XCTAssertNil((__bridge id)list.tail_ptr,        @"The tail pointer should point to NULL");
+}
+
+- (void)testListGeneralOperation{
+       mStatus error;
+       list_t  list;
+       mDNSBool empty;
+       mDNSu8 count;
+       test_struct_t *append_node_data_1, *append_node_data_2, *prepend_node_data_1, *prepend_node_data_2;
+       list_node_t *first_node, *second_node, *third_node, *fourth_node;
+
+       // Initialize
+       list_init(&list, sizeof(test_struct_t));
+
+       // Get first and last for empty list
+       XCTAssertNil((__bridge id)list_get_first(&list));
+       XCTAssertNil((__bridge id)list_get_last(&list));
+
+       // Append succeeds
+       error = list_append_uinitialized(&list, sizeof(test_struct_t), (void **)&append_node_data_1);
+       XCTAssertEqual(error, mStatus_NoError, @"list_append_uinitialized failed; error_description='%s'", mStatusDescription(error));
+       first_node = list.head_ptr->next;
+       XCTAssertEqual(first_node->prev, list.head_ptr,                 @"The first node should point to head");
+       XCTAssertEqual(first_node->next, list.tail_ptr,                 @"The next of first node should point to tail");
+       XCTAssertEqual(list.tail_ptr->prev, first_node,                 @"The previous of tail should point to first node");
+       XCTAssertEqual((void *)first_node->data, (void *)append_node_data_1, @"The data field should be equal to the returned data pointer");
+
+       // Get first and last
+       XCTAssertEqual(list_get_first(&list),   first_node);
+       XCTAssertEqual(list_get_last(&list),    first_node);
+
+       // Append fails
+       error = list_append_uinitialized(&list, sizeof(test_struct_t) - 1, (void **)&append_node_data_2);
+       XCTAssertEqual(error, mStatus_BadParamErr, @"List should not add new node since the type of embedded data does not match");
+
+       // Empty test
+       empty = list_empty(&list);
+       XCTAssertFalse(empty, @"List should not be empty");
+
+       // Count test
+       count = list_count_node(&list);
+       XCTAssertEqual(count, 1, @"There should be only 1 node");
+
+       // Append 2nd succeeds
+       error = list_append_uinitialized(&list, sizeof(test_struct_t), (void **)&append_node_data_2);
+       XCTAssertEqual(error, mStatus_NoError, @"list_append_uinitialized failed; error_description='%s'", mStatusDescription(error));
+       second_node = list.tail_ptr->prev;
+       XCTAssertEqual(first_node->next,                        second_node);
+       XCTAssertEqual(first_node->prev,                        list.head_ptr);
+       XCTAssertEqual(first_node->next,                        second_node);
+       XCTAssertEqual(second_node->next,                       list.tail_ptr);
+       XCTAssertEqual(second_node->prev,                       first_node);
+       XCTAssertEqual((void *)second_node->data,       (void *)append_node_data_2);
+
+       // Count test
+       count = list_count_node(&list);
+       XCTAssertEqual(count, 2);
+
+       // Get first and last
+       XCTAssertEqual(list_get_first(&list),   first_node);
+       XCTAssertEqual(list_get_last(&list),    second_node);
+
+       // Prepend 3rd succeeds
+       error = list_prepend_uinitialized(&list, sizeof(test_struct_t), (void **)&prepend_node_data_1);
+       XCTAssertEqual(error, mStatus_NoError);
+       third_node = list.head_ptr->next;
+       XCTAssertEqual(third_node->next,                        first_node);
+       XCTAssertEqual(third_node->prev,                        list.head_ptr);
+       XCTAssertEqual(first_node->prev,                        third_node);
+       XCTAssertEqual((void *)third_node->data,        (void *)prepend_node_data_1);
+
+       // Count test
+       count = list_count_node(&list);
+       XCTAssertEqual(count, 3);
+
+       // Get first and last
+       XCTAssertEqual(list_get_first(&list),   third_node);
+       XCTAssertEqual(list_get_last(&list),    second_node);
+
+       // Prepend fails
+       error = list_prepend_uinitialized(&list, sizeof(test_struct_t) - 1, (void **)&prepend_node_data_2);
+       XCTAssertEqual(error, mStatus_BadParamErr);
+
+       // Preopend 4th succeeds
+       error = list_prepend_uinitialized(&list, sizeof(test_struct_t), (void **)&prepend_node_data_2);
+       XCTAssertEqual(error, mStatus_NoError);
+       fourth_node = list.head_ptr->next;
+       XCTAssertEqual(fourth_node->next,                       third_node);
+       XCTAssertEqual(fourth_node->prev,                       list.head_ptr);
+       XCTAssertEqual(third_node->prev,                        fourth_node);
+       XCTAssertEqual((void *)fourth_node->data,       (void *)prepend_node_data_2);
+
+       // Count test
+       count = list_count_node(&list);
+       XCTAssertEqual(count, 4);
+
+       // Get first and last
+       XCTAssertEqual(list_get_first(&list),   fourth_node);
+       XCTAssertEqual(list_get_last(&list),    second_node);
+
+       // Iteration test
+       for (list_node_t *node = list_get_first(&list); !list_has_ended(&list, node); node = list_next(node)) {
+               if (node == first_node) {
+                       XCTAssertEqual((void *)node->data, (void *)append_node_data_1);
+               } else if (node == second_node) {
+                       XCTAssertEqual((void *)node->data, (void *)append_node_data_2);
+               } else if (node == third_node) {
+                       XCTAssertEqual((void *)node->data, (void *)prepend_node_data_1);
+               } else if (node == fourth_node) {
+                       XCTAssertEqual((void *)node->data, (void *)prepend_node_data_2);
+               } else {
+                       XCTAssertTrue(mDNSfalse, @"Unknown node added into the list");
+               }
+       }
+
+       // Delete one node with node pointer
+       list_node_delete(first_node);
+       XCTAssertEqual(third_node->next,                second_node);
+       XCTAssertEqual(third_node->prev,                fourth_node);
+       XCTAssertEqual(second_node->prev,               third_node);
+       XCTAssertEqual(list_count_node(&list),  3);
+       XCTAssertFalse(list_empty(&list));
+
+       // Delete one node with data pointer that exists
+       error = list_delete_node_with_data_ptr(&list, prepend_node_data_2);
+       XCTAssertEqual(error, mStatus_NoError);
+       XCTAssertEqual(list_get_first(&list),   third_node);
+       XCTAssertEqual(list_get_last(&list),    second_node);
+       XCTAssertEqual(third_node->next,                second_node);
+       XCTAssertEqual(third_node->prev,                list.head_ptr);
+       XCTAssertEqual(second_node->next,               list.tail_ptr);
+       XCTAssertEqual(list_count_node(&list),  2);
+       XCTAssertFalse(list_empty(&list));
+
+       // Delete one node with data pointer that does not exist
+       error = list_delete_node_with_data_ptr(&list, prepend_node_data_2 + 1);
+       XCTAssertEqual(error,                                   mStatus_NoSuchKey);
+       XCTAssertEqual(list_get_first(&list),   third_node);
+       XCTAssertEqual(list_get_last(&list),    second_node);
+       XCTAssertEqual(third_node->next,                second_node);
+       XCTAssertEqual(third_node->prev,                list.head_ptr);
+       XCTAssertEqual(second_node->next,               list.tail_ptr);
+       XCTAssertEqual(list_count_node(&list),  2);
+       XCTAssertFalse(list_empty(&list));
+
+       // Delete all the nodes
+       list_node_delete_all(&list);
+
+       // Count test
+       count = list_count_node(&list);
+       XCTAssertEqual(count, 0);
+
+       // Empty test
+       empty = list_empty(&list);
+       XCTAssertTrue(empty);
+
+       // Iterate through the empty list
+       for (list_node_t *node = list_get_first(&list); !list_has_ended(&list, node); node = list_next(node)) {
+               XCTAssertFalse(mDNStrue, @"Should never excute this line if the line is empty");
+       }
+
+       // uninitialize the list
+       list_uninit(&list);
+       XCTAssertEqual(list.data_size, 0);
+       XCTAssertNil((__bridge id)list.head.next);
+       XCTAssertNil((__bridge id)list.tail.prev);
+       XCTAssertNil((__bridge id)list.head_ptr);
+       XCTAssertNil((__bridge id)list.tail_ptr);
+}
+@end
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
index 4497a9dda8e6fcc670800a68cf4c82cfbd6c3141..5b0a4371f711bfa76a0d3aadaa7fc400fecc2334 100644 (file)
@@ -7,6 +7,7 @@
 
 #import <XCTest/XCTest.h>
 #include "unittest_common.h"
+#include "helper.h"
 
 @interface HelperFunctionTest : XCTestCase
 
     }
 }
 
+- (void)testHelperRequestBPF
+{
+    fprintf(stdout, "Start %s\n", __FUNCTION__);
+    mDNSRequestBPF();
+    fprintf(stdout, "Completed %s\n", __FUNCTION__);
+}
+
 @end
index 9e0e952f5373d13a955e2f6fac9e85b0d773ed38..05fc201b6381605521af163f70185dfb39942fba 100644 (file)
@@ -165,12 +165,8 @@ mDNSlocal mStatus InitEtcHostsRecords(void)
        XCTAssertEqual(q->TimeoutQuestion, 1);
        XCTAssertEqual(q->WakeOnResolve, 0);
        XCTAssertEqual(q->UseBackgroundTraffic, 0);
-       XCTAssertEqual(q->ValidationRequired, 0);
-       XCTAssertEqual(q->ValidatingResponse, 0);
        XCTAssertEqual(q->ProxyQuestion, 0);
     XCTAssertNotEqual((void*)q->QuestionCallback, (void*)mDNSNULL);
-       XCTAssertNil((__bridge id)q->DNSSECAuthInfo);
-    XCTAssertNil((__bridge id)(void*)q->DAIFreeCallback);
        XCTAssertNotEqual(q->StopTime, 0);
        XCTAssertEqual(q->AppendSearchDomains, 0);
     XCTAssertNil((__bridge id)q->DuplicateOf);
@@ -309,12 +305,8 @@ mDNSlocal mStatus InitEtcHostsRecords(void)
        XCTAssertEqual(q->TimeoutQuestion, 1);
        XCTAssertEqual(q->WakeOnResolve, 0);
        XCTAssertEqual(q->UseBackgroundTraffic, 0);
-       XCTAssertEqual(q->ValidationRequired, 0);
-       XCTAssertEqual(q->ValidatingResponse, 0);
        XCTAssertEqual(q->ProxyQuestion, 0);
     XCTAssertNotEqual((void*)q->QuestionCallback, (void*)mDNSNULL);
-    XCTAssertNil((__bridge id)q->DNSSECAuthInfo);
-    XCTAssertNil((__bridge id)(void*)q->DAIFreeCallback);
     XCTAssertNotEqual(q->StopTime, 0);
     XCTAssertEqual(q->AppendSearchDomains, 0);
     XCTAssertNil((__bridge id)q->DuplicateOf);
index 07656c4273c3416bbe81af0c91c8f28e29cf0a61..c115f9b4cdc357f8b19e9e96532173ea31202e34 100644 (file)
@@ -283,25 +283,43 @@ mDNSlocal mDNSBool HasReplyWithInterfaceIndex(reply_state * reply, mDNSu32 inter
 {
     request_state* req = client_request_message;
 
+    fprintf(stdout, "testLocalOnlyWithInterfacesTestSeries: primary_interfaceID %d\n", primary_interfaceID);
+
     // Verify Any index returns 2 results.
-    [self _executeClientQueryRequest: req andMsgBuf: test_query_any_msgbuf];
-    XCTAssertEqual(NumReplies(req->replies), 2);
-    XCTAssertTrue(HasReplyWithInterfaceIndex(req->replies, kDNSServiceInterfaceIndexP2P));
-    XCTAssertTrue(HasReplyWithInterfaceIndex(req->replies, kDNSServiceInterfaceIndexLocalOnly));
+ #if !TARGET_OS_WATCH
+    if (primary_interfaceID)
+    {
+        // Path evaluation on watch causes this query to get scoped to en0 (primary_interfaceID) so it's the same as #3
+        [self _executeClientQueryRequest: req andMsgBuf: test_query_any_msgbuf];
+        XCTAssertEqual(NumReplies(req->replies), 2);
+        XCTAssertTrue(HasReplyWithInterfaceIndex(req->replies, kDNSServiceInterfaceIndexP2P));
+        XCTAssertTrue(HasReplyWithInterfaceIndex(req->replies, kDNSServiceInterfaceIndexLocalOnly));
+    }
+    else
+    {
+        fprintf(stdout, "testLocalOnlyWithInterfacesTestSeries: skipping test_query_any_msgbuf test because interface not found\n");
+    }
+#endif
 
     // Verify LocalOnly index returns 3 results.
     [self _executeClientQueryRequest: req andMsgBuf: test_query_local_msgbuf];
     XCTAssertEqual(NumReplies(req->replies), 3);
     XCTAssertTrue(HasReplyWithInterfaceIndex(req->replies, kDNSServiceInterfaceIndexP2P));
     XCTAssertTrue(HasReplyWithInterfaceIndex(req->replies, kDNSServiceInterfaceIndexLocalOnly));
-    XCTAssertTrue(HasReplyWithInterfaceIndex(req->replies, primary_interfaceID));
-
-    // Verify en0 index returns 1 result.
-    test_query_interface_msgbuf[7] = primary_interfaceID;
-    [self _executeClientQueryRequest: req andMsgBuf: test_query_interface_msgbuf];
-    XCTAssertEqual(NumReplies(req->replies), 1);
-    XCTAssertTrue(HasReplyWithInterfaceIndex(req->replies, primary_interfaceID));
+    if (primary_interfaceID) XCTAssertTrue(HasReplyWithInterfaceIndex(req->replies, primary_interfaceID));
 
+    if (primary_interfaceID)
+    {
+        // Verify en0 index returns 1 result.
+        test_query_interface_msgbuf[7] = primary_interfaceID;
+        [self _executeClientQueryRequest: req andMsgBuf: test_query_interface_msgbuf];
+        XCTAssertEqual(NumReplies(req->replies), 1);
+        XCTAssertTrue(HasReplyWithInterfaceIndex(req->replies, primary_interfaceID));
+    }
+    else
+    {
+        fprintf(stdout, "testLocalOnlyWithInterfacesTestSeries: skipping primary_interfaceID test because interface not found\n");
+    }
 }
 
 // Simulate a uds client request by setting up a client request and then
@@ -374,12 +392,8 @@ mDNSlocal mDNSBool HasReplyWithInterfaceIndex(reply_state * reply, mDNSu32 inter
     XCTAssertEqual(q->TimeoutQuestion, 0);
     XCTAssertEqual(q->WakeOnResolve, 0);
     XCTAssertEqual(q->UseBackgroundTraffic, 0);
-    XCTAssertEqual(q->ValidationRequired, 0);
-    XCTAssertEqual(q->ValidatingResponse, 0);
     XCTAssertEqual(q->ProxyQuestion, 0);
     XCTAssertNotEqual((void*)q->QuestionCallback, (void*)mDNSNULL);
-    XCTAssertNil((__bridge id)q->DNSSECAuthInfo);
-    XCTAssertNil((__bridge id)(void*)q->DAIFreeCallback);
     XCTAssertEqual(q->AppendSearchDomains, 0);
     XCTAssertNil((__bridge id)q->DuplicateOf);
     
index 037108648647cf38a1d21aeec9dfa0799019adb2..1b4cc9fc66e3f3ec0dd63f78d45ca035f3f9a65c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -39,7 +39,6 @@
 - (void)testPathDeny
 {
     if(!getenv("DNSSDUTIL_XCTEST")) return;   //   Don't run without this environment variable
-    mDNSBool        isBlocked;
     DNSQuestion     q;
     mDNSInterfaceID routableIndex;
 
@@ -53,8 +52,8 @@
     routableIndex = IndexForInterfaceByName_ut( "pdp_ip0" );
     fprintf(stdout, "Testing blocked by (%s)\n", routableIndex ? "policy" : "no route");
 
-    mDNSPlatformGetDNSRoutePolicy(&q, &isBlocked);
-    XCTAssertFalse(isBlocked);
+    mDNSPlatformGetDNSRoutePolicy(&q);
+    XCTAssertFalse(q.BlockedByPolicy);
 
     // Now block it
     NSMutableArray *routeRules = [NSMutableArray array];
     [policySession addPolicy:policy];
     [policySession apply];
 
-    mDNSPlatformGetDNSRoutePolicy(&q, &isBlocked);
+    mDNSPlatformGetDNSRoutePolicy(&q);
     //  Either if these asserts indicate a regression in mDNSPlatformGetDNSRoutePolicy
-    if (routableIndex)  XCTAssertTrue(isBlocked, "blocked by (policy) test failure");
-    else                XCTAssertFalse(isBlocked, "blocked by (no route) test failure");
+    if (routableIndex)  XCTAssertTrue(q.BlockedByPolicy, "blocked by (policy) test failure");
+    else                XCTAssertFalse(q.BlockedByPolicy, "blocked by (no route) test failure");
 
     [policySession removeAllPolicies];
     [policySession apply];
diff --git a/mDNSMacOSX/Tests/Unit Tests/SuspiciousReplyTest.m b/mDNSMacOSX/Tests/Unit Tests/SuspiciousReplyTest.m
deleted file mode 100644 (file)
index 534bbb8..0000000
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (c) 2017-2019 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "unittest_common.h"
-#import <XCTest/XCTest.h>
-
-struct UDPSocket_struct
-{
-       mDNSIPPort port; // MUST BE FIRST FIELD -- mDNSCoreReceive expects every UDPSocket_struct to begin with mDNSIPPort port
-};
-typedef struct UDPSocket_struct UDPSocket;
-
-// This client request was generated using the following command: "dns-sd -Q 123server.dotbennu.com. A".
-uint8_t test_query_client_msgbuf[35] = {
-       0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x32, 0x33, 0x73, 0x65, 0x72, 0x76, 0x65,
-       0x72, 0x2e, 0x64, 0x6f, 0x74, 0x62, 0x65, 0x6e, 0x6e, 0x75, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x00,
-       0x01, 0x00, 0x01
-};
-
-// This uDNS message is a canned response that was originally captured by wireshark.
-uint8_t test_query_response_msgbuf[108] = {
-    0x69, 0x41, // transaction id
-       0x85, 0x80, // flags
-       0x00, 0x01, // 1 question for 123server.dotbennu.com. Addr
-       0x00, 0x02,     // 2 anwsers: 123server.dotbennu.com. CNAME test212.dotbennu.com., test212.dotbennu.com. Addr 10.100.0.1,
-       0x00, 0x01,     // 1 authorities anwser: dotbennu.com. NS cardinal2.apple.com.
-       0x00, 0x00, 0x09, 0x31, 0x32, 0x33,
-    0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x08, 0x64, 0x6f, 0x74, 0x62, 0x65, 0x6e, 0x6e, 0x75, 0x03,
-    0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00,
-    0x02, 0x56, 0x00, 0x0a, 0x07, 0x74, 0x65, 0x73, 0x74, 0x32, 0x31, 0x32, 0xc0, 0x16, 0xc0, 0x34,
-    0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x04, 0x0a, 0x64, 0x00, 0x01, 0xc0, 0x16,
-    0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x51, 0x80, 0x00, 0x12, 0x09, 0x63, 0x61, 0x72, 0x64, 0x69,
-    0x6e, 0x61, 0x6c, 0x32, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x65, 0xc0, 0x1f
-};
-
-// Variables associated with contents of the above uDNS message
-#define uDNS_TargetQID 16745
-char test_original_domainname_cstr[] = "123server.dotbennu.com.";
-char test_cname_domainname_cstr[] = "test212.dotbennu.com.";
-
-@interface SuspiciousReplyTest : XCTestCase
-{
-    UDPSocket* local_socket;
-    request_state* client_request_message;}
-@end
-
-@implementation SuspiciousReplyTest
-
-// The InitThisUnitTest() initializes the mDNSResponder environment as well as
-// a DNSServer. It also allocates memory for a local_socket and client request.
-// Note: This unit test does not send packets on the wire and it does not open sockets.
-- (void)setUp
-{
-    mDNSPlatformMemZero(&mDNSStorage, sizeof(mDNS));
-
-    // Init unit test environment and verify no error occurred.
-    mStatus result = init_mdns_environment(mDNStrue);
-    XCTAssertEqual(result, mStatus_NoError);
-    
-    // Add one DNS server and verify it was added.
-    AddDNSServer_ut();
-    XCTAssertEqual(CountOfUnicastDNSServers(&mDNSStorage), 1);
-    
-    // Create memory for a socket that is never used or opened.
-    local_socket = (UDPSocket *) mDNSPlatformMemAllocateClear(sizeof(*local_socket));
-    
-    // Create memory for a request that is used to make this unit test's client request.
-    client_request_message = calloc(1, sizeof(request_state));
-}
-
-- (void)tearDown
-{
-    mDNS *m = &mDNSStorage;
-    request_state* req = client_request_message;
-    DNSServer   *ptr, **p = &m->DNSServers;
-    
-    while (req->replies)
-    {
-        reply_state *reply = req->replies;
-        req->replies = req->replies->next;
-        mDNSPlatformMemFree(reply);
-    }
-    mDNSPlatformMemFree(req);
-    
-    mDNSPlatformMemFree(local_socket);
-    
-    while (*p)
-    {
-        ptr = *p;
-        *p = (*p)->next;
-        LogInfo("FinalizeUnitTest: Deleting server %p %#a:%d (%##s)", ptr, &ptr->addr, mDNSVal16(ptr->port), ptr->domain.c);
-        mDNSPlatformMemFree(ptr);
-    }
-}
-
-- (void)testSuspiciousReplyTestSeries
-{
-    [self _clientQueryRequest];
-    [self _verifySuspiciousResponseBehavior];
-}
-
-// Simulate a uds client request by setting up a client request and then
-// calling mDNSResponder's handle_client_request.  The handle_client_request function
-// processes the request and starts a query.  This unit test verifies
-// the client request and query were setup as expected.  This unit test also calls
-// mDNS_execute which determines the cache does not contain the new question's
-// answer.
-- (void)_clientQueryRequest
-{
-    mDNS *const m = &mDNSStorage;
-    request_state* req = client_request_message;
-    char *msgptr = (char *)test_query_client_msgbuf;
-    size_t msgsz = sizeof(test_query_client_msgbuf);
-    mDNSs32 min_size = sizeof(DNSServiceFlags) + sizeof(mDNSu32) + 4;
-    DNSQuestion *q;
-    mStatus err = mStatus_NoError;
-    char qname_cstr[MAX_ESCAPED_DOMAIN_NAME];
-    
-    // Process the unit test's client request
-    start_client_request(req, msgptr, msgsz, query_request, local_socket);
-    XCTAssertEqual(err, mStatus_NoError);
-    
-    // Verify the request fields were set as expected
-    XCTAssertNil((__bridge id)req->next);
-    XCTAssertNil((__bridge id)req->primary);
-    XCTAssertEqual(req->sd, client_req_sd);
-    XCTAssertEqual(req->process_id, client_req_process_id);
-    XCTAssertFalse(strcmp(req->pid_name, client_req_pid_name));
-    XCTAssertEqual(req->validUUID, mDNSfalse);
-    XCTAssertEqual(req->errsd, 0);
-    XCTAssertEqual(req->uid, client_req_uid);
-    XCTAssertEqual(req->ts, t_complete);
-    XCTAssertGreaterThan((mDNSs32)req->data_bytes, min_size);
-    XCTAssertEqual(req->msgend, msgptr+msgsz);
-    XCTAssertNil((__bridge id)(void*)req->msgbuf);
-    XCTAssertEqual(req->hdr.version, VERSION);
-    XCTAssertNil((__bridge id)req->replies);
-    XCTAssertNotEqual(req->terminate, (req_termination_fn)0);
-    XCTAssertEqual(req->flags, kDNSServiceFlagsReturnIntermediates);
-    XCTAssertEqual(req->interfaceIndex, kDNSServiceInterfaceIndexAny);
-    
-    // Verify the query fields were set as expected
-    q = &req->u.queryrecord.op.q;
-    XCTAssertNotEqual(q, (DNSQuestion *)mDNSNULL);
-    XCTAssertEqual(q, m->Questions);
-    XCTAssertEqual(q, m->NewQuestions);
-    XCTAssertEqual(q->SuppressUnusable, mDNSfalse);
-    XCTAssertEqual(q->ReturnIntermed, mDNStrue);
-    XCTAssertEqual(q->Suppressed, mDNSfalse);
-    
-    ConvertDomainNameToCString(&q->qname, qname_cstr);
-    XCTAssertFalse(strcmp(qname_cstr, test_original_domainname_cstr));
-    XCTAssertEqual(q->qnamehash, DomainNameHashValue(&q->qname));
-    
-    XCTAssertEqual(q->InterfaceID, mDNSInterface_Any);
-    XCTAssertEqual(q->flags, req->flags);
-    XCTAssertEqual(q->qtype, 1);
-    XCTAssertEqual(q->qclass, 1);
-    XCTAssertEqual(q->LongLived, 0);
-    XCTAssertEqual(q->ExpectUnique, mDNSfalse);
-    XCTAssertEqual(q->ForceMCast, 0);
-    XCTAssertEqual(q->TimeoutQuestion, 0);
-    XCTAssertEqual(q->WakeOnResolve, 0);
-    XCTAssertEqual(q->UseBackgroundTraffic, 0);
-    XCTAssertEqual(q->ValidationRequired, 0);
-    XCTAssertEqual(q->ValidatingResponse, 0);
-    XCTAssertEqual(q->ProxyQuestion, 0);
-    XCTAssertNotEqual((void*)q->QuestionCallback, (void*)mDNSNULL);
-    XCTAssertNil((__bridge id)q->DNSSECAuthInfo);
-    XCTAssertNil((__bridge id)(void*)q->DAIFreeCallback);
-    XCTAssertEqual(q->AppendSearchDomains, 0);
-    XCTAssertNil((__bridge id)q->DuplicateOf);
-    
-    // Call mDNS_Execute to see if the new question, q, has an answer in the cache.
-    // It won't be yet because the cache is empty.
-    m->NextScheduledEvent = mDNS_TimeNow_NoLock(m);
-    mDNS_Execute(m);
-    
-    // Verify mDNS_Execute processed the new question.
-    XCTAssertNil((__bridge id)m->NewQuestions);
-    
-    // Verify the cache is empty and the request got no reply.
-    XCTAssertEqual(m->rrcache_totalused, 0);
-    XCTAssertNil((__bridge id)req->replies);
-}
-
-// This unit test tries to receive a response but changes the QID so it is ignored and can trigger suspicious mode
-// 1) Test a suspicious response is ignored, but if it was previously requested, then don't go into suspicious mode
-// 2) Test a suspicious response is ignored, and it does trigger suspicious mode
-// 3) Test a configuration change event will reset suspicious mode
-- (void)_verifySuspiciousResponseBehavior
-{
-    mDNS *const m = &mDNSStorage;
-    DNSMessage *msgptr = (DNSMessage *)test_query_response_msgbuf;
-    size_t msgsz = sizeof(test_query_response_msgbuf);
-    request_state* req = client_request_message;
-    mDNSOpaque16    suspiciousQID;
-
-    // 1)
-    // Receive and verify it is suspicious  (ignored response)
-    // But not too suspicious               (did NOT go into suspicious mode)
-
-    suspiciousQID.NotAnInteger = 0xDEAD;
-    receive_suspicious_response_ut(req, msgptr, msgsz, suspiciousQID, true);
-    
-    // Verify 0 records recevied
-    mDNSu32 CacheUsed =0, notUsed =0;
-    LogCacheRecords_ut(mDNS_TimeNow(m), &CacheUsed, &notUsed);
-    XCTAssertEqual(CacheUsed, 0);               // Verify 0 records recevied
-    XCTAssertFalse(m->NextSuspiciousTimeout);   // And NOT in suspicious mode
-
-    // 2)
-    // Receive and verify it is suspicious  (ignored response)
-    // And put itself in suspicious mode    (did go into suspicious mode)
-
-    receive_suspicious_response_ut(req, msgptr, msgsz, suspiciousQID, false);
-    LogCacheRecords_ut(mDNS_TimeNow(m), &CacheUsed, &notUsed);
-    XCTAssertEqual(CacheUsed, 0);               // Verify 0 records recevied
-    XCTAssertTrue(m->NextSuspiciousTimeout);    // And IS in suspicious mode
-
-    // 3)
-    // Verify suspicious mode is stopped when a configuration change occurs.
-
-    force_uDNS_SetupDNSConfig_ut(m);
-    XCTAssertFalse(m->NextSuspiciousTimeout);
-}
-
-
-@end
index 79dc17486767b8987ccfceeeb4f2d395e579292e..4a282d1fd4ac5350c0052a2e2d22cb1ef7d98501 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
        <key>Project</key>
                                <string>mDNSResponder</string>
                        </array>
                </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>DNS Server Retry</string>
+                       <key>Description</key>
+                       <string>Tests whether mDNSResponder retries queries with a different server if a given server is unresponsive. Also tests mDNSResponder's handling of DNS responses with every possible RCODE.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>900</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>rcodes</string>
+                               <string>--format</string>
+                               <string>json</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>Suspicious UDP Reply Defense</string>
+                       <key>Description</key>
+                       <string>Tests mDNSResponder&apos;s defense mechanism of falling back to TCP when a response with an invalid message ID, but which is otherwise acceptable, is received over UDP.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>90</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>gaiperf</string>
+                               <string>--suite</string>
+                               <string>basic</string>
+                               <string>--timeLimit</string>
+                               <string>250</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--skipPathEval</string>
+                               <string>--badUDPMode</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
                <dict>
                        <key>TestName</key>
                        <string>mDNS Discovery 1-1-1</string>
                </dict>
                <dict>
                        <key>TestName</key>
-                       <string>mDNS Discovery w/Packet Drops 10</string>
+                       <string>mDNS Discovery w/Packet Drops 10 (IPv4)</string>
                        <key>Description</key>
-                       <string>Tests mDNS discovery and resolution of ten service instances with one 100-byte TXT record, two A records, and two AAAA records. The first three responses per service instance are subject to a 0.5 probability of being dropped to test query retries.</string>
+                       <string>Tests mDNS discovery and resolution of ten service instances with one 100-byte TXT record, two A records, and two AAAA records over IPv4. The first three responses per service instance are subject to a 0.5 probability of being dropped to test query retries.</string>
                        <key>AsRoot</key>
                        <true/>
                        <key>RequiresWiFi</key>
                                <string>2</string>
                                <string>--countAAAA</string>
                                <string>2</string>
-                               <string>--ipv6</string>
+                               <string>--ipv4</string>
                                <string>--udrop</string>
                                <string>0.5</string>
                                <string>--mdrop</string>
                </dict>
                <dict>
                        <key>TestName</key>
-                       <string>mDNS Discovery w/Packet Drops 100</string>
+                       <string>mDNS Discovery w/Packet Drops 10 (IPv6)</string>
                        <key>Description</key>
-                       <string>Tests mDNS discovery and resolution of 100 service instances with one 100-byte TXT record, two A records, and two AAAA records. The first three responses per service instance are subject to a 0.5 probability of being dropped to test query retries.</string>
+                       <string>Tests mDNS discovery and resolution of ten service instances with one 100-byte TXT record, two A records, and two AAAA records over IPv6. The first three responses per service instance are subject to a 0.5 probability of being dropped to test query retries.</string>
                        <key>AsRoot</key>
                        <true/>
                        <key>RequiresWiFi</key>
                                <string>--interface</string>
                                <string>lo0</string>
                                <string>--instanceCount</string>
-                               <string>100</string>
+                               <string>10</string>
                                <string>--txtSize</string>
                                <string>100</string>
                                <string>--browseTime</string>
-                               <string>18</string>
+                               <string>16</string>
                                <string>--countA</string>
                                <string>2</string>
                                <string>--countAAAA</string>
                </dict>
                <dict>
                        <key>TestName</key>
-                       <string>DotLocal Queries</string>
+                       <string>mDNS Discovery w/Packet Drops 100 (IPv4)</string>
                        <key>Description</key>
-                       <string>Tests DNS and mDNS queries for domain names in the local domain.</string>
+                       <string>Tests mDNS discovery and resolution of 100 service instances with one 100-byte TXT record, two A records, and two AAAA records over IPv4. The first three responses per service instance are subject to a 0.5 probability of being dropped to test query retries.</string>
                        <key>AsRoot</key>
-                       <false/>
+                       <true/>
                        <key>RequiresWiFi</key>
                        <false/>
                        <key>Timeout</key>
-                       <integer>40</integer>
+                       <integer>30</integer>
                        <key>IgnoreOutput</key>
                        <true/>
                        <key>Command</key>
                        <array>
                                <string>/usr/local/bin/dnssdutil</string>
                                <string>test</string>
-                               <string>dotlocal</string>
+                               <string>mdnsdiscovery</string>
                                <string>--interface</string>
                                <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>100</string>
+                               <string>--txtSize</string>
+                               <string>100</string>
+                               <string>--browseTime</string>
+                               <string>18</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv4</string>
+                               <string>--udrop</string>
+                               <string>0.5</string>
+                               <string>--mdrop</string>
+                               <string>0.5</string>
+                               <string>--maxDropCount</string>
+                               <string>3</string>
                                <string>--format</string>
                                <string>json</string>
+                               <string>--flushCache</string>
                        </array>
                </dict>
                <dict>
                </dict>
                <dict>
                        <key>TestName</key>
-                       <string>Service Registration</string>
+                       <string>mDNS Discovery w/Packet Drops 100 (IPv6)</string>
                        <key>Description</key>
-                       <string>Tests Bonjour service registration.</string>
+                       <string>Tests mDNS discovery and resolution of 100 service instances with one 100-byte TXT record, two A records, and two AAAA records over IPv6. The first three responses per service instance are subject to a 0.5 probability of being dropped to test query retries.</string>
                        <key>AsRoot</key>
-                       <false/>
-                       <key>RequiresWiFi</key>
                        <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
                        <key>Timeout</key>
-                       <integer>120</integer>
+                       <integer>30</integer>
                        <key>IgnoreOutput</key>
                        <true/>
                        <key>Command</key>
                        <array>
                                <string>/usr/local/bin/dnssdutil</string>
                                <string>test</string>
-                               <string>registration</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>100</string>
+                               <string>--txtSize</string>
+                               <string>100</string>
+                               <string>--browseTime</string>
+                               <string>18</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv6</string>
+                               <string>--udrop</string>
+                               <string>0.5</string>
+                               <string>--mdrop</string>
+                               <string>0.5</string>
+                               <string>--maxDropCount</string>
+                               <string>3</string>
                                <string>--format</string>
                                <string>json</string>
-                               <string>--bats</string>
+                               <string>--flushCache</string>
                        </array>
                </dict>
                <dict>
                </dict>
                <dict>
                        <key>TestName</key>
-                       <string>KeepAlive Record Registration</string>
+                       <string>mDNS Discovery 1-1-1 (New GAI)</string>
                        <key>Description</key>
-                       <string>Tests KeepAlive record registrations.</string>
+                       <string>Tests mDNS discovery and resolution of one service instance with one one-byte TXT record, one A record, and one AAAA record.</string>
                        <key>AsRoot</key>
-                       <false/>
+                       <true/>
                        <key>RequiresWiFi</key>
                        <false/>
                        <key>Timeout</key>
-                       <integer>60</integer>
+                       <integer>10</integer>
                        <key>IgnoreOutput</key>
                        <true/>
                        <key>Command</key>
                        <array>
                                <string>/usr/local/bin/dnssdutil</string>
                                <string>test</string>
-                               <string>keepalive</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>1</string>
+                               <string>--txtSize</string>
+                               <string>1</string>
+                               <string>--browseTime</string>
+                               <string>3</string>
+                               <string>--countA</string>
+                               <string>1</string>
+                               <string>--countAAAA</string>
+                               <string>1</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
                                <string>--format</string>
                                <string>json</string>
+                               <string>--flushCache</string>
+                               <string>--useNewGAI</string>
                        </array>
                </dict>
                <dict>
                </dict>
                <dict>
                        <key>TestName</key>
-                       <string>Probe Conflicts</string>
+                       <string>mDNS Discovery 1-1-1 (No Additionals, New GAI)</string>
                        <key>Description</key>
-                       <string>Tests various probe conflict scenarios, some of which are expected to result in service instance and record renames.</string>
+                       <string>Tests mDNS discovery and resolution of one service instance with one one-byte TXT record, one A record, and one AAAA record. Responses from mdnsreplier contain no additional answers.</string>
                        <key>AsRoot</key>
-                       <false/>
+                       <true/>
                        <key>RequiresWiFi</key>
                        <false/>
                        <key>Timeout</key>
-                       <integer>300</integer>
+                       <integer>10</integer>
                        <key>IgnoreOutput</key>
                        <true/>
                        <key>Command</key>
                        <array>
                                <string>/usr/local/bin/dnssdutil</string>
                                <string>test</string>
-                               <string>probeconflicts</string>
+                               <string>mdnsdiscovery</string>
                                <string>--interface</string>
                                <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>1</string>
+                               <string>--txtSize</string>
+                               <string>1</string>
+                               <string>--browseTime</string>
+                               <string>3</string>
+                               <string>--countA</string>
+                               <string>1</string>
+                               <string>--countAAAA</string>
+                               <string>1</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
                                <string>--format</string>
                                <string>json</string>
+                               <string>--noAdditionals</string>
+                               <string>--flushCache</string>
+                               <string>--useNewGAI</string>
                        </array>
                </dict>
                <dict>
                </dict>
                <dict>
                        <key>TestName</key>
-                       <string>TCP Fallback</string>
+                       <string>mDNS Discovery 10-100-2 (New GAI)</string>
                        <key>Description</key>
-                       <string>Tests mDNSResponder&apos;s TCP fallback mechanism, which is triggered by UDP responses with invalid message IDs that would otherwise be acceptable.</string>
+                       <string>Tests mDNS discovery and resolution of ten service instances with one 100-byte TXT record, two A records, and two AAAA records.</string>
                        <key>AsRoot</key>
                        <true/>
                        <key>RequiresWiFi</key>
                        <false/>
                        <key>Timeout</key>
-                       <integer>90</integer>
+                       <integer>10</integer>
                        <key>IgnoreOutput</key>
                        <true/>
                        <key>Command</key>
                        <array>
                                <string>/usr/local/bin/dnssdutil</string>
                                <string>test</string>
-                               <string>gaiperf</string>
-                               <string>--suite</string>
-                               <string>basic</string>
-                               <string>--timeLimit</string>
-                               <string>250</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>10</string>
+                               <string>--txtSize</string>
+                               <string>100</string>
+                               <string>--browseTime</string>
+                               <string>3</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
                                <string>--format</string>
                                <string>json</string>
-                               <string>--skipPathEval</string>
-                               <string>--badUDPMode</string>
+                               <string>--flushCache</string>
+                               <string>--useNewGAI</string>
                        </array>
                </dict>
                <dict>
                </dict>
                <dict>
                        <key>TestName</key>
-                       <string>State Dump</string>
+                       <string>mDNS Discovery 10-100-2 (No Additionals, New GAI)</string>
                        <key>Description</key>
-                       <string>1.  Tests whether the state dump can be triggered correctly, and whether the file (or stdout&apos;s output) contains the full state information. 2. Checks whether the number of state dump files has an upper limit to avoid wasting disk space.</string>
+                       <string>Tests mDNS discovery and resolution of ten service instances with one 100-byte TXT record, two A records, and two AAAA records. Responses from mdnsreplier contain no additional answers.</string>
                        <key>AsRoot</key>
                        <true/>
                        <key>RequiresWiFi</key>
-                       <true/>
+                       <false/>
                        <key>Timeout</key>
-                       <integer>60</integer>
+                       <integer>10</integer>
                        <key>IgnoreOutput</key>
-                       <false/>
+                       <true/>
                        <key>Command</key>
                        <array>
                                <string>/usr/local/bin/dnssdutil</string>
-                               <string>browseAll</string>
-                               <string>&amp;&amp;</string>
-                               <string>/bin/sh</string>
-                               <string>/AppleInternal/Tests/mDNSResponder/bats_test_state_dump.sh</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>10</string>
+                               <string>--txtSize</string>
+                               <string>100</string>
+                               <string>--browseTime</string>
+                               <string>3</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--noAdditionals</string>
+                               <string>--flushCache</string>
+                               <string>--useNewGAI</string>
                        </array>
                </dict>
                <dict>
                </dict>
                <dict>
                        <key>TestName</key>
-                       <string>DNS Proxy</string>
+                       <string>mDNS Discovery 100-500-2 (New GAI)</string>
                        <key>Description</key>
-                       <string>1.  Tests the DNS Proxy by doing a DNS UDP query. 2. Tests the DNS proxy by doing a DNS TCP query.</string>
+                       <string>Tests mDNS discovery and resolution of 100 service instances with one 500-byte TXT record, two A records, and two AAAA records.</string>
                        <key>AsRoot</key>
                        <true/>
                        <key>RequiresWiFi</key>
-                       <true/>
+                       <false/>
                        <key>Timeout</key>
-                       <integer>60</integer>
+                       <integer>10</integer>
                        <key>IgnoreOutput</key>
-                       <false/>
+                       <true/>
                        <key>Command</key>
                        <array>
-                               <string>/bin/sh</string>
-                               <string>/AppleInternal/Tests/mDNSResponder/bats_test_proxy.sh</string>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>100</string>
+                               <string>--txtSize</string>
+                               <string>500</string>
+                               <string>--browseTime</string>
+                               <string>5</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--flushCache</string>
+                               <string>--useNewGAI</string>
                        </array>
                </dict>
                <dict>
                </dict>
                <dict>
                        <key>TestName</key>
-                       <string>Expensive/Constrained Interface</string>
+                       <string>mDNS Discovery 100-500-2 (No Additionals, New GAI)</string>
                        <key>Description</key>
-                       <string>Test the following situation:
-1. The interface is set to expensive and inexpensive, and the query is set to DenyExpensive, a continuous ADD/REMOVE sequence is expected.
-2. The interface is set to expensive and inexpensive, and the query does not DenyExpensive, no update is expected.
-3. The interface is set to constrained and unconstrained, and the query is set to DenyConstrained, a continuous ADD/REMOVE sequence is expected.
-4. The interface is set to constrained and unconstrained, and the query does not DenyConstrained, no update is expected.
-5. The interface is set to expensive and constrained, and the query is set to DenyExpensive and DenyConstrained.</string>
+                       <string>Tests mDNS discovery and resolution of 100 service instances with one 500-byte TXT record, two A records, and two AAAA records. Responses from mdnsreplier contain no additional answers.</string>
                        <key>AsRoot</key>
                        <true/>
                        <key>RequiresWiFi</key>
                        <false/>
                        <key>Timeout</key>
-                       <integer>1200</integer>
+                       <integer>10</integer>
                        <key>IgnoreOutput</key>
-                       <false/>
+                       <true/>
                        <key>Command</key>
                        <array>
                                <string>/usr/local/bin/dnssdutil</string>
                                <string>test</string>
-                               <string>expensive_constrained_updates</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>100</string>
+                               <string>--txtSize</string>
+                               <string>500</string>
+                               <string>--browseTime</string>
+                               <string>5</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--noAdditionals</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--flushCache</string>
+                               <string>--useNewGAI</string>
                        </array>
                </dict>
                <dict>
                </dict>
                <dict>
                        <key>TestName</key>
-                       <string>Fix Verification #1</string>
+                       <string>mDNS Discovery 1-1-1 (No Cache Flush, New GAI)</string>
                        <key>Description</key>
-                       <string>Fix Verification #1</string>
+                       <string>Tests mDNS discovery and resolution of one service instance with one one-byte TXT record, one A record, and one AAAA record. Cache is not flushed beforehand.</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>1</string>
+                               <string>--txtSize</string>
+                               <string>1</string>
+                               <string>--browseTime</string>
+                               <string>3</string>
+                               <string>--countA</string>
+                               <string>1</string>
+                               <string>--countAAAA</string>
+                               <string>1</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--useNewGAI</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
                        <key>AsRoot</key>
                        <true/>
                        <key>RequiresWiFi</key>
                        <false/>
                        <key>Timeout</key>
-                       <integer>45</integer>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery 1-1-1 (No Cache Flush, No Additionals, New GAI)</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of one service instance with one one-byte TXT record, one A record, and one AAAA record. Cache is not flushed beforehand. Responses from mdnsreplier contain no additional answers.</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
                        <key>IgnoreOutput</key>
                        <true/>
                        <key>Command</key>
                        <array>
                                <string>/usr/local/bin/dnssdutil</string>
-                               <string>verifyFix</string>
-                               <string>earlyAWDL</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>1</string>
+                               <string>--txtSize</string>
+                               <string>1</string>
+                               <string>--browseTime</string>
+                               <string>3</string>
+                               <string>--countA</string>
+                               <string>1</string>
+                               <string>--countAAAA</string>
+                               <string>1</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
                                <string>--format</string>
                                <string>json</string>
+                               <string>--noAdditionals</string>
+                               <string>--useNewGAI</string>
                        </array>
                </dict>
                <dict>
                </dict>
                <dict>
                        <key>TestName</key>
-                       <string>XCTests</string>
+                       <string>mDNS Discovery 10-100-2 (No Cache Flush, New GAI)</string>
                        <key>Description</key>
-                       <string>mDNSResponder XCTests</string>
-                       <key>WorkingDirectory</key>
-                       <string>/AppleInternal/XCTests/com.apple.mDNSResponder/</string>
+                       <string>Tests mDNS discovery and resolution of ten service instances with one 100-byte TXT record, two A records, and two AAAA records. Cache is not flushed beforehand.</string>
                        <key>AsRoot</key>
                        <false/>
                        <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>10</string>
+                               <string>--txtSize</string>
+                               <string>100</string>
+                               <string>--browseTime</string>
+                               <string>3</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--useNewGAI</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
                        <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
                        <key>Timeout</key>
-                       <integer>20</integer>
-                       <key>ShowSubtestResults</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
                        <true/>
                        <key>Command</key>
                        <array>
-                               <string>BATS_XCTEST_CMD</string>
-                               <string>-NSTreatUnknownArgumentsAsOpen</string>
-                               <string>NO</string>
-                               <string>-ApplePersistenceIgnoreState</string>
-                               <string>YES</string>
-                               <string>-XCTest</string>
-                               <string>Self</string>
-                               <string>Tests.xctest</string>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
                        </array>
                </dict>
                <dict>
                        <key>TestName</key>
-                       <string>PathEvaluationTest</string>
+                       <string>mDNS Discovery 10-100-2 (No Cache Flush, No Additionals, New GAI)</string>
                        <key>Description</key>
-                       <string>PathEvaluationTest from Tests.xctest</string>
+                       <string>Tests mDNS discovery and resolution of ten service instances with one 100-byte TXT record, two A records, and two AAAA records. Cache is not flushed beforehand. Responses from mdnsreplier contain no additional answers.</string>
                        <key>AsRoot</key>
                        <false/>
                        <key>RequiresWiFi</key>
                        <false/>
                        <key>Timeout</key>
-                       <integer>5</integer>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>10</string>
+                               <string>--txtSize</string>
+                               <string>100</string>
+                               <string>--browseTime</string>
+                               <string>3</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--noAdditionals</string>
+                               <string>--useNewGAI</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
                        <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery 100-500-2 (No Cache Flush, New GAI)</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of 100 service instances with one 500-byte TXT record, two A records, and two AAAA records. Cache is not flushed beforehand.</string>
+                       <key>AsRoot</key>
                        <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
                        <key>Command</key>
                        <array>
                                <string>/usr/local/bin/dnssdutil</string>
                                <string>test</string>
-                               <string>xctest</string>
-                               <string>-c</string>
-                               <string>PathEvaluationTest</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>100</string>
+                               <string>--txtSize</string>
+                               <string>500</string>
+                               <string>--browseTime</string>
+                               <string>5</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--useNewGAI</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery 100-500-2 (No Cache Flush, No Additionals, New GAI)</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of 100 service instances with one 500-byte TXT record, two A records, and two AAAA records. Cache is not flushed beforehand. Responses from mdnsreplier contain no additional answers.</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>100</string>
+                               <string>--txtSize</string>
+                               <string>500</string>
+                               <string>--browseTime</string>
+                               <string>5</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--noAdditionals</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--useNewGAI</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery w/Packet Drops 10 (IPv4, New GAI)</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of ten service instances with one 100-byte TXT record, two A records, and two AAAA records over IPv4. The first three responses per service instance are subject to a 0.5 probability of being dropped to test query retries.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>30</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>10</string>
+                               <string>--txtSize</string>
+                               <string>100</string>
+                               <string>--browseTime</string>
+                               <string>16</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv4</string>
+                               <string>--udrop</string>
+                               <string>0.5</string>
+                               <string>--mdrop</string>
+                               <string>0.5</string>
+                               <string>--maxDropCount</string>
+                               <string>3</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--flushCache</string>
+                               <string>--useNewGAI</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery w/Packet Drops 10 (IPv6, New GAI)</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of ten service instances with one 100-byte TXT record, two A records, and two AAAA records over IPv6. The first three responses per service instance are subject to a 0.5 probability of being dropped to test query retries.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>30</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>10</string>
+                               <string>--txtSize</string>
+                               <string>100</string>
+                               <string>--browseTime</string>
+                               <string>16</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv6</string>
+                               <string>--udrop</string>
+                               <string>0.5</string>
+                               <string>--mdrop</string>
+                               <string>0.5</string>
+                               <string>--maxDropCount</string>
+                               <string>3</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--flushCache</string>
+                               <string>--useNewGAI</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery w/Packet Drops 100 (IPv4, New GAI)</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of 100 service instances with one 100-byte TXT record, two A records, and two AAAA records over IPv4. The first three responses per service instance are subject to a 0.5 probability of being dropped to test query retries.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>30</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>100</string>
+                               <string>--txtSize</string>
+                               <string>100</string>
+                               <string>--browseTime</string>
+                               <string>18</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv4</string>
+                               <string>--udrop</string>
+                               <string>0.5</string>
+                               <string>--mdrop</string>
+                               <string>0.5</string>
+                               <string>--maxDropCount</string>
+                               <string>3</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--flushCache</string>
+                               <string>--useNewGAI</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery w/Packet Drops 100 (IPv6, New GAI)</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of 100 service instances with one 100-byte TXT record, two A records, and two AAAA records over IPv6. The first three responses per service instance are subject to a 0.5 probability of being dropped to test query retries.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>30</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--instanceCount</string>
+                               <string>100</string>
+                               <string>--txtSize</string>
+                               <string>100</string>
+                               <string>--browseTime</string>
+                               <string>18</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv6</string>
+                               <string>--udrop</string>
+                               <string>0.5</string>
+                               <string>--mdrop</string>
+                               <string>0.5</string>
+                               <string>--maxDropCount</string>
+                               <string>3</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--flushCache</string>
+                               <string>--useNewGAI</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>DotLocal Queries</string>
+                       <key>Description</key>
+                       <string>Tests DNS and mDNS queries for domain names in the local domain.</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>40</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>dotlocal</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--format</string>
+                               <string>json</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>Service Registration</string>
+                       <key>Description</key>
+                       <string>Tests Bonjour service registration.</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>120</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>registration</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--bats</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>KeepAlive Record Registration</string>
+                       <key>Description</key>
+                       <string>Tests KeepAlive record registrations.</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>60</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>keepalive</string>
+                               <string>--format</string>
+                               <string>json</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>Probe Conflicts (IPv4)</string>
+                       <key>Description</key>
+                       <string>Tests various probe conflict scenarios, some of which are expected to result in service instance and record renames. The probe conflicts occur via IPv4.</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>300</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>probeconflicts</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--ipv4</string>
+                               <string>--format</string>
+                               <string>json</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>Probe Conflicts (IPv6)</string>
+                       <key>Description</key>
+                       <string>Tests various probe conflict scenarios, some of which are expected to result in service instance and record renames. The probe conflicts occur via IPv6.</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>300</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>probeconflicts</string>
+                               <string>--interface</string>
+                               <string>lo0</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>Expensive/Constrained Interface</string>
+                       <key>Description</key>
+                       <string>Test the following situation:
+1. The interface is set to expensive and inexpensive, and the query is set to DenyExpensive, a continuous ADD/REMOVE sequence is expected.
+2. The interface is set to expensive and inexpensive, and the query does not DenyExpensive, no update is expected.
+3. The interface is set to constrained and unconstrained, and the query is set to DenyConstrained, a continuous ADD/REMOVE sequence is expected.
+4. The interface is set to constrained and unconstrained, and the query does not DenyConstrained, no update is expected.
+5. The interface is set to expensive and constrained, and the query is set to DenyExpensive and DenyConstrained.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>1200</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>expensive_constrained_updates</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>DNS Proxy</string>
+                       <key>Description</key>
+                       <string>Tests mDNSResponder&apos;s DNS proxy by sending it a variety of queries and verifying the responses. The queries are sent via UDP and TCP to the DNS proxy&apos;s IPV4 and IPv6 addresses. The DNS proxy is tested while it runs in different modes. Aside from the mode without a DNS64 prefix, the DNS proxy is tested while it runs with all of the valid DNS64 prefix lengths: 32-bit, 40-bit, 48-bit, 56-bit, 64-bit, and 96-bit.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>600</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>dnsproxy</string>
+                               <string>--format</string>
+                               <string>json</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>State Dump</string>
+                       <key>Description</key>
+                       <string>1.  Tests whether the state dump can be triggered correctly, and whether the file (or stdout&apos;s output) contains the full state information. 2. Checks whether the number of state dump files has an upper limit to avoid wasting disk space.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>60</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>browseAll</string>
+                               <string>&amp;&amp;</string>
+                               <string>/bin/sh</string>
+                               <string>/AppleInternal/Tests/mDNSResponder/bats_test_state_dump.sh</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>Fix Verification #1</string>
+                       <key>Description</key>
+                       <string>Fix Verification #1</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>45</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>verifyFix</string>
+                               <string>earlyAWDL</string>
+                               <string>--format</string>
+                               <string>json</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>Multiple Stub Connections</string>
+                       <key>Description</key>
+                       <string>Verifies any issues with multiple simultanious connections from a client</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>60</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>multiconnect</string>
+                               <string>--connections</string>
+                               <string>100</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>DNSSEC Test - Basic Validation</string>
+                       <key>Description</key>
+                       <string>Verifies if mDNSResponder could handle the basic DNSSEC validation</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>dnssec</string>
+                               <string>-n</string>
+                               <string>&quot;basic validation&quot;</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>Paragon (Daemon Score Card)</string>
+                       <key>Description</key>
+                       <string>Gathers performance metrics and performs daemon-related checks after a simple kickstart of mDNSResponder daemon</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>60</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/perfcheck</string>
+                               <string>daemon</string>
+                               <string>-p</string>
+                               <string>mDNSResponder</string>
+                               <string>--perfdata</string>
+                               <string>/tmp/scorecard-mDNSResponder.pdj</string>
+                               <string>--xpc-trace</string>
+                               <string>-s</string>
+                               <string>2</string>
+                               <string>-c</string>
+                               <string>launchctl</string>
+                               <string>kickstart</string>
+                               <string>-kp</string>
+                               <string>system/com.apple.mDNSResponder.reloaded</string>
+                               <string>2&gt;</string>
+                               <string>/tmp/perf_mDNSResponder</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>LocalOnlyATimeoutTest</string>
+                       <key>Description</key>
+                       <string>LocalOnlyATimeoutTest from Tests.xctest</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>5</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>xctest</string>
+                               <string>-c</string>
+                               <string>LocalOnlyATimeoutTest</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>CNameRecordTest</string>
+                       <key>Description</key>
+                       <string>CNameRecordTest from Tests.xctest</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>5</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>xctest</string>
+                               <string>-c</string>
+                               <string>CNameRecordTest</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSCoreReceiveTest</string>
+                       <key>Description</key>
+                       <string>mDNSCoreReceiveTest from Tests.xctest</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>5</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>xctest</string>
+                               <string>-c</string>
+                               <string>mDNSCoreReceiveTest</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>ResourceRecordTest</string>
+                       <key>Description</key>
+                       <string>ResourceRecordTest from Tests.xctest</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>5</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>xctest</string>
+                               <string>-c</string>
+                               <string>ResourceRecordTest</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>DNSMessageTest</string>
+                       <key>Description</key>
+                       <string>DNSMessageTest from Tests.xctest</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>5</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>xctest</string>
+                               <string>-c</string>
+                               <string>DNSMessageTest</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>HelperFunctionTest</string>
+                       <key>Description</key>
+                       <string>HelperFunctionTest from Tests.xctest</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>5</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>xctest</string>
+                               <string>-c</string>
+                               <string>HelperFunctionTest</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>CacheOrderTest</string>
+                       <key>Description</key>
+                       <string>CacheOrderTest from Tests.xctest</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>5</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>xctest</string>
+                               <string>-c</string>
+                               <string>CacheOrderTest</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>LocalOnlyWithInterfacesTest</string>
+                       <key>Description</key>
+                       <string>LocalOnlyWithInterfacesTest from Tests.xctest</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>5</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>xctest</string>
+                               <string>-c</string>
+                               <string>LocalOnlyWithInterfacesTest</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>PathEvaluationTest</string>
+                       <key>Description</key>
+                       <string>PathEvaluationTest from Tests.xctest</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>5</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>xctest</string>
+                               <string>-c</string>
+                               <string>PathEvaluationTest</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>ListTMethodsTest</string>
+                       <key>Description</key>
+                       <string>ListTMethodsTest from Tests.xctest</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>5</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>xctest</string>
+                               <string>-c</string>
+                               <string>ListTMethodsTest</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>BaseNEncodingDecodingTest</string>
+                       <key>Description</key>
+                       <string>BaseNEncodingDecodingTest from Tests.xctest</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>5</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>xctest</string>
+                               <string>-c</string>
+                               <string>BaseNEncodingDecodingTest</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>CanonicalMethodsTest</string>
+                       <key>Description</key>
+                       <string>CanonicalMethodsTest from Tests.xctest</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>5</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>xctest</string>
+                               <string>-c</string>
+                               <string>CanonicalMethodsTest</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>DigestCalculationTest</string>
+                       <key>Description</key>
+                       <string>DigestCalculationTest from Tests.xctest</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>5</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>xctest</string>
+                               <string>-c</string>
+                               <string>DigestCalculationTest</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>NSEC3HashTest</string>
+                       <key>Description</key>
+                       <string>NSEC3HashTest from Tests.xctest</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>5</integer>
+                       <key>IgnoreOutput</key>
+                       <false/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>xctest</string>
+                               <string>-c</string>
+                               <string>NSEC3HashTest</string>
                        </array>
                </dict>
        </array>
diff --git a/mDNSMacOSX/Tests/mDNSResponderTests-Entitlements.plist b/mDNSMacOSX/Tests/mDNSResponderTests-Entitlements.plist
new file mode 100644 (file)
index 0000000..330560c
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>com.apple.wifi.manager-access</key>
+       <true/>
+</dict>
+</plist>
diff --git a/mDNSMacOSX/com.apple.srp-mdns-proxy.plist b/mDNSMacOSX/com.apple.srp-mdns-proxy.plist
new file mode 100644 (file)
index 0000000..21b4a65
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+  <dict>
+    <key>Label</key>
+    <string>com.apple.srp-mdns-proxy</string>
+
+    <key>InitGroups</key>
+    <false/>
+
+    <key>GroupName</key>
+    <string>_mdnsresponder</string>
+
+    <key>ProgramArguments</key>
+    <array>
+      <string>/usr/libexec/srp-mdns-proxy</string>
+    </array>
+
+    <key>MachServices</key>
+    <dict>
+      <key>com.apple.srp-mdns-proxy.proxy</key>
+      <true/>
+    </dict>
+
+    <key>POSIXSpawnType</key>
+    <string>Interactive</string>
+
+    <key>EnablePressuredExit</key>
+    <true/>
+
+    <key>LimitLoadToHardware</key>
+    <dict>
+      <key>features.fillmore</key>
+      <true/>
+    </dict>
+
+  </dict>
+</plist>
index 3823528426594699a8816cd1c623bb12689a7502..5b9ca70d0cfcb452e30dcb496ea87a72f399709e 100644 (file)
@@ -4,6 +4,9 @@
 <dict>
        <key>com.apple.mDNSResponder.log_utility</key>
        <true/>
+       <key>com.apple.developer.networking.multicast.BYPASS</key>
+       <true/>
+       <key>com.apple.developer.on-demand-install-capable.BYPASS</key>
+       <true/>
 </dict>
 </plist>
-
index 53dcead0566d6d56330a26593aca4aba4a598bf5..b5754f166f935451b19710d62553b0b80ab10841 100644 (file)
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4; c-file-style: "bsd"; c-basic-offset: 4; fill-column: 108; indent-tabs-mode: nil -*-
  *
- * Copyright (c) 2002-2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "dnssd_server.h"
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+#include "mdns_managed_defaults.h"
+#include "QuerierSupport.h"
+#endif
+
 // Used on OSX(10.11.x onwards) for manipulating mDNSResponder program arguments
 #if APPLE_OSX_mDNSResponder
 // plist file to read the user's preferences
@@ -75,6 +80,7 @@
 #if MDNSRESPONDER_SUPPORTS(APPLE, PREALLOCATED_CACHE)
 #define kPreferencesKey_PreallocateCacheMemory    CFSTR("PreallocateCacheMemory")
 #endif
+#define kPreferencesKey_PQWorkaroundThreshold     CFSTR("PQWorkaroundThreshold")
 #endif
 
 //*************************************************************************************************************
@@ -289,10 +295,12 @@ mDNSlocal void mDNS_StatusCallback(mDNS *const m, mStatus result)
 mDNSlocal void ExitCallback(int sig)
 {
     (void)sig; // Unused
-    LogMsg("%s stopping", mDNSResponderVersionString);
+    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, PUB_S " stopping", mDNSResponderVersionString);
 
-    if (udsserver_exit() < 0) 
-        LogMsg("ExitCallback: udsserver_exit failed");
+    if (udsserver_exit() < 0)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "ExitCallback: udsserver_exit failed");
+    }
 
     debugf("ExitCallback: mDNS_StartExit");
     mDNS_StartExit(&mDNSStorage);
@@ -327,14 +335,18 @@ mDNSlocal void HandleSIG(int sig)
 
 mDNSexport void dump_state_to_fd(int fd)
 {
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     mDNS *const m = &mDNSStorage;
+#endif
     char buffer[1024];
     buffer[0] = '\0';
 
     mDNSs32 utc = mDNSPlatformUTC();
     const mDNSs32 now = mDNS_TimeNow(&mDNSStorage);
     NetworkInterfaceInfoOSX     *i;
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     DNSServer *s;
+#endif
     McastResolver *mr;
     char timestamp[64]; // 64 is enough to store the UTC timestmp
 
@@ -403,6 +415,23 @@ mDNSexport void dump_state_to_fd(int fd)
         }
     }
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    LogToFD(fd, "----------- DNS Services -----------");
+    {
+        const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
+        if (manager)
+        {
+            mdns_dns_service_manager_iterate(manager,
+            ^ bool (const mdns_dns_service_t service)
+            {
+                char *const desc = mdns_copy_description(service);
+                LogToFD(fd, "%s", desc ? desc : "<missing description>");
+                free(desc);
+                return false;
+            });
+        }
+    }
+#else
     LogToFD(fd, "--------- DNS Servers(%d) ----------", CountOfUnicastDNSServers(&mDNSStorage));
     if (!mDNSStorage.DNSServers) LogToFD(fd, "<None>");
     else
@@ -410,7 +439,7 @@ mDNSexport void dump_state_to_fd(int fd)
         for (s = mDNSStorage.DNSServers; s; s = s->next)
         {
             NetworkInterfaceInfoOSX *ifx = IfindexToInterfaceInfoOSX(s->interface);
-            LogToFD(fd, "DNS Server %##s %s%s%#a:%d %d %s %d %d %sv4 %sv6 %scell %sexp %sconstrained %sCLAT46 %sDNSSECAware",
+            LogToFD(fd, "DNS Server %##s %s%s%#a:%d %d %s %d %d %sv4 %sv6 %scell %sexp %sconstrained %sCLAT46",
                     s->domain.c, ifx ? ifx->ifinfo.ifname : "", ifx ? " " : "", &s->addr, mDNSVal16(s->port),
                     s->penaltyTime ? (s->penaltyTime - mDNS_TimeNow(&mDNSStorage)) : 0, DNSScopeToString(s->scopeType),
                     s->timeout, s->resGroupID,
@@ -419,15 +448,16 @@ mDNSexport void dump_state_to_fd(int fd)
                     s->isCell        ? "" : "!",
                     s->isExpensive   ? "" : "!",
                     s->isConstrained ? "" : "!",
-                    s->isCLAT46      ? "" : "!",
-                    s->DNSSECAware   ? "" : "!");
+                    s->isCLAT46      ? "" : "!");
         }
     }
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 
     LogToFD(fd, "v4answers %d", mDNSStorage.p->v4answers);
     LogToFD(fd, "v6answers %d", mDNSStorage.p->v6answers);
     LogToFD(fd, "Last DNS Trigger: %d ms ago", (now - mDNSStorage.p->DNSTrigger));
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     LogToFD(fd, "-------- Interface Monitors --------");
     const CFIndex n = m->p->InterfaceMonitors ? CFArrayGetCount(m->p->InterfaceMonitors) : 0;
     if (n > 0)
@@ -451,6 +481,7 @@ mDNSexport void dump_state_to_fd(int fd)
     {
         LogToFD(fd, "No interface monitors");
     }
+#endif
 
     LogToFD(fd, "--------- Mcast Resolvers ----------");
     if (!mDNSStorage.McastResolvers) LogToFD(fd, "<None>");
@@ -572,7 +603,6 @@ mDNSlocal void SignalCallback(CFMachPortRef port, void *msg, CFIndex size, void
         mDNS_Lock(m);
         FORALL_CACHERECORDS(slot, cg, rr)
         {
-            rr->resrec.mortality = Mortality_Mortal;
             mDNS_PurgeCacheResourceRecord(m, rr);
         }
         // Restart unicast and multicast queries
@@ -684,7 +714,6 @@ mDNSlocal void SignalDispatch(dispatch_source_t source)
         mDNS_Lock(m);
         FORALL_CACHERECORDS(slot, cg, rr)
         {
-           rr->resrec.mortality = Mortality_Mortal;
            mDNS_PurgeCacheResourceRecord(m, rr);
         }
         // Restart unicast and multicast queries
@@ -861,7 +890,7 @@ mDNSlocal mDNSu32 DHCPWakeTime(void)
     if (!now) LogMsg("DHCPWakeTime: CFAbsoluteTimeGetCurrent failed");
     else
     {
-        int ic, j;
+        CFIndex ic, j;
 
         const void *pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetDHCP);
         if (!pattern)
@@ -897,7 +926,7 @@ mDNSlocal mDNSu32 DHCPWakeTime(void)
                             else
                             {
                                 const UInt8 *d = CFDataGetBytePtr(lease);
-                                if (!d) LogMsg("DHCPWakeTime: CFDataGetBytePtr %d failed", j);
+                                if (!d) LogMsg("DHCPWakeTime: CFDataGetBytePtr %ld failed", (long)j);
                                 else
                                 {
                                     const mDNSu32 elapsed   = now - CFDateGetAbsoluteTime(start);
@@ -952,20 +981,55 @@ mDNSlocal mDNSBool AllowSleepNow(mDNSs32 now)
         {
             mDNSs32 dhcp = DHCPWakeTime();
             LogSPS("ComputeWakeTime: DHCP Wake %d", dhcp);
-            mDNSs32 interval = mDNSCoreIntervalToNextWake(m, now) / mDNSPlatformOneSecond;
-            if (interval > dhcp) interval = dhcp;
-
+            mDNSNextWakeReason reason = mDNSNextWakeReason_Null;
+            mDNSs32 interval = mDNSCoreIntervalToNextWake(m, now, &reason) / mDNSPlatformOneSecond;
+            if (interval > dhcp)
+            {
+                interval = dhcp;
+                reason = mDNSNextWakeReason_DHCPLeaseRenewal;
+            }
             // If we're not ready to sleep (failed to register with Sleep Proxy, maybe because of
             // transient network problem) then schedule a wakeup in one hour to try again. Otherwise,
             // a single SPS failure could result in a remote machine falling permanently asleep, requiring
             // someone to go to the machine in person to wake it up again, which would be unacceptable.
-            if (!ready && interval > 3600) interval = 3600;
-
+            if (!ready && interval > 3600)
+            {
+                interval = 3600;
+                reason = mDNSNextWakeReason_SleepProxyRegistrationRetry;
+            }
             //interval = 48; // For testing
 
 #if TARGET_OS_OSX && defined(kIOPMAcknowledgmentOptionSystemCapabilityRequirements)
             if (m->p->IOPMConnection)   // If lightweight-wake capability is available, use that
             {
+                CFStringRef reasonStr;
+                switch (reason)
+                {
+                case mDNSNextWakeReason_NATPortMappingRenewal:
+                    reasonStr = CFSTR("NAT port mapping renewal");
+                    break;
+
+                case mDNSNextWakeReason_RecordRegistrationRenewal:
+                    reasonStr = CFSTR("record registration renewal");
+                    break;
+
+                case mDNSNextWakeReason_UpkeepWake:
+                    reasonStr = CFSTR("upkeep wake");
+                    break;
+
+                case mDNSNextWakeReason_DHCPLeaseRenewal:
+                    reasonStr = CFSTR("DHCP lease renewal");
+                    break;
+
+                case mDNSNextWakeReason_SleepProxyRegistrationRetry:
+                    reasonStr = CFSTR("sleep proxy registration retry");
+                    break;
+
+                case mDNSNextWakeReason_Null:
+                default:
+                    reasonStr = CFSTR("unspecified");
+                    break;
+                }
                 const CFDateRef WakeDate = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent() + interval);
                 if (!WakeDate) LogMsg("ScheduleNextWake: CFDateCreate failed");
                 else
@@ -975,9 +1039,9 @@ mDNSlocal mDNSBool AllowSleepNow(mDNSs32 now)
                     if (Requirements == NULL) LogMsg("ScheduleNextWake: CFNumberCreate failed");
                     else
                     {
-                        const void *OptionKeys[2] = { kIOPMAckDHCPRenewWakeDate, kIOPMAckSystemCapabilityRequirements };
-                        const void *OptionVals[2] = {        WakeDate,          Requirements   };
-                        opts = CFDictionaryCreate(NULL, (void*)OptionKeys, (void*)OptionVals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+                        const void *OptionKeys[3] = { kIOPMAckDHCPRenewWakeDate, kIOPMAckSystemCapabilityRequirements, kIOPMAckClientInfoKey };
+                        const void *OptionVals[3] = { WakeDate, Requirements, reasonStr };
+                        opts = CFDictionaryCreate(NULL, OptionKeys, OptionVals, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
                         if (!opts) LogMsg("ScheduleNextWake: CFDictionaryCreate failed");
                         CFRelease(Requirements);
                     }
@@ -1043,7 +1107,7 @@ mDNSlocal mDNSBool AllowSleepNow(mDNSs32 now)
     m->TimeSlept = mDNSPlatformUTC();
 
 #if TARGET_OS_OSX && defined(kIOPMAcknowledgmentOptionSystemCapabilityRequirements)
-    if (m->p->IOPMConnection) IOPMConnectionAcknowledgeEventWithOptions(m->p->IOPMConnection, m->p->SleepCookie, opts);
+    if (m->p->IOPMConnection) IOPMConnectionAcknowledgeEventWithOptions(m->p->IOPMConnection, (IOPMConnectionMessageToken)m->p->SleepCookie, opts);
     else
 #endif
     if (result == kIOReturnSuccess) IOAllowPowerChange (m->p->PowerConnection, m->p->SleepCookie);
@@ -1166,6 +1230,9 @@ mDNSlocal void * KQueueLoop(void *m_param)
     const int multiplier = 1000000000 / mDNSPlatformOneSecond;
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSD_XPC_SERVICE)
+    dnssd_server_init();
+#endif
     pthread_mutex_lock(&PlatformStorage.BigMutex);
     LogInfo("Starting time value 0x%08lX (%ld)", (mDNSu32)mDNSStorage.timenow_last, mDNSStorage.timenow_last);
 
@@ -1219,7 +1286,7 @@ mDNSlocal void * KQueueLoop(void *m_param)
             }
             if (mDNS_ExitNow(m, now))
             {
-                LogInfo("mDNS_FinalExit");
+                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_FinalExit");
                 mDNS_FinalExit(&mDNSStorage);
                 usleep(1000);       // Little 1ms pause before exiting, so we don't lose our final syslog messages
                 exit(0);
@@ -1316,7 +1383,7 @@ mDNSlocal void * KQueueLoop(void *m_param)
                 const KQueueEntry *const kqentry = new_events[i].udata;
                 mDNSs32 stime = mDNSPlatformRawTime();
                 const char *const KQtask = kqentry->KQtask; // Grab a copy in case KQcallback deletes the task
-                kqentry->KQcallback(new_events[i].ident, new_events[i].filter, kqentry->KQcontext, (new_events[i].flags & EV_EOF) != 0);
+                kqentry->KQcallback((int)new_events[i].ident, new_events[i].filter, kqentry->KQcontext, (new_events[i].flags & EV_EOF) != 0);
                 mDNSs32 etime = mDNSPlatformRawTime();
                 if (etime - stime >= WatchDogReportingThreshold)
                 {
@@ -1421,11 +1488,13 @@ mDNSlocal void SandboxProcess(void)
     } \
     while (0)
 
-os_log_t mDNSLogCategory_Default = NULL;
-os_log_t mDNSLogCategory_mDNS    = NULL;
-os_log_t mDNSLogCategory_uDNS    = NULL;
-os_log_t mDNSLogCategory_SPS     = NULL;
-os_log_t mDNSLogCategory_XPC     = NULL;
+os_log_t mDNSLogCategory_Default    = NULL;
+os_log_t mDNSLogCategory_mDNS       = NULL;
+os_log_t mDNSLogCategory_uDNS       = NULL;
+os_log_t mDNSLogCategory_SPS        = NULL;
+os_log_t mDNSLogCategory_XPC        = NULL;
+os_log_t mDNSLogCategory_Analytics  = NULL;
+os_log_t mDNSLogCategory_DNSSEC     = NULL;
 
 mDNSlocal void init_logging(void)
 {
@@ -1434,6 +1503,8 @@ mDNSlocal void init_logging(void)
     MDNS_OS_LOG_CATEGORY_INIT(uDNS);
     MDNS_OS_LOG_CATEGORY_INIT(SPS);
     MDNS_OS_LOG_CATEGORY_INIT(XPC);
+    MDNS_OS_LOG_CATEGORY_INIT(Analytics);
+    MDNS_OS_LOG_CATEGORY_INIT(DNSSEC);
 }
 #endif
 
@@ -1466,11 +1537,13 @@ mDNSexport int main(int argc, char **argv)
     LogMsg("block bytes wasted  %5d", 32*1024 - sizeof(CacheEntity) * RR_CACHE_SIZE);
 #endif
 
+#if !DEBUG
     if (0 == geteuid())
     {
         LogMsg("mDNSResponder cannot be run as root !! Exiting..");
         return -1;
     }
+#endif // !DEBUG
 
     for (i=1; i<argc; i++)
     {
@@ -1539,6 +1612,17 @@ mDNSexport int main(int argc, char **argv)
 #if MDNSRESPONDER_SUPPORTS(APPLE, PREALLOCATED_CACHE)
     PreallocateCacheMemory    = PreferencesGetValueBool(kPreferencesKey_PreallocateCacheMemory,    PreallocateCacheMemory);
 #endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    PQWorkaroundThreshold     = PreferencesGetValueInt(kPreferencesKey_PQWorkaroundThreshold,      PQWorkaroundThreshold);
+    CFDictionaryRef managedDefaults = mdns_managed_defaults_create("com.apple.mDNSResponder", NULL);
+    if (managedDefaults)
+    {
+        PQWorkaroundThreshold = mdns_managed_defaults_get_int_clamped(managedDefaults,
+            kPreferencesKey_PQWorkaroundThreshold, PQWorkaroundThreshold, NULL);
+        CFRelease(managedDefaults);
+        managedDefaults = NULL;
+    }
+#endif
 
     // Note that mDNSPlatformInit will set DivertMulticastAdvertisements in the mDNS structure
     if (NoMulticastAdvertisements)
@@ -1614,9 +1698,6 @@ mDNSexport int main(int argc, char **argv)
 
     // Need to Start XPC Server Before LaunchdCheckin() (Reason: radar:11023750)
     xpc_server_init();
-#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSD_XPC_SERVICE)
-    dnssd_server_init();
-#endif
 #if DEBUG
     if (!useDebugSocket) {
         if (LaunchdCheckin() == 0)
@@ -1628,7 +1709,7 @@ mDNSexport int main(int argc, char **argv)
     LaunchdCheckin();
 #endif
 
-    status = udsserver_init(launchd_fds, launchd_fds_count);
+    status = udsserver_init(launchd_fds, (mDNSu32)launchd_fds_count);
     if (status) { LogMsg("Daemon start: udsserver_init failed"); goto exit; }
 
     mDNSMacOSXNetworkChanged();
@@ -1706,7 +1787,7 @@ mDNSexport mStatus udsSupportAddFDToEventLoop(int fd, udsEventCallback callback,
 int udsSupportReadFD(dnssd_sock_t fd, char *buf, int len, int flags, void *platform_data)
 {
     (void) platform_data;
-    return recv(fd, buf, len, flags);
+    return (int)recv(fd, buf, len, flags);
 }
 
 mDNSexport mStatus udsSupportRemoveFDFromEventLoop(int fd, void *platform_data)     // Note: This also CLOSES the file descriptor
index 565dda6902eb4e7381525c66b4d708b15a113a31..a924d8937b482923b00f41a9d4a72dadd45b6df2 100644 (file)
@@ -1,11 +1,11 @@
 /*
- * Copyright (c) 2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *     https://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
 
 #include "dnssd_object.h"
 #include "dnssd_xpc.h"
+#include "dnssd_svcb.h"
 
 #include <CoreUtils/CoreUtils.h>
 #include <os/object_private.h>
 #include <xpc/private.h>
 
-#if 0
 //======================================================================================================================
-#pragma mark - Kind Declarations
-#endif
+// MARK: - Kind Declarations
 
 #define DNSSD_STRUCT(NAME)     struct dnssd_ ## NAME ## _s
 #define DNSSD_TYPE(NAME)       dnssd_ ## NAME ## _t
@@ -74,6 +73,7 @@
 
 DNSSD_KIND_DECLARE(getaddrinfo);
 DNSSD_KIND_DECLARE(getaddrinfo_result);
+DNSSD_KIND_DECLARE(cname_array);
 
 typedef char * (*dnssd_copy_description_f)(dnssd_any_t object, bool debug, bool privacy);
 typedef void   (*dnssd_finalize_f)(dnssd_any_t object);
@@ -86,13 +86,11 @@ struct dnssd_kind_s {
        dnssd_finalize_f                        finalize;                       // Releases object's resources right before the object is freed.
 };
 
-#if 0
 //======================================================================================================================
-#pragma mark - dnssd_object Kind Definition
-#endif
+// MARK: - Object Kind Definition
 
 struct dnssd_object_s {
-       _OS_OBJECT_HEADER(const void *_os_obj_isa, _os_obj_refcnt, _os_obj_xref_cnt);
+       _OS_OBJECT_HEADER(const void * __ptrauth_objc_isa_pointer _os_obj_isa, _os_obj_refcnt, _os_obj_xref_cnt);
        dnssd_kind_t    kind;   // Pointer to an object's kind.
 };
 
@@ -103,10 +101,8 @@ static const struct dnssd_kind_s _dnssd_object_kind = {
        NULL,           // No finalize method.
 };
 
-#if 0
 //======================================================================================================================
-#pragma mark - dnssd_getaddrinfo Kind Definition
-#endif
+// MARK: - GetAddrInfo Kind Definition
 
 typedef enum {
        dnssd_getaddrinfo_state_nascent         = 0,
@@ -124,6 +120,8 @@ struct dnssd_getaddrinfo_s {
        dispatch_queue_t                                        mutex_queue;    // Mutex for accessing result_list from different queues.
        xpc_object_t                                            params;                 // Parameters dictionary for getaddrinfo command.
        xpc_object_t                                            hostname;               // Reference to hostname from parameters dictionary.
+       dnssd_cname_array_t                                     cnames_a;               // Array of hostname's canonical names for A results.
+       dnssd_cname_array_t                                     cnames_aaaa;    // Array of hostname's canonical names for AAAA results.
        dispatch_source_t                                       event_source;   // Data source for triggering result and event handlers.
        dnssd_getaddrinfo_result_t                      result_list;    // List of getaddrinfo results.
        dnssd_getaddrinfo_result_handler_t      result_handler; // User's result handler.
@@ -135,28 +133,46 @@ struct dnssd_getaddrinfo_s {
 
 DNSSD_KIND_DEFINE(getaddrinfo, object);
 
-#if 0
 //======================================================================================================================
-#pragma mark - dnssd_getaddrinfo_result Kind Definition
-#endif
+// MARK: - GetAddrInfo Result Kind Definition
 
 struct dnssd_getaddrinfo_result_s {
-       struct dnssd_object_s                   base;                           // Object base.
-       sockaddr_ip                                             addr;                           // IPv4 or IPv6 address of hostname.
-       xpc_object_t                                    hostname;                       // Requested hostname to resolve.
-       xpc_object_t                                    actual_hostname;        // The actual/canonical hostname of the requested hostname.
-       xpc_object_t                                    auth_tag;                       // Authentication tag.
-       uint32_t                                                interface_index;        // Interface index to which the result pertains.
-       dnssd_getaddrinfo_result_type_t type;                           // Type of getaddrinfo result.
-       dnssd_getaddrinfo_result_t              next;                           // Next getaddrinfo result in list.
+       struct dnssd_object_s                           base;                           // Object base.
+       dnssd_getaddrinfo_result_t                      next;                           // Next getaddrinfo result in list.
+       sockaddr_ip                                                     addr;                           // IPv4 or IPv6 address of hostname.
+       xpc_object_t                                            hostname;                       // Requested hostname to resolve.
+       xpc_object_t                                            actual_hostname;        // The actual/canonical hostname of the requested hostname.
+       dnssd_cname_array_t                                     cnames;                         // Array of hostname's canonical names.
+       xpc_object_t                                            auth_tag;                       // Authentication tag.
+       xpc_object_t                                            provider_name;          // Provider name.
+       xpc_object_t                                            ech_config;                     // SVCB ECH config.
+       xpc_object_t                                            address_hints;          // SVCB address hints.
+       xpc_object_t                                            doh_uri;                        // SVCB DoH URI.
+       xpc_object_t                                            alpn_values;            // SVCB ALPN values.
+       xpc_object_t                                            service_name;           // SVCB name.
+       uint16_t                                                        port;                           // SVCB port.
+       uint16_t                                                        priority;                       // SVCB priority.
+       uint32_t                                                        if_index;                       // Interface index to which the result pertains.
+       dnssd_getaddrinfo_result_type_t         type;                           // Type of getaddrinfo result.
+       dnssd_getaddrinfo_result_protocol_t     protocol;                       // Protocol used for getaddrinfo result.
+       bool                                                            is_from_cache;          // True if the result was an answer from the cache.
+       bool                                                            valid_svcb;                     // True if SVCB info is valid.
 };
 
 DNSSD_KIND_DEFINE(getaddrinfo_result, object);
 
-#if 0
 //======================================================================================================================
-#pragma mark - Constants
-#endif
+// MARK: - CName Array Kind Definition
+
+struct dnssd_cname_array_s {
+       struct dnssd_object_s   base;           // Object base.
+       xpc_object_t                    xpc_array;      // Underlying array of cnames as strings. Important: Must not be modified.
+};
+
+DNSSD_KIND_DEFINE(cname_array, object);
+
+//======================================================================================================================
+// MARK: - Constants
 
 #define DNSSD_EVENT_HAVE_RESULTS       (1U << 0)       // Results are available.
 #define DNSSD_EVENT_REMOVE_ALL         (1U << 1)       // Previously delivered results are no longer valid.
@@ -164,15 +180,12 @@ DNSSD_KIND_DEFINE(getaddrinfo_result, object);
 
 // Strings for redacted description items.
 
-#define DNSSD_REDACTED_HOSTNAME_STR                    "<redacted hostname>"
-#define DNSSD_REDACTED_ACTUAL_HOSTNAME_STR     "<redacted actual hostname>"
-#define DNSSD_REDACTED_IPv4_ADDRESS_STR                "<redacted IPv4 address>"
-#define DNSSD_REDACTED_IPv6_ADDRESS_STR                "<redacted IPv6 address>"
+#define DNSSD_REDACTED_HOSTNAME_STR            "<redacted hostname>"
+#define DNSSD_REDACTED_IPv4_ADDRESS_STR        "<redacted IPv4 address>"
+#define DNSSD_REDACTED_IPv6_ADDRESS_STR        "<redacted IPv6 address>"
 
-#if 0
 //======================================================================================================================
-#pragma mark - Local Prototypes
-#endif
+// MARK: - Local Prototypes
 
 static dispatch_queue_t
 _dnssd_client_queue(void);
@@ -211,11 +224,21 @@ static void
 _dnssd_getaddrinfo_post_error_event(dnssd_getaddrinfo_t gai, OSStatus error);
 
 static dnssd_getaddrinfo_result_t
-_dnssd_getaddrinfo_result_create_from_dictionary(xpc_object_t hostname, xpc_object_t result_dict, OSStatus *out_error);
+_dnssd_getaddrinfo_create_result_from_dictionary(dnssd_getaddrinfo_t gai, xpc_object_t result_dict,
+       OSStatus *out_error);
+
+static dnssd_cname_array_t
+_dnssd_cname_array_create(xpc_object_t xpc_array, OSStatus *out_error);
+
+static dnssd_cname_array_t
+_dnssd_get_empty_cname_array(void);
 
 static DNSServiceErrorType
 _dnssd_osstatus_to_dns_service_error(OSStatus status);
 
+static int
+_dnssd_snprintf(char **dst, const char *end, const char *format, ...);
+
 #if !defined(dnssd_release_null_safe)
        #define dnssd_release_null_safe(X)      \
                do {                                                    \
@@ -229,23 +252,21 @@ _dnssd_osstatus_to_dns_service_error(OSStatus status);
        #define dnssd_forget(X) ForgetCustom(X, dnssd_release)
 #endif
 
-#if 0
 //======================================================================================================================
-#pragma mark - dnssd_object Public Methods
-#endif
+// MARK: - Object Public Methods
 
 void
-dnssd_retain(dnssd_any_t object)
+dnssd_retain(const dnssd_any_t any)
 {
-       os_retain(object.base);
+       os_retain(any.object);
 }
 
 //======================================================================================================================
 
 void
-dnssd_release(dnssd_any_t object)
+dnssd_release(const dnssd_any_t any)
 {
-       os_release(object.base);
+       os_release(any.object);
 }
 
 //======================================================================================================================
@@ -256,17 +277,16 @@ dnssd_copy_description(dnssd_any_t object)
        return dnssd_object_copy_description(object, false, false);
 }
 
-#if 0
 //======================================================================================================================
-#pragma mark - dnssd_object Private Methods
-#endif
+// MARK: - Object Private Methods
 
 char *
-dnssd_object_copy_description(dnssd_any_t object, bool debug, bool privacy)
+dnssd_object_copy_description(const dnssd_any_t any, const bool debug, const bool privacy)
 {
-       for (dnssd_kind_t kind = object.base->kind; kind; kind = kind->superkind) {
+       const dnssd_object_t me = any.object;
+       for (dnssd_kind_t kind = me->kind; kind; kind = kind->superkind) {
                if (kind->copy_description) {
-                       char *desc = kind->copy_description(object, debug, privacy);
+                       char *desc = kind->copy_description(me, debug, privacy);
                        return desc;
                }
        }
@@ -276,19 +296,18 @@ dnssd_object_copy_description(dnssd_any_t object, bool debug, bool privacy)
 //======================================================================================================================
 
 void
-dnssd_object_finalize(dnssd_any_t object)
+dnssd_object_finalize(const dnssd_any_t any)
 {
-       for (dnssd_kind_t kind = object.base->kind; kind; kind = kind->superkind) {
+       const dnssd_object_t me = any.object;
+       for (dnssd_kind_t kind = me->kind; kind; kind = kind->superkind) {
                if (kind->finalize) {
-                       kind->finalize(object);
+                       kind->finalize(me);
                }
        }
 }
 
-#if 0
 //======================================================================================================================
-#pragma mark - dnssd_getaddrinfo Public Methods
-#endif
+// MARK: - GetAddrInfo Public Methods
 
 dnssd_getaddrinfo_t
 dnssd_getaddrinfo_create(void)
@@ -339,6 +358,16 @@ dnssd_getaddrinfo_set_flags(dnssd_getaddrinfo_t me, DNSServiceFlags flags)
 
 //======================================================================================================================
 
+void
+dnssd_getaddrinfo_set_account_id(dnssd_getaddrinfo_t me, const char * account_id)
+{
+       if (!me->user_activated) {
+               dnssd_xpc_parameters_set_account_id(me->params, account_id);
+       }
+}
+
+//======================================================================================================================
+
 void
 dnssd_getaddrinfo_set_hostname(dnssd_getaddrinfo_t me, const char *hostname)
 {
@@ -369,6 +398,16 @@ dnssd_getaddrinfo_set_protocols(dnssd_getaddrinfo_t me, DNSServiceProtocol proto
 
 //======================================================================================================================
 
+void
+dnssd_getaddrinfo_set_service_scheme(dnssd_getaddrinfo_t me, const char *service_scheme)
+{
+       if (!me->user_activated) {
+               dnssd_xpc_parameters_set_service_scheme(me->params, service_scheme);
+       }
+}
+
+//======================================================================================================================
+
 void
 dnssd_getaddrinfo_set_delegate_pid(dnssd_getaddrinfo_t me, pid_t pid)
 {
@@ -389,6 +428,16 @@ dnssd_getaddrinfo_set_delegate_uuid(dnssd_getaddrinfo_t me, uuid_t uuid)
 
 //======================================================================================================================
 
+void
+dnssd_getaddrinfo_set_delegate_audit_token(dnssd_getaddrinfo_t me, audit_token_t audit_token)
+{
+       if (!me->user_activated) {
+               dnssd_xpc_parameters_set_delegate_audit_token(me->params, &audit_token);
+       }
+}
+
+//======================================================================================================================
+
 void
 dnssd_getaddrinfo_set_result_handler(dnssd_getaddrinfo_t me, dnssd_getaddrinfo_result_handler_t handler)
 {
@@ -423,6 +472,26 @@ dnssd_getaddrinfo_set_need_authenticated_results(dnssd_getaddrinfo_t me, bool ne
 
 //======================================================================================================================
 
+void
+dnssd_getaddrinfo_set_need_encrypted_query(dnssd_getaddrinfo_t me, bool need, _Nullable xpc_object_t fallback_config)
+{
+       if (!me->user_activated) {
+               dnssd_xpc_parameters_set_need_encrypted_query(me->params, need, fallback_config);
+       }
+}
+
+//======================================================================================================================
+
+void
+dnssd_getaddrinfo_add_resolver_uuid(dnssd_getaddrinfo_t me, uuid_t _Nonnull uuid)
+{
+       if (!me->user_activated) {
+               dnssd_xpc_parameters_add_resolver_uuid(me->params, uuid);
+       }
+}
+
+//======================================================================================================================
+
 void
 dnssd_getaddrinfo_activate(dnssd_getaddrinfo_t me)
 {
@@ -497,59 +566,50 @@ _dnssd_getaddrinfo_invalidate(dnssd_getaddrinfo_t me)
        }
 }
 
-#if 0
 //======================================================================================================================
-#pragma mark - dnssd_getaddrinfo Private Methods
-#endif
+// MARK: - GetAddrInfo Private Methods
 
 static char *
 _dnssd_getaddrinfo_copy_description(dnssd_getaddrinfo_t me, const bool debug, const bool privacy)
 {
-       const char *hostname;
+       const char *hostname_str;
        if (me->hostname) {
-               hostname = xpc_string_get_string_ptr(me->hostname);
-               if (privacy && hostname) {
-                       hostname = DNSSD_REDACTED_HOSTNAME_STR;
+               hostname_str = xpc_string_get_string_ptr(me->hostname);
+               if (privacy && hostname_str) {
+                       hostname_str = DNSSD_REDACTED_HOSTNAME_STR;
                }
        } else {
-               hostname = NULL;
+               hostname_str = NULL;
        }
-
-       char *  desc    = NULL;
-       char *  buf_ptr = NULL;
-       size_t  buf_len = 0;
+       char *desc = NULL;
+       char *buf_ptr = NULL;
+       size_t buf_len = 0;
        for (;;)
        {
-               size_t                  need    = 0;
-               char *                  dst             = buf_ptr;
-               char * const    end             = &buf_ptr[buf_len];
-               int                             n;
-
+               int n;
+               char * dst = buf_ptr;
+               char * const end = &buf_ptr[buf_len];
+               size_t desc_len = 0;
                if (debug) {
-                       const size_t len = (size_t)(end - dst);
-                       n = snprintf(dst, len, "dnssd_%s (%p): ", me->base.kind->name, (void *)me);
+                       n = _dnssd_snprintf(&dst, end, "dnssd_%s (%p): ", me->base.kind->name, (void *)me);
                        require_quiet(n >= 0, exit);
-
-                       if (buf_ptr) {
-                               dst = (((size_t)n) < len) ? &dst[n] : end;
-                       } else {
-                               need += (size_t)n;
-                       }
+                       desc_len += (size_t)n;
                }
-               n = snprintf(dst, (size_t)(end - dst), "hostname = %s", hostname ? hostname : "<NO HOSTNAME>");
+               n = _dnssd_snprintf(&dst, end, "hostname: %s", hostname_str ? hostname_str : "<NO HOSTNAME>");
                require_quiet(n >= 0, exit);
+               desc_len += (size_t)n;
 
                if (!buf_ptr) {
-                       need += (size_t)n;
-                       buf_len = need + 1;
+                       buf_len = desc_len + 1;
                        buf_ptr = malloc(buf_len);
                        require_quiet(buf_ptr, exit);
+                       buf_ptr[0] = '\0';
                } else {
                        break;
                }
        }
-       desc    = buf_ptr;
-       buf_ptr = NULL;
+       desc = buf_ptr;
+       buf_ptr = NULL;
 
 exit:
        FreeNullSafe(buf_ptr);
@@ -565,6 +625,8 @@ _dnssd_getaddrinfo_finalize(dnssd_getaddrinfo_t me)
        dispatch_forget(&me->mutex_queue);
        xpc_forget(&me->params);
        xpc_forget(&me->hostname);
+       dnssd_forget(&me->cnames_a);
+       dnssd_forget(&me->cnames_aaaa);
        BlockForget(&me->result_handler);
        BlockForget(&me->event_handler);
 }
@@ -590,6 +652,8 @@ _dnssd_getaddrinfo_append_results(dnssd_getaddrinfo_t me, dnssd_getaddrinfo_resu
 static void
 _dnssd_getaddrinfo_remove_all_results(dnssd_getaddrinfo_t me)
 {
+       dnssd_forget(&me->cnames_a);
+       dnssd_forget(&me->cnames_aaaa);
        dnssd_getaddrinfo_result_t result_list = _dnssd_getaddrinfo_take_results(me);
        if (me->event_source) {
                dispatch_source_merge_data(me->event_source, DNSSD_EVENT_REMOVE_ALL);
@@ -628,10 +692,8 @@ _dnssd_getaddrinfo_post_error_event(dnssd_getaddrinfo_t me, OSStatus error)
        dispatch_source_merge_data(me->event_source, DNSSD_EVENT_ERROR);
 }
 
-#if 0
 //======================================================================================================================
-#pragma mark - dnssd_getaddrinfo_result Public Methods
-#endif
+// MARK: - GetAddrInfo Result Public Methods
 
 dnssd_getaddrinfo_result_type_t
 dnssd_getaddrinfo_result_get_type(dnssd_getaddrinfo_result_t me)
@@ -665,10 +727,99 @@ dnssd_getaddrinfo_result_get_hostname(dnssd_getaddrinfo_result_t me)
 
 //======================================================================================================================
 
+const char *
+dnssd_getaddrinfo_result_get_doh_uri(dnssd_getaddrinfo_result_t me)
+{
+       return xpc_string_get_string_ptr(me->doh_uri);
+}
+
+//======================================================================================================================
+
+uint16_t
+dnssd_getaddrinfo_result_get_service_port(dnssd_getaddrinfo_result_t me)
+{
+       return me->port;
+}
+
+//======================================================================================================================
+
+uint16_t
+dnssd_getaddrinfo_result_get_service_priority(dnssd_getaddrinfo_result_t me)
+{
+       return me->priority;
+}
+
+//======================================================================================================================
+
+const char *
+dnssd_getaddrinfo_result_get_service_name(dnssd_getaddrinfo_result_t me)
+{
+       return xpc_string_get_string_ptr(me->service_name);
+}
+
+//======================================================================================================================
+
+bool
+dnssd_getaddrinfo_result_service_is_valid(dnssd_getaddrinfo_result_t me)
+{
+       return me->valid_svcb;
+}
+
+//======================================================================================================================
+
+void
+dnssd_getaddrinfo_result_enumerate_alpn_values(dnssd_getaddrinfo_result_t me,
+                                                                                          DNSSD_NOESCAPE dnssd_getaddrinfo_enumerate_alpn_values_block_t enumerator)
+{
+       if (me->alpn_values != NULL) {
+               xpc_array_apply(me->alpn_values, ^bool(__unused size_t index, xpc_object_t _Nonnull value) {
+                       const char *string = xpc_string_get_string_ptr(value);
+                       return enumerator(string);
+               });
+       }
+}
+
+//======================================================================================================================
+
+void
+dnssd_getaddrinfo_result_enumerate_service_address_hints(dnssd_getaddrinfo_result_t me,
+                                                                                                                DNSSD_NOESCAPE dnssd_getaddrinfo_enumerate_addresses_block_t enumerator)
+{
+       if (me->address_hints != NULL) {
+               xpc_array_apply(me->address_hints, ^bool(__unused size_t index, xpc_object_t _Nonnull value) {
+                       const void *bytes = xpc_data_get_bytes_ptr(value);
+                       return enumerator((const struct sockaddr *)bytes);
+               });
+       }
+}
+
+//======================================================================================================================
+
+const void *
+dnssd_getaddrinfo_result_get_ech_config(dnssd_getaddrinfo_result_t me, size_t *out_length)
+{
+       const void *    ech_ptr;
+       size_t                  ech_len;
+
+       if (me->ech_config) {
+               ech_ptr = xpc_data_get_bytes_ptr(me->ech_config);
+               ech_len = xpc_data_get_length(me->ech_config);
+       } else {
+               ech_ptr = NULL;
+               ech_len = 0;
+       }
+       if (out_length) {
+               *out_length = ech_len;
+       }
+       return ech_ptr;
+}
+
+//======================================================================================================================
+
 uint32_t
 dnssd_getaddrinfo_result_get_interface_index(dnssd_getaddrinfo_result_t me)
 {
-       return me->interface_index;
+       return me->if_index;
 }
 
 //======================================================================================================================
@@ -692,10 +843,40 @@ dnssd_getaddrinfo_result_get_authentication_tag(dnssd_getaddrinfo_result_t me, s
        return auth_tag_ptr;
 }
 
-#if 0
 //======================================================================================================================
-#pragma mark - dnssd_getaddrinfo_result Private Methods
-#endif
+
+dnssd_getaddrinfo_result_protocol_t
+dnssd_getaddrinfo_result_get_protocol(dnssd_getaddrinfo_result_t me)
+{
+       return me->protocol;
+}
+
+//======================================================================================================================
+
+const char *
+dnssd_getaddrinfo_result_get_provider_name(dnssd_getaddrinfo_result_t me)
+{
+       return xpc_string_get_string_ptr(me->provider_name);
+}
+
+//======================================================================================================================
+
+dnssd_cname_array_t
+dnssd_getaddrinfo_result_get_cnames(const dnssd_getaddrinfo_result_t me)
+{
+       return (me->cnames ? me->cnames : _dnssd_get_empty_cname_array());
+}
+
+//======================================================================================================================
+
+bool
+dnssd_getaddrinfo_result_is_from_cache(const dnssd_getaddrinfo_result_t me)
+{
+       return me->is_from_cache;
+}
+
+//======================================================================================================================
+// MARK: - GetAddrInfo Result Private Methods
 
 static char *
 _dnssd_getaddrinfo_result_copy_description(dnssd_getaddrinfo_result_t me, const bool debug, const bool privacy)
@@ -709,74 +890,66 @@ _dnssd_getaddrinfo_result_copy_description(dnssd_getaddrinfo_result_t me, const
        } else {
                hostname = NULL;
        }
-
-       const char *actual_hostname;
-       if (me->actual_hostname) {
-               actual_hostname = xpc_string_get_string_ptr(me->actual_hostname);
-               if (privacy && actual_hostname) {
-                       actual_hostname = DNSSD_REDACTED_ACTUAL_HOSTNAME_STR;
-               }
-       } else {
-               actual_hostname = NULL;
-       }
-       char addr_buf[INET6_ADDRSTRLEN];
-       check_compile_time_code(sizeof(addr_buf) >= INET_ADDRSTRLEN);
-       check_compile_time_code(sizeof(addr_buf) >= INET6_ADDRSTRLEN);
-
-       const char *addr;
+       char addr_buf[INET6_ADDRSTRLEN + 1 + Max(IF_NAMESIZE, 10) + 1];
+       const char *addr_str;
        if (me->addr.sa.sa_family == AF_INET) {
                if (privacy) {
-                       addr = DNSSD_REDACTED_IPv4_ADDRESS_STR;
+                       addr_str = DNSSD_REDACTED_IPv4_ADDRESS_STR;
                } else {
-                       addr = inet_ntop(AF_INET, &me->addr.v4.sin_addr.s_addr, addr_buf, (socklen_t)sizeof(addr_buf));
+                       check_compile_time_code(sizeof(addr_buf) >= INET_ADDRSTRLEN);
+                       addr_str = inet_ntop(AF_INET, &me->addr.v4.sin_addr.s_addr, addr_buf, (socklen_t)sizeof(addr_buf));
                }
        } else if (me->addr.sa.sa_family == AF_INET6) {
                if (privacy) {
-                       addr = DNSSD_REDACTED_IPv6_ADDRESS_STR;
+                       addr_str = DNSSD_REDACTED_IPv6_ADDRESS_STR;
                } else {
-                       addr = inet_ntop(AF_INET6, me->addr.v6.sin6_addr.s6_addr, addr_buf, (socklen_t)sizeof(addr_buf));
+                       const struct sockaddr_in6 * const sin6 = &me->addr.v6;
+                       check_compile_time_code(sizeof(addr_buf) >= INET6_ADDRSTRLEN);
+                       addr_str = inet_ntop(AF_INET6, sin6->sin6_addr.s6_addr, addr_buf, (socklen_t)sizeof(addr_buf));
+                       if (addr_str && (sin6->sin6_scope_id > 0)) {
+                               char * const            dst = &addr_buf[strlen(addr_buf)];
+                               const char * const      end = &addr_buf[countof(addr_buf)];
+                               char ifname[IF_NAMESIZE + 1];
+                               if (if_indextoname(sin6->sin6_scope_id, ifname)) {
+                                       snprintf(dst, (size_t)(end - dst), "%%%s", ifname);
+                               } else {
+                                       snprintf(dst, (size_t)(end - dst), "%%%u", sin6->sin6_scope_id);
+                               }
+                       }
                }
        } else {
-               addr = NULL;
+               addr_str = NULL;
        }
-
-       char *  desc    = NULL;
-       char *  buf_ptr = NULL;
-       size_t  buf_len = 0;
+       char *desc = NULL;
+       char *buf_ptr = NULL;
+       size_t buf_len = 0;
        for (;;)
        {
-               char *                  dst             = buf_ptr;
-               char * const    end             = &buf_ptr[buf_len];
-               size_t                  need    = 0;
-               int                             n;
-
+               char *dst = buf_ptr;
+               char * const end = &buf_ptr[buf_len];
+               size_t desc_len = 0;
+               int n;
                if (debug) {
-                       const size_t len = (size_t)(end - dst);
-                       n = snprintf(dst, len, "dnssd_%s (%p): ", me->base.kind->name, (void *)me);
+                       n = _dnssd_snprintf(&dst, end, "dnssd_%s (%p): ", me->base.kind->name, (void *)me);
                        require_quiet(n >= 0, exit);
-
-                       if (buf_ptr) {
-                               dst = (((size_t)n) < len) ? &dst[n] : end;
-                       } else {
-                               need += (size_t)n;
-                       }
+                       desc_len += (size_t)n;
                }
-               n = snprintf(dst, (size_t)(end - dst), "[%s] %s (%s) -> %s (interface index %lu)",
-                       dnssd_getaddrinfo_result_type_to_string(me->type), hostname ? hostname : "<NO HOSTNAME>",
-                       actual_hostname ? actual_hostname : "<NO ACTUAL HOSTNAME>", addr ? addr : "<NO IP ADDRESS>",
-                       (unsigned long)me->interface_index);
+               n = _dnssd_snprintf(&dst, end, "hostname: %s, address: %s, type: %s, ifindex: %lu",
+                       hostname ? hostname : "<NO HOSTNAME>", addr_str ? addr_str : "<NO ADDR>",
+                       dnssd_getaddrinfo_result_type_to_string(me->type), (unsigned long)me->if_index);
                require_quiet(n >= 0, exit);
+               desc_len += (size_t)n;
 
                if (!buf_ptr) {
-                       need += (size_t)n;
-                       buf_len = need + 1;
+                       buf_len = desc_len + 1;
                        buf_ptr = malloc(buf_len);
                        require_quiet(buf_ptr, exit);
+                       buf_ptr[0] = '\0';
                } else {
                        break;
                }
        }
-       desc    = buf_ptr;
+       desc = buf_ptr;
        buf_ptr = NULL;
 
 exit:
@@ -791,13 +964,185 @@ _dnssd_getaddrinfo_result_finalize(dnssd_getaddrinfo_result_t me)
 {
        xpc_forget(&me->hostname);
        xpc_forget(&me->actual_hostname);
+       dnssd_forget(&me->cnames);
        xpc_forget(&me->auth_tag);
+       xpc_forget(&me->provider_name);
+       xpc_forget(&me->doh_uri);
+       xpc_forget(&me->alpn_values);
+       xpc_forget(&me->service_name);
+       xpc_forget(&me->ech_config);
+       xpc_forget(&me->address_hints);
 }
 
-#if 0
 //======================================================================================================================
-#pragma mark - dnssd Client
-#endif
+
+static OSStatus
+_dnssd_getaddrinfo_set_cnames(const dnssd_getaddrinfo_t me, const int record_type, const xpc_object_t xpc_cname_array)
+{
+       dnssd_cname_array_t *cnames_ptr;
+       switch (record_type) {
+               case kDNSServiceType_A:
+                       cnames_ptr = &me->cnames_a;
+                       break;
+
+               case kDNSServiceType_AAAA:
+                       cnames_ptr = &me->cnames_aaaa;
+                       break;
+
+               default:
+                       cnames_ptr = NULL;
+                       break;
+       }
+       OSStatus err;
+       if (cnames_ptr) {
+               dnssd_forget(cnames_ptr);
+               *cnames_ptr = _dnssd_cname_array_create(xpc_cname_array, &err);
+               require_noerr_quiet(err, exit);
+       }
+       err = kNoErr;
+
+exit:
+       return err;
+}
+
+//======================================================================================================================
+
+static xpc_object_t
+_dnssd_getaddrinfo_get_cname_array(const dnssd_getaddrinfo_t me, const int type)
+{
+       switch (type) {
+               case kDNSServiceType_A:
+                       return me->cnames_a;
+
+               case kDNSServiceType_AAAA:
+                       return me->cnames_aaaa;
+
+               default:
+                       return NULL;
+       }
+}
+
+//======================================================================================================================
+// MARK: - dnssd_cname_array Public Methods
+
+size_t
+dnssd_cname_array_get_count(const dnssd_cname_array_t me)
+{
+       return (me->xpc_array ? xpc_array_get_count(me->xpc_array) : 0);
+}
+
+//======================================================================================================================
+
+const char *
+dnssd_cname_array_get_cname(const dnssd_cname_array_t me, const size_t index)
+{
+       return (me->xpc_array ? xpc_array_get_string(me->xpc_array, index) : NULL);
+}
+
+//======================================================================================================================
+// MARK: - dnssd_cname_array Private Methods
+
+static dnssd_cname_array_t
+_dnssd_cname_array_create(const xpc_object_t xpc_array, OSStatus * const out_error)
+{
+       OSStatus err;
+       dnssd_cname_array_t array = NULL;
+       dnssd_cname_array_t obj = _dnssd_cname_array_alloc();
+       require_action_quiet(obj, exit, err = kNoMemoryErr);
+
+       if (xpc_array) {
+               obj->xpc_array = xpc_copy(xpc_array);
+               require_action_quiet(obj->xpc_array, exit, err = kNoResourcesErr);
+       }
+       array = obj;
+       obj = NULL;
+       err = kNoErr;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       dnssd_release_null_safe(obj);
+       return array;
+}
+
+//======================================================================================================================
+
+static char *
+_dnssd_cname_array_copy_description(const dnssd_cname_array_t me, const bool debug, const bool privacy)
+{
+       char *desc = NULL;
+       char *buf_ptr = NULL;
+       size_t buf_len = 0;
+       for (;;)
+       {
+               __block int n;
+               __block char *dst = buf_ptr;
+               const char * const end = &buf_ptr[buf_len];
+               __block size_t desc_len = 0;
+               if (debug) {
+                       n = _dnssd_snprintf(&dst, end, "dnssd_%s (%p): ", me->base.kind->name, (void *)me);
+                       require_quiet(n >= 0, exit);
+                       desc_len += (size_t)n;
+               }
+               n = _dnssd_snprintf(&dst, end, "[");
+               require_quiet(n >= 0, exit);
+               desc_len += (size_t)n;
+
+               if (privacy) {
+                       n = _dnssd_snprintf(&dst, end, "<%zu redacted cnames>",
+                               me->xpc_array ? xpc_array_get_count(me->xpc_array) : 0);
+                       require_quiet(n >= 0, exit);
+                       desc_len += (size_t)n;
+               } else if (me->xpc_array) {
+                       const bool ok = xpc_array_apply(me->xpc_array,
+                       ^ bool (const size_t index, const xpc_object_t _Nonnull cname)
+                       {
+                               const char *cname_str = xpc_string_get_string_ptr(cname);
+                               if (!cname_str) {
+                                       cname_str = "";
+                               }
+                               n = _dnssd_snprintf(&dst, end, "%s%s", (index == 0) ? "" : ", ", cname_str);
+                               if (likely(n >= 0)) {
+                                       desc_len += (size_t)n;
+                                       return true;
+                               } else {
+                                       return false;
+                               }
+                       });
+                       require_quiet(ok, exit);
+               }
+               n = _dnssd_snprintf(&dst, end, "]");
+               require_quiet(n >= 0, exit);
+               desc_len += (size_t)n;
+
+               if (!buf_ptr) {
+                       buf_len = desc_len + 1;
+                       buf_ptr = malloc(buf_len);
+                       require_quiet(buf_ptr, exit);
+                       buf_ptr[0] = '\0';
+               } else {
+                       break;
+               }
+       }
+       desc = buf_ptr;
+       buf_ptr = NULL;
+
+exit:
+       FreeNullSafe(buf_ptr);
+       return desc;
+}
+
+//======================================================================================================================
+
+void
+_dnssd_cname_array_finalize(dnssd_cname_array_t me)
+{
+       xpc_forget(&me->xpc_array);
+}
+
+//======================================================================================================================
+// MARK: - dnssd Client
 
 static dnssd_getaddrinfo_t g_gai_list = NULL;
 
@@ -809,9 +1154,7 @@ _dnssd_client_queue(void)
 
        dispatch_once(&once,
        ^{
-               dispatch_queue_attr_t const attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL,
-                       QOS_CLASS_UTILITY, 0);
-               queue = dispatch_queue_create("com.apple.dnssd.client", attr);
+               queue = dispatch_queue_create("com.apple.dnssd.client", DISPATCH_QUEUE_SERIAL);
        });
        return queue;
 }
@@ -867,10 +1210,10 @@ _dnssd_client_handle_message(xpc_object_t msg)
                dnssd_getaddrinfo_result_t                              result_list     = NULL;
                __block dnssd_getaddrinfo_result_t *    result_ptr      = &result_list;
                xpc_array_apply(result_array,
-               ^bool(__unused size_t index, xpc_object_t result_dict)
+               ^ bool (__unused size_t index, xpc_object_t _Nonnull result_dict)
                {
-                       dnssd_getaddrinfo_result_t result = _dnssd_getaddrinfo_result_create_from_dictionary(gai->hostname,
-                               result_dict, NULL);
+                       const dnssd_getaddrinfo_result_t result = _dnssd_getaddrinfo_create_result_from_dictionary(gai, result_dict,
+                               NULL);
                        if (result) {
                                *result_ptr     = result;
                                result_ptr      = &result->next;
@@ -1142,65 +1485,96 @@ _dnssd_client_fail_getaddrinfo(dnssd_getaddrinfo_t gai, OSStatus error)
        _dnssd_getaddrinfo_post_error_event(gai, error);
 }
 
-//======================================================================================================================
-//     _dnssd_getaddrinfo_result_create_from_dictionary
 //======================================================================================================================
 
 static bool
 _dnssd_extract_result_dict_values(xpc_object_t result, xpc_object_t *out_hostname, DNSServiceErrorType *out_error,
        DNSServiceFlags *out_flags, uint32_t *out_interface_index, uint16_t *out_type, uint16_t *out_class,
-       xpc_object_t *out_rdata, xpc_object_t *out_auth_tag);
+       xpc_object_t *out_rdata, xpc_object_t *out_auth_tag, dnssd_getaddrinfo_result_protocol_t *out_protocol,
+       xpc_object_t *out_provider_name);
 
 static dnssd_getaddrinfo_result_t
 _dnssd_getaddrinfo_result_create(dnssd_getaddrinfo_result_type_t type, xpc_object_t hostname,
-       xpc_object_t actual_hostname, int addr_family, const void *addr_data, uint32_t interface_index,
-       xpc_object_t auth_tag, OSStatus *out_error);
+       xpc_object_t actual_hostname, dnssd_cname_array_t cname_array, int addr_family, const void *addr_data,
+       uint32_t interface_index, xpc_object_t auth_tag, dnssd_getaddrinfo_result_protocol_t protocol,
+       xpc_object_t provider_name, OSStatus *out_error);
+
+static dnssd_getaddrinfo_result_t
+_dnssd_getaddrinfo_result_create_svcb(xpc_object_t hostname, xpc_object_t actual_hostname, const void *svcb_data,
+       size_t svcb_length, uint32_t interface_index, xpc_object_t auth_tag, dnssd_getaddrinfo_result_protocol_t protocol,
+       xpc_object_t provider_name, OSStatus *out_error);
 
 static dnssd_getaddrinfo_result_t
-_dnssd_getaddrinfo_result_create_from_dictionary(xpc_object_t hostname, xpc_object_t result_dict, OSStatus *out_error)
+_dnssd_getaddrinfo_create_result_from_dictionary(dnssd_getaddrinfo_t me, xpc_object_t result_dict, OSStatus *out_error)
 {
        OSStatus                                        err;
-       xpc_object_t                            actual_hostname, rdata, auth_tag;
+       xpc_object_t                            actual_hostname, rdata, auth_tag, provider_name;
        DNSServiceErrorType                     error;
        DNSServiceFlags                         flags;
-       uint32_t                                        interface_index;
+       uint32_t                                        if_index;
        uint16_t                                        rtype;
-       dnssd_getaddrinfo_result_t      result = NULL;
+       dnssd_getaddrinfo_result_protocol_t protocol;
 
-       const bool success = _dnssd_extract_result_dict_values(result_dict, &actual_hostname, &error, &flags,
-               &interface_index, &rtype, NULL, &rdata, &auth_tag);
-       require_action_quiet(success, exit, err = kMalformedErr);
-
-       if ((error != kDNSServiceErr_NoError) &&
-               (error != kDNSServiceErr_NoSuchRecord)) {
-               err = kUnexpectedErr;
-               goto exit;
-       }
-       require_action_quiet((rtype == kDNSServiceType_A) || (rtype == kDNSServiceType_AAAA), exit, err = kTypeErr);
-
-       dnssd_getaddrinfo_result_type_t result_type;
-       if (error == kDNSServiceErr_NoSuchRecord) {
-               result_type = dnssd_getaddrinfo_result_type_no_address;
-       } else {
-               if (flags & kDNSServiceFlagsAdd) {
-                       if (flags & kDNSServiceFlagsExpiredAnswer) {
-                               result_type = dnssd_getaddrinfo_result_type_expired;
+       dnssd_getaddrinfo_result_t result = NULL;
+       const bool ok = _dnssd_extract_result_dict_values(result_dict, &actual_hostname, &error, &flags, &if_index,
+               &rtype, NULL, &rdata, &auth_tag, &protocol, &provider_name);
+       require_action_quiet(ok, exit, err = kMalformedErr);
+       require_action_quiet((error == kDNSServiceErr_NoError) || (error == kDNSServiceErr_NoSuchRecord), exit,
+               err = kUnexpectedErr);
+
+       switch(rtype) {
+               case kDNSServiceType_A:
+               case kDNSServiceType_AAAA: {
+                       const xpc_object_t cname_update = dnssd_xpc_result_get_cname_update(result_dict);
+                       if (cname_update) {
+                               _dnssd_getaddrinfo_set_cnames(me, rtype, cname_update);
+                       }
+                       dnssd_getaddrinfo_result_type_t result_type;
+                       if (error == kDNSServiceErr_NoSuchRecord) {
+                               result_type = dnssd_getaddrinfo_result_type_no_address;
                        } else {
-                               result_type = dnssd_getaddrinfo_result_type_add;
+                               if (flags & kDNSServiceFlagsAdd) {
+                                       if (flags & kDNSServiceFlagsExpiredAnswer) {
+                                               result_type = dnssd_getaddrinfo_result_type_expired;
+                                       } else {
+                                               result_type = dnssd_getaddrinfo_result_type_add;
+                                       }
+                               } else {
+                                       result_type = dnssd_getaddrinfo_result_type_remove;
+                               }
+                               if (rtype == kDNSServiceType_A) {
+                                       require_action_quiet(xpc_data_get_length(rdata) == 4, exit, err = kMalformedErr);
+                               } else {
+                                       require_action_quiet(xpc_data_get_length(rdata) == 16, exit, err = kMalformedErr);
+                               }
                        }
-               } else {
-                       result_type = dnssd_getaddrinfo_result_type_remove;
+                       const int addr_family = (rtype == kDNSServiceType_A) ? AF_INET : AF_INET6;
+                       result = _dnssd_getaddrinfo_result_create(result_type, me->hostname, actual_hostname,
+                               _dnssd_getaddrinfo_get_cname_array(me, rtype), addr_family, xpc_data_get_bytes_ptr(rdata), if_index,
+                               auth_tag, protocol, provider_name, &err);
+                       require_noerr_quiet(err, exit);
+                       break;
                }
-               if (rtype == kDNSServiceType_A) {
-                       require_action_quiet(xpc_data_get_length(rdata) == 4, exit, err = kMalformedErr);
-               } else {
-                       require_action_quiet(xpc_data_get_length(rdata) == 16, exit, err = kMalformedErr);
+               case kDNSServiceType_SVCB:
+               case kDNSServiceType_HTTPS: {
+                       if (error != kDNSServiceErr_NoSuchRecord) {
+                               require_action_quiet(xpc_data_get_length(rdata) > 0, exit, err = kMalformedErr);
+                       }
+
+                       // SVCB type answer
+                       result = _dnssd_getaddrinfo_result_create_svcb(me->hostname, actual_hostname,
+                               xpc_data_get_bytes_ptr(rdata), xpc_data_get_length(rdata), if_index, auth_tag, protocol,
+                               provider_name, &err);
+                       require_noerr_quiet(err, exit);
+                       break;
                }
+               default:
+                       err = kTypeErr;
+                       goto exit;
+       }
+       if ((flags & kDNSServiceFlagsAdd) && (flags & kDNSServiceFlagAnsweredFromCache)) {
+               result->is_from_cache = true;
        }
-       const int addr_family = (rtype == kDNSServiceType_A) ? AF_INET : AF_INET6;
-       result = _dnssd_getaddrinfo_result_create(result_type, hostname, actual_hostname, addr_family,
-               xpc_data_get_bytes_ptr(rdata), interface_index, auth_tag, &err);
-       require_noerr_quiet(err, exit);
 
 exit:
        if (err) {
@@ -1215,7 +1589,8 @@ exit:
 static bool
 _dnssd_extract_result_dict_values(xpc_object_t result, xpc_object_t *out_hostname, DNSServiceErrorType *out_error,
        DNSServiceFlags *out_flags, uint32_t *out_interface_index, uint16_t *out_type, uint16_t *out_class,
-       xpc_object_t *out_rdata, xpc_object_t *out_auth_tag)
+       xpc_object_t *out_rdata, xpc_object_t *out_auth_tag, dnssd_getaddrinfo_result_protocol_t *out_protocol,
+       xpc_object_t *out_provider_name)
 {
        bool result_is_valid = false;
        xpc_object_t const hostname = dnssd_xpc_result_get_record_name_object(result);
@@ -1248,6 +1623,12 @@ _dnssd_extract_result_dict_values(xpc_object_t result, xpc_object_t *out_hostnam
        if (out_auth_tag) {
                *out_auth_tag = dnssd_xpc_result_get_authentication_tag_object(result);
        }
+       if (out_protocol) {
+               *out_protocol = dnssd_xpc_result_get_record_protocol(result, NULL);
+       }
+       if (out_provider_name) {
+               *out_provider_name = dnssd_xpc_result_get_provider_name_object(result);
+       }
        result_is_valid = true;
 
 exit:
@@ -1255,9 +1636,10 @@ exit:
 }
 
 static dnssd_getaddrinfo_result_t
-_dnssd_getaddrinfo_result_create(dnssd_getaddrinfo_result_type_t type, xpc_object_t hostname,
-       xpc_object_t actual_hostname, int addr_family, const void *addr_data, uint32_t interface_index,
-       xpc_object_t auth_tag, OSStatus *out_error)
+_dnssd_getaddrinfo_result_create(const dnssd_getaddrinfo_result_type_t type, const xpc_object_t hostname,
+       const xpc_object_t actual_hostname, const dnssd_cname_array_t cnames, const int addr_family,
+       const void * const addr_data, const uint32_t if_index, const xpc_object_t auth_tag,
+       const dnssd_getaddrinfo_result_protocol_t protocol, const xpc_object_t provider_name, OSStatus * const out_error)
 {
        OSStatus err;
        dnssd_getaddrinfo_result_t result = NULL;
@@ -1275,8 +1657,9 @@ _dnssd_getaddrinfo_result_create(dnssd_getaddrinfo_result_type_t type, xpc_objec
                        err = kTypeErr;
                        goto exit;
        }
-       obj->type                               = type;
-       obj->interface_index    = interface_index;
+       obj->type               = type;
+       obj->if_index   = if_index;
+       obj->protocol   = protocol;
 
        require_action_quiet(xpc_get_type(hostname) == XPC_TYPE_STRING, exit, err = kTypeErr);
 
@@ -1288,6 +1671,8 @@ _dnssd_getaddrinfo_result_create(dnssd_getaddrinfo_result_type_t type, xpc_objec
        obj->actual_hostname = xpc_copy(actual_hostname);
        require_action_quiet(obj->actual_hostname, exit, err = kNoResourcesErr);
 
+       obj->cnames = cnames ? cnames : _dnssd_get_empty_cname_array();
+       dnssd_retain(obj->cnames);
        require_action_quiet((addr_family == AF_INET) || (addr_family == AF_INET6), exit, err = kTypeErr);
 
        if (addr_family == AF_INET) {
@@ -1296,12 +1681,116 @@ _dnssd_getaddrinfo_result_create(dnssd_getaddrinfo_result_type_t type, xpc_objec
                if (obj->type != dnssd_getaddrinfo_result_type_no_address) {
                        memcpy(&obj->addr.v4.sin_addr.s_addr, addr_data, 4);
                }
-       } else {
-               obj->addr.sa.sa_family  = AF_INET6;
-               obj->addr.v6.sin6_len   = sizeof(struct sockaddr_in6);
+       } else if (addr_family == AF_INET6) {
+               struct sockaddr_in6 * const sin6 = &obj->addr.v6;
+               sin6->sin6_family       = AF_INET6;
+               sin6->sin6_len          = sizeof(struct sockaddr_in6);
                if (obj->type != dnssd_getaddrinfo_result_type_no_address) {
-                       memcpy(&obj->addr.v6.sin6_addr.s6_addr, addr_data, 16);
+                       memcpy(&sin6->sin6_addr.s6_addr, addr_data, 16);
+                       if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
+                               sin6->sin6_scope_id = obj->if_index;
+                       }
+               }
+       }
+       if (auth_tag) {
+               require_action_quiet(xpc_get_type(auth_tag) == XPC_TYPE_DATA, exit, err = kTypeErr);
+
+               obj->auth_tag = xpc_copy(auth_tag);
+               require_action_quiet(obj->auth_tag, exit, err = kNoResourcesErr);
+       }
+       if (provider_name) {
+               require_action_quiet(xpc_get_type(provider_name) == XPC_TYPE_STRING, exit, err = kTypeErr);
+
+               obj->provider_name = xpc_copy(provider_name);
+               require_action_quiet(obj->provider_name, exit, err = kNoResourcesErr);
+       }
+       result  = obj;
+       obj             = NULL;
+       err = kNoErr;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       dnssd_release_null_safe(obj);
+       return result;
+}
+
+static dnssd_getaddrinfo_result_t
+_dnssd_getaddrinfo_result_create_svcb(xpc_object_t hostname, xpc_object_t actual_hostname, const void *svcb_data,
+       size_t svcb_length, uint32_t interface_index, xpc_object_t auth_tag, dnssd_getaddrinfo_result_protocol_t protocol,
+       xpc_object_t provider_name, OSStatus *out_error)
+{
+       OSStatus err;
+       dnssd_getaddrinfo_result_t result = NULL;
+       dnssd_getaddrinfo_result_t obj = _dnssd_getaddrinfo_result_alloc();
+       require_action_quiet(obj, exit, err = kNoMemoryErr);
+
+       obj->type               = dnssd_getaddrinfo_result_type_service_binding;
+       obj->if_index   = interface_index;
+       obj->protocol   = protocol;
+
+       require_action_quiet(xpc_get_type(hostname) == XPC_TYPE_STRING, exit, err = kTypeErr);
+       obj->hostname = xpc_copy(hostname);
+
+       require_action_quiet(xpc_get_type(actual_hostname) == XPC_TYPE_STRING, exit, err = kTypeErr);
+
+       obj->actual_hostname = xpc_copy(actual_hostname);
+       require_action_quiet(obj->actual_hostname, exit, err = kNoResourcesErr);
+
+       if (svcb_data != NULL && svcb_length > 0) {
+               obj->valid_svcb = dnssd_svcb_is_valid(svcb_data, svcb_length);
+               obj->priority = dnssd_svcb_get_priority(svcb_data, svcb_length);
+               obj->port = dnssd_svcb_get_port(svcb_data, svcb_length);
+
+               char *service_name = dnssd_svcb_copy_domain(svcb_data, svcb_length);
+               if (service_name != NULL) {
+                       if (strcmp(service_name, ".") == 0) {
+                               // The empty name is an placeholder for the name for the record
+                               obj->service_name = xpc_copy(obj->hostname);
+                       } else {
+                               obj->service_name = xpc_string_create(service_name);
+                       }
+                       free(service_name);
+                       require_action_quiet(obj->service_name, exit, err = kNoResourcesErr);
+               }
+
+               char *doh_uri = dnssd_svcb_copy_doh_uri(svcb_data, svcb_length);
+               if (doh_uri != NULL) {
+                       obj->doh_uri = xpc_string_create(doh_uri);
+                       free(doh_uri);
+                       require_action_quiet(obj->doh_uri, exit, err = kNoResourcesErr);
+               }
+
+               size_t ech_config_length = 0;
+               uint8_t *ech_config = dnssd_svcb_copy_ech_config(svcb_data, svcb_length, &ech_config_length);
+               if (ech_config != NULL) {
+                       obj->ech_config = xpc_data_create(ech_config, ech_config_length);
+                       free(ech_config);
+                       require_action_quiet(obj->ech_config, exit, err = kNoResourcesErr);
                }
+
+               dnssd_svcb_access_alpn_values(svcb_data, svcb_length, ^bool(const char *alpn) {
+                       xpc_object_t alpn_string = xpc_string_create(alpn);
+                       if (obj->alpn_values == NULL) {
+                               obj->alpn_values = xpc_array_create(NULL, 0);
+                       }
+                       xpc_array_append_value(obj->alpn_values, alpn_string);
+                       xpc_release(alpn_string);
+                       return true;
+               });
+
+               dnssd_svcb_access_address_hints(svcb_data, svcb_length, ^bool(const struct sockaddr *address) {
+                       xpc_object_t address_hint = xpc_data_create(address, address->sa_len);
+                       if (obj->address_hints == NULL) {
+                               obj->address_hints = xpc_array_create(NULL, 0);
+                       }
+                       xpc_array_append_value(obj->address_hints, address_hint);
+                       xpc_release(address_hint);
+                       return true;
+               });
+       } else {
+               obj->valid_svcb = false;
        }
 
        if (auth_tag) {
@@ -1310,6 +1799,14 @@ _dnssd_getaddrinfo_result_create(dnssd_getaddrinfo_result_type_t type, xpc_objec
                obj->auth_tag = xpc_copy(auth_tag);
                require_action_quiet(obj->auth_tag, exit, err = kNoResourcesErr);
        }
+
+       if (provider_name) {
+               require_action_quiet(xpc_get_type(provider_name) == XPC_TYPE_STRING, exit, err = kTypeErr);
+
+               obj->provider_name = xpc_copy(provider_name);
+               require_action_quiet(obj->provider_name, exit, err = kNoResourcesErr);
+       }
+
        result  = obj;
        obj             = NULL;
        err = kNoErr;
@@ -1322,10 +1819,24 @@ exit:
        return result;
 }
 
-#if 0
 //======================================================================================================================
-#pragma mark - Misc. Helpers
-#endif
+// MARK: - Misc. Helpers
+
+static dnssd_cname_array_t
+_dnssd_get_empty_cname_array(void)
+{
+       static dispatch_once_t          s_once = 0;
+       static dnssd_cname_array_t      s_empty_cname_array = NULL;
+       dispatch_once(&s_once,
+       ^{
+               s_empty_cname_array = _dnssd_cname_array_create(NULL, NULL);
+               s_empty_cname_array->base._os_obj_refcnt        = _OS_OBJECT_GLOBAL_REFCNT;
+               s_empty_cname_array->base._os_obj_xref_cnt      = _OS_OBJECT_GLOBAL_REFCNT;
+       });
+       return s_empty_cname_array;
+}
+
+//======================================================================================================================
 
 static DNSServiceErrorType
 _dnssd_osstatus_to_dns_service_error(OSStatus error)
@@ -1348,3 +1859,20 @@ _dnssd_osstatus_to_dns_service_error(OSStatus error)
        }
        return error;
 }
+
+//======================================================================================================================
+
+static int
+_dnssd_snprintf(char ** const dst, const char * const end, const char * const format, ...)
+{
+       char * const ptr = *dst;
+       const size_t len = (size_t)(end - ptr);
+       va_list args;
+       va_start(args, format);
+       const int n = vsnprintf(ptr, len, format, args);
+       va_end(args);
+       if (n >= 0) {
+               *dst = ptr + Min((size_t)n, len);
+       }
+       return n;
+}
diff --git a/mDNSMacOSX/dnssd_analytics.c b/mDNSMacOSX/dnssd_analytics.c
new file mode 100644 (file)
index 0000000..32968bf
--- /dev/null
@@ -0,0 +1,351 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "dnssd_analytics.h"
+#include "mDNSMacOSX.h"
+#include "uds_daemon.h"
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, ANALYTICS)
+
+#include <xpc/xpc.h>
+#include <CoreAnalytics/CoreAnalytics.h>
+typedef xpc_object_t COREANALYTICS_RETURNS_RETAINED
+(^event_create_block_t)(void);
+
+#define UNSET_STR      "unset"
+
+#endif // ANALYTICS
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, CACHE_ANALYTICS)
+
+// Local aggregate counters to track request counts
+
+mDNSlocal uint64_t sCacheUsage_UnicastHitCount = 0;
+mDNSlocal uint64_t sCacheUsage_UnicastMissCount = 0;
+mDNSlocal uint64_t sCacheUsage_MulticastHitCount = 0;
+mDNSlocal uint64_t sCacheUsage_MulticastMissCount = 0;
+
+mDNSlocal uint64_t sCacheRequest_UnicastHitCount = 0;
+mDNSlocal uint64_t sCacheRequest_UnicastMissCount = 0;
+mDNSlocal uint64_t sCacheRequest_MulticastHitCount = 0;
+mDNSlocal uint64_t sCacheRequest_MulticastMissCount = 0;
+
+#if COMPILER_LIKES_PRAGMA_MARK
+#pragma mark -
+#pragma mark - Private CacheUsage Functions
+#endif
+
+mDNSlocal void
+dnssd_analytics_post_cache_request_count(CacheRequestType inType, CacheState inState, uint64_t inRequestCount)
+{
+    event_create_block_t        create_event;
+    bool                        posted;
+
+    create_event = ^{
+        xpc_object_t        dict;
+        dict = xpc_dictionary_create(NULL, NULL, 0);
+        xpc_dictionary_set_string(dict, "requestType",  inType  == CacheRequestType_multicast    ? "multicast"   : "unicast");
+        xpc_dictionary_set_string(dict, "cacheState",   inState == CacheState_hit                ? "hit"         : "miss");
+        xpc_dictionary_set_uint64(dict, "requestCount", inRequestCount);
+        return (dict);
+    };
+    posted = analytics_send_event_lazy("com.apple.mDNSResponder.CacheUsage.request", create_event);
+    if (!posted) {
+        LogRedact(MDNS_LOG_CATEGORY_ANALYTICS, MDNS_LOG_WARNING, "com.apple.mDNSResponder.CacheUsage.request:  Failed to post");
+    }
+}
+
+mDNSlocal void
+dnssd_analytics_post_cache_request_counts()
+{
+    if (sCacheRequest_UnicastHitCount > 0) {
+        dnssd_analytics_post_cache_request_count(CacheRequestType_unicast, CacheState_hit, sCacheRequest_UnicastHitCount);
+        sCacheRequest_UnicastHitCount = 0;
+    }
+    if (sCacheRequest_UnicastMissCount > 0) {
+        dnssd_analytics_post_cache_request_count(CacheRequestType_unicast, CacheState_miss, sCacheRequest_UnicastMissCount);
+        sCacheRequest_UnicastMissCount = 0;
+    }
+    if (sCacheRequest_MulticastHitCount > 0) {
+        dnssd_analytics_post_cache_request_count(CacheRequestType_multicast, CacheState_hit, sCacheRequest_MulticastHitCount);
+        sCacheRequest_MulticastHitCount = 0;
+    }
+    if (sCacheRequest_MulticastMissCount > 0) {
+        dnssd_analytics_post_cache_request_count(CacheRequestType_multicast, CacheState_miss, sCacheRequest_MulticastMissCount);
+        sCacheRequest_MulticastMissCount = 0;
+    }
+}
+
+mDNSlocal void
+dnssd_analytics_post_cache_usage_counts_for_type(CacheRequestType inType, uint64_t inHitCount, uint64_t inMissCount)
+{
+    event_create_block_t    create_event;
+    bool                    posted;
+
+    create_event = ^{
+        xpc_object_t        dict;
+        dict = xpc_dictionary_create(NULL, NULL, 0);
+        xpc_dictionary_set_string(dict, "requestType",  inType == CacheRequestType_multicast ? "multicast" : "unicast");
+        xpc_dictionary_set_uint64(dict, "hitCount",     inHitCount);
+        xpc_dictionary_set_uint64(dict, "missCount",    inMissCount);
+        return (dict);
+    };
+    posted = analytics_send_event_lazy("com.apple.mDNSResponder.CacheUsage.entries", create_event);
+    if (!posted) {
+        LogRedact(MDNS_LOG_CATEGORY_ANALYTICS, MDNS_LOG_WARNING, "com.apple.mDNSResponder.CacheUsage.entries:  Failed to post");
+    }
+}
+
+mDNSlocal void
+dnssd_analytics_post_cache_usage_counts()
+{
+       if (sCacheUsage_MulticastHitCount || sCacheUsage_MulticastMissCount) {
+               dnssd_analytics_post_cache_usage_counts_for_type(CacheRequestType_multicast, sCacheUsage_MulticastHitCount, sCacheUsage_MulticastMissCount);
+               sCacheUsage_MulticastHitCount = 0;
+               sCacheUsage_MulticastMissCount = 0;
+       }
+       if (sCacheUsage_UnicastHitCount || sCacheUsage_UnicastMissCount) {
+               dnssd_analytics_post_cache_usage_counts_for_type(CacheRequestType_unicast, sCacheUsage_UnicastHitCount, sCacheUsage_UnicastMissCount);
+               sCacheUsage_UnicastHitCount = 0;
+               sCacheUsage_UnicastMissCount = 0;
+       }
+}
+
+#if COMPILER_LIKES_PRAGMA_MARK
+#pragma mark -
+#pragma mark - Exported CacheUsage Functions
+#endif
+
+mDNSexport void
+dnssd_analytics_update_cache_request(CacheRequestType inType, CacheState inState)
+{
+    if (inType == CacheRequestType_unicast) {
+        if (inState == CacheState_hit) {
+            sCacheRequest_UnicastHitCount++;
+        } else if (inState == CacheState_miss) {
+            sCacheRequest_UnicastMissCount++;
+        } else {
+                       LogRedact(MDNS_LOG_CATEGORY_ANALYTICS, MDNS_LOG_WARNING, "dnssd_analytics_update_cache_request:  unknown CacheState %d for unicast", inState);
+        }
+    } else if (inType == CacheRequestType_multicast) {
+        if (inState == CacheState_hit) {
+            sCacheRequest_MulticastHitCount++;
+        } else if (inState == CacheState_miss) {
+            sCacheRequest_MulticastMissCount++;
+        } else {
+            LogRedact(MDNS_LOG_CATEGORY_ANALYTICS, MDNS_LOG_WARNING, "dnssd_analytics_update_cache_request:  unknown CacheState %d for multicast", inState);
+        }
+    } else {
+        LogRedact(MDNS_LOG_CATEGORY_ANALYTICS, MDNS_LOG_WARNING, "dnssd_analytics_update_cache_request:  unknown CacheRequestType %d", inType);
+    }
+}
+
+mDNSexport void
+dnssd_analytics_update_cache_usage_counts(uint32_t inHitMulticastCount, uint32_t inMissMulticastCount, uint32_t inHitUnicastCount, uint32_t inMissUnicastCount)
+{
+       sCacheUsage_MulticastHitCount += inHitMulticastCount;
+       sCacheUsage_MulticastMissCount += inMissMulticastCount;
+       sCacheUsage_UnicastHitCount += inHitUnicastCount;
+       sCacheUsage_UnicastMissCount += inMissUnicastCount;
+}
+
+#endif // CACHE_ANALYTICS
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, WAB_ANALYTICS)
+
+#if COMPILER_LIKES_PRAGMA_MARK
+#pragma mark -
+#pragma mark - Exported WABUsage Functions
+#endif
+
+mDNSexport void
+dnssd_analytics_post_WAB_usage_event_count(WABUsageKind inKind, WABUsageType inType, WABUsageEvent inEvent, uint64_t inEventCount)
+{
+    event_create_block_t    create_event;
+    bool                    posted;
+    char *                  kind  = UNSET_STR;
+    char *                  type  = UNSET_STR;
+    char *                  event = UNSET_STR;
+
+       if (analytics_send_event_lazy) {
+               switch (inKind) {
+                       case WABUsageKind_results: {
+                               kind = "results";
+                               break;
+                       }
+                       case WABUsageKind_session: {
+                               kind = "session";
+                               break;
+                       }
+                       case WABUsageKind_operation: {
+                               kind = "operation";
+                               break;
+                       }
+               }
+
+               switch (inType) {
+                       case WABUsageType_enumeration: {
+                               type = "enumeration";
+                               break;
+                       }
+                       case WABUsageType_query: {
+                               type = "query";
+                               break;
+                       }
+                       case WABUsageType_push: {
+                               type = "push";
+                               break;
+                       }
+                       case WABUsageType_llq: {
+                               type = "llq";
+                               break;
+                       }
+               }
+
+               switch (inEvent) {
+                       case WABUsageEvent_positive: {
+                               event = "positive";
+                               break;
+                       }
+                       case WABUsageEvent_negative: {
+                               event = "negative";
+                               break;
+                       }
+                       case WABUsageEvent_null: {
+                               event = "null";
+                               break;
+                       }
+                       case WABUsageEvent_error: {
+                               event = "error";
+                               break;
+                       }
+
+                       case WABUsageEvent_connected: {
+                               event = "connected";
+                               break;
+                       }
+                       case WABUsageEvent_session: {
+                               event = "session";
+                               break;
+                       }
+                       case WABUsageEvent_reset: {
+                               event = "reset";
+                               break;
+                       }
+                       case WABUsageEvent_idledOut: {
+                               event = "idledOut";
+                               break;
+                       }
+                       case WABUsageEvent_goAway: {
+                               event = "goAway";
+                               break;
+                       }
+                       case WABUsageEvent_resumedGood: {
+                               event = "resumedGood";
+                               break;
+                       }
+                       case WABUsageEvent_resumedBad: {
+                               event = "resumedBad";
+                               break;
+                       }
+
+                       case WABUsageEvent_succeeded: {
+                               event = "succeeded";
+                               break;
+                       }
+                       case WABUsageEvent_rejected: {
+                               event = "rejected";
+                               break;
+                       }
+                       case WABUsageEvent_dsoni: {
+                               event = "dsoni";
+                               break;
+                       }
+                       case WABUsageEvent_answered: {
+                               event = "answered";
+                               break;
+                       }
+               }
+
+               create_event = ^{
+                       xpc_object_t        dict;
+                       dict = xpc_dictionary_create(NULL, NULL, 0);
+                       xpc_dictionary_set_string(dict, "kind",         kind);
+                       xpc_dictionary_set_string(dict, "type",         type);
+                       xpc_dictionary_set_string(dict, "event",        event);
+                       xpc_dictionary_set_uint64(dict, "eventCount",   inEventCount);
+                       return (dict);
+               };
+               posted = analytics_send_event_lazy("com.apple.mDNSResponder.CacheUsage.entries", create_event);
+               if (!posted) {
+                       LogRedact(MDNS_LOG_CATEGORY_ANALYTICS, MDNS_LOG_WARNING, "com.apple.mDNSResponder.CacheUsage.entries:  Failed to post");
+               }
+       }
+}
+
+#endif // WAB_ANALYTICS
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, ANALYTICS)
+#if COMPILER_LIKES_PRAGMA_MARK
+#pragma mark -
+#pragma mark - Exported Analytics Functions
+#endif
+
+mDNSexport void
+dnssd_analytics_init()
+{
+       static dispatch_once_t  sInitAnalyticsOnce = 0;
+       static dispatch_queue_t sAnalyticsQueue = NULL;
+       dispatch_once(&sInitAnalyticsOnce, ^{
+               sAnalyticsQueue = dispatch_queue_create("com.apple.mDNSResponder.analytics-reporting-queue", DISPATCH_QUEUE_SERIAL);
+               xpc_object_t criteria = xpc_dictionary_create(NULL, NULL, 0);
+               xpc_dictionary_set_bool(criteria, XPC_ACTIVITY_REPEATING, true);
+               xpc_dictionary_set_bool(criteria, XPC_ACTIVITY_ALLOW_BATTERY, true);
+               xpc_dictionary_set_int64(criteria, XPC_ACTIVITY_DELAY, XPC_ACTIVITY_INTERVAL_1_DAY);
+               xpc_dictionary_set_int64(criteria, XPC_ACTIVITY_GRACE_PERIOD, XPC_ACTIVITY_INTERVAL_5_MIN);
+               xpc_dictionary_set_string(criteria, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_MAINTENANCE);
+
+               xpc_activity_register("com.apple.mDNSResponder.analytics.daily", criteria, ^(xpc_activity_t activity) {
+                       if (xpc_activity_should_defer(activity)) {
+                           if (xpc_activity_set_state(activity, XPC_ACTIVITY_STATE_DEFER)) {
+                                       LogRedact(MDNS_LOG_CATEGORY_ANALYTICS, MDNS_LOG_INFO, "com.apple.mDNSResponder.analytics.daily: Asked to defer");
+                               } else {
+                                       LogRedact(MDNS_LOG_CATEGORY_ANALYTICS, MDNS_LOG_ERROR, "com.apple.mDNSResponder.analytics.daily: Asked to defer but failed to set state");
+                               }
+                       } else {
+                               dispatch_async(sAnalyticsQueue, ^{
+#if MDNSRESPONDER_SUPPORTS(APPLE, ANALYTICS)
+                                       KQueueLock();
+                                       mDNS_Lock(&mDNSStorage);
+#if MDNSRESPONDER_SUPPORTS(APPLE, CACHE_ANALYTICS)
+                                       dnssd_analytics_post_cache_request_counts();
+                                       dnssd_analytics_post_cache_usage_counts();
+#endif //      CACHE_ANALYTICS
+                                       LogRedact(MDNS_LOG_CATEGORY_ANALYTICS, MDNS_LOG_INFO, "Analytics Posted");
+                                       mDNS_Unlock(&mDNSStorage);
+                                       KQueueUnlock("Analytics Update");
+#endif //      ANALYTICS
+                               });
+                               if (!xpc_activity_set_state(activity, XPC_ACTIVITY_STATE_DONE)) {
+                                       LogRedact(MDNS_LOG_CATEGORY_ANALYTICS, MDNS_LOG_ERROR, "com.apple.mDNSResponder.analytics.daily: Analytics XPC_ACTIVITY_STATE_DONE failed");
+                               }
+                       }
+               });
+               xpc_release(criteria);
+       });
+}
+
+#endif // ANALYTICS
diff --git a/mDNSMacOSX/dnssd_analytics.h b/mDNSMacOSX/dnssd_analytics.h
new file mode 100644 (file)
index 0000000..8f40f44
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2019 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __DNSSD_ANALYTICS_H__
+#define __DNSSD_ANALYTICS_H__
+
+#include "DNSCommon.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, ANALYTICS)
+
+extern void
+dnssd_analytics_init(void);
+
+#endif // ANALYTICS
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, CACHE_ANALYTICS)
+
+typedef enum {
+    CacheRequestType_multicast,
+    CacheRequestType_unicast
+} CacheRequestType;
+
+typedef enum {
+    CacheState_hit,
+    CacheState_miss
+} CacheState;
+
+extern void
+dnssd_analytics_update_cache_request(CacheRequestType inType, CacheState inState);
+
+extern void
+dnssd_analytics_update_cache_usage_counts(uint32_t inHitMulticastCount, uint32_t inMissMulticastCount, uint32_t inHitUnicastCount, uint32_t inMissUnicastCount);
+
+#endif // CACHE_ANALYTICS
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, WAB_ANALYTICS)
+
+typedef enum {
+    WABUsageKind_results,
+    WABUsageKind_session,
+    WABUsageKind_operation
+} WABUsageKind;
+
+typedef enum {
+    WABUsageType_enumeration,
+    WABUsageType_query,
+    WABUsageType_push,
+    WABUsageType_llq
+} WABUsageType;
+
+typedef enum {
+    // Kind: results
+    // Type: enumeration, query, llq
+    WABUsageEvent_positive,
+    WABUsageEvent_negative,
+    WABUsageEvent_null,
+    WABUsageEvent_error,
+
+    // Kind: session
+    // Type: push, llq
+    WABUsageEvent_connected,
+    WABUsageEvent_session,
+    WABUsageEvent_reset,
+    WABUsageEvent_idledOut,
+    WABUsageEvent_goAway,
+    WABUsageEvent_resumedGood,
+    WABUsageEvent_resumedBad,
+
+    // Kind: operation
+    // Type: push, llq
+    WABUsageEvent_succeeded,
+    WABUsageEvent_rejected,
+    WABUsageEvent_dsoni,
+    WABUsageEvent_answered
+} WABUsageEvent;
+
+extern void
+dnssd_analytics_post_WAB_usage_event_count(WABUsageKind inKind, WABUsageType inType, WABUsageEvent inEvent, uint64_t inEventCount);
+
+#endif // WAB_ANALYTICS
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif // __DNSSD_ANALYTICS_H__
diff --git a/mDNSMacOSX/dnssd_descriptions.m b/mDNSMacOSX/dnssd_descriptions.m
new file mode 100644 (file)
index 0000000..cca0126
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2019 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !__has_feature(objc_arc)
+#pragme "GCC error \"This file must be built with ARC\""
+#endif
+
+#import "dnssd_object.h"
+
+#import <CoreFoundation/CoreFoundation.h>
+
+#if 0
+//======================================================================================================================
+#pragma mark - Description Extensions
+#endif
+
+@interface OS_OBJECT_CLASS(dnssd_object);
+@end
+
+@implementation OS_OBJECT_CLASS(dnssd_object) (descriptions)
+
+- (NSString *)description
+{
+       char * const desc = dnssd_object_copy_description((dnssd_any_t)self, false, false);
+       if (desc) {
+               NSString * const string = (__bridge_transfer NSString *)CFStringCreateWithCString(kCFAllocatorDefault, desc, kCFStringEncodingUTF8);
+               free(desc);
+               return string;
+       } else {
+               return nil;
+       }
+}
+
+- (NSString *)debugDescription
+{
+       char * const desc = dnssd_object_copy_description((dnssd_any_t)self, true, false);
+       if (desc) {
+               NSString * const string = (__bridge_transfer NSString *)CFStringCreateWithCString(kCFAllocatorDefault, desc, kCFStringEncodingUTF8);
+               free(desc);
+               return string;
+       } else {
+               return nil;
+       }
+}
+
+- (NSString *)redactedDescription
+{
+       char * const desc = dnssd_object_copy_description((dnssd_any_t)self, false, true);
+       if (desc) {
+               NSString * const string = (__bridge_transfer NSString *)CFStringCreateWithCString(kCFAllocatorDefault, desc, kCFStringEncodingUTF8);
+               free(desc);
+               return string;
+       } else {
+               return nil;
+       }
+}
+@end
index 3a1c73d3525ef704a7231367b6946c79c746a814..816365bc8e3265aad09012b617c4dc9d260c63fd 100644 (file)
@@ -1,11 +1,11 @@
 /*
- * Copyright (c) 2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *     https://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -20,7 +20,7 @@
 #include "dnssd_private.h"
 
 //======================================================================================================================
-#pragma mark - dnssd_object Private Method Declarations
+// MARK: - Object Private Method Declarations
 
 char *
 dnssd_object_copy_description(dnssd_any_t object, bool debug, bool privacy);
@@ -34,5 +34,6 @@ dnssd_object_finalize(dnssd_any_t object);
 
 DNSSD_OBJECT_ALLOC_DECLARE(getaddrinfo);
 DNSSD_OBJECT_ALLOC_DECLARE(getaddrinfo_result);
+DNSSD_OBJECT_ALLOC_DECLARE(cname_array);
 
 #endif // __DNSSD_OBJECT_H__
index 104dee3bf3dade021f9f1b0a6004934138724c39..8aed0c1ec4ca9d17d23247b3badbc9e1b2d1a692 100644 (file)
@@ -1,11 +1,11 @@
 /*
- * Copyright (c) 2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *     https://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
 #import "dnssd_object.h"
 
 #import <stdlib.h>
-#import <Foundation/Foundation.h>
 #import <os/object_private.h>
 
-#if 0
 //======================================================================================================================
-#pragma mark - Class Declarations
-#endif
+// MARK: - Class Declarations
 
 #define DNSSD_OBJECT_CLASS_DECLARE(NAME)                                                               \
        _OS_OBJECT_DECL_SUBCLASS_INTERFACE(dnssd_ ## NAME, dnssd_object)        \
@@ -33,11 +30,10 @@ _OS_OBJECT_DECL_SUBCLASS_INTERFACE(dnssd_object, object)
 
 DNSSD_OBJECT_CLASS_DECLARE(getaddrinfo);
 DNSSD_OBJECT_CLASS_DECLARE(getaddrinfo_result);
+DNSSD_OBJECT_CLASS_DECLARE(cname_array);
 
-#if 0
 //======================================================================================================================
-#pragma mark - Class Definitions
-#endif
+// MARK: - Class Definitions
 
 @implementation OS_OBJECT_CLASS(dnssd_object)
 - (void)dealloc
@@ -45,42 +41,6 @@ DNSSD_OBJECT_CLASS_DECLARE(getaddrinfo_result);
        dnssd_object_finalize(self);
        [super dealloc];
 }
-
-- (NSString *)description
-{
-       char * const desc = dnssd_object_copy_description(self, false, false);
-       if (desc) {
-               NSString * const string = [NSString stringWithUTF8String:desc];
-               free(desc);
-               return string;
-       } else {
-               return nil;
-       }
-}
-
-- (NSString *)debugDescription
-{
-       char * const desc = dnssd_object_copy_description(self, true, false);
-       if (desc) {
-               NSString * const string = [NSString stringWithUTF8String:desc];
-               free(desc);
-               return string;
-       } else {
-               return nil;
-       }
-}
-
-- (NSString *)redactedDescription
-{
-       char * const desc = dnssd_object_copy_description(self, false, true);
-       if (desc) {
-               NSString * const string = [NSString stringWithUTF8String:desc];
-               free(desc);
-               return string;
-       } else {
-               return nil;
-       }
-}
 @end
 
 #define DNSSD_CLASS(NAME)      OS_OBJECT_CLASS(dnssd_ ## NAME)
@@ -97,3 +57,4 @@ DNSSD_OBJECT_CLASS_DECLARE(getaddrinfo_result);
 
 DNSSD_OBJECT_CLASS_DEFINE(getaddrinfo);
 DNSSD_OBJECT_CLASS_DEFINE(getaddrinfo_result);
+DNSSD_OBJECT_CLASS_DEFINE(cname_array);
index cfe87f9b728f69ed9e8e1bf32b479a333ca32659..961fdd0bbecc8d98b0170f9cbb819757c36fd91f 100644 (file)
@@ -1,11 +1,11 @@
 /*
- * Copyright (c) 2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *     https://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -18,6 +18,7 @@
 #define __DNSSD_PRIVATE_H__
 
 #include <dispatch/dispatch.h>
+#include <xpc/xpc.h>
 #include <dns_sd.h>
 #include <os/object.h>
 
@@ -38,6 +39,7 @@
 
 DNSSD_DECL(getaddrinfo);
 DNSSD_DECL(getaddrinfo_result);
+DNSSD_DECL(cname_array);
 
 DNSSD_ASSUME_NONNULL_BEGIN
 
@@ -46,9 +48,10 @@ DNSSD_ASSUME_NONNULL_BEGIN
 #else
        #if !defined(__cplusplus)
                typedef union {
-                       dnssd_object_t                          base;
+                       dnssd_object_t                          object;
                        dnssd_getaddrinfo_t                     gai;
                        dnssd_getaddrinfo_result_t      gai_result;
+                       dnssd_cname_array_t                     cname_array;
                } dnssd_any_t __attribute__((__transparent_union__));
        #else
                typedef void *  dnssd_any_t;
@@ -58,6 +61,12 @@ DNSSD_ASSUME_NONNULL_BEGIN
 #define DNSSD_MALLOC   __attribute__((__malloc__))
 #define DNSSD_AVAILABLE        SPI_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0))
 
+#if __has_attribute(noescape)
+#define DNSSD_NOESCAPE __attribute__((__noescape__))
+#else // noescape
+#define DNSSD_NOESCAPE
+#endif // noescape
+
 __BEGIN_DECLS
 
 /*!
@@ -164,6 +173,25 @@ DNSSD_AVAILABLE
 void
 dnssd_getaddrinfo_set_flags(dnssd_getaddrinfo_t gai, DNSServiceFlags flags);
 
+#define DNSSD_GETADDRINFO_SUPPORTS_ACCOUNT_ID 1
+
+/*!
+ *     @brief
+ *             Specifies the account id to use for the getaddrinfo operation.
+ *
+ *     @param gai
+ *             The getaddrinfo object.
+ *
+ *     @param account_id
+ *             The account id.
+ *
+ *     @discussion
+ *             This function has no effect on a getaddrinfo object that has been activated or invalidated.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+void
+dnssd_getaddrinfo_set_account_id(dnssd_getaddrinfo_t gai, const char * account_id);
+
 /*!
  *     @brief
  *             Specifies the hostname to resolve.
@@ -181,6 +209,25 @@ DNSSD_AVAILABLE
 void
 dnssd_getaddrinfo_set_hostname(dnssd_getaddrinfo_t gai, const char *hostname);
 
+#define DNSSD_GETADDRINFO_SUPPORTS_SERVICE_SCHEME 1
+
+/*!
+ *     @brief
+ *             Specifies the optional service scheme to resolve.
+ *
+ *     @param gai
+ *             The getaddrinfo object.
+ *
+ *     @param service_scheme
+ *             Service scheme as a string, such as "_443._https".
+ *
+ *     @discussion
+ *             This function has no effect on a getaddrinfo object that has been activated or invalidated.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+void
+dnssd_getaddrinfo_set_service_scheme(dnssd_getaddrinfo_t gai, const char * _Nullable service_scheme);
+
 /*!
  *     @brief
  *             Specifies the index of the interface via which to resolve the hostname.
@@ -247,6 +294,7 @@ dnssd_getaddrinfo_set_protocols(dnssd_getaddrinfo_t gai, DNSServiceProtocol prot
  *             getaddrinfo operation to not fail with a kDNSServiceErr_NotAuth error.
  *
  *             This function is an alternative to <code>dnssd_getaddrinfo_set_delegate_uuid()</code>.
+ *             This function is an alternative to <code>dnssd_getaddrinfo_set_delegate_audit_token()</code>.
  *
  *             This function has no effect on a getaddrinfo object that has been activated or invalidated.
  */
@@ -269,6 +317,7 @@ dnssd_getaddrinfo_set_delegate_pid(dnssd_getaddrinfo_t gai, pid_t pid);
  *             getaddrinfo operation to not fail with the <code>kDNSServiceErr_NotAuth</code> error.
  *
  *             This function is an alternative to <code>dnssd_getaddrinfo_set_delegate_pid()</code>.
+ *             This function is an alternative to <code>dnssd_getaddrinfo_set_delegate_audit_token()</code>.
  *
  *             This function has no effect on a getaddrinfo object that has been activated or invalidated.
  */
@@ -276,6 +325,31 @@ DNSSD_AVAILABLE
 void
 dnssd_getaddrinfo_set_delegate_uuid(dnssd_getaddrinfo_t gai, uuid_t _Nonnull uuid);
 
+#define DNSSD_GETADDRINFO_SUPPORTS_DELEGATE_AUDIT_TOKEN 1
+
+/*!
+ *     @brief
+ *             Sets the audit token of the process on whose behalf the getaddrinfo operation is being performed.
+ *
+ *     @param gai
+ *             The getaddrinfo object.
+ *
+ *     @param audit_token
+ *             audit token of the process being represented.
+ *
+ *     @discussion
+ *             If a delegate audit token is set, then the calling process must have the proper entitlement in order for the
+ *             getaddrinfo operation to not fail with the <code>kDNSServiceErr_NotAuth</code> error.
+ *
+ *             This function is an alternative to <code>dnssd_getaddrinfo_set_delegate_pid()</code>.
+ *             This function is an alternative to <code>dnssd_getaddrinfo_set_delegate_uuid()</code>.
+ *
+ *             This function has no effect on a getaddrinfo object that has been activated or invalidated.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+void
+dnssd_getaddrinfo_set_delegate_audit_token(dnssd_getaddrinfo_t gai, audit_token_t audit_token);
+
 /*!
  *     @brief
  *             Specifies whether or not getaddrinfo results (of types <code>dnssd_getaddrinfo_result_type_add</code> and
@@ -294,6 +368,48 @@ DNSSD_AVAILABLE
 void
 dnssd_getaddrinfo_set_need_authenticated_results(dnssd_getaddrinfo_t gai, bool need);
 
+#define DNSSD_GETADDRINFO_SUPPORTS_ENCRYPTED_QUERIES 1
+
+/*!
+ *     @brief
+ *             Specifies whether or not getaddrinfo queries must use encrypted transports to the next DNS server.
+ *
+ *     @param gai
+ *             The getaddrinfo object.
+ *
+ *     @param need
+ *             Pass <code>true</code> if encrypted queries are required, otherwise, pass <code>false</code>.
+ *
+ *     @param fallback_config
+ *             If not NULL, specify a custom resolver configuration to use if no encrypted resolver configuation is otherwise
+ *             available.
+ *
+ *     @discussion
+ *             This function has no effect on a getaddrinfo object that has been activated or invalidated.
+ */
+DNSSD_AVAILABLE
+void
+dnssd_getaddrinfo_set_need_encrypted_query(dnssd_getaddrinfo_t gai, bool need, _Nullable xpc_object_t fallback_config);
+
+/*!
+ *     @brief
+ *             Add a resolver UUID that represents a resolver configuration registered with the system that should
+ *             be applied to this resolution. Multiple UUIDs can be set.
+ *
+ *     @param gai
+ *             The getaddrinfo object.
+ *
+ *     @param uuid
+ *             UUID of a resolver configuration registered with the system.
+ *
+ *     @discussion
+ *             This function has no effect on a getaddrinfo object that has been activated or invalidated.
+
+ */
+DNSSD_AVAILABLE
+void
+dnssd_getaddrinfo_add_resolver_uuid(dnssd_getaddrinfo_t gai, uuid_t _Nonnull uuid);
+
 /*!
  *     @brief
  *             Activates a getaddrinfo object.
@@ -402,13 +518,15 @@ dnssd_getaddrinfo_set_event_handler(dnssd_getaddrinfo_t gai, dnssd_event_handler
  */
 typedef enum {
        /*! @const dnssd_getaddrinfo_result_type_add The contained hostname and address pair is valid. */
-       dnssd_getaddrinfo_result_type_add                       = 1,
+       dnssd_getaddrinfo_result_type_add                               = 1,
        /*! @const dnssd_getaddrinfo_result_type_remove The contained hostname and address pair is no longer valid. */
-       dnssd_getaddrinfo_result_type_remove            = 2,
+       dnssd_getaddrinfo_result_type_remove                    = 2,
        /*! @const dnssd_getaddrinfo_result_type_no_address The contained hostname has no address of a particular type. */
-       dnssd_getaddrinfo_result_type_no_address        = 3,
+       dnssd_getaddrinfo_result_type_no_address                = 3,
        /*! @const dnssd_getaddrinfo_result_type_expired A hostname and address pair contained came from an expired cached record and may no longer be valid. */
-       dnssd_getaddrinfo_result_type_expired           = 4,
+       dnssd_getaddrinfo_result_type_expired                   = 4,
+       /*! @const dnssd_getaddrinfo_result_type_service_binding A hostname has associated service binding information. */
+       dnssd_getaddrinfo_result_type_service_binding   = 5,
 } dnssd_getaddrinfo_result_type_t;
 
 /*!
@@ -518,14 +636,287 @@ DNSSD_AVAILABLE
 const void * _Nullable
 dnssd_getaddrinfo_result_get_authentication_tag(dnssd_getaddrinfo_result_t gai_result, size_t *_Nullable out_length);
 
+/*!
+ *     @brief
+ *             Types of protocols used for getaddrinfo results.
+ */
+typedef enum {
+       /*! @const dnssd_getaddrinfo_result_protocol_udp The answers were retrieved using UDP. */
+       dnssd_getaddrinfo_result_protocol_udp           = 1,
+       /*! @const dnssd_getaddrinfo_result_protocol_tcp The answers were retrieved using TCP. */
+       dnssd_getaddrinfo_result_protocol_tcp           = 2,
+       /*! @const dnssd_getaddrinfo_result_protocol_tls The answers were retrieved using DNS over TLS. */
+       dnssd_getaddrinfo_result_protocol_tls           = 3,
+       /*! @const dnssd_getaddrinfo_result_protocol_https The answers were retrieved using DNS over HTTPS. */
+       dnssd_getaddrinfo_result_protocol_https         = 4,
+} dnssd_getaddrinfo_result_protocol_t;
+
+#define DNSSD_GETADDRINFO_HAS_GET_PROTOCOL 1
+
+/*!
+ *     @brief
+ *             Gets a getaddrinfo result's protocol.
+ *
+ *     @param gai_result
+ *             The getaddrinfo result.
+ *
+ *     @result
+ *             Result protocol.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+dnssd_getaddrinfo_result_protocol_t
+dnssd_getaddrinfo_result_get_protocol(dnssd_getaddrinfo_result_t gai_result);
+
+#define DNSSD_GETADDRINFO_HAS_GET_PROVIDER_NAME 1
+
+/*!
+ *     @brief
+ *             Gets a getaddrinfo result's DNS provider name, if applicable.
+ *
+ *     @param gai_result
+ *             The getaddrinfo result.
+ *
+ *     @result
+ *             DNS provider name.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+const char * _Nullable
+dnssd_getaddrinfo_result_get_provider_name(dnssd_getaddrinfo_result_t gai_result);
+
+/*!
+ *     @brief
+ *             Checks if a getaddrinfo result has valid service binding information.
+ *
+ *     @param gai_result
+ *             The getaddrinfo result.
+ *
+ *     @result
+ *             Returns true if service binding information is valid.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+bool
+dnssd_getaddrinfo_result_service_is_valid(dnssd_getaddrinfo_result_t gai_result);
+
+#define DNSSD_GETADDRINFO_SERVICE_PRIORITY_ALIAS 0
+
+/*!
+ *     @brief
+ *             Gets a getaddrinfo result's service binding priority.
+ *
+ *     @param gai_result
+ *             The getaddrinfo result.
+ *
+ *     @result
+ *             Service binding priority.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+uint16_t
+dnssd_getaddrinfo_result_get_service_priority(dnssd_getaddrinfo_result_t gai_result);
+
+/*!
+ *     @brief
+ *             Gets a getaddrinfo result's service binding port.
+ *
+ *     @param gai_result
+ *             The getaddrinfo result.
+ *
+ *     @result
+ *             Service binding port.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+uint16_t
+dnssd_getaddrinfo_result_get_service_port(dnssd_getaddrinfo_result_t gai_result);
+
+/*!
+ *     @brief
+ *             Gets a getaddrinfo result's service name.
+ *
+ *     @param gai_result
+ *             The getaddrinfo result.
+ *
+ *     @result
+ *             String containing the service binding name.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+const char * _Nullable
+dnssd_getaddrinfo_result_get_service_name(dnssd_getaddrinfo_result_t gai_result);
+
+/*!
+ *     @brief
+ *             Gets a getaddrinfo result's associated DoH URI if present.
+ *
+ *     @param gai_result
+ *             The getaddrinfo result.
+ *
+ *     @result
+ *             String containing a DoH URI, if present.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+const char * _Nullable
+dnssd_getaddrinfo_result_get_doh_uri(dnssd_getaddrinfo_result_t gai_result);
+
+/*!
+ *     @brief
+ *             Gets a getaddrinfo result's ECH (Encrypted Client Hello) configuration.
+ *
+ *     @param gai_result
+ *             The getaddrinfo result.
+ *
+ *     @param out_length
+ *             If non-NULL, gets set to the length of the ECH config.
+ *
+ *     @result
+ *             A pointer to the getaddrinfo result's ECH config, if it has one. Otherwise, NULL.
+ *
+ *     @discussion
+ *             The returned pointer, if non-NULL, is valid until the getaddrinfo result is released.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+const void * _Nullable
+dnssd_getaddrinfo_result_get_ech_config(dnssd_getaddrinfo_result_t gai_result, size_t *_Nullable out_length);
+
+#ifdef __BLOCKS__
+
+typedef bool (^dnssd_getaddrinfo_enumerate_alpn_values_block_t)(const char *alpn);
+
+/*!
+ *     @brief
+ *             Enumerates a getaddrinfo result's service binding ALPN (application layer protocol negotiation) hints.
+ *
+ *     @param gai_result
+ *             The getaddrinfo result.
+ *
+ *     @param enumerator
+ *             A block to invoke for each ALPN hint.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+void
+dnssd_getaddrinfo_result_enumerate_alpn_values(dnssd_getaddrinfo_result_t gai_result,
+                                                                                          DNSSD_NOESCAPE dnssd_getaddrinfo_enumerate_alpn_values_block_t enumerator);
+
+typedef bool (^dnssd_getaddrinfo_enumerate_addresses_block_t)(const struct sockaddr *address);
+
+/*!
+ *     @brief
+ *             Enumerates a getaddrinfo result's service address hints.
+ *
+ *     @param gai_result
+ *             The getaddrinfo result.
+ *
+ *     @param enumerator
+ *             A block to invoke for each address hint.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+void
+dnssd_getaddrinfo_result_enumerate_service_address_hints(dnssd_getaddrinfo_result_t gai_result,
+                                                                                                                DNSSD_NOESCAPE dnssd_getaddrinfo_enumerate_addresses_block_t enumerator);
+
+#endif // __BLOCKS__
+
+
+/*!
+ *     @brief
+ *             Gets the canonical names, if any, of a getaddrinfo result's hostname.
+ *
+ *     @param gai_result
+ *             The getaddrinfo result.
+ *
+ *     @result
+ *             An array containing zero or more canonical names.
+ *
+ *     @discussion
+ *             Canonical names will not be provided for a getaddrinfo objects's results unless the
+ *             <code>kDNSServiceFlagsReturnIntermediates</code> flag was set for the getaddrinfo object with
+ *             <code>dnssd_getaddrinfo_set_flags()</code>.
+ *
+ *             The canonical names in the array are ordered in the following way. For non-empty arrays, the first
+ *             canonical name in the array is the immediate canonical name of the getaddrinfo operation's hostname.
+ *             For all canonical names beyond the first, a canonical name is the immediate canonical name of the
+ *             canonical name that precedes it in the array.
+ *
+ *             For hostnames that have no canonical names, i.e., hostnames that aren't aliases, an empty array will be
+ *             returned.
+ *
+ *             The array returned by this function is guaranteed to be valid during the lifetime of the getaddrinfo
+ *             result. If the array is needed beyond the lifetime of the getaddrinfo result, then the array must be
+ *             retained.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+dnssd_cname_array_t
+dnssd_getaddrinfo_result_get_cnames(dnssd_getaddrinfo_result_t gai_result);
+
+/*!
+ *     @brief
+ *             Determines whether a getaddrinfo result was the product of cached DNS records, i.e., no additional DNS
+ *             queries needed to be sent by mDNSResponder to provide the result.
+ *
+ *     @param gai_result
+ *             The getaddrinfo result.
+ *
+ *     @result
+ *             Returns <code>true</code> if the result came from cached DNS records, otherwise, returns
+ *             <code>false</code>.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+bool
+dnssd_getaddrinfo_result_is_from_cache(dnssd_getaddrinfo_result_t gai_result);
+
+/*!
+ *     @brief
+ *             Gets the count of canonical names in a canonical name array.
+ *
+ *     @param cname_array
+ *             The canonical name array.
+ *
+ *     @result
+ *             The count of canonical names in the canonical name array.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+size_t
+dnssd_cname_array_get_count(dnssd_cname_array_t cname_array);
+
+/*!
+ *     @brief
+ *             Gets the canonical name at a specific index in a canonical name array.
+ *
+ *     @param cname_array
+ *             The canonical name array.
+ *
+ *     @param index
+ *             The zero-based index of the canonical name to get. The index must be no greater than the array's count,
+ *             i.e., the value returned by <code>dnssd_cname_array_get_count()</code> for the array.
+ *
+ *     @result
+ *             The canonical name at the specified index as a C string.
+ *
+ *     @discussion
+ *             The pointer returned by this function is guaranteed to be valid until the array is released.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+const char * _Nullable
+dnssd_cname_array_get_cname(dnssd_cname_array_t cname_array, size_t index);
+
 static inline const char *
 dnssd_getaddrinfo_result_type_to_string(dnssd_getaddrinfo_result_type_t result)
 {
        switch (result) {
-               case dnssd_getaddrinfo_result_type_add:                 return "Add";
-               case dnssd_getaddrinfo_result_type_remove:              return "Remove";
-               case dnssd_getaddrinfo_result_type_no_address:  return "NoAddress";
-               case dnssd_getaddrinfo_result_type_expired:             return "Expired";
+               case dnssd_getaddrinfo_result_type_add:                         return "Add";
+               case dnssd_getaddrinfo_result_type_remove:                      return "Remove";
+               case dnssd_getaddrinfo_result_type_no_address:          return "NoAddress";
+               case dnssd_getaddrinfo_result_type_expired:                     return "Expired";
+               case dnssd_getaddrinfo_result_type_service_binding:     return "ServiceBinding";
+               default:                                                                                        return "?";
+       }
+}
+
+static inline const char *
+dnssd_getaddrinfo_result_protocol_to_string(dnssd_getaddrinfo_result_protocol_t protocol)
+{
+       switch (protocol) {
+               case dnssd_getaddrinfo_result_protocol_udp:             return "UDP";
+               case dnssd_getaddrinfo_result_protocol_tcp:             return "TCP";
+               case dnssd_getaddrinfo_result_protocol_tls:             return "TLS";
+               case dnssd_getaddrinfo_result_protocol_https:   return "HTTPS";
                default:                                                                                return "?";
        }
 }
@@ -545,4 +936,22 @@ __END_DECLS
 
 DNSSD_ASSUME_NONNULL_END
 
+
+#if OS_OBJECT_USE_OBJC && __has_feature(objc_arc)
+       #define dnssd_retain_arc_safe(OBJ)      (OBJ)
+       #define dnssd_release_arc_safe(OBJ)     do {} while (0)
+#else
+       #define dnssd_retain_arc_safe(OBJ)      dnssd_retain(OBJ)
+       #define dnssd_release_arc_safe(OBJ)     dnssd_release(OBJ)
+#endif
+
+#define dnssd_getaddrinfo_forget(X)                            \
+       do {                                                                            \
+               if (*(X)) {                                                             \
+                       dnssd_getaddrinfo_invalidate(*(X));     \
+                       dnssd_release_arc_safe(*(X));           \
+                       *(X) = NULL;                                            \
+               }                                                                               \
+       } while (0)
+
 #endif // __DNSSD_PRIVATE_H__
index 6db523ac4ede617903b91bed11b0758ce1985353..7b124bc8c3bfabe789b057fb3412497ff47290f1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include "ClientRequests.h"
 #include "dnssd_xpc.h"
+#include "dnssd_svcb.h"
+#include "dnssd_private.h"
+#include "mdns_helpers.h"
 #include "mDNSMacOSX.h"
 
+#include <bsm/libbsm.h>
 #include <CoreUtils/CommonServices.h>
 #include <CoreUtils/DebugServices.h>
 #include <libproc.h>
+#include <mach/mach_time.h>
 #include <net/necp.h>
+#include <os/lock.h>
+#include <stdatomic.h>
 #include <sys/proc_info.h>
 #include <xpc/private.h>
 
-#if 0
-//======================================================================================================================
-#pragma mark - Kind Declarations
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+#include "QuerierSupport.h"
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+#include "mdns_trust.h"
+#include <os/feature_private.h>
 #endif
 
-#define DX_STRUCT(NAME)                        struct dx_ ## NAME ## _s
-#define DX_TYPE(NAME)                  dx_ ## NAME ## _t
-#define DX_KIND_DECLARE(NAME)  typedef DX_STRUCT(NAME) *       DX_TYPE(NAME)
-
-#define DX_KIND_DECLARE_FULL(NAME)                                             \
-       DX_KIND_DECLARE(NAME);                                                          \
-                                                                                                               \
-       static void                                                                                     \
-       _dx_ ## NAME ## _invalidate(DX_TYPE(NAME) object);      \
-                                                                                                               \
-       static void                                                                                     \
-       _dx_ ## NAME ## _finalize(DX_TYPE(NAME) object);        \
-                                                                                                               \
-       static DX_TYPE(NAME)                                                            \
-       _dx_ ## NAME ## _alloc(void)
+//======================================================================================================================
+// MARK: - Kind Declarations
+
+#define DX_STRUCT(NAME)                                        struct dx_ ## NAME ## _s
+#define DX_KIND_DECLARE_ABSTRACT(NAME) typedef DX_STRUCT(NAME) *       dx_ ## NAME ## _t
+#define DX_KIND_DECLARE(NAME)          \
+       DX_KIND_DECLARE_ABSTRACT(NAME); \
+                                                                       \
+       static dx_ ## NAME ## _t                \
+       _dx_ ## NAME ## _alloc_and_init(void)
 
 // Note: The last check checks if the base's type is equal to that of the superkind. If it's not, then the pointer
 // comparison used as the argument to sizeof will cause a "comparison of distinct pointer types" warning, so long as
 // the warning hasn't been disabled.
 
-#define DX_BASE_CHECK(NAME, SUPER)                                                                                                                             \
-       check_compile_time(offsetof(DX_STRUCT(NAME), base) == 0);                                                                       \
-       check_compile_time(sizeof_field(DX_STRUCT(NAME), base) == sizeof(DX_STRUCT(SUPER)));            \
-       extern int _dx_base_type_check[sizeof(&(((DX_TYPE(NAME))0)->base) == ((DX_TYPE(SUPER))0))]
-
-#define DX_KIND_DEFINE(NAME, SUPER)                                                                                    \
-       static const struct dx_kind_s _dx_ ## NAME ## _kind = {                                 \
-               &_dx_ ## SUPER ##_kind,                                                                                         \
-               _dx_ ## NAME ## _invalidate,                                                                            \
-               _dx_ ## NAME ## _finalize,                                                                                      \
-       };                                                                                                                                              \
-                                                                                                                                                       \
-       static DX_TYPE(NAME)                                                                                                    \
-       _dx_ ## NAME ## _alloc(void)                                                                                    \
-       {                                                                                                                                               \
-               const DX_TYPE(NAME) obj = (DX_TYPE(NAME))calloc(1, sizeof(*obj));       \
-               require_quiet(obj, exit);                                                                                       \
-                                                                                                                                                       \
-               const dx_base_t base = (dx_base_t)obj;                                                          \
-               base->ref_count = 1;                                                                                            \
-               base->kind              = &_dx_ ## NAME ## _kind;                                                       \
-                                                                                                                                                       \
-       exit:                                                                                                                                   \
-               return obj;                                                                                                                     \
-       }                                                                                                                                               \
+#define DX_BASE_CHECK(NAME, SUPER)                                                                                                                     \
+       check_compile_time(offsetof(DX_STRUCT(NAME), base) == 0);                                                               \
+       check_compile_time(sizeof_field(DX_STRUCT(NAME), base) == sizeof(DX_STRUCT(SUPER)));    \
+       extern int _dx_base_type_check[sizeof(&(((dx_ ## NAME ## _t)0)->base) == ((dx_ ## SUPER ## _t)0))]
+
+#define DX_SUBKIND_DEFINE_ABSTRACT(NAME, SUPER, ...)           \
+       static const struct dx_kind_s _dx_ ## NAME ## _kind = { \
+               .superkind = &_dx_ ## SUPER ##_kind,                            \
+               __VA_ARGS__                                                                                     \
+       };                                                                                                              \
        DX_BASE_CHECK(NAME, SUPER)
 
-DX_KIND_DECLARE(base);
-DX_KIND_DECLARE(request);
-DX_KIND_DECLARE_FULL(session);
-DX_KIND_DECLARE_FULL(getaddrinfo_request);
+#define DX_SUBKIND_DEFINE(NAME, SUPER, ...)                                                                                    \
+       DX_SUBKIND_DEFINE_ABSTRACT(NAME, SUPER, __VA_ARGS__);                                                   \
+                                                                                                                                                                       \
+       static dx_ ## NAME ## _t                                                                                                                \
+       _dx_ ## NAME ## _alloc_and_init(void)                                                                                   \
+       {                                                                                                                                                               \
+               const dx_ ## NAME ## _t obj = (dx_ ## NAME ## _t)calloc(1, sizeof(*obj));       \
+               require_quiet(obj, exit);                                                                                                       \
+                                                                                                                                                                       \
+               const dx_object_t object = (dx_object_t)obj;                                                            \
+               object->ref_count       = 1;                                                                                                    \
+               object->kind            = &_dx_ ## NAME ## _kind;                                                               \
+               _dx_init(object);                                                                                                                       \
+                                                                                                                                                                       \
+       exit:                                                                                                                                                   \
+               return obj;                                                                                                                                     \
+       }
+
+#define DX_OBJECT_SUBKIND_DEFINE_ABSTRACT(NAME, ...)   DX_SUBKIND_DEFINE_ABSTRACT(NAME, object, __VA_ARGS__)
+#define DX_OBJECT_SUBKIND_DEFINE(NAME, ...)                            DX_SUBKIND_DEFINE(NAME, object, __VA_ARGS__)
+#define DX_REQUEST_SUBKIND_DEFINE(NAME, ...)                   DX_SUBKIND_DEFINE(NAME, request, __VA_ARGS__)
+
+DX_KIND_DECLARE_ABSTRACT(object);
+DX_KIND_DECLARE(session);
+DX_KIND_DECLARE_ABSTRACT(request);
+DX_KIND_DECLARE(gai_request);
+
+#define DX_TRANSPARENT_UNION_MEMBER(NAME)      DX_STRUCT(NAME) *       NAME
 
 typedef union {
-       dx_base_t                                       base;
-       dx_session_t                            session;
-       dx_request_t                            request;
-       dx_getaddrinfo_request_t        getaddrinfo_request;
+       DX_TRANSPARENT_UNION_MEMBER(object);
+       DX_TRANSPARENT_UNION_MEMBER(session);
+       DX_TRANSPARENT_UNION_MEMBER(request);
+       DX_TRANSPARENT_UNION_MEMBER(gai_request);
 } dx_any_t __attribute__((__transparent_union__));
 
-typedef void (*dx_invalidate_f)(dx_any_t object);
-typedef void (*dx_finalize_f)(dx_any_t object);
+typedef void
+(*dx_init_f)(dx_any_t object);
+
+typedef void
+(*dx_invalidate_f)(dx_any_t object);
+
+typedef void
+(*dx_finalize_f)(dx_any_t object);
 
 typedef const struct dx_kind_s *       dx_kind_t;
 struct dx_kind_s {
        dx_kind_t               superkind;      // This kind's superkind. All kinds have a superkind, except the base kind.
+       dx_init_f               init;           // Initializes an object.
        dx_invalidate_f invalidate;     // Stops an object's outstanding operations, if any.
        dx_finalize_f   finalize;       // Releases object's resources right before the object is freed.
 };
 
-#if 0
 //======================================================================================================================
-#pragma mark - Base Kind Definition
-#endif
+// MARK: - Object Kind Definition
 
-struct dx_base_s {
-       dx_kind_t       kind;           // The object's kind.
-       int32_t         ref_count;      // Reference count.
+struct dx_object_s {
+       dx_kind_t                       kind;           // The object's kind.
+       _Atomic(int32_t)        ref_count;      // Reference count.
 };
 
-static const struct dx_kind_s _dx_base_kind = {
-       NULL,   // No superkind.
-       NULL,   // No invalidate method.
-       NULL,   // No finalize method.
-};
+static void
+_dx_init(dx_object_t object);
 
-#if 0
-//======================================================================================================================
-#pragma mark - Request Kind Definition
-#endif
+static void
+_dx_retain(dx_any_t object);
 
-struct dx_request_s {
-       struct dx_base_s        base;                   // Object base.
-       dx_request_t            next;                   // Next request in list.
-       xpc_object_t            result_array;   // Array of pending results.
-       uint64_t                        command_id;             // ID to distinquish multiple commands during a session.
-       uint32_t                        request_id;             // Request ID, used for logging purposes.
-       DNSServiceErrorType     error;                  // Pending error.
-       bool                            sent_error;             // True if the pending error has been sent to client.
-};
+static void
+_dx_release(dx_any_t object);
+#define _dx_release_null_safe(X)       \
+       do {                                                    \
+               if (X) {                                        \
+                       _dx_release(X);                 \
+               }                                                       \
+       } while (0)
+#define _dx_forget(X)  ForgetCustom(X, _dx_release)
 
 static void
-_dx_request_finalize(dx_any_t request);
+_dx_invalidate(dx_any_t object);
 
-static const struct dx_kind_s _dx_request_kind = {
-       &_dx_base_kind,
-       NULL,                                   // No invalidate method.
-       _dx_request_finalize,
+static const struct dx_kind_s _dx_object_kind = {
+       .superkind      = NULL,
+       .init           = NULL,
+       .invalidate     = NULL,
+       .finalize       = NULL
 };
-DX_BASE_CHECK(request, base);
 
-#if 0
 //======================================================================================================================
-#pragma mark - Session Kind Definition
-#endif
+// MARK: - Session Kind Definition
 
 struct dx_session_s {
-       struct dx_base_s        base;                                           // Object base;
+       struct dx_object_s      base;                                           // Object base;
        dx_session_t            next;                                           // Next session in list.
        dx_request_t            request_list;                           // List of outstanding requests.
        xpc_connection_t        connection;                                     // Underlying XPC connection.
+       uint64_t                        pending_send_start_ticks;       // Start time in mach ticks of the current pending send condition.
+       audit_token_t           audit_token;                            // Client's audit_token.
+       uid_t                           client_euid;                            // Client's EUID.
+       pid_t                           client_pid;                                     // Client's PID.
+       uint32_t                        pending_send_count;                     // Count of sent messages that still haven't been processed.
+       uint32_t                        pending_send_count_max;         // Maximum pending_send_count value.
+       char                            client_name[MAXCOMLEN];         // Client's process name.
        bool                            has_delegate_entitlement;       // True if the client is entitled to be a delegate.
+       bool                            terminated;                                     // True if the session was prematurely ended due to a fatal error.
 };
 
-DX_KIND_DEFINE(session, base);
+static void
+_dx_session_invalidate(dx_session_t session);
+
+DX_OBJECT_SUBKIND_DEFINE(session,
+       .invalidate     = _dx_session_invalidate
+);
+
+typedef union {
+       DX_TRANSPARENT_UNION_MEMBER(request);
+       DX_TRANSPARENT_UNION_MEMBER(gai_request);
+} dx_any_request_t __attribute__((__transparent_union__));
 
-#if 0
 //======================================================================================================================
-#pragma mark - GetAddrInfo Request Kind Definition
-#endif
+// MARK: - Request Kind Definition
 
-struct dx_getaddrinfo_request_s {
-       struct dx_request_s                     base;                   // Request object base.
-       GetAddrInfoClientRequest        gai;                    // Underlying GetAddrInfoClientRequest.
-       xpc_object_t                            hostname;               // Hostname to be resolved for getaddrinfo requests.
-       uuid_t                                          client_uuid;    // Client's UUID for authenticating results.
-       bool                                            need_auth;              // True if results need to be authenticated.
-       bool                                            active;                 // True if the GetAddrInfoClientRequest is currently active.
+struct dx_request_s {
+       struct dx_object_s      base;                   // Object base.
+       dx_request_t            next;                   // Next request in list.
+       dx_session_t            session;                // Back pointer to parent session.
+       xpc_object_t            results;                // Array of pending results.
+       uint64_t                        command_id;             // ID to distinquish multiple commands during a session.
+       uint32_t                        request_id;             // Request ID, used for logging purposes.
+       DNSServiceErrorType     error;                  // Pending error.
+       os_unfair_lock          lock;                   // Lock for pending error and results array.
+       bool                            sent_error;             // True if the pending error has been sent to client.
 };
 
-DX_KIND_DEFINE(getaddrinfo_request, request);
+static void
+_dx_request_init(dx_request_t request);
+
+static void
+_dx_request_finalize(dx_request_t request);
+
+DX_OBJECT_SUBKIND_DEFINE_ABSTRACT(request,
+       .init           = _dx_request_init,
+       .finalize       = _dx_request_finalize
+);
 
-#if 0
 //======================================================================================================================
-#pragma mark - Local Prototypes
+// MARK: - GetAddrInfo Request Kind Definition
+
+struct dx_gai_request_s {
+       struct dx_request_s                     base;                                   // Request object base.
+       GetAddrInfoClientRequest        gai;                                    // Underlying GAI request.
+       QueryRecordClientRequest        query;                                  // Underlying SVCB/HTTPS query request.
+       xpc_object_t                            params;                                 // Parameter dictionary from client's message.
+       xpc_object_t                            hostname_obj;                   // Hostname string from parameter dictionary.
+       const char *                            hostname;                               // Hostname C string to be resolved for getaddrinfo request.
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+       mdns_trust_t                            trust;                                  // Trust instance if status is mdns_trust_status_pending
 #endif
+       mdns_dns_service_id_t           custom_service_id;              // ID for this request's custom DNS service.
+       xpc_object_t                            cnames_a;                               // Hostname's canonical names for A records as an XPC array.
+       ssize_t                                         cnames_a_expire_idx;    // Index of the first expired canonical name in cnames_a.
+       xpc_object_t                            cnames_aaaa;                    // Hostname's canonical names for AAAA records as an XPC array.
+       ssize_t                                         cnames_aaaa_expire_idx; // Index of the first expired canonical name in cnames_aaaa.
+       uuid_t                                          effective_uuid;                 // Effective client UUID for creating result auth tags.
+       bool                                            cnames_a_changed;               // True if cnames_a has changed.
+       bool                                            cnames_aaaa_changed;    // True if cnames_aaaa has changed.
+       bool                                            gai_active;                             // True if the GAI request is currently active.
+       bool                                            query_active;                   // True if the SVCB/HTTPS query request is currently active.
+       bool                                            need_auth;                              // True if GAI results need to be authenticated.
+};
 
-static dispatch_queue_t
-_dx_server_queue(void);
+static void
+_dx_gai_request_invalidate(dx_gai_request_t request);
 
 static void
-_dx_server_register_session(dx_session_t session);
+_dx_gai_request_finalize(dx_gai_request_t request);
+
+DX_REQUEST_SUBKIND_DEFINE(gai_request,
+       .invalidate     = _dx_gai_request_invalidate,
+       .finalize       = _dx_gai_request_finalize
+);
+
+//======================================================================================================================
+// MARK: - Local Prototypes
+
+static dispatch_queue_t
+_dx_server_queue(void);
 
 static void
-_dx_server_deregister_session(dx_session_t session);
+_dx_server_handle_new_connection(xpc_connection_t connection);
 
 static void
-_dx_retain(dx_any_t object);
+_dx_server_register_session(dx_session_t session);
 
 static void
-_dx_release(dx_any_t object);
-#define _dx_release_null_safe(X)       do { if (X) { _dx_release(X); } } while (0)
+_dx_server_deregister_session(dx_session_t session);
 
 static void
-_dx_invalidate(dx_any_t object);
+_dx_server_check_sessions(void);
 
 static dx_session_t
 _dx_session_create(xpc_connection_t connection);
 
 static void
-_dx_session_activate(dx_session_t me);
+_dx_session_activate(dx_session_t session);
+
+static void
+_dx_session_handle_message(dx_session_t session, xpc_object_t msg);
 
 static DNSServiceErrorType
 _dx_session_handle_getaddrinfo_command(dx_session_t session, xpc_object_t msg);
@@ -210,75 +285,204 @@ static DNSServiceErrorType
 _dx_session_handle_stop_command(dx_session_t session, xpc_object_t msg);
 
 static void
-_dx_session_send_results(dx_session_t session);
+_dx_session_append_request(dx_session_t session, dx_any_request_t any);
 
-static dx_getaddrinfo_request_t
-_dx_getaddrinfo_request_create(uint64_t command_id, uint32_t request_id);
+static void
+_dx_session_check(dx_session_t session, uint64_t now_ticks);
 
-static DNSServiceErrorType
-_dx_getaddrinfo_request_set_hostname(dx_getaddrinfo_request_t request, xpc_object_t hostname);
+static void
+_dx_session_send_message(dx_session_t session, xpc_object_t msg);
+
+static void
+_dx_session_terminate(dx_session_t session);
+
+static void
+_dx_session_log_error(dx_session_t session, DNSServiceErrorType error);
+
+static void
+_dx_session_log_pending_send_count_increase(dx_session_t session);
+
+static void
+_dx_session_log_pending_send_count_decrease(dx_session_t session);
+
+static void
+_dx_session_log_termination(dx_session_t session);
+
+static xpc_object_t
+_dx_request_take_results(dx_request_t request);
+
+typedef void (^dx_block_t)(void);
+
+static void
+_dx_request_locked(dx_any_request_t request, dx_block_t block);
 
 static void
-_dx_getaddrinfo_request_set_need_authenticaed_results(dx_getaddrinfo_request_t request, bool need,
-       const uuid_t client_uuid);
+_dx_request_append_result(dx_any_request_t request, xpc_object_t result);
+
+static DNSServiceErrorType
+_dx_request_get_error(dx_any_request_t request);
+
+static bool
+_dx_request_set_error(dx_any_request_t request, DNSServiceErrorType error);
+
+static bool
+_dx_request_send_pending_error(dx_any_request_t request);
+
+static dx_gai_request_t
+_dx_gai_request_create(uint64_t command_id, dx_session_t session);
+
+static DNSServiceErrorType
+_dx_gai_request_activate(dx_gai_request_t request);
 
 static DNSServiceErrorType
-_dx_getaddrinfo_request_activate(dx_getaddrinfo_request_t request, uint32_t interface_index, DNSServiceFlags flags,
-       DNSServiceProtocol protocols, pid_t pid, const uuid_t uuid, uid_t uid);
+_dx_gai_request_activate_internal(dx_gai_request_t request);
+
+static DNSServiceErrorType
+_dx_gai_request_start_client_requests(dx_gai_request_t request, GetAddrInfoClientRequestParams *gai_params,
+       QueryRecordClientRequestParams *query_params, const uint8_t *resolver_uuid, xpc_object_t fallback_config);
 
 static void
-_dx_getaddrinfo_request_result_handler(mDNS *m, DNSQuestion *question, const ResourceRecord *answer,
-       QC_result qc_result, DNSServiceErrorType error, void *context);
+_dx_gai_request_stop_client_requests(dx_gai_request_t request);
 
-#if 0
-//======================================================================================================================
-#pragma mark - Server Functions
+static xpc_object_t *
+_dx_gai_request_get_cnames_ptr(dx_gai_request_t request, int qtype, bool **out_changed_ptr,
+       ssize_t **out_expire_idx_ptr);
+
+static void
+_dx_gai_request_append_cname(dx_gai_request_t request, int qtype, const domainname *cname, bool expired, bool unwind);
+#define _dx_gai_request_unwind_cnames_if_necessary(R, T)       _dx_gai_request_append_cname(R, T, NULL, false, true)
+
+static xpc_object_t
+_dx_gai_request_copy_cname_update(dx_gai_request_t request, int qtype);
+
+static void
+_dx_gai_request_gai_result_handler(mDNS *m, DNSQuestion *q, const ResourceRecord *answer, QC_result qc_result,
+       DNSServiceErrorType error, void *context);
+
+static void
+_dx_gai_request_query_result_handler(mDNS *m, DNSQuestion *q, const ResourceRecord *answer, QC_result qc_result,
+       DNSServiceErrorType error, void *context);
+
+static void
+_dx_gai_request_enqueue_result(dx_gai_request_t request, uint32_t if_index, const domainname *name, uint16_t type,
+       uint16_t class, const uint8_t *rdata_ptr, size_t rdata_len, bool is_expired, bool is_add, bool answered_from_cache,
+       DNSServiceErrorType error, dnssd_getaddrinfo_result_protocol_t protocol, const char *provider_name);
+
+static void
+_dx_gai_request_get_delegator_ids(dx_gai_request_t request, pid_t *out_delegator_pid,
+       const uint8_t **out_delegator_uuid, const audit_token_t **out_delegator_audit_token, audit_token_t *storage);
+
+static DNSServiceErrorType
+_dx_gai_request_get_svcb_name_and_type(dx_gai_request_t request, const char **out_svcb_name,
+       uint16_t *out_svcb_type, char **out_svcb_memory);
+
+static DNSServiceErrorType
+_dx_gai_request_set_need_authenticated_results(dx_gai_request_t request, pid_t effective_pid,
+       const uuid_t effective_uuid);
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+static const uint8_t *
+_dx_gai_request_get_resolver_uuid(dx_gai_request_t request);
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+static bool
+_dx_gai_request_is_for_in_app_browser(dx_gai_request_t request);
 #endif
 
 static void
-_dx_server_handle_new_connection(xpc_connection_t connection);
+_dx_gai_request_log_start(dx_gai_request_t request, DNSServiceFlags flags, uint32_t if_index,
+       DNSServiceProtocol protocols, pid_t delegator_pid, const uuid_t delegator_uuid);
+
+static void
+_dx_gai_request_log_stop(dx_gai_request_t request);
+
+static void
+_dx_gai_request_log_a_result(dx_gai_request_t request, uint32_t query_id, uint32_t if_index, const domainname *name,
+       const uint8_t *rdata, MortalityState mortality, bool is_add_event);
+
+static void
+_dx_gai_request_log_aaaa_result(dx_gai_request_t request, uint32_t query_id, uint32_t if_index, const domainname *name,
+       const uint8_t *rdata, MortalityState mortality, bool is_add);
+
+static void
+_dx_gai_request_log_svcb_result(dx_gai_request_t request, uint32_t query_id, uint32_t if_index, const domainname *name,
+       const char *type_str, const uint8_t *rdata_ptr, size_t rdata_len, const ResourceRecord *answer, bool is_add_event);
+
+static void
+_dx_gai_request_log_no_such_record_result(dx_gai_request_t request, uint32_t query_id, uint32_t if_index,
+       const domainname *name, const char *type_str, MortalityState mortality, bool is_add);
+
+static void
+_dx_gai_request_log_error(dx_gai_request_t request, DNSServiceErrorType error);
+
+#define DNSSD_AUTHENTICATION_TAG_SIZE  32      // XXX: Defined as a workaround until NECP header defines this length.
+
+static bool
+_dx_authenticate_address_rdata(uuid_t effective_uuid, const char *hostname, int type, const uint8_t *rdata,
+       uint8_t out_auth_tag[STATIC_PARAM DNSSD_AUTHENTICATION_TAG_SIZE]);
+
+static char *
+_dx_pid_to_name(pid_t pid, char out_name[STATIC_PARAM MAXCOMLEN]);
+
+static void
+_dx_kqueue_locked(const char *description, dx_block_t block);
+
+//======================================================================================================================
+// MARK: - Globals
+
+static dx_session_t    g_session_list = NULL;
+
+//======================================================================================================================
+// MARK: - Server Functions
 
 mDNSexport void
 dnssd_server_init(void)
 {
-       static xpc_connection_t listener = NULL;
+       static dispatch_once_t  s_once = 0;
+       static xpc_connection_t s_listener = NULL;
 
-       listener = xpc_connection_create_mach_service(DNSSD_MACH_SERVICE_NAME, _dx_server_queue(),
-               XPC_CONNECTION_MACH_SERVICE_LISTENER);
-    xpc_connection_set_event_handler(listener,
-       ^(xpc_object_t event)
-       {
-               if (xpc_get_type(event) == XPC_TYPE_CONNECTION) {
-                       _dx_server_handle_new_connection((xpc_connection_t)event);
-               }
+       dispatch_once(&s_once,
+       ^{
+               s_listener = xpc_connection_create_mach_service(DNSSD_MACH_SERVICE_NAME, _dx_server_queue(),
+                       XPC_CONNECTION_MACH_SERVICE_LISTENER);
+               xpc_connection_set_event_handler(s_listener,
+               ^(xpc_object_t event)
+               {
+                       if (xpc_get_type(event) == XPC_TYPE_CONNECTION) {
+                               _dx_server_handle_new_connection((xpc_connection_t)event);
+                       }
+               });
+               xpc_connection_activate(s_listener);
        });
-       xpc_connection_activate(listener);
 }
 
-static void
-_dx_server_handle_new_connection(xpc_connection_t connection)
+//======================================================================================================================
+
+mDNSexport void
+dnssd_server_idle(void)
 {
-       const dx_session_t session = _dx_session_create(connection);
-       if (session) {
-               _dx_session_activate(session);
-               _dx_server_register_session(session);
-               _dx_release(session);
-       } else {
-               xpc_connection_cancel(connection);
-       }
+       static dispatch_once_t          s_once = 0;
+       static dispatch_source_t        s_source = NULL;
+       dispatch_once(&s_once,
+       ^{
+               s_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0, _dx_server_queue());
+               dispatch_source_set_event_handler(s_source,
+               ^{
+                       _dx_server_check_sessions();
+               });
+               dispatch_activate(s_source);
+       });
+       dispatch_source_merge_data(s_source, 1);
 }
 
-
 //======================================================================================================================
 
-static dx_session_t    g_session_list  = NULL;
-
-mDNSexport void
-dnssd_server_idle(void)
+uint32_t
+dnssd_server_get_new_request_id(void)
 {
-       for (dx_session_t session = g_session_list; session; session = session->next) {
-               _dx_session_send_results(session);
-       }
+       static _Atomic(uint32_t) s_next_id = 1;
+       return atomic_fetch_add(&s_next_id, 1);
 }
 
 //======================================================================================================================
@@ -292,7 +496,7 @@ _dx_server_queue(void)
        dispatch_once(&once,
        ^{
                const dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL,
-                       QOS_CLASS_UTILITY, 0);
+                       QOS_CLASS_USER_INITIATED, 0);
                queue = dispatch_queue_create("com.apple.dnssd.server", attr);
        });
        return queue;
@@ -300,12 +504,31 @@ _dx_server_queue(void)
 
 //======================================================================================================================
 
+static void
+_dx_server_handle_new_connection(const xpc_connection_t connection)
+{
+       dx_session_t session = _dx_session_create(connection);
+       if (session) {
+               _dx_session_activate(session);
+               _dx_server_register_session(session);
+               _dx_forget(&session);
+       } else {
+               xpc_connection_cancel(connection);
+       }
+}
+
+//======================================================================================================================
+
 static void
 _dx_server_register_session(dx_session_t session)
 {
-       session->next   = g_session_list;
-       g_session_list  = session;
-       _dx_retain(session);
+       dx_session_t *ptr = &g_session_list;
+       while (*ptr) {
+               ptr = &(*ptr)->next;
+       }
+       session->next = NULL;
+       *ptr = session;
+       _dx_retain(*ptr);
 }
 
 //======================================================================================================================
@@ -313,71 +536,126 @@ _dx_server_register_session(dx_session_t session)
 static void
 _dx_server_deregister_session(dx_session_t session)
 {
-       dx_session_t *ptr;
-       for (ptr = &g_session_list; *ptr; ptr = &(*ptr)->next) {
-               if (*ptr == session) {
-                       break;
-               }
+       dx_session_t *ptr = &g_session_list;
+       while (*ptr && (*ptr != session)) {
+               ptr = &(*ptr)->next;
        }
        if (*ptr) {
-               *ptr                    = session->next;
-               session->next   = NULL;
-               _dx_release(session);
+               *ptr = session->next;
+               session->next = NULL;
+               _dx_forget(&session);
        }
 }
 
-#if 0
 //======================================================================================================================
-#pragma mark - Base Methods
-#endif
 
 static void
-_dx_retain(dx_any_t object)
+_dx_server_check_sessions(void)
 {
-       ++object.base->ref_count;
+       if (g_session_list) {
+               const uint64_t now_ticks = mach_continuous_time();
+               for (dx_session_t session = g_session_list; session; session = session->next) {
+                       _dx_session_check(session, now_ticks);
+               }
+       }
 }
 
 //======================================================================================================================
+// MARK: - Object Methods
 
 static void
-_dx_release(dx_any_t object)
+_dx_recursive_init(dx_object_t object, dx_kind_t kind);
+
+static void
+_dx_init(const dx_object_t me)
 {
-       if (--object.base->ref_count == 0) {
-               for (dx_kind_t kind = object.base->kind; kind; kind = kind->superkind) {
-                       if (kind->finalize) {
-                               kind->finalize(object);
-                       }
+       _dx_recursive_init(me, me->kind);
+}
+
+static void
+_dx_recursive_init(const dx_object_t me, const dx_kind_t kind)
+{
+       if (kind->superkind) {
+               _dx_recursive_init(me, kind->superkind);
+       }
+       if (kind->init) {
+               kind->init(me);
+       }
+}
+
+//======================================================================================================================
+
+static void
+_dx_retain(const dx_any_t any)
+{
+       const dx_object_t me = any.object;
+       atomic_fetch_add(&me->ref_count, 1);
+}
+
+//======================================================================================================================
+
+static void
+_dx_finalize(dx_object_t object);
+
+static void
+_dx_release(const dx_any_t any)
+{
+       const dx_object_t me = any.object;
+       if (atomic_fetch_sub(&me->ref_count, 1) == 1) {
+               _dx_finalize(me);
+               free(me);
+       }
+}
+
+static void
+_dx_finalize(const dx_object_t me)
+{
+       for (dx_kind_t kind = me->kind; kind; kind = kind->superkind) {
+               if (kind->finalize) {
+                       kind->finalize(me);
                }
-               free(object.base);
        }
 }
 
 //======================================================================================================================
 
 static void
-_dx_invalidate(dx_any_t object)
+_dx_invalidate(const dx_any_t any)
 {
-       for (dx_kind_t kind = object.base->kind; kind; kind = kind->superkind) {
+       const dx_object_t me = any.object;
+       for (dx_kind_t kind = me->kind; kind; kind = kind->superkind) {
                if (kind->invalidate) {
-                       kind->invalidate(object);
+                       kind->invalidate(me);
                        return;
                }
        }
 }
 
-#if 0
 //======================================================================================================================
-#pragma mark - Session Methods
-#endif
+// MARK: - Session Methods
+
+#define DNSSD_DELEGATE_ENTITLEMENT     "com.apple.private.network.socket-delegate"
 
 static dx_session_t
-_dx_session_create(xpc_connection_t connection)
+_dx_session_create(const xpc_connection_t connection)
 {
-       const dx_session_t obj = _dx_session_alloc();
+       const dx_session_t obj = _dx_session_alloc_and_init();
        require_quiet(obj, exit);
 
        obj->connection = connection;
        xpc_retain(obj->connection);
+       xpc_connection_get_audit_token(obj->connection, &obj->audit_token);
+       obj->client_pid         = xpc_connection_get_pid(obj->connection);
+       obj->client_euid        = xpc_connection_get_euid(obj->connection);
+       _dx_pid_to_name(obj->client_pid, obj->client_name);
+
+       xpc_object_t value = xpc_connection_copy_entitlement_value(obj->connection, DNSSD_DELEGATE_ENTITLEMENT);
+       if (value) {
+               if (value == XPC_BOOL_TRUE) {
+                       obj->has_delegate_entitlement = true;
+               }
+               xpc_forget(&value);
+       }
 
 exit:
        return obj;
@@ -386,74 +664,32 @@ exit:
 //======================================================================================================================
 
 static void
-_dx_session_handle_message(dx_session_t session, xpc_object_t msg);
-
-#define DNSSD_DELEGATE_ENTITLEMENT "com.apple.private.network.socket-delegate"
-
-static void
-_dx_session_activate(dx_session_t me)
+_dx_session_activate(const dx_session_t me)
 {
-       const xpc_object_t value = xpc_connection_copy_entitlement_value(me->connection, DNSSD_DELEGATE_ENTITLEMENT);
-       if (value) {
-               if (value == XPC_BOOL_TRUE) {
-                       me->has_delegate_entitlement = true;
-               }
-               xpc_release(value);
-       }
        _dx_retain(me);
        xpc_connection_set_target_queue(me->connection, _dx_server_queue());
        xpc_connection_set_event_handler(me->connection,
-       ^(xpc_object_t event) {
+       ^(const xpc_object_t event) {
                const xpc_type_t type = xpc_get_type(event);
                if (type == XPC_TYPE_DICTIONARY) {
-               KQueueLock();
-                       _dx_session_handle_message(me, event);
-               KQueueUnlock("_dx_session_handle_message");
+                       if (me->connection) {
+                               _dx_session_handle_message(me, event);
+                       }
                } else if (event == XPC_ERROR_CONNECTION_INVALID) {
-               KQueueLock();
                        _dx_server_deregister_session(me);
-               KQueueUnlock("_dx_server_deregister_session");
                        _dx_session_invalidate(me);
                        _dx_release(me);
                } else {
-                       xpc_connection_cancel(me->connection);
+                       xpc_connection_forget(&me->connection);
                }
        });
        xpc_connection_activate(me->connection);
 }
 
-static void
-_dx_session_handle_message(dx_session_t me, xpc_object_t msg)
-{
-       DNSServiceErrorType error;
-       const char * const command = dnssd_xpc_message_get_command(msg);
-       require_action_quiet(command, exit, error = kDNSServiceErr_BadParam);
-
-       if (strcmp(command, DNSSD_COMMAND_GETADDRINFO) == 0) {
-               error = _dx_session_handle_getaddrinfo_command(me, msg);
-       } else if (strcmp(command, DNSSD_COMMAND_STOP) == 0) {
-               error = _dx_session_handle_stop_command(me, msg);
-       } else {
-               error = kDNSServiceErr_BadParam;
-       }
-
-exit:
-       {
-               const xpc_object_t reply = xpc_dictionary_create_reply(msg);
-               if (likely(reply)) {
-                       dnssd_xpc_message_set_error(reply, error);
-                       xpc_connection_send_message(me->connection, reply);
-                       xpc_release(reply);
-               } else {
-                       xpc_connection_cancel(me->connection);
-               }
-       }
-}
-
 //======================================================================================================================
 
 static void
-_dx_session_invalidate(dx_session_t me)
+_dx_session_invalidate(const dx_session_t me)
 {
        xpc_connection_forget(&me->connection);
        dx_request_t req;
@@ -468,443 +704,1312 @@ _dx_session_invalidate(dx_session_t me)
 //======================================================================================================================
 
 static void
-_dx_session_finalize(dx_session_t me)
+_dx_session_handle_message(const dx_session_t me, const xpc_object_t msg)
 {
-       (void)me;
-}
-
-//======================================================================================================================
-
-static bool
-_dx_get_getaddrinfo_params(xpc_object_t msg, uint64_t *out_command_id, xpc_object_t *out_hostname,
-       uint32_t *out_interface_index, DNSServiceFlags *out_flags, DNSServiceProtocol *out_protocols,
-       pid_t *out_delegate_pid, const uint8_t **out_delegate_uuid, bool *out_need_auth_tags);
-
-extern mDNS    mDNSStorage;
-#define g_mdns mDNSStorage
+       DNSServiceErrorType err;
+       const char * const command = dnssd_xpc_message_get_command(msg);
+       require_action_quiet(command, exit, err = kDNSServiceErr_BadParam);
 
-static DNSServiceErrorType
-_dx_session_handle_getaddrinfo_command(dx_session_t me, xpc_object_t msg)
-{
-       dx_getaddrinfo_request_t        req = NULL;
-       DNSServiceErrorType                     error;
-       uint64_t                                        command_id;
-       xpc_object_t                            hostname;
-       uint32_t                                        interface_index;
-       DNSServiceFlags                         flags;
-       DNSServiceProtocol                      protocols;
-       pid_t                                           pid;
-       const uint8_t *                         uuid;
-       bool                                            need_auth;
-
-       const bool valid = _dx_get_getaddrinfo_params(msg, &command_id, &hostname, &interface_index, &flags, &protocols,
-               &pid, &uuid, &need_auth);
-       require_action_quiet(valid, exit, error = kDNSServiceErr_BadParam);
-
-       if (uuid || (pid != 0)) {
-               require_action_quiet(me->has_delegate_entitlement, exit, error = kDNSServiceErr_NoAuth);
+       if (strcmp(command, DNSSD_COMMAND_GETADDRINFO) == 0) {
+               err = _dx_session_handle_getaddrinfo_command(me, msg);
+       } else if (strcmp(command, DNSSD_COMMAND_STOP) == 0) {
+               err = _dx_session_handle_stop_command(me, msg);
        } else {
-               pid = xpc_connection_get_pid(me->connection);
+               err = kDNSServiceErr_BadParam;
        }
 
-       req = _dx_getaddrinfo_request_create(command_id, g_mdns.next_request_id++);
-       require_action_quiet(req, exit, error = kDNSServiceErr_NoMemory);
+exit:;
+       xpc_object_t reply = xpc_dictionary_create_reply(msg);
+       if (likely(reply)) {
+               dnssd_xpc_message_set_error(reply, err);
+               _dx_session_send_message(me, reply);
+               xpc_forget(&reply);
+       } else {
+               _dx_session_terminate(me);
+       }
+}
 
-       error = _dx_getaddrinfo_request_set_hostname(req, hostname);
-       require_noerr_quiet(error, exit);
+//======================================================================================================================
 
-       if (need_auth) {
-               struct proc_uniqidentifierinfo info;
-               const int n = proc_pidinfo(pid, PROC_PIDUNIQIDENTIFIERINFO, 1, &info, sizeof(info));
-               if (n == (int)sizeof(info)) {
-                       _dx_getaddrinfo_request_set_need_authenticaed_results(req, true, info.p_uuid);
-               }
-       }
-
-       const uid_t euid = xpc_connection_get_euid(me->connection);
-       error = _dx_getaddrinfo_request_activate(req, interface_index, flags, protocols, pid, uuid, euid);
-       require_noerr_quiet(error, exit);
-
-       req->base.next          = me->request_list;
-       me->request_list        = (dx_request_t)req;
-       req = NULL;
-
-exit:
-       _dx_release_null_safe(req);
-       static_analyzer_malloc_freed(req);
-       return error;
-}
-
-static bool
-_dx_get_getaddrinfo_params(xpc_object_t msg, uint64_t *out_command_id, xpc_object_t *out_hostname,
-       uint32_t *out_interface_index, DNSServiceFlags *out_flags, DNSServiceProtocol *out_protocols,
-       pid_t *out_delegate_pid, const uint8_t **out_delegate_uuid, bool *out_need_auth_tags)
+static DNSServiceErrorType
+_dx_session_handle_getaddrinfo_command(const dx_session_t me, const xpc_object_t msg)
 {
-       bool params_are_valid = false;
+       dx_gai_request_t req = NULL;
        bool valid;
+       DNSServiceErrorType err;
        const uint64_t command_id = dnssd_xpc_message_get_id(msg, &valid);
-       require_quiet(valid, exit);
+       require_action_quiet(valid, exit, err = kDNSServiceErr_BadParam);
 
        const xpc_object_t params = dnssd_xpc_message_get_parameters(msg);
-       require_quiet(params, exit);
-
-       xpc_object_t hostname = dnssd_xpc_parameters_get_hostname_object(params);
-       require_quiet(hostname, exit);
+       require_action_quiet(params, exit, err = kDNSServiceErr_BadParam);
 
-       const uint32_t interface_index = dnssd_xpc_parameters_get_interface_index(params, &valid);
-       require_quiet(valid, exit);
+       req = _dx_gai_request_create(command_id, me);
+       require_action_quiet(req, exit, err = kDNSServiceErr_NoMemory);
 
-       const DNSServiceFlags flags = dnssd_xpc_parameters_get_flags(params, &valid);
-       require_quiet(valid, exit);
+       req->params = params;
+       xpc_retain(req->params);
 
-       const uint32_t protocols = dnssd_xpc_parameters_get_protocols(params, &valid);
-       require_quiet(valid, exit);
-
-       pid_t pid;
-       const uint8_t * const uuid = dnssd_xpc_parameters_get_delegate_uuid(params);
-       if (uuid) {
-               pid = 0;
-       } else {
-               pid = dnssd_xpc_parameters_get_delegate_pid(params, NULL);
-       }
+       err = _dx_gai_request_activate(req);
+       require_noerr_quiet(err, exit);
 
-       *out_command_id                 = command_id;
-       *out_hostname                   = hostname;
-       *out_interface_index    = interface_index;
-       *out_flags                              = flags;
-       *out_protocols                  = protocols;
-       *out_delegate_pid               = pid;
-       *out_delegate_uuid              = uuid;
-       *out_need_auth_tags             = dnssd_xpc_parameters_get_need_authentication_tags(params);
-       params_are_valid = true;
+       _dx_session_append_request(me, req);
 
 exit:
-       return params_are_valid;
+       if (err) {
+               if (req) {
+                       _dx_gai_request_log_error(req, err);
+               } else {
+                       _dx_session_log_error(me, err);
+               }
+       }
+       _dx_release_null_safe(req);
+       return err;
 }
 
 //======================================================================================================================
 
 static DNSServiceErrorType
-_dx_session_handle_stop_command(dx_session_t me, xpc_object_t msg)
+_dx_session_handle_stop_command(const dx_session_t me, const xpc_object_t msg)
 {
        bool valid;
-       DNSServiceErrorType error;
+       DNSServiceErrorType err;
        const uint64_t command_id = dnssd_xpc_message_get_id(msg, &valid);
-       require_action_quiet(valid, exit, error = kDNSServiceErr_BadParam);
+       require_action_quiet(valid, exit, err = kDNSServiceErr_BadParam);
 
-       dx_request_t * ptr;
+       dx_request_t *ptr;
        dx_request_t req;
        for (ptr = &me->request_list; (req = *ptr) != NULL; ptr = &req->next) {
                if (req->command_id == command_id) {
                        break;
                }
        }
-       require_action_quiet(req, exit, error = kDNSServiceErr_BadReference);
-
-       *ptr            = req->next;
-       req->next       = NULL;
+       require_action_quiet(req, exit, err = kDNSServiceErr_BadReference);
 
+       *ptr = req->next;
+       req->next = NULL;
        _dx_invalidate(req);
-       _dx_release(req);
-       error = kDNSServiceErr_NoError;
+       _dx_forget(&req);
+       err = kDNSServiceErr_NoError;
 
 exit:
-       return error;
+       return err;
 }
 
 //======================================================================================================================
 
 static void
-_dx_session_send_results(dx_session_t me)
+_dx_session_append_request(const dx_session_t me, const dx_any_request_t any)
 {
-       bool success = false;
+       dx_request_t *ptr = &me->request_list;
+       while (*ptr) {
+               ptr = &(*ptr)->next;
+       }
+       const dx_request_t req = any.request;
+       req->next = NULL;
+       *ptr = req;
+       _dx_retain(*ptr);
+}
+
+//======================================================================================================================
+
+#define DX_SESSION_BACK_PRESSURE_TIMEOUT_SECS  5
+
+static void
+_dx_session_check(const dx_session_t me, const uint64_t now_ticks)
+{
+       bool terminate;
+       xpc_object_t results = NULL;
+       require_action_quiet(me->connection, exit, terminate = false);
+
+       if (me->pending_send_count > 0) {
+               const uint64_t elapsed_secs = (now_ticks - me->pending_send_start_ticks) / mdns_mach_ticks_per_second();
+               require_action_quiet(elapsed_secs < DX_SESSION_BACK_PRESSURE_TIMEOUT_SECS, exit, terminate = true);
+       }
        for (dx_request_t req = me->request_list; req; req = req->next) {
-               if (req->result_array && (xpc_array_get_count(req->result_array) > 0)) {
-                       const xpc_object_t msg = xpc_dictionary_create(NULL, NULL, 0);
-                       require_quiet(msg, exit);
+               results = _dx_request_take_results(req);
+               if (results) {
+                       xpc_object_t msg = xpc_dictionary_create(NULL, NULL, 0);
+                       require_action_quiet(msg, exit, terminate = true);
 
                        dnssd_xpc_message_set_id(msg, req->command_id);
                        dnssd_xpc_message_set_error(msg, kDNSServiceErr_NoError);
-                       dnssd_xpc_message_set_results(msg, req->result_array);
-                       xpc_connection_send_message(me->connection, msg);
-                       xpc_release(msg);
-
-                       xpc_release(req->result_array);
-                       req->result_array = xpc_array_create(NULL, 0);
-                       require_quiet(req->result_array, exit);
+                       dnssd_xpc_message_set_results(msg, results);
+                       xpc_forget(&results);
+                       _dx_session_send_message(me, msg);
+                       xpc_forget(&msg);
                }
-               if (req->error && !req->sent_error) {
-                       const xpc_object_t msg = xpc_dictionary_create(NULL, NULL, 0);
-                       require_quiet(msg, exit);
+               const bool ok = _dx_request_send_pending_error(req);
+               require_action_quiet(ok, exit, terminate = true);
+       }
+       terminate = false;
 
-                       dnssd_xpc_message_set_id(msg, req->command_id);
-                       dnssd_xpc_message_set_error(msg, req->error);
-                       xpc_connection_send_message(me->connection, msg);
-                       xpc_release(msg);
-                       req->sent_error = true;
-               }
+exit:
+       if (unlikely(terminate)) {
+               _dx_session_terminate(me);
+       }
+       xpc_forget(&results);
+}
+
+//======================================================================================================================
+
+static void
+_dx_session_send_message(const dx_session_t me, const xpc_object_t msg)
+{
+       require_quiet(me->connection, exit);
+
+       xpc_connection_send_message(me->connection, msg);
+       if (me->pending_send_count++ == 0) {
+               me->pending_send_start_ticks = mach_continuous_time();
+       } else {
+               _dx_session_log_pending_send_count_increase(me);
        }
-       success = true;
+       me->pending_send_count_max = me->pending_send_count;
+       _dx_retain(me);
+       xpc_connection_send_barrier(me->connection,
+       ^{
+               --me->pending_send_count;
+               if (me->pending_send_count_max > 1) {
+                       _dx_session_log_pending_send_count_decrease(me);
+               }
+               if (me->pending_send_count == 0) {
+                       me->pending_send_count_max = 0;
+               }
+               _dx_release(me);
+       });
 
 exit:
-       if (unlikely(!success)) {
-               xpc_connection_cancel(me->connection);
+       return;
+}
+
+//======================================================================================================================
+
+static void
+_dx_session_terminate(const dx_session_t me)
+{
+       if (!me->terminated) {
+               _dx_session_log_termination(me);
+               xpc_connection_forget(&me->connection);
+               me->terminated = true;
        }
 }
 
-#if 0
 //======================================================================================================================
-#pragma mark - Request Methods
-#endif
 
 static void
-_dx_request_finalize(dx_request_t me)
+_dx_session_log_error(const dx_session_t me, const DNSServiceErrorType error)
 {
-       xpc_forget(&me->result_array);
+       LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+               "XPC session error -- error: %{mdns:err}ld, client pid: %lld (" PUB_S ")",
+               (long)error, (long long)me->client_pid, me->client_name);
 }
 
-#if 0
 //======================================================================================================================
-#pragma mark - GetAddrInfo Request Methods
-#endif
 
-static dx_getaddrinfo_request_t
-_dx_getaddrinfo_request_create(uint64_t command_id, uint32_t request_id)
+static void
+_dx_session_log_pending_send_count_increase(const dx_session_t me)
+{
+       LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
+               "XPC session to client with pid %lld (%s) pending send count increased to %d",
+               (long long)me->client_pid, me->client_name, me->pending_send_count);
+}
+
+//======================================================================================================================
+
+static void
+_dx_session_log_pending_send_count_decrease(const dx_session_t me)
+{
+       LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
+               "XPC session to client with pid %lld (%s) pending send count decreased to %d",
+               (long long)me->client_pid, me->client_name, me->pending_send_count);
+}
+
+//======================================================================================================================
+
+static void
+_dx_session_log_termination(const dx_session_t me)
+{
+       LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+               "XPC session termination -- pending send count: %u, pending send count max: %u, client pid: %lld (" PUB_S ")",
+               me->pending_send_count, me->pending_send_count_max, (long long)me->client_pid, me->client_name);
+}
+
+//======================================================================================================================
+// MARK: - Request Methods
+
+static void
+_dx_request_init(const dx_request_t me)
+{
+       me->request_id  = dnssd_server_get_new_request_id();
+       me->lock                = OS_UNFAIR_LOCK_INIT;
+}
+
+//======================================================================================================================
+
+static void
+_dx_request_finalize(const dx_request_t me)
+{
+       _dx_forget(&me->session);
+       xpc_forget(&me->results);
+}
+
+//======================================================================================================================
+
+static xpc_object_t
+_dx_request_take_results(const dx_request_t me)
+{
+       __block xpc_object_t results = NULL;
+       _dx_request_locked(me,
+       ^{
+               if (me->results && (xpc_array_get_count(me->results) > 0)) {
+                       results = me->results;
+                       me->results = NULL;
+               }
+       });
+       return results;
+}
+
+//======================================================================================================================
+
+static void
+_dx_request_locked(const dx_any_request_t any, const dx_block_t block)
+{
+       const dx_request_t me = any.request;
+       os_unfair_lock_lock(&me->lock);
+       block();
+       os_unfair_lock_unlock(&me->lock);
+}
+
+//======================================================================================================================
+
+static void
+_dx_request_append_result(const dx_any_request_t any, const xpc_object_t result)
+{
+       const dx_request_t me = any.request;
+       _dx_request_locked(me,
+       ^{
+               if (!me->results) {
+                       me->results = xpc_array_create(NULL, 0);
+               }
+               if (likely(me->results)) {
+                       xpc_array_append_value(me->results, result);
+               } else {
+                       if (!me->error) {
+                               me->error = kDNSServiceErr_NoMemory;
+                       }
+               }
+       });
+}
+
+//======================================================================================================================
+
+static DNSServiceErrorType
+_dx_request_get_error(const dx_any_request_t any)
+{
+       const dx_request_t me = any.request;
+       __block DNSServiceErrorType error;
+       _dx_request_locked(me,
+       ^{
+               error = me->error;
+       });
+       return error;
+}
+
+//======================================================================================================================
+
+static bool
+_dx_request_set_error(const dx_any_request_t any, const DNSServiceErrorType error)
+{
+       __block bool did_set = false;
+       if (error) {
+               const dx_request_t me = any.request;
+               _dx_request_locked(me,
+               ^{
+                       if (!me->error) {
+                               me->error = error;
+                               did_set = true;
+                       }
+               });
+       }
+       return did_set;
+}
+
+//======================================================================================================================
+
+static bool
+_dx_request_send_pending_error(const dx_any_request_t any)
+{
+       bool ok = false;
+       const dx_request_t me = any.request;
+       const DNSServiceErrorType error = _dx_request_get_error(me);
+       if (error && !me->sent_error) {
+               xpc_object_t msg = xpc_dictionary_create(NULL, NULL, 0);
+               require_quiet(msg, exit);
+
+               dnssd_xpc_message_set_id(msg, me->command_id);
+               dnssd_xpc_message_set_error(msg, error);
+               _dx_session_send_message(me->session, msg);
+               xpc_forget(&msg);
+               me->sent_error = true;
+       }
+       ok = true;
+
+exit:
+       return ok;
+}
+
+//======================================================================================================================
+// MARK: - GetAddrInfo Request Methods
+
+static dx_gai_request_t
+_dx_gai_request_create(const uint64_t command_id, const dx_session_t session)
 {
-       dx_getaddrinfo_request_t req = NULL;
-       dx_getaddrinfo_request_t obj = _dx_getaddrinfo_request_alloc();
+       dx_gai_request_t obj = _dx_gai_request_alloc_and_init();
        require_quiet(obj, exit);
 
        obj->base.command_id    = command_id;
-       obj->base.request_id    = request_id;
-       obj->base.result_array  = xpc_array_create(NULL, 0);
-       require_quiet(obj->base.result_array, exit);
+       obj->base.session               = session;
+       _dx_retain(obj->base.session);
 
-       req = obj;
-       obj = NULL;
+       obj->cnames_a_expire_idx        = -1;
+       obj->cnames_aaaa_expire_idx     = -1;
 
 exit:
-       _dx_release_null_safe(obj);
-       return req;
+       return obj;
 }
 
 //======================================================================================================================
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
 static DNSServiceErrorType
-_dx_getaddrinfo_request_set_hostname(dx_getaddrinfo_request_t me, xpc_object_t hostname)
+_dx_gai_request_trust_check(dx_gai_request_t request, bool *out_activate_deferred);
+#endif
+
+static DNSServiceErrorType
+_dx_gai_request_activate(const dx_gai_request_t me)
 {
        DNSServiceErrorType err;
-       require_action_quiet(xpc_string_get_length(hostname) <= MAX_ESCAPED_DOMAIN_NAME, exit,
-               err = kDNSServiceErr_BadParam);
+       me->hostname_obj = dnssd_xpc_parameters_get_hostname_object(me->params);
+       require_action_quiet(me->hostname_obj, exit, err = kDNSServiceErr_BadParam);
+
+       xpc_retain(me->hostname_obj);
+       me->hostname = xpc_string_get_string_ptr(me->hostname_obj);
+       require_action_quiet(me->hostname, exit, err = kDNSServiceErr_Unknown);
+
+       bool defer_activation = false;
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+       if (os_feature_enabled(mDNSResponder, bonjour_privacy)) {
+               err = _dx_gai_request_trust_check(me, &defer_activation);
+               require_noerr_quiet(err, exit);
+       }
+#endif
+       if (!defer_activation) {
+               err = _dx_gai_request_activate_internal(me);
+               require_noerr_quiet(err, exit);
+       }
+       err = kDNSServiceErr_NoError;
 
-       xpc_release_null_safe(me->hostname);
-       me->hostname = xpc_copy(hostname);
-       require_action_quiet(me->hostname, exit, err = kDNSServiceErr_NoMemory);
+exit:
+       return err;
+}
 
-       err = kDNSServiceErr_NoError;
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+static DNSServiceErrorType
+_dx_gai_request_trust_check(const dx_gai_request_t me, bool * const out_defer_activation)
+{
+       DNSServiceErrorType err;
+       bool defer_activation = false;
+       const dx_session_t session = me->base.session;
+       mdns_trust_flags_t flags = mdns_trust_flags_none;
+       const mdns_trust_status_t status = mdns_trust_check_getaddrinfo(session->audit_token, me->hostname, &flags);
+       switch (status) {
+               case mdns_trust_status_granted:
+                       err = kDNSServiceErr_NoError;
+                       break;
+
+               case mdns_trust_status_denied:
+               case mdns_trust_status_pending:
+                       me->trust = mdns_trust_create(session->audit_token, NULL, flags);
+                       require_action_quiet(me->trust, exit, err = kDNSServiceErr_NoMemory);
+
+                       _dx_retain(me);
+                       mdns_trust_set_queue(me->trust, _dx_server_queue());
+                       mdns_trust_set_event_handler(me->trust,
+                       ^(const mdns_trust_event_t event, const mdns_trust_status_t update)
+                       {
+                               if (me->trust && (event == mdns_trust_event_result)) {
+                                       DNSServiceErrorType handler_err;
+                                       if (update == mdns_trust_status_granted) {
+                                               handler_err = _dx_gai_request_activate_internal(me);
+                                       } else {
+                                               handler_err = kDNSServiceErr_PolicyDenied;
+                                       }
+                                       if (handler_err && _dx_request_set_error(me, handler_err)) {
+                                               _dx_gai_request_log_error(me, handler_err);
+                                               _dx_request_send_pending_error(me);
+                                       }
+                               }
+                               mdns_forget(&me->trust);
+                               _dx_release(me);
+                       });
+                       mdns_trust_activate(me->trust);
+                       defer_activation = true;
+                       err = kDNSServiceErr_NoError;
+                       break;
+
+               case mdns_trust_status_no_entitlement:
+                       err = kDNSServiceErr_NoAuth;
+                       break;
+
+               default:
+                       err = kDNSServiceErr_Unknown;
+                       break;
+       }
 
 exit:
+       if (out_defer_activation) {
+               *out_defer_activation = defer_activation;
+       }
        return err;
 }
+#endif
 
 //======================================================================================================================
 
 static void
-_dx_getaddrinfo_request_set_need_authenticaed_results(dx_getaddrinfo_request_t me, bool need, const uuid_t client_uuid)
+_dx_gai_request_invalidate(const dx_gai_request_t me)
 {
-       if (need) {
-               uuid_copy(me->client_uuid, client_uuid);
-               me->need_auth = true;
-       } else {
-               uuid_clear(me->client_uuid);
-               me->need_auth = false;
-       }
+       _dx_gai_request_log_stop(me);
+       _dx_gai_request_stop_client_requests(me);
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+       mdns_trust_forget(&me->trust);
+#endif
 }
 
 //======================================================================================================================
 
-static DNSServiceErrorType
-_dx_getaddrinfo_request_activate(dx_getaddrinfo_request_t me, uint32_t interface_index, DNSServiceFlags flags,
-       DNSServiceProtocol protocols, pid_t pid, const uuid_t uuid, uid_t uid)
+static void
+_dx_gai_request_finalize(const dx_gai_request_t me)
 {
-       DNSServiceErrorType err;
-       const char * const hostname_str = xpc_string_get_string_ptr(me->hostname);
-       require_action_quiet(hostname_str, exit, err = kDNSServiceErr_Unknown);
-
-       err = GetAddrInfoClientRequestStart(&me->gai, me->base.request_id, hostname_str, interface_index, flags,
-               protocols, pid, uuid, uid, _dx_getaddrinfo_request_result_handler, me);
-       require_noerr_quiet(err, exit);
+       me->hostname = NULL;
+       xpc_forget(&me->params);
+       xpc_forget(&me->hostname_obj);
+       xpc_forget(&me->cnames_a);
+       xpc_forget(&me->cnames_aaaa);
+}
 
-       _dx_retain(me);
-       me->active = true;
+//======================================================================================================================
 
-exit:
+static DNSServiceErrorType
+_dx_gai_request_start_client_requests(const dx_gai_request_t me, GetAddrInfoClientRequestParams * const gai_params,
+       QueryRecordClientRequestParams * const query_params, const uint8_t * const resolver_uuid,
+       const xpc_object_t fallback_config)
+{
+       __block DNSServiceErrorType err = kDNSServiceErr_NoError;
+       _dx_kqueue_locked("dx_gai_request: starting client requests",
+       ^{
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+               if (resolver_uuid && !uuid_is_null(resolver_uuid)) {
+                       Querier_RegisterPathResolver(resolver_uuid);
+               }
+               if ((me->custom_service_id == 0) && fallback_config) {
+                       me->custom_service_id = Querier_RegisterCustomDNSService(fallback_config);
+               }
+               if (gai_params) {
+                       gai_params->resolverUUID        = resolver_uuid;
+                       gai_params->customID            = me->custom_service_id;
+               }
+               if (query_params) {
+                       query_params->resolverUUID      = resolver_uuid;
+                       query_params->customID          = me->custom_service_id;
+               }
+#else
+        (void)resolver_uuid;
+        (void)fallback_config;
+#endif
+               // If present, run the query for SVCB/HTTPSSVC first, in case the ALPN and address hints come back first.
+               if (query_params && !me->query_active) {
+                       err = QueryRecordClientRequestStart(&me->query, query_params, _dx_gai_request_query_result_handler, me);
+                       require_noerr_return(err);
+                       me->query_active = true;
+               }
+               // Run the A/AAAA lookup.
+               if (gai_params && !me->gai_active) {
+                       err = GetAddrInfoClientRequestStart(&me->gai, gai_params, _dx_gai_request_gai_result_handler, me);
+                       require_noerr_return(err);
+                       me->gai_active = true;
+               }
+       });
+       if (err) {
+               _dx_gai_request_stop_client_requests(me);
+       }
        return err;
 }
 
 //======================================================================================================================
 
 static void
-_dx_getaddrinfo_request_invalidate(dx_getaddrinfo_request_t me)
+_dx_gai_request_stop_client_requests(const dx_gai_request_t me)
 {
-       if (me->active) {
-               GetAddrInfoClientRequestStop(&me->gai);
-               me->active = false;
-               _dx_release(me);
+       _dx_kqueue_locked("dx_gai_request: stopping client requests",
+       ^{
+               if (me->gai_active) {
+                       GetAddrInfoClientRequestStop(&me->gai);
+                       me->gai_active = false;
+               }
+               if (me->query_active) {
+                       QueryRecordClientRequestStop(&me->query);
+                       me->query_active = false;
+               }
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+               if (me->custom_service_id != 0) {
+                       Querier_DeregisterCustomDNSService(me->custom_service_id);
+                       me->custom_service_id = 0;
+               }
+#endif
+       });
+}
+
+//======================================================================================================================
+
+static xpc_object_t *
+_dx_gai_request_get_cnames_ptr(const dx_gai_request_t me, const int qtype, bool ** const out_changed_ptr,
+       ssize_t **out_expire_idx_ptr)
+{
+       ssize_t *expire_idx_ptr;
+       xpc_object_t *cnames_ptr;
+       bool *changed_ptr;
+       switch (qtype) {
+               case kDNSServiceType_A:
+                       cnames_ptr              = &me->cnames_a;
+                       changed_ptr             = &me->cnames_a_changed;
+                       expire_idx_ptr  = &me->cnames_a_expire_idx;
+                       break;
+
+               case kDNSServiceType_AAAA:
+                       cnames_ptr              = &me->cnames_aaaa;
+                       changed_ptr             = &me->cnames_aaaa_changed;
+                       expire_idx_ptr  = &me->cnames_aaaa_expire_idx;
+                       break;
+
+               default:
+                       cnames_ptr              = NULL;
+                       expire_idx_ptr  = NULL;
+                       changed_ptr             = NULL;
+                       break;
+       }
+       if (out_expire_idx_ptr) {
+               *out_expire_idx_ptr = expire_idx_ptr;
        }
+       if (out_changed_ptr) {
+               *out_changed_ptr = changed_ptr;
+       }
+       return cnames_ptr;
 }
 
 //======================================================================================================================
 
 static void
-_dx_getaddrinfo_request_finalize(dx_getaddrinfo_request_t me)
+_dx_gai_request_append_cname(const dx_gai_request_t me, const int qtype, const domainname * const cname,
+       const bool expired, const bool unwind)
 {
-       xpc_forget(&me->hostname);
+       bool *changed_ptr;
+       ssize_t *expire_idx_ptr;
+       xpc_object_t * const cnames_ptr = _dx_gai_request_get_cnames_ptr(me, qtype, &changed_ptr, &expire_idx_ptr);
+       require_quiet(cnames_ptr, exit);
+
+       const char *cname_str = NULL;
+       char cname_buf[MAX_ESCAPED_DOMAIN_NAME];
+       if (cname) {
+               if (!ConvertDomainNameToCString(cname, cname_buf)) {
+                       cname_buf[0] = '\0';
+               }
+               cname_str = cname_buf;
+       }
+       _dx_request_locked(me,
+       ^{
+               if (unwind) {
+                       const ssize_t expire_idx = *expire_idx_ptr;
+                       if (*cnames_ptr && (expire_idx >= 0)) {
+                               xpc_object_t new_cnames = xpc_array_create(NULL, 0);
+                               if (new_cnames && (expire_idx > 0)) {
+                                       xpc_array_apply(*cnames_ptr,
+                                       ^ bool (const size_t index, const xpc_object_t _Nonnull value)
+                                       {
+                                               bool proceed = false;
+                                               if (index < (size_t)expire_idx) {
+                                                       xpc_array_append_value(new_cnames, value);
+                                                       proceed = true;
+                                               }
+                                               return proceed;
+                                       });
+                               }
+                               xpc_forget(cnames_ptr);
+                               *cnames_ptr             = new_cnames;
+                               *changed_ptr    = true;
+                       }
+                       *expire_idx_ptr = -1;
+               }
+               if (cname_str) {
+                       xpc_object_t cnames = *cnames_ptr;
+                       if (expired && (*expire_idx_ptr < 0)) {
+                               *expire_idx_ptr = cnames ? (ssize_t)xpc_array_get_count(cnames) : 0;
+                       }
+                       if (!cnames) {
+                               cnames = xpc_array_create(NULL, 0);
+                               *cnames_ptr = cnames;
+                       }
+                       if (cnames) {
+                               xpc_array_set_string(cnames, XPC_ARRAY_APPEND, cname_str);
+                               *changed_ptr = true;
+                       }
+               }
+       });
+
+exit:
+       return;
 }
 
 //======================================================================================================================
 
-#if defined(NECP_CLIENT_ACTION_SIGN)
-#define DNSSD_AUTHENTICATION_TAG_SIZE  32      // XXX: Defined as a workaround until NECP header defines this length.
+static xpc_object_t
+_dx_gai_request_copy_cname_update(const dx_gai_request_t me, const int qtype)
+{
+       __block xpc_object_t result = NULL;
+       bool *changed_ptr;
+       xpc_object_t * const cnames_ptr = _dx_gai_request_get_cnames_ptr(me, qtype, &changed_ptr, NULL);
+       require_quiet(cnames_ptr, exit);
 
-static bool
-_dx_authenticate_answer(uuid_t client_id, xpc_object_t hostname, int record_type, const void *record_data,
-       uint8_t out_auth_tag[STATIC_PARAM DNSSD_AUTHENTICATION_TAG_SIZE]);
-#endif
+       _dx_request_locked(me,
+       ^{
+               if (*changed_ptr) {
+                       const xpc_object_t cnames = *cnames_ptr;
+                       if (cnames) {
+                               result = xpc_copy(cnames);
+                       }
+                       *changed_ptr = false;
+               }
+       });
+
+exit:
+       return result;
+}
+
+//======================================================================================================================
 
 static void
-_dx_getaddrinfo_request_result_handler(mDNS *m, DNSQuestion *question, const ResourceRecord *answer,
-       QC_result qc_result, DNSServiceErrorType error, void *context)
+_dx_gai_request_gai_result_handler(mDNS * const m, DNSQuestion * const q, const ResourceRecord * const answer,
+       const QC_result qc_result, const DNSServiceErrorType error, void * const context)
 {
-       (void)question;
+       const dx_gai_request_t me = (dx_gai_request_t)context;
+       if (!error || (error == kDNSServiceErr_NoSuchRecord)) {
+               const bool expired = (answer->mortality == Mortality_Ghost) ? true : false;
+               if (answer->rrtype == kDNSServiceType_CNAME) {
+                       require_quiet(!error, exit);
 
-       const dx_getaddrinfo_request_t me = (dx_getaddrinfo_request_t)context;
-       if (error && (error != kDNSServiceErr_NoSuchRecord)) {
-               if (!me->base.error) {
-                       me->base.error = error;
+                       _dx_gai_request_append_cname(me, q->qtype, &answer->rdata->u.name, expired, q->CNAMEReferrals == 0);
+               }
+               require_quiet((answer->rrtype == kDNSServiceType_A) || (answer->rrtype == kDNSServiceType_AAAA), exit);
+
+               if (q->CNAMEReferrals == 0) {
+                       _dx_gai_request_unwind_cnames_if_necessary(me, q->qtype);
+               }
+               const uint8_t * rdata_ptr;
+               size_t                  rdata_len;
+               const uint32_t query_id = mDNSVal16(q->TargetQID);
+               const uint32_t if_index = mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNStrue);
+               const bool add_result = (qc_result != QC_rmv) ? true : false;
+               if (!error) {
+                       if (answer->rrtype == kDNSServiceType_A) {
+                               rdata_ptr = answer->rdata->u.ipv4.b;
+                               rdata_len = 4;
+                               _dx_gai_request_log_a_result(me, query_id, if_index, answer->name, rdata_ptr, answer->mortality,
+                                       add_result);
+                       } else {
+                               rdata_ptr = answer->rdata->u.ipv6.b;
+                               rdata_len = 16;
+                               _dx_gai_request_log_aaaa_result(me, query_id, if_index, answer->name, rdata_ptr, answer->mortality,
+                                       add_result);
+                       }
+               } else {
+                       rdata_ptr = NULL;
+                       rdata_len = 0;
+                       const char * const type_str = (answer->rrtype == kDNSServiceType_A) ? "A" : "AAAA";
+                       _dx_gai_request_log_no_such_record_result(me, query_id, if_index, answer->name, type_str, answer->mortality,
+                               add_result);
+               }
+               const bool answered_from_cache = !q->InitialCacheMiss ? true : false;
+               const char *provider_name = NULL;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+               if (answer->dnsservice) {
+                       provider_name = mdns_dns_service_get_provider_name(answer->dnsservice);
                }
-               goto exit;
+               const dnssd_getaddrinfo_result_protocol_t protocol = answer->protocol;
+#else
+               const dnssd_getaddrinfo_result_protocol_t protocol = dnssd_getaddrinfo_result_protocol_udp;
+#endif
+               _dx_gai_request_enqueue_result(me, if_index, answer->name, answer->rrtype, answer->rrclass, rdata_ptr,
+                       rdata_len, expired, add_result, answered_from_cache, error, protocol, provider_name);
+       } else {
+               _dx_request_set_error(me, error);
        }
-       require_quiet((answer->rrtype == kDNSServiceType_A) || (answer->rrtype == kDNSServiceType_AAAA), exit);
 
-       const void *    rdata_ptr;
-       size_t                  rdata_len;
-       if (!error) {
-               if (answer->rrtype == kDNSServiceType_A) {
-                       rdata_ptr = answer->rdata->u.ipv4.b;
-                       rdata_len = 4;
+exit:
+       return;
+}
+
+//======================================================================================================================
+
+static void
+_dx_gai_request_query_result_handler(mDNS * const m, DNSQuestion * const q, const ResourceRecord * const answer,
+       const QC_result qc_result, const DNSServiceErrorType error, void * const context)
+{
+       const dx_gai_request_t me = (dx_gai_request_t)context;
+       if (!error || (error == kDNSServiceErr_NoSuchRecord)) {
+               require_quiet((answer->rrtype == kDNSServiceType_SVCB) || (answer->rrtype == kDNSServiceType_HTTPS), exit);
+
+               const uint8_t * rdata_ptr;
+               size_t                  rdata_len;
+               const uint32_t query_id = mDNSVal16(q->TargetQID);
+               const uint32_t if_index = mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNStrue);
+               const bool add_result = (qc_result != QC_rmv) ? true : false;
+               const char * const type_str = (answer->rrtype == kDNSServiceType_SVCB) ? "SVCB" : "HTTPS";
+               if (!error) {
+                       rdata_ptr = answer->rdata->u.data;
+                       rdata_len = answer->rdlength;
+
+                       _dx_gai_request_log_svcb_result(me, query_id, if_index, answer->name, type_str, rdata_ptr, rdata_len,
+                               answer, add_result);
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                       char * const svcb_doh_uri = dnssd_svcb_copy_doh_uri(rdata_ptr, rdata_len);
+                       // Check for a valid DoH URI.
+                       if (svcb_doh_uri) {
+                               // Pass the domain to map if the record is DNSSEC signed.
+                               char *svcb_domain = NULL;
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+                               char svcb_domain_buffer[MAX_ESCAPED_DOMAIN_NAME] = "";
+                               if (answer->dnssec_result == dnssec_secure) {
+                                       if (ConvertDomainNameToCString(answer->name, svcb_domain_buffer)) {
+                                               svcb_domain = svcb_domain_buffer;
+                                       }
+                               }
+#endif
+                               Querier_RegisterDoHURI(svcb_doh_uri, svcb_domain);
+                               free(svcb_doh_uri);
+                       }
+#endif
                } else {
-                       rdata_ptr = answer->rdata->u.ipv6.b;
-                       rdata_len = 16;
+                       rdata_ptr = NULL;
+                       rdata_len = 0;
+                       _dx_gai_request_log_no_such_record_result(me, query_id, if_index, answer->name, type_str, answer->mortality,
+                               add_result);
                }
+               const bool answered_from_cache = !q->InitialCacheMiss ? true : false;
+               const char *provider_name = NULL;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+               if (answer->dnsservice) {
+                       provider_name = mdns_dns_service_get_provider_name(answer->dnsservice);
+               }
+               const dnssd_getaddrinfo_result_protocol_t protocol = answer->protocol;
+#else
+               const dnssd_getaddrinfo_result_protocol_t protocol = dnssd_getaddrinfo_result_protocol_udp;
+#endif
+               _dx_gai_request_enqueue_result(me, if_index, answer->name, answer->rrtype, answer->rrclass, rdata_ptr,
+                       rdata_len, answer->mortality == Mortality_Ghost, add_result, answered_from_cache, error, protocol,
+                       provider_name);
        } else {
-               rdata_ptr = NULL;
-               rdata_len = 0;
+               _dx_request_set_error(me, error);
        }
 
+exit:
+       return;
+}
+
+//======================================================================================================================
+
+static void
+_dx_gai_request_enqueue_result(const dx_gai_request_t me, const uint32_t if_index, const domainname * const name,
+       const uint16_t type, const uint16_t class, const uint8_t * const rdata_ptr, const size_t rdata_len,
+       const bool is_expired, const bool is_add, const bool answered_from_cache, const DNSServiceErrorType result_error,
+       const dnssd_getaddrinfo_result_protocol_t protocol, const char * const provider_name)
+{
+       DNSServiceErrorType err;
+       xpc_object_t result = xpc_dictionary_create(NULL, NULL, 0);
+       require_action_quiet(result, exit, err = kDNSServiceErr_NoMemory);
+
+       char name_str[MAX_ESCAPED_DOMAIN_NAME];
+       if (!ConvertDomainNameToCString(name, name_str)) {
+               name_str[0] = '\0';
+       }
        DNSServiceFlags flags = 0;
-       if (qc_result != QC_rmv) {
+       if (is_add) {
                flags |= kDNSServiceFlagsAdd;
+               if (answered_from_cache) {
+                       flags |= kDNSServiceFlagAnsweredFromCache;
+               }
        }
-    if (answer->mortality == Mortality_Ghost) {
+       if (is_expired) {
                flags |= kDNSServiceFlagsExpiredAnswer;
        }
-       if (!question->InitialCacheMiss) {
-               flags |= kDNSServiceFlagAnsweredFromCache;
-       }
-       
-       const uint32_t interface_index = mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNStrue);
-       const xpc_object_t result = xpc_dictionary_create(NULL, NULL, 0);
-       if (likely(result)) {
-       char name_str[MAX_ESCAPED_DOMAIN_NAME];
-       ConvertDomainNameToCString(answer->name, name_str);
-
-               dnssd_xpc_result_set_error(result, error);
-               dnssd_xpc_result_set_flags(result, flags);
-               dnssd_xpc_result_set_interface_index(result, interface_index);
-               dnssd_xpc_result_set_record_name(result, name_str);
-               dnssd_xpc_result_set_record_type(result, answer->rrtype);
-               dnssd_xpc_result_set_record_class(result, answer->rrclass);
-               dnssd_xpc_result_set_record_data(result, rdata_ptr, rdata_len);
-
-#if defined(NECP_CLIENT_ACTION_SIGN)
-               if (me->need_auth && !error && (flags & kDNSServiceFlagsAdd)) {
-                       uint8_t auth_tag[DNSSD_AUTHENTICATION_TAG_SIZE];
-                       const bool success = _dx_authenticate_answer(me->client_uuid, me->hostname, answer->rrtype, rdata_ptr,
-                               auth_tag);
-                       if (success) {
-                               dnssd_xpc_result_set_authentication_tag(result, auth_tag, sizeof(auth_tag));
-                       }
+       dnssd_xpc_result_set_error(result, result_error);
+       dnssd_xpc_result_set_flags(result, flags);
+       dnssd_xpc_result_set_interface_index(result, if_index);
+       dnssd_xpc_result_set_record_name(result, name_str);
+       dnssd_xpc_result_set_record_type(result, type);
+       dnssd_xpc_result_set_record_protocol(result, protocol);
+       dnssd_xpc_result_set_record_class(result, class);
+       dnssd_xpc_result_set_record_data(result, rdata_ptr, rdata_len);
+       if (provider_name) {
+               dnssd_xpc_result_set_provider_name(result, provider_name);
+       }
+       if (me->need_auth && is_add && !result_error) {
+               uint8_t auth_tag[DNSSD_AUTHENTICATION_TAG_SIZE];
+               const bool ok = _dx_authenticate_address_rdata(me->effective_uuid, me->hostname, type, rdata_ptr, auth_tag);
+               if (ok) {
+                       dnssd_xpc_result_set_authentication_tag(result, auth_tag, sizeof(auth_tag));
+               }
+       }
+       xpc_object_t cname_update = _dx_gai_request_copy_cname_update(me, type);
+       if (cname_update) {
+               dnssd_xpc_result_set_cname_update(result, cname_update);
+               xpc_forget(&cname_update);
+       }
+       _dx_request_append_result(me, result);
+       xpc_forget(&result);
+       err = kDNSServiceErr_NoError;
+
+exit:
+       if (err) {
+               _dx_request_set_error(me, err);
+       }
+}
+
+//======================================================================================================================
+
+static void
+_dx_gai_request_get_delegator_ids(const dx_gai_request_t me, pid_t * const out_delegator_pid,
+       const uint8_t ** const out_delegator_uuid, const audit_token_t ** const out_delegator_audit_token,
+       audit_token_t * const storage)
+{
+       pid_t pid;
+       const uint8_t *uuid;
+       const audit_token_t * const token = dnssd_xpc_parameters_get_delegate_audit_token(me->params, storage);
+       if (token) {
+               pid             = audit_token_to_pid(*token);
+               uuid    = NULL;
+       } else {
+               uuid = dnssd_xpc_parameters_get_delegate_uuid(me->params);
+               if (uuid) {
+                       pid = 0;
+               } else {
+                       pid = dnssd_xpc_parameters_get_delegate_pid(me->params, NULL);
                }
+       }
+       if (out_delegator_pid) {
+               *out_delegator_pid = pid;
+       }
+       if (out_delegator_uuid) {
+               *out_delegator_uuid = uuid;
+       }
+       if (out_delegator_audit_token) {
+               *out_delegator_audit_token = token;
+       }
+}
+
+//======================================================================================================================
+
+static DNSServiceErrorType
+_dx_gai_request_get_svcb_name_and_type(const dx_gai_request_t me, const char ** const out_svcb_name,
+       uint16_t * const out_svcb_type, char ** const out_svcb_memory)
+{
+       DNSServiceErrorType err;
+       const char *    svcb_name       = NULL;
+       uint16_t                svcb_type       = 0;
+       char *                  svcb_memory     = NULL;
+       const char * const service_scheme = dnssd_xpc_parameters_get_service_scheme(me->params);
+       if (service_scheme) {
+               if (strcasecmp(service_scheme, "_443._https") == 0) {
+                       svcb_name = me->hostname;
+                       svcb_type = kDNSType_HTTPS;
+               } else {
+                       asprintf(&svcb_memory, "%s.%s", service_scheme, me->hostname);
+                       require_action_quiet(svcb_memory, exit, err = kDNSServiceErr_NoMemory);
+
+                       svcb_name = svcb_memory;
+                       svcb_type = kDNSType_SVCB;
+               }
+       }
+       if (out_svcb_name) {
+               *out_svcb_name = svcb_name;
+       }
+       if (out_svcb_type) {
+               *out_svcb_type = svcb_type;
+       }
+       *out_svcb_memory = svcb_memory;
+       err = kDNSServiceErr_NoError;
+
+exit:
+       return err;
+}
+
+//======================================================================================================================
+
+static DNSServiceErrorType
+_dx_gai_request_set_need_authenticated_results(const dx_gai_request_t me, const pid_t effective_pid,
+       const uuid_t effective_uuid)
+{
+       DNSServiceErrorType err;
+       if (effective_uuid) {
+               uuid_copy(me->effective_uuid, effective_uuid);
+       } else {
+               struct proc_uniqidentifierinfo info;
+               const int n = proc_pidinfo(effective_pid, PROC_PIDUNIQIDENTIFIERINFO, 1, &info, sizeof(info));
+               require_action_quiet(n == (int)sizeof(info), exit, err = kDNSServiceErr_Unknown);
+               uuid_copy(me->effective_uuid, info.p_uuid);
+       }
+       me->need_auth = true;
+       err = kDNSServiceErr_NoError;
+
+exit:
+       return err;
+}
+
+//======================================================================================================================
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+static const uint8_t *
+_dx_gai_request_get_resolver_uuid(const dx_gai_request_t me)
+{
+       const xpc_object_t resolver_uuids = dnssd_xpc_parameters_get_resolver_uuid_array(me->params);
+       if (resolver_uuids && (xpc_array_get_count(resolver_uuids) > 0)) {
+               return xpc_array_get_uuid(resolver_uuids, 0);
+       } else {
+               return NULL;
+       }
+}
 #endif
-               xpc_array_append_value(me->base.result_array, result);
-               xpc_release(result);
+
+//======================================================================================================================
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+static bool
+_dx_gai_request_is_for_in_app_browser(const dx_gai_request_t me)
+{
+       const char * const account_id = dnssd_xpc_parameters_get_account_id(me->params);
+       if (account_id && (strcmp(account_id, "com.apple.WebKit.InAppBrowser") == 0)) {
+               return true;
        } else {
-               me->base.error = kDNSServiceErr_NoMemory;
+               return false;
        }
+}
+#endif
+
+//======================================================================================================================
+
+static void
+_dx_gai_request_log_start(const dx_gai_request_t me, const DNSServiceFlags flags, const uint32_t if_index,
+       const DNSServiceProtocol protocols, const pid_t delegator_pid, const uuid_t delegator_uuid)
+{
+       char delegator_str[64];
+       if (delegator_uuid) {
+               uuid_string_t delegator_uuid_str;
+               uuid_unparse_lower(delegator_uuid, delegator_uuid_str);
+               snprintf(delegator_str, sizeof(delegator_str), ", delegator uuid: %s", delegator_uuid_str);
+       } else if (delegator_pid != 0) {
+               char delegator_name[MAXCOMLEN];
+               snprintf(delegator_str, sizeof(delegator_str),
+                       ", delegator pid: %lld (%s)", (long long)delegator_pid, _dx_pid_to_name(delegator_pid, delegator_name));
+       } else {
+               delegator_str[0] = '\0';
+       }
+       char options_str[64];
+       snprintf(options_str, sizeof(options_str), "%s", me->need_auth ? "A" : "");
+       const dx_session_t session = me->base.session;
+       LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+               "[R%u] getaddrinfo start -- flags: 0x%X, ifindex: %d, protocols: %u, hostname: " PRI_S ", "
+               "options: {" PUB_S "}, client pid: %lld (" PUB_S ")" PUB_S,
+               me->base.request_id, flags, (int32_t)if_index, protocols, me->hostname, options_str,
+               (long long)session->client_pid, session->client_name, delegator_str);
+}
+
+//======================================================================================================================
+
+static void
+_dx_gai_request_log_stop(const dx_gai_request_t me)
+{
+       const dx_session_t session = me->base.session;
+       LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+               "[R%u] getaddrinfo stop" PUB_S " -- hostname: " PRI_S ", client pid: %lld (" PUB_S ")",
+               me->base.request_id, session->terminated ? " (forced)" : "", me->hostname, (long long)session->client_pid,
+               session->client_name);
+}
+
+//======================================================================================================================
+
+static void
+_dx_gai_request_log_a_result(const dx_gai_request_t me, const uint32_t query_id, const uint32_t if_index,
+       const domainname * const name, const uint8_t * const rdata, const MortalityState mortality, const bool is_add)
+{
+       const char * const event_str = is_add ? "add" : "rmv";
+       LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+               "[R%u->Q%u] getaddrinfo result -- event: " PUB_S ", ifindex: %d, name: " PRI_DM_NAME ", type: A, "
+               "rdata: " PRI_IPv4_ADDR " (" PUB_S ")",
+               me->base.request_id, query_id, event_str, if_index, DM_NAME_PARAM(name), rdata,
+               MortalityDisplayString(mortality));
+}
+
+//======================================================================================================================
+
+static void
+_dx_gai_request_log_aaaa_result(const dx_gai_request_t me, const uint32_t query_id, const uint32_t if_index,
+       const domainname * const name, const uint8_t * const rdata, const MortalityState mortality, const bool is_add)
+{
+       const char * const event_str = is_add ? "add" : "rmv";
+       LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+               "[R%u->Q%u] getaddrinfo result -- event: " PUB_S ", ifindex: %d, name: " PRI_DM_NAME ", type: AAAA, "
+               "rdata: " PRI_IPv6_ADDR " (" PUB_S ")",
+               me->base.request_id, query_id, event_str, if_index, DM_NAME_PARAM(name), rdata,
+               MortalityDisplayString(mortality));
+}
+
+//======================================================================================================================
+
+static void
+_dx_gai_request_log_svcb_result(const dx_gai_request_t me, const uint32_t query_id, const uint32_t if_index,
+       const domainname * const name, const char * const type_str, const uint8_t * const rdata_ptr, const size_t rdata_len,
+       const ResourceRecord * const answer, const bool is_add_event)
+{
+       const char * const event_str = is_add_event ? "add" : "rmv";
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+       const char * const dnssec_str = (answer->dnssec_result == dnssec_secure) ? "secure" : "insecure";
+#else
+       const char * const dnssec_str = "insecure";
+#endif
+       LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+               "[R%u->Q%u] getaddrinfo result -- event: " PUB_S ", ifindex: %d, name: " PRI_DM_NAME ", type: " PUB_S ", "
+               "rdata: " PRI_SVCB ", DNSSEC: " PUB_S " (" PUB_S ")",
+               me->base.request_id, query_id, event_str, if_index, DM_NAME_PARAM(name), type_str,
+               SVCB_PARAM(rdata_ptr, (int)rdata_len), dnssec_str, MortalityDisplayString(answer->mortality));
+}
+
+//======================================================================================================================
+
+static void
+_dx_gai_request_log_no_such_record_result(const dx_gai_request_t me, const uint32_t query_id, const uint32_t if_index,
+       const domainname * const name, const char * const type_str, const MortalityState mortality, const bool is_add)
+{
+       const char * const event_str = is_add ? "add" : "rmv";
+       LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+               "[R%u->Q%u] getaddrinfo result -- event: " PUB_S ", ifindex: %d, name: " PRI_DM_NAME ", type: " PUB_S ", "
+               "rdata: <none> (" PUB_S ")",
+               me->base.request_id, query_id, event_str, if_index, DM_NAME_PARAM(name), type_str,
+               MortalityDisplayString(mortality));
+}
+
+//======================================================================================================================
+
+static void
+_dx_gai_request_log_error(const dx_gai_request_t me, const DNSServiceErrorType error)
+{
+       const dx_session_t session = me->base.session;
+       LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+               "[R%u] getaddrinfo error -- error: %{mdns:err}ld, client pid: %lld (" PUB_S ")",
+               me->base.request_id, (long)error, (long long)session->client_pid, session->client_name);
+}
+
+//======================================================================================================================
+
+static DNSServiceErrorType
+_dx_gai_request_activate_internal(const dx_gai_request_t me)
+{
+       char *svcb_memory = NULL;
+
+       // Get standard parameters.
+       bool valid;
+       DNSServiceErrorType err;
+       const DNSServiceFlags flags = dnssd_xpc_parameters_get_flags(me->params, &valid);
+       require_action_quiet(valid, exit, err = kDNSServiceErr_BadParam);
+
+       const uint32_t if_index = dnssd_xpc_parameters_get_interface_index(me->params, &valid);
+       require_action_quiet(valid, exit, err = kDNSServiceErr_BadParam);
+
+       const uint32_t protocols = dnssd_xpc_parameters_get_protocols(me->params, &valid);
+       require_action_quiet(valid, exit, err = kDNSServiceErr_BadParam);
+
+       const dx_session_t session = me->base.session;
+
+       // Get delegator IDs.
+       pid_t delegator_pid;
+       const uint8_t *delegator_uuid;
+       const audit_token_t *delegator_audit_token;
+       audit_token_t storage;
+       _dx_gai_request_get_delegator_ids(me, &delegator_pid, &delegator_uuid, &delegator_audit_token, &storage);
+       if (delegator_audit_token || delegator_uuid || (delegator_pid != 0)) {
+               require_action_quiet(session->has_delegate_entitlement, exit, err = kDNSServiceErr_NoAuth);
+       }
+       _dx_gai_request_log_start(me, flags, if_index, protocols, delegator_pid, delegator_uuid);
+
+       // Determine effective IDs.
+       // Note: The mDNS core requires that the effective PID be set to zero if the effective UUID is set.
+       const uint8_t * effective_uuid;
+       pid_t                   effective_pid;
+       if (delegator_uuid) {
+               effective_uuid  = delegator_uuid;
+               effective_pid   = 0;
+       } else {
+               effective_uuid  = NULL;
+               effective_pid   = (delegator_pid != 0) ? delegator_pid : session->client_pid;
+       }
+       const bool need_auth_tags = dnssd_xpc_parameters_get_need_authentication_tags(me->params);
+       if (need_auth_tags) {
+               err = _dx_gai_request_set_need_authenticated_results(me, effective_pid, effective_uuid);
+               require_noerr_quiet(err, exit);
+       }
+
+       // Set up GetAddrInfo parameters.
+       GetAddrInfoClientRequestParams gai_params;
+       GetAddrInfoClientRequestParamsInit(&gai_params);
+       gai_params.hostnameStr          = me->hostname;
+       gai_params.requestID            = me->base.request_id;
+       gai_params.interfaceIndex       = if_index;
+       gai_params.flags                        = flags;
+       gai_params.protocols            = protocols;
+       gai_params.effectivePID         = effective_pid;
+       gai_params.effectiveUUID        = effective_uuid;
+       gai_params.peerUID                      = session->client_euid;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+       const uint8_t * const resolver_uuid = _dx_gai_request_get_resolver_uuid(me);
+       const xpc_object_t fallback_config = dnssd_xpc_parameters_get_fallback_config(me->params);
+       const bool need_encryption      = dnssd_xpc_parameters_get_need_encrypted_query(me->params);
+       gai_params.needEncryption       = need_encryption ? mDNStrue : mDNSfalse;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+       const bool for_in_app_browser           = _dx_gai_request_is_for_in_app_browser(me);
+       gai_params.peerAuditToken                       = &session->audit_token;
+       gai_params.delegatorAuditToken          = delegator_audit_token;
+       gai_params.isInAppBrowserRequest        = for_in_app_browser ? mDNStrue : mDNSfalse;
+#endif
+       // Set up QueryRecord parameters.
+       QueryRecordClientRequestParams query_params;
+       QueryRecordClientRequestParams *query_params_ptr = NULL;
+       const char *svcb_name = NULL;
+       uint16_t svcb_type = 0;
+       err = _dx_gai_request_get_svcb_name_and_type(me, &svcb_name, &svcb_type, &svcb_memory);
+       require_noerr_quiet(err, exit);
+       if (svcb_name) {
+               QueryRecordClientRequestParamsInit(&query_params);
+               query_params.requestID                          = me->base.request_id;
+               query_params.qnameStr                           = svcb_name;
+               query_params.interfaceIndex                     = if_index;
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+               query_params.flags                                      = flags | kDNSServiceFlagsEnableDNSSEC;
+#else
+               query_params.flags                                      = flags;
+#endif
+               query_params.qtype                                      = svcb_type;
+               query_params.qclass                                     = kDNSServiceClass_IN;
+               query_params.effectivePID                       = effective_pid;
+               query_params.effectiveUUID                      = effective_uuid;
+               query_params.peerUID                            = session->client_euid;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+               query_params.needEncryption                     = need_encryption ? mDNStrue : mDNSfalse;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+               query_params.peerAuditToken                     = &session->audit_token;
+               query_params.delegatorAuditToken        = delegator_audit_token;
+               query_params.isInAppBrowserRequest      = for_in_app_browser ? mDNStrue : mDNSfalse;
+#endif
+               query_params_ptr = &query_params;
+       }
+       // Activate request.
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+       err = _dx_gai_request_start_client_requests(me, &gai_params, query_params_ptr, resolver_uuid, fallback_config);
+#else
+       err = _dx_gai_request_start_client_requests(me, &gai_params, query_params_ptr, NULL, NULL);
+#endif
+       require_noerr_quiet(err, exit);
 
 exit:
-       return;
+       ForgetMem(&svcb_memory);
+       return err;
 }
 
-#if defined(NECP_CLIENT_ACTION_SIGN)
+//======================================================================================================================
+// MARK: - Helper Functions
+
 typedef struct {
        struct necp_client_resolver_answer      hdr;
        uint8_t                                                         hostname[MAX_ESCAPED_DOMAIN_NAME];
 } dx_necp_answer_t;
 
-check_compile_time(offsetof(dx_necp_answer_t, hdr) == 0);
-check_compile_time(endof_field(struct necp_client_resolver_answer, hostname_length) == offsetof(dx_necp_answer_t, hostname));
+check_compile_time(sizeof_field(dx_necp_answer_t, hdr) == offsetof(dx_necp_answer_t, hostname));
 
 static bool
-_dx_authenticate_answer(uuid_t client_id, xpc_object_t hostname, int record_type, const void *record_data,
-       uint8_t out_auth_tag[STATIC_PARAM DNSSD_AUTHENTICATION_TAG_SIZE])
+_dx_authenticate_address_rdata(uuid_t effective_uuid, const char * const hostname, const int type,
+       const uint8_t * const rdata, uint8_t out_auth_tag[STATIC_PARAM DNSSD_AUTHENTICATION_TAG_SIZE])
 {
-       static int necp_fd = -1;
-
-       bool success = false;
-       if (necp_fd < 0) {
-               necp_fd = necp_open(0);
-       }
-       require_quiet(necp_fd >= 0, exit);
+       bool ok = false;
+       require_quiet((type == kDNSServiceType_A) || (type == kDNSServiceType_AAAA), exit);
 
        dx_necp_answer_t answer;
-       memset(&answer, 0, sizeof(answer));
-
        struct necp_client_resolver_answer * const hdr = &answer.hdr;
-       uuid_copy(hdr->client_id, client_id);
+       memset(hdr, 0, sizeof(*hdr));
+       uuid_copy(hdr->client_id, effective_uuid);
 
        hdr->sign_type = NECP_CLIENT_SIGN_TYPE_RESOLVER_ANSWER;
-
-       switch (record_type) {
-               case kDNSServiceType_A:
-                       hdr->address_answer.sa.sa_family        = AF_INET;
-                       hdr->address_answer.sa.sa_len           = sizeof(struct sockaddr_in);
-                       memcpy(&hdr->address_answer.sin.sin_addr.s_addr, record_data, 4);
-                       break;
-
-               case kDNSServiceType_AAAA:
-                       hdr->address_answer.sa.sa_family        = AF_INET6;
-                       hdr->address_answer.sa.sa_len           = sizeof(struct sockaddr_in6);
-                       memcpy(hdr->address_answer.sin6.sin6_addr.s6_addr, record_data, 16);
-                       break;
-
-               default:
-                       goto exit;
+       if (type == kDNSServiceType_A) {
+               hdr->address_answer.sa.sa_family        = AF_INET;
+               hdr->address_answer.sa.sa_len           = sizeof(struct sockaddr_in);
+               memcpy(&hdr->address_answer.sin.sin_addr.s_addr, rdata, 4);
+       } else {
+               hdr->address_answer.sa.sa_family        = AF_INET6;
+               hdr->address_answer.sa.sa_len           = sizeof(struct sockaddr_in6);
+               memcpy(hdr->address_answer.sin6.sin6_addr.s6_addr, rdata, 16);
        }
-       const size_t hostname_len = xpc_string_get_length(hostname);
+       const size_t hostname_len = strlen(hostname);
        require_quiet(hostname_len <= sizeof(answer.hostname), exit);
 
        hdr->hostname_length = (uint32_t)hostname_len;
-       memcpy(answer.hostname, xpc_string_get_string_ptr(hostname), hdr->hostname_length);
+       memcpy(answer.hostname, hostname, hdr->hostname_length);
 
-       const int necp_err = necp_client_action(necp_fd, NECP_CLIENT_ACTION_SIGN, (void *)&answer,
+       static int necp_fd = -1;
+       if (necp_fd < 0) {
+               necp_fd = necp_open(0);
+       }
+       require_quiet(necp_fd >= 0, exit);
+
+       const int err = necp_client_action(necp_fd, NECP_CLIENT_ACTION_SIGN, (uint8_t *)&answer.hdr,
                sizeof(answer.hdr) + hdr->hostname_length, out_auth_tag, DNSSD_AUTHENTICATION_TAG_SIZE);
-       require_noerr_quiet(necp_err, exit);
+       require_noerr_quiet(err, exit);
 
-       success = true;
+       ok = true;
 
 exit:
-       return success;
+       return ok;
+}
+
+//======================================================================================================================
+
+static char *
+_dx_pid_to_name(const pid_t pid, char out_name[STATIC_PARAM MAXCOMLEN])
+{
+       out_name[0] = '\0';
+       if (pid != 0) {
+               struct proc_bsdshortinfo info;
+               const int n = proc_pidinfo(pid, PROC_PIDT_SHORTBSDINFO, 1, &info, PROC_PIDT_SHORTBSDINFO_SIZE);
+               if (n == (int)sizeof(info)) {
+                       check_compile_time_code(sizeof(info.pbsi_comm) == MAXCOMLEN);
+                       strlcpy(out_name, info.pbsi_comm, MAXCOMLEN);
+               }
+       }
+       return out_name;
+}
+
+//======================================================================================================================
+
+static void
+_dx_kqueue_locked(const char * const description, const dx_block_t block)
+{
+       KQueueLock();
+       block();
+       KQueueUnlock(description);
 }
-#endif // defined(NECP_CLIENT_ACTION_SIGN)
index ff617360ab08fd81f604be98c536eb9bc9f13eca..75a4ae85ef035ef13e5a1612e53f46e07f6ac7e9 100644 (file)
@@ -1,11 +1,11 @@
 /*
- * Copyright (c) 2018 Apple Inc. All rights reserved.
+ * Copyright (c) 2018-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *     https://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
 #ifndef __DNSSD_SERVER_H__
 #define __DNSSD_SERVER_H__
 
-#include "mDNSEmbeddedAPI.h"
+#include <stdint.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-mDNSexport void
+void
 dnssd_server_init(void);
 
-mDNSexport void
+void
 dnssd_server_idle(void);
 
+uint32_t
+dnssd_server_get_new_request_id(void);
+
 #ifdef __cplusplus
 }
 #endif
 
-#endif  // __DNSSD_SERVER_H__
+#endif // __DNSSD_SERVER_H__
diff --git a/mDNSMacOSX/dnssd_svcb.c b/mDNSMacOSX/dnssd_svcb.c
new file mode 100644 (file)
index 0000000..8f76470
--- /dev/null
@@ -0,0 +1,365 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "dnssd_svcb.h"
+
+#include <netinet/in.h>
+
+typedef enum
+{
+    dnssd_svcb_key_mandatory = 0,
+    dnssd_svcb_key_alpn = 1,
+    dnssd_svcb_key_no_default_alpn = 2,
+    dnssd_svcb_key_port = 3,
+    dnssd_svcb_key_ipv4_hint = 4,
+    dnssd_svcb_key_ech_config = 5,
+    dnssd_svcb_key_ipv6_hint = 6,
+    dnssd_svcb_key_doh_uri = 32768,
+} dnssd_svcb_key_t;
+
+typedef bool (^_dnssd_svcb_access_value_block_t)(const void *value, size_t value_size);
+
+uint16_t
+dnssd_svcb_get_priority(const uint8_t *buffer, size_t buffer_size)
+{
+       if (buffer_size < sizeof(uint16_t)) {
+               return 0;
+       }
+
+       const uint16_t *priority_p = (const uint16_t *)buffer;
+       return (uint16_t)htons(*priority_p);
+}
+
+#define DNSSD_MAX_DOMAIN_NAME 256
+#define DNSSD_MAX_DOMAIN_LABEL 63
+#define DNSSD_MAX_ESCAPED_DOMAIN_NAME 1009
+
+static bool
+_dnssd_svcb_get_domain_name_length(const uint8_t *buffer, size_t buffer_size, size_t *out_name_length)
+{
+       const uint8_t *limit = buffer + buffer_size;
+       const uint8_t *cursor = buffer;
+    while (cursor != NULL && cursor < limit) {
+               if (*cursor == 0) {
+                       *out_name_length = ((uint16_t)(cursor - buffer + 1));
+                       if (*out_name_length > DNSSD_MAX_DOMAIN_NAME) {
+                               return false;
+                       }
+                       return true;
+               }
+        cursor += 1 + *cursor;
+    }
+       return false;
+}
+
+static char *
+_dnssd_svcb_convert_label_to_string(const uint8_t *source, char *string_buffer)
+{
+    const uint8_t length = *source++; // Read length of this (non-null) label
+    const uint8_t *limit = source + length; // Work out where the label ends
+       if (length > DNSSD_MAX_DOMAIN_LABEL) {
+               return NULL;
+       }
+       while (source < limit) {
+        uint8_t character = *source++;
+               if (character == '.' || character == '\\') { // If character is a dot or the escape character
+                       *string_buffer++ = '\\'; // Output escape character
+               } else if (character <= ' ') { // Output decimal escape sequence
+                       *string_buffer++ = '\\';
+                       *string_buffer++ = (char)  ('0' + (character / 100));
+                       *string_buffer++ = (char)  ('0' + (character / 10) % 10);
+                       character = (uint8_t)('0' + (character) % 10);
+               }
+        *string_buffer++ = (char)character; // Copy the character
+    }
+    *string_buffer = 0; // Null-terminate the string
+    return(string_buffer); // and return
+}
+
+static char *
+_dnssd_svcb_get_string_from_domain_name(const uint8_t *source, char *string_buffer)
+{
+    const uint8_t *limit = source + DNSSD_MAX_DOMAIN_NAME;
+
+       if (*source == 0) {
+               *string_buffer++ = '.'; // Special case: For root, just write a dot
+       }
+
+    while (*source) {
+               if (source + 1 + *source >= limit) {
+                       return NULL;
+               }
+        string_buffer = _dnssd_svcb_convert_label_to_string(source, string_buffer);
+               if (string_buffer == NULL) {
+                       return NULL;
+               }
+        source += 1 + *source;
+        *string_buffer++ = '.'; // Write the dot after the label
+    }
+
+    *string_buffer++ = 0; // Null-terminate the string
+    return string_buffer; // and return
+}
+
+char *
+dnssd_svcb_copy_domain(const uint8_t *buffer, size_t buffer_size)
+{
+       if (buffer_size < sizeof(uint16_t)) {
+               return NULL;
+       }
+
+       buffer += sizeof(uint16_t);
+       buffer_size -= sizeof(uint16_t);
+
+       size_t domain_length = 0;
+       if (!_dnssd_svcb_get_domain_name_length(buffer, buffer_size, &domain_length)) {
+               return NULL;
+       }
+
+       char *name_str = calloc(1, DNSSD_MAX_ESCAPED_DOMAIN_NAME);
+       if (_dnssd_svcb_get_string_from_domain_name(buffer, name_str) == NULL) {
+               free(name_str);
+               return NULL;
+       }
+       return name_str;
+}
+
+static bool
+_dnssd_svcb_extract_values(const uint8_t *buffer, size_t buffer_size,
+                                                  dnssd_svcb_key_t match_key, _dnssd_svcb_access_value_block_t value_block)
+{
+       if (buffer_size < sizeof(uint16_t)) {
+               return false;
+       }
+
+       const uint16_t *priority_p = (const uint16_t *)buffer;
+       uint16_t priority = (uint16_t)htons(*priority_p);
+       if (priority == 0) {
+               // Alias form, no value
+               return false;
+       }
+
+       buffer += sizeof(uint16_t);
+       buffer_size -= sizeof(uint16_t);
+
+       size_t domain_length = 0;
+       if (!_dnssd_svcb_get_domain_name_length(buffer, buffer_size, &domain_length)) {
+               return false;
+       }
+
+       buffer += domain_length;
+       buffer_size -= domain_length;
+
+       while (buffer != NULL && buffer_size >= (sizeof(uint16_t) + sizeof(uint16_t))) {
+               const uint16_t *param_key_p = (const uint16_t *)buffer;
+               uint16_t param_key = (uint16_t)htons(*param_key_p);
+
+               buffer += sizeof(uint16_t);
+               buffer_size -= sizeof(uint16_t);
+
+               const uint16_t *param_value_length_p = (const uint16_t *)buffer;
+               uint16_t param_value_length = (uint16_t)htons(*param_value_length_p);
+
+               buffer += sizeof(uint16_t);
+               buffer_size -= sizeof(uint16_t);
+
+               if (param_value_length > buffer_size) {
+                       break;
+               }
+
+               if (match_key == param_key) {
+                       bool continue_looping = value_block(buffer, param_value_length);
+                       if (!continue_looping) {
+                               break;
+                       }
+               }
+
+               buffer += param_value_length;
+               buffer_size -= param_value_length;
+       }
+
+       return true;
+}
+
+bool
+dnssd_svcb_is_valid(const uint8_t *buffer, size_t buffer_size)
+{
+       if (buffer_size < sizeof(uint16_t)) {
+               return false;
+       }
+
+       uint16_t priority = dnssd_svcb_get_priority(buffer, buffer_size);
+       if (priority == 0) {
+               // Alias forms don't need further validation
+               return true;
+       }
+
+       __block bool invalid_mandatory_value = false;
+       (void)_dnssd_svcb_extract_values(buffer, buffer_size, dnssd_svcb_key_mandatory, ^bool(const void *value, size_t value_size) {
+               if (value != NULL && value_size > 0) {
+                       if ((value_size % sizeof(uint16_t)) != 0) {
+                               // Value must be a list of keys, as 16-bit integers
+                               invalid_mandatory_value = true;
+                       } else {
+                               const uint16_t mandatory_key_count = (uint16_t)(value_size / sizeof(uint16_t));
+                               for (uint16_t i = 0; i < mandatory_key_count && !invalid_mandatory_value; i++) {
+                                       const uint16_t *param_key_p = ((const uint16_t *)value) + i;
+                                       uint16_t param_key = (uint16_t)htons(*param_key_p);
+                                       switch (param_key) {
+                                               case dnssd_svcb_key_mandatory:
+                                                       // Mandatory key cannot be listed
+                                                       invalid_mandatory_value = true;
+                                                       break;
+                                               case dnssd_svcb_key_alpn:
+                                               case dnssd_svcb_key_no_default_alpn:
+                                               case dnssd_svcb_key_port:
+                                               case dnssd_svcb_key_ipv4_hint:
+                                               case dnssd_svcb_key_ech_config:
+                                               case dnssd_svcb_key_ipv6_hint:
+                                               case dnssd_svcb_key_doh_uri:
+                                                       // Known keys are fine
+                                                       break;
+                                               default:
+                                                       // Unknown mandatory key means we should ignore the record
+                                                       invalid_mandatory_value = true;
+                                                       break;
+                                       }
+                               }
+                       }
+               }
+               return false;
+       });
+       if (invalid_mandatory_value) {
+               return false;
+       } else {
+               return true;
+       }
+}
+
+uint16_t
+dnssd_svcb_get_port(const uint8_t *buffer, size_t buffer_size)
+{
+       __block uint16_t port = false;
+       (void)_dnssd_svcb_extract_values(buffer, buffer_size, dnssd_svcb_key_port, ^bool(const void *value, size_t value_size) {
+               if (value != NULL && value_size == sizeof(uint16_t)) {
+                       port = (uint16_t)htons(*(const uint16_t *)value);
+               }
+               return false;
+       });
+       return port;
+}
+
+char *
+dnssd_svcb_copy_doh_uri(const uint8_t *buffer, size_t buffer_size)
+{
+       __block char *doh_uri = NULL;
+       (void)_dnssd_svcb_extract_values(buffer, buffer_size, dnssd_svcb_key_doh_uri, ^bool(const void *value, size_t value_size) {
+               if (value != NULL && value_size > 0) {
+                       asprintf(&doh_uri, "%.*s", (int)value_size, value);
+               }
+               return false;
+       });
+       return doh_uri;
+}
+
+uint8_t *
+dnssd_svcb_copy_ech_config(const uint8_t *buffer, size_t buffer_size, size_t *out_length)
+{
+       __block uint8_t *ech_config = NULL;
+       (void)_dnssd_svcb_extract_values(buffer, buffer_size, dnssd_svcb_key_ech_config, ^bool(const void *value, size_t value_size) {
+               if (value != NULL && value_size > 0) {
+                       ech_config = calloc(1, value_size);
+                       *out_length = value_size;
+                       memcpy(ech_config, value, value_size);
+               }
+               return false;
+       });
+       return ech_config;
+}
+
+void
+dnssd_svcb_access_alpn_values(const uint8_t *buffer, size_t buffer_size,
+                                                         DNSSD_NOESCAPE _dnssd_svcb_access_alpn_t block)
+{
+       (void)_dnssd_svcb_extract_values(buffer, buffer_size, dnssd_svcb_key_alpn, ^bool(const void *value, size_t value_size) {
+               if (value != NULL) {
+                       size_t value_read = 0;
+                       while (value_size > 0 && value_read < value_size) {
+                               char alpn_value[UINT8_MAX] = "";
+
+                               uint8_t alpn_length = *(const uint8_t *)value;
+                               value_read++;
+
+                               if (value_read + alpn_length > value_size) {
+                                       break;
+                               }
+
+                               memcpy(alpn_value, ((const uint8_t *)value) + value_read, alpn_length);
+                               if (!block((const char *)alpn_value)) {
+                                       break;
+                               }
+                               value_read += alpn_length;
+                       }
+               }
+               return false;
+       });
+}
+
+void
+dnssd_svcb_access_address_hints(const uint8_t *buffer, size_t buffer_size, DNSSD_NOESCAPE _dnssd_svcb_access_address_t block)
+{
+       __block bool continue_enumerating = true;
+       (void)_dnssd_svcb_extract_values(buffer, buffer_size, dnssd_svcb_key_ipv4_hint, ^bool(const void *value, size_t value_size) {
+               if (value != NULL && (value_size % sizeof(struct in_addr)) == 0) {
+                       size_t value_read = 0;
+                       while (value_read < value_size) {
+                               struct sockaddr_in v4addr;
+                               memset(&v4addr, 0, sizeof(v4addr));
+                               v4addr.sin_family = AF_INET;
+                               v4addr.sin_len = sizeof(v4addr);
+                               memcpy(&v4addr.sin_addr, ((const uint8_t *)value) + value_read, sizeof(struct in_addr));
+                               continue_enumerating = block((const struct sockaddr *)&v4addr);
+                               if (!continue_enumerating) {
+                                       break;
+                               }
+                               value_read += sizeof(struct in_addr);
+                       }
+               }
+               return false;
+       });
+       if (!continue_enumerating) {
+               return;
+       }
+       (void)_dnssd_svcb_extract_values(buffer, buffer_size, dnssd_svcb_key_ipv6_hint, ^bool(const void *value, size_t value_size) {
+
+               if (value != NULL && (value_size % sizeof(struct in6_addr)) == 0) {
+                       size_t value_read = 0;
+                       while (value_read < value_size) {
+                               struct sockaddr_in6 v6addr;
+                               memset(&v6addr, 0, sizeof(v6addr));
+                               v6addr.sin6_family = AF_INET6;
+                               v6addr.sin6_len = sizeof(v6addr);
+                               memcpy(&v6addr.sin6_addr, ((const uint8_t *)value) + value_read, sizeof(struct in6_addr));
+                               continue_enumerating = block((const struct sockaddr *)&v6addr);
+                               if (!continue_enumerating) {
+                                       break;
+                               }
+                               value_read += sizeof(struct in6_addr);
+                       }
+               }
+               return false;
+       });
+}
diff --git a/mDNSMacOSX/dnssd_svcb.h b/mDNSMacOSX/dnssd_svcb.h
new file mode 100644 (file)
index 0000000..1573c03
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __DNSSD_SVCB_H__
+#define __DNSSD_SVCB_H__
+
+#include "dnssd_private.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+bool
+dnssd_svcb_is_valid(const uint8_t *buffer, size_t buffer_size);
+
+uint16_t
+dnssd_svcb_get_priority(const uint8_t *buffer, size_t buffer_size);
+
+char *
+dnssd_svcb_copy_domain(const uint8_t *buffer, size_t buffer_size);
+
+uint16_t
+dnssd_svcb_get_port(const uint8_t *buffer, size_t buffer_size);
+
+char *
+dnssd_svcb_copy_doh_uri(const uint8_t *buffer, size_t buffer_size);
+
+uint8_t *
+dnssd_svcb_copy_ech_config(const uint8_t *buffer, size_t buffer_size,
+                                                  size_t *out_length);
+
+#ifdef __BLOCKS__
+
+typedef bool (^_dnssd_svcb_access_alpn_t)(const char *alpn);
+
+void
+dnssd_svcb_access_alpn_values(const uint8_t *buffer, size_t buffer_size,
+                                                         DNSSD_NOESCAPE _dnssd_svcb_access_alpn_t block);
+
+typedef bool (^_dnssd_svcb_access_address_t)(const struct sockaddr *address);
+
+void
+dnssd_svcb_access_address_hints(const uint8_t *buffer, size_t buffer_size,
+                                                               DNSSD_NOESCAPE _dnssd_svcb_access_address_t block);
+
+#endif //  __BLOCKS__
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // __DNSSD_SVCB_H__
index c304c8b8ef6f6142f21ac2fc7649e1c193392764..f1b76ad85738f21912deaf44c5781e22402d43e3 100644 (file)
@@ -1,11 +1,11 @@
 /*
- * Copyright (c) 2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *     https://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
 
 #include "dnssd_xpc.h"
 
-#if 0
 //======================================================================================================================
-#pragma mark - XPC Dictionary Helper Declarations
-#endif
+// MARK: - XPC Dictionary Helper Declarations
 
 static int32_t
 _dnssd_xpc_dictionary_get_int32(xpc_object_t dict, const char *key, bool *out_valid);
@@ -46,10 +44,8 @@ _dnssd_xpc_dictionary_get_uint64_limited(xpc_object_t dict, const char *key, uin
 static xpc_object_t
 _dnssd_xpc_dictionary_get_value(xpc_object_t dict, const char *key, xpc_type_t type);
 
-#if 0
 //======================================================================================================================
-#pragma mark - Top-Level Message Dictionaries
-#endif
+// MARK: - Top-Level Message Dictionaries
 
 #define DNSSD_XPC_MESSAGE_KEY_COMMAND  "command"
 #define DNSSD_XPC_MESSAGE_KEY_ERROR            "error"
@@ -59,7 +55,7 @@ _dnssd_xpc_dictionary_get_value(xpc_object_t dict, const char *key, xpc_type_t t
 
 //======================================================================================================================
 
-const char *
+const char * _Nullable
 dnssd_xpc_message_get_command(xpc_object_t msg)
 {
        return xpc_dictionary_get_string(msg, DNSSD_XPC_MESSAGE_KEY_COMMAND);
@@ -137,17 +133,20 @@ dnssd_xpc_message_set_results(xpc_object_t msg, xpc_object_t results)
        xpc_dictionary_set_value(msg, DNSSD_XPC_MESSAGE_KEY_RESULTS, results);
 }
 
-#if 0
 //======================================================================================================================
-#pragma mark - Parameter Dictionaries
-#endif
+// MARK: - Parameter Dictionaries
 
 #define DNSSD_XPC_PARAMETERS_KEY_DELEGATE_ID           "delegate_id"
 #define DNSSD_XPC_PARAMETERS_KEY_FLAGS                         "flags"
+#define DNSSD_XPC_PARAMETERS_KEY_ACCOUNT_ID                    "account_id"
 #define DNSSD_XPC_PARAMETERS_KEY_HOSTNAME                      "hostname"
 #define DNSSD_XPC_PARAMETERS_KEY_INTERFACE_INDEX       "interface_index"
 #define DNSSD_XPC_PARAMETERS_KEY_NEED_AUTH_TAGS                "need_auth_tags"
 #define DNSSD_XPC_PARAMETERS_KEY_PROTOCOLS                     "protocols"
+#define DNSSD_XPC_PARAMETERS_KEY_NEED_ENCRYPTION       "need_encryption"
+#define DNSSD_XPC_PARAMETERS_KEY_FALLBACK_CONFIG       "fallback_config"
+#define DNSSD_XPC_PARAMETERS_KEY_RESOLVER_UUIDS                "resolver_uuids"
+#define DNSSD_XPC_PARAMETERS_KEY_SERVICE_SCHEME                "service_scheme"
 
 //======================================================================================================================
 
@@ -183,6 +182,29 @@ dnssd_xpc_parameters_set_delegate_uuid(xpc_object_t params, uuid_t uuid)
 
 //======================================================================================================================
 
+audit_token_t * _Nullable
+dnssd_xpc_parameters_get_delegate_audit_token(const xpc_object_t params, audit_token_t * const audit_token_storage)
+{
+       size_t size;
+       const void * const data = xpc_dictionary_get_data(params, DNSSD_XPC_PARAMETERS_KEY_DELEGATE_ID, &size);
+       if (data && (size == sizeof(*audit_token_storage))) {
+               memcpy(audit_token_storage, data, size);
+               return audit_token_storage;
+       } else {
+               return NULL;
+       }
+}
+
+//======================================================================================================================
+
+void
+dnssd_xpc_parameters_set_delegate_audit_token(const xpc_object_t params, const audit_token_t * const audit_token)
+{
+       xpc_dictionary_set_data(params, DNSSD_XPC_PARAMETERS_KEY_DELEGATE_ID, audit_token, sizeof(*audit_token));
+}
+
+//======================================================================================================================
+
 DNSServiceFlags
 dnssd_xpc_parameters_get_flags(xpc_object_t params, bool *out_valid)
 {
@@ -199,6 +221,22 @@ dnssd_xpc_parameters_set_flags(xpc_object_t params, DNSServiceFlags flags)
 
 //======================================================================================================================
 
+const char *
+dnssd_xpc_parameters_get_account_id(xpc_object_t params)
+{
+       return xpc_dictionary_get_string(params, DNSSD_XPC_PARAMETERS_KEY_ACCOUNT_ID);
+}
+
+//======================================================================================================================
+
+void
+dnssd_xpc_parameters_set_account_id(xpc_object_t params, const char *account_id)
+{
+       xpc_dictionary_set_string(params, DNSSD_XPC_PARAMETERS_KEY_ACCOUNT_ID, account_id);
+}
+
+//======================================================================================================================
+
 xpc_object_t
 dnssd_xpc_parameters_get_hostname_object(xpc_object_t params)
 {
@@ -247,6 +285,55 @@ dnssd_xpc_parameters_set_need_authentication_tags(xpc_object_t params, bool need
 
 //======================================================================================================================
 
+void
+dnssd_xpc_parameters_set_need_encrypted_query(xpc_object_t params, bool need, _Nullable xpc_object_t fallback_config)
+{
+       xpc_dictionary_set_bool(params, DNSSD_XPC_PARAMETERS_KEY_NEED_ENCRYPTION, need);
+       if (fallback_config != NULL) {
+               xpc_dictionary_set_value(params, DNSSD_XPC_PARAMETERS_KEY_FALLBACK_CONFIG, fallback_config);
+       }
+}
+
+//======================================================================================================================
+
+bool
+dnssd_xpc_parameters_get_need_encrypted_query(xpc_object_t params)
+{
+       return xpc_dictionary_get_bool(params, DNSSD_XPC_PARAMETERS_KEY_NEED_ENCRYPTION);
+}
+
+//======================================================================================================================
+
+xpc_object_t _Nullable
+dnssd_xpc_parameters_get_fallback_config(xpc_object_t params)
+{
+       return xpc_dictionary_get_value(params, DNSSD_XPC_PARAMETERS_KEY_FALLBACK_CONFIG);
+}
+
+//======================================================================================================================
+
+xpc_object_t _Nullable
+dnssd_xpc_parameters_get_resolver_uuid_array(xpc_object_t params)
+{
+       return xpc_dictionary_get_value(params, DNSSD_XPC_PARAMETERS_KEY_RESOLVER_UUIDS);
+}
+
+//======================================================================================================================
+
+void
+dnssd_xpc_parameters_add_resolver_uuid(xpc_object_t params, uuid_t _Nonnull uuid)
+{
+       xpc_object_t resolver_uuid_array = xpc_dictionary_get_value(params, DNSSD_XPC_PARAMETERS_KEY_RESOLVER_UUIDS);
+       if (resolver_uuid_array == NULL) {
+               resolver_uuid_array = xpc_array_create(NULL, 0);
+               xpc_dictionary_set_value(params, DNSSD_XPC_PARAMETERS_KEY_RESOLVER_UUIDS, resolver_uuid_array);
+               xpc_release(resolver_uuid_array);
+       }
+       xpc_array_set_uuid(resolver_uuid_array, XPC_ARRAY_APPEND, uuid);
+}
+
+//======================================================================================================================
+
 DNSServiceProtocol
 dnssd_xpc_parameters_get_protocols(xpc_object_t params, bool *out_valid)
 {
@@ -261,18 +348,35 @@ dnssd_xpc_parameters_set_protocols(xpc_object_t params, DNSServiceProtocol proto
        xpc_dictionary_set_uint64(params, DNSSD_XPC_PARAMETERS_KEY_PROTOCOLS, protocols);
 }
 
-#if 0
 //======================================================================================================================
-#pragma mark - Result Dictionaries
-#endif
+
+const char * _Nullable
+dnssd_xpc_parameters_get_service_scheme(xpc_object_t params)
+{
+       return xpc_dictionary_get_string(params, DNSSD_XPC_PARAMETERS_KEY_SERVICE_SCHEME);
+}
+
+//======================================================================================================================
+
+void
+dnssd_xpc_parameters_set_service_scheme(xpc_object_t params, const char *service_scheme)
+{
+       xpc_dictionary_set_string(params, DNSSD_XPC_PARAMETERS_KEY_SERVICE_SCHEME, service_scheme);
+}
+
+//======================================================================================================================
+// MARK: - Result Dictionaries
 
 #define DNSSD_XPC_RESULT_KEY_AUTH_TAG                  "auth_tag"
+#define DNSSD_XPC_RESULT_KEY_CNAME_UPDATE              "cname_update"
 #define DNSSD_XPC_RESULT_KEY_ERROR                             "error"
 #define DNSSD_XPC_RESULT_KEY_FLAGS                             "flags"
 #define DNSSD_XPC_RESULT_KEY_INTERFACE_INDEX   "interface_index"
+#define DNSSD_XPC_RESULT_KEY_PROVIDER_NAME             "provider_name"
 #define DNSSD_XPC_RESULT_KEY_RECORD_CLASS              "rclass"
 #define DNSSD_XPC_RESULT_KEY_RECORD_DATA               "rdata"
 #define DNSSD_XPC_RESULT_KEY_RECORD_NAME               "rname"
+#define DNSSD_XPC_RESULT_KEY_RECORD_PROTOCOL   "rprotocol"
 #define DNSSD_XPC_RESULT_KEY_RECORD_TYPE               "rtype"
 
 //======================================================================================================================
@@ -403,10 +507,56 @@ dnssd_xpc_result_set_record_type(xpc_object_t result, uint16_t type)
        xpc_dictionary_set_uint64(result, DNSSD_XPC_RESULT_KEY_RECORD_TYPE, type);
 }
 
-#if 0
 //======================================================================================================================
-#pragma mark - XPC Dictionary Helpers
-#endif
+
+uint16_t
+dnssd_xpc_result_get_record_protocol(xpc_object_t result, bool * out_valid)
+{
+       return _dnssd_xpc_dictionary_get_uint16(result, DNSSD_XPC_RESULT_KEY_RECORD_PROTOCOL, out_valid);
+}
+
+//======================================================================================================================
+
+void
+dnssd_xpc_result_set_record_protocol(xpc_object_t result, uint16_t protocol)
+{
+       xpc_dictionary_set_uint64(result, DNSSD_XPC_RESULT_KEY_RECORD_PROTOCOL, protocol);
+}
+
+//======================================================================================================================
+
+xpc_object_t
+dnssd_xpc_result_get_provider_name_object(xpc_object_t result)
+{
+       return _dnssd_xpc_dictionary_get_value(result, DNSSD_XPC_RESULT_KEY_PROVIDER_NAME, XPC_TYPE_STRING);
+}
+
+//======================================================================================================================
+
+void
+dnssd_xpc_result_set_provider_name(xpc_object_t result, const char *name)
+{
+       xpc_dictionary_set_string(result, DNSSD_XPC_RESULT_KEY_PROVIDER_NAME, name);
+}
+
+//======================================================================================================================
+
+xpc_object_t
+dnssd_xpc_result_get_cname_update(xpc_object_t result)
+{
+       return xpc_dictionary_get_array(result, DNSSD_XPC_RESULT_KEY_CNAME_UPDATE);
+}
+
+//======================================================================================================================
+
+void
+dnssd_xpc_result_set_cname_update(xpc_object_t result, xpc_object_t cname_update)
+{
+       xpc_dictionary_set_value(result, DNSSD_XPC_RESULT_KEY_CNAME_UPDATE, cname_update);
+}
+
+//======================================================================================================================
+// MARK: - XPC Dictionary Helpers
 
 static int32_t
 _dnssd_xpc_dictionary_get_int32(xpc_object_t dict, const char *key, bool *out_valid)
index ed392aea7239b6f12e0959773b924ddbef09b63a..544753160441f2328706f49badf0e82a116e591a 100644 (file)
@@ -1,11 +1,11 @@
 /*
- * Copyright (c) 2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *     https://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -28,9 +28,7 @@
 
 CU_ASSUME_NONNULL_BEGIN
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+__BEGIN_DECLS
 
 /*!
  *     @brief
@@ -197,6 +195,23 @@ dnssd_xpc_parameters_get_delegate_pid(xpc_object_t params, bool * _Nullable out_
 const uint8_t * _Nullable
 dnssd_xpc_parameters_get_delegate_uuid(xpc_object_t params);
 
+/*!
+ *     @brief
+ *             Gets a delegate audit token from a command parameters dictionary.
+ *
+ *     @param params
+ *             Command parameters dictionary.
+ *
+ *     @param audit_token_storage
+ *             Pointer to an audit token to overwrite with parameters dictionary's delegate audit token data.
+ *
+ *     @result
+ *             If the parameters dictionary contains a delegate audit token, this function copies it to
+ *             audit_token_storage and returns audit_token_storage. Otherwise, it returns NULL.
+ */
+audit_token_t * _Nullable
+dnssd_xpc_parameters_get_delegate_audit_token(xpc_object_t params, audit_token_t *audit_token_storage);
+
 /*!
  *     @brief
  *             Gets flags from a command parameters dictionary.
@@ -213,6 +228,19 @@ dnssd_xpc_parameters_get_delegate_uuid(xpc_object_t params);
 DNSServiceFlags
 dnssd_xpc_parameters_get_flags(xpc_object_t params, bool * _Nullable out_valid);
 
+/*!
+*      @brief
+*              Gets account id from a command parameters dictionary.
+*
+*      @param params
+*              Command parameters dictionary.
+*
+*      @result
+*              Account, if present, as a const char *. Otherwise, NULL.
+*/
+const char * _Nullable
+dnssd_xpc_parameters_get_account_id(xpc_object_t params);
+
 /*!
  *     @brief
  *             Gets hostname from a command parameters dictionary.
@@ -255,6 +283,45 @@ dnssd_xpc_parameters_get_interface_index(xpc_object_t params, bool * _Nullable o
 bool
 dnssd_xpc_parameters_get_need_authentication_tags(xpc_object_t params);
 
+/*!
+ *     @brief
+ *             Gets need encryption boolean value from a command parameters dictionary.
+ *
+ *     @param params
+ *             Command parameters dictionary.
+ *
+ *     @result
+ *             A boolean value.
+ */
+bool
+dnssd_xpc_parameters_get_need_encrypted_query(xpc_object_t params);
+
+/*!
+ *     @brief
+ *             Gets fallback resolver configuration dictionary from a command parameters dictionary.
+ *
+ *     @param params
+ *             Command parameters dictionary.
+ *
+ *     @result
+ *             A dictionary containing resolver configuration to use in the absence of encrypted resolvers, or NULL.
+ */
+xpc_object_t _Nullable
+dnssd_xpc_parameters_get_fallback_config(xpc_object_t params);
+
+/*!
+ *     @brief
+ *             Gets resolver UUID array from a command parameters dictionary.
+ *
+ *     @param params
+ *             Command parameters dictionary.
+ *
+ *     @result
+ *             An array of UUIDs, or NULL.
+ */
+xpc_object_t _Nullable
+dnssd_xpc_parameters_get_resolver_uuid_array(xpc_object_t params);
+
 /*!
  *     @brief
  *             Gets protocols from a command parameters dictionary.
@@ -271,6 +338,19 @@ dnssd_xpc_parameters_get_need_authentication_tags(xpc_object_t params);
 DNSServiceProtocol
 dnssd_xpc_parameters_get_protocols(xpc_object_t params, bool * _Nullable out_valid);
 
+/*!
+ *     @brief
+ *             Gets the service scheme from a command parameters dictionary.
+ *
+ *     @param params
+ *             Command parameters dictionary.
+ *
+ *     @result
+ *             A string containing service scheme for the query, or NULL.
+ */
+const char * _Nullable
+dnssd_xpc_parameters_get_service_scheme(xpc_object_t params);
+
 /*!
  *     @brief
  *             Sets delegate ID as a PID in a command parameters dictionary.
@@ -297,6 +377,19 @@ dnssd_xpc_parameters_set_delegate_pid(xpc_object_t params, pid_t pid);
 void
 dnssd_xpc_parameters_set_delegate_uuid(xpc_object_t params, uuid_t _Nonnull uuid);
 
+/*!
+ *     @brief
+ *             Sets the delegate audit token in a command parameters dictionary.
+ *
+ *     @param params
+ *             Command parameters dictionary.
+ *
+ *     @param audit_token
+ *             The delegate audit token.
+ */
+void
+dnssd_xpc_parameters_set_delegate_audit_token(xpc_object_t params, const audit_token_t *audit_token);
+
 /*!
  *     @brief
  *             Sets flags in a command parameters dictionary.
@@ -310,6 +403,19 @@ dnssd_xpc_parameters_set_delegate_uuid(xpc_object_t params, uuid_t _Nonnull uuid
 void
 dnssd_xpc_parameters_set_flags(xpc_object_t params, DNSServiceFlags flags);
 
+/*!
+*      @brief
+*              Sets account id in a command parameters dictionary.
+*
+*      @param params
+*              Command parameters dictionary.
+*
+*      @param account_id
+*              Account id.
+*/
+void
+dnssd_xpc_parameters_set_account_id(xpc_object_t params, const char *account_id);
+
 /*!
  *     @brief
  *             Sets hostname in a command parameters dictionary.
@@ -349,6 +455,50 @@ dnssd_xpc_parameters_set_interface_index(xpc_object_t params, uint32_t interface
 void
 dnssd_xpc_parameters_set_need_authentication_tags(xpc_object_t params, bool need);
 
+/*!
+ *     @brief
+ *             Specifies whether or not queries must use encrypted transports to the next DNS server.
+ *
+ *     @param params
+ *             Command parameters dictionary.
+ *
+ *     @param need
+ *             Pass <code>true</code> if encrypted queries are required, otherwise, pass <code>false</code>.
+ *
+ *     @param fallback_config
+ *             If not NULL, specify a custom resolver configuration to use if no encrypted resolver configuation is otherwise
+ *             available.
+ */
+void
+dnssd_xpc_parameters_set_need_encrypted_query(xpc_object_t params, bool need, _Nullable xpc_object_t fallback_config);
+
+/*!
+ *     @brief
+ *             Add a resolver UUID that represents a resolver configuration registered with the system that should
+ *             be applied to this resolution. Multiple UUIDs can be set.
+ *
+ *     @param params
+ *             Command parameters dictionary.
+ *
+ *     @param uuid
+ *             UUID of a resolver configuration registered with the system.
+ */
+void
+dnssd_xpc_parameters_add_resolver_uuid(xpc_object_t params, uuid_t _Nonnull uuid);
+
+/*!
+ *     @brief
+ *             Sets a service scheme in a command parameters dictionary.
+ *
+ *     @param params
+ *             Command parameters dictionary.
+ *
+ *     @param service_scheme
+ *             Service scheme.
+ */
+void
+dnssd_xpc_parameters_set_service_scheme(xpc_object_t params, const char *service_scheme);
+
 /*!
  *     @brief
  *             Sets protocols in a command parameters dictionary.
@@ -465,6 +615,19 @@ dnssd_xpc_result_get_record_data_object(xpc_object_t result);
 xpc_object_t _Nullable
 dnssd_xpc_result_get_record_name_object(xpc_object_t result);
 
+/*!
+* @brief
+*      Gets record canonical name from a command result dictionary.
+*
+*  @param result
+*      The command result dictionary.
+*
+*  @result
+*      Record canonical name, if present, as an XPC string object. Otherwise, NULL.
+*/
+xpc_object_t _Nullable
+dnssd_xpc_result_get_record_cname_object(xpc_object_t result);
+
 /*!
  *     @brief
  *             Gets record type from a command result dictionary.
@@ -481,6 +644,48 @@ dnssd_xpc_result_get_record_name_object(xpc_object_t result);
 uint16_t
 dnssd_xpc_result_get_record_type(xpc_object_t result, bool * _Nullable out_valid);
 
+/*!
+ *     @brief
+ *             Gets used record protocol from a command result dictionary.
+ *
+ *     @param result
+ *             The command result dictionary.
+ *
+ *     @param out_valid
+ *             If non-NULL, set to true if value is present and of correct type, otherwise, set to false.
+ *
+ *     @result
+ *             Used record protocol, if present. Otherwise, 0.
+ */
+uint16_t
+dnssd_xpc_result_get_record_protocol(xpc_object_t result, bool * _Nullable out_valid);
+
+/*!
+ *     @brief
+ *             Gets provider name from a command result dictionary.
+ *
+ *     @param result
+ *             The command result dictionary.
+ *
+ *     @result
+ *             Provider name, if present, as an XPC string object. Otherwise, NULL.
+ */
+xpc_object_t _Nullable
+dnssd_xpc_result_get_provider_name_object(xpc_object_t result);
+
+/*!
+ *     @brief
+ *             Gets canonical name updates from a command result dictionary.
+ *
+ *     @param result
+ *             The command result dictionary.
+ *
+ *     @result
+ *             The canonical name update, if present, as an XPC array object. Otherwise, NULL.
+ */
+xpc_object_t _Nullable
+dnssd_xpc_result_get_cname_update(xpc_object_t result);
+
 /*!
  *     @brief
  *             Sets the authentication tag in a command result dictionary.
@@ -578,6 +783,19 @@ dnssd_xpc_result_set_record_data(xpc_object_t result, const void * _Nullable dat
 void
 dnssd_xpc_result_set_record_name(xpc_object_t result, const char *name);
 
+/*!
+* @brief
+*      Sets record canonical name in a command result dictionary.
+*
+*  @param result
+*      The command result dictionary.
+*
+*  @param cname
+*      Record canonical name.
+*/
+void
+dnssd_xpc_result_set_record_cname(xpc_object_t result, const char *cname);
+
 /*!
  *     @brief
  *             Sets record type in a command result dictionary.
@@ -591,9 +809,46 @@ dnssd_xpc_result_set_record_name(xpc_object_t result, const char *name);
 void
 dnssd_xpc_result_set_record_type(xpc_object_t result, uint16_t type);
 
-#ifdef __cplusplus
-}
-#endif
+/*!
+ *     @brief
+ *             Sets record protocol in a command result dictionary.
+ *
+ *     @param result
+ *             The command result dictionary.
+ *
+ *     @param protocol
+ *             Record protocol.
+ */
+void
+dnssd_xpc_result_set_record_protocol(xpc_object_t result, uint16_t protocol);
+
+/*!
+ *     @brief
+ *             Sets the DNS provider name in a command result dictionary.
+ *
+ *     @param result
+ *             The command result dictionary.
+ *
+ *     @param provider_name
+ *             DNS provider name.
+ */
+void
+dnssd_xpc_result_set_provider_name(xpc_object_t result, const char *provider_name);
+
+/*!
+ *     @brief
+ *             Sets a canonical name update in a command result dictionary.
+ *
+ *     @param result
+ *             The command result dictionary.
+ *
+ *     @param cname_update
+ *             The canonical name update as an array of canonical names as strings.
+ */
+void
+dnssd_xpc_result_set_cname_update(xpc_object_t result, xpc_object_t cname_update);
+
+__END_DECLS
 
 CU_ASSUME_NONNULL_END
 
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2.c b/mDNSMacOSX/dnssec_v2/dnssec_v2.c
new file mode 100644 (file)
index 0000000..fe80ebc
--- /dev/null
@@ -0,0 +1,403 @@
+//
+//     dnssec_v2.c
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#include <AssertMacros.h>              // for require_* macro
+#include <os/feature_private.h> // for feature flag
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include "uds_daemon.h"
+#include "DNSCommon.h"
+#include "dnssec_v2.h"
+#include "dnssec_v2_helper.h"
+#include "dnssec_v2_validation.h"
+#include "dnssec_v2_trust_anchor.h"
+#include "dnssec_v2_client.h"
+
+// MARK: - Macros
+
+#define DNSSEC_OK_BIT 0x8000
+
+// MARK: - External Functions
+
+mDNSexport mDNSBool
+enables_dnssec_validation(const DNSQuestion * _Nonnull q) {
+       return q->DNSSECStatus.enable_dnssec;
+}
+
+//======================================================================================================================
+
+// Check if the question could be validated with DNSSEC.
+mDNSexport mDNSBool
+is_eligible_for_dnssec(const domainname * const _Nonnull name, mDNSu16 question_type) {
+       mDNSBool is_eligible = mDNSfalse;
+
+       require_quiet(!IsLocalDomain(name), exit);
+       require_quiet(question_type != kDNSServiceType_RRSIG, exit);
+       require_quiet(question_type != kDNSServiceType_ANY, exit);
+
+       is_eligible = mDNStrue;
+exit:
+       return is_eligible;
+}
+
+//======================================================================================================================
+
+mDNSexport void
+get_denial_records_from_negative_cache_to_dnssec_context(
+       const mDNSBool                                                  enable_dnssec,
+       dnssec_context_t * const        _Nonnull        context,
+       CacheRecord * const                     _Nonnull        rr) {
+
+       if (enable_dnssec) {
+               context->denial_of_existence_records = rr->denial_of_existence_records;
+       }
+}
+
+//======================================================================================================================
+
+mDNSexport void
+set_denial_records_in_cache_record(
+       CacheRecord * const                             _Nonnull                                cache_record,
+       denial_of_existence_records_t *         _Nullable *     _Nonnull        denial_records_ptr) {
+
+       cache_record->denial_of_existence_records = *denial_records_ptr;
+       *denial_records_ptr = mDNSNULL;
+}
+
+//======================================================================================================================
+
+mDNSexport void
+release_denial_records_in_cache_record(CacheRecord * const _Nonnull cache_record) {
+       if (cache_record->denial_of_existence_records != mDNSNULL) {
+               destroy_denial_of_existence_records_t(cache_record->denial_of_existence_records);
+               cache_record->denial_of_existence_records = mDNSNULL;
+       }
+}
+
+//======================================================================================================================
+
+mDNSexport void
+update_denial_records_in_cache_record(
+       CacheRecord * const                             _Nonnull                                cache_record,
+       denial_of_existence_records_t *         _Nullable *     _Nonnull        denial_records_ptr) {
+
+       if (cache_record->denial_of_existence_records != mDNSNULL) {
+               destroy_denial_of_existence_records_t(cache_record->denial_of_existence_records);
+       }
+       cache_record->denial_of_existence_records = *denial_records_ptr;
+       *denial_records_ptr = mDNSNULL;
+}
+
+//======================================================================================================================
+
+mDNSexport mDNSBool
+adds_denial_records_in_cache_record(
+       const ResourceRecord *                  _Nonnull const                  rr,
+       const mDNSBool                                                                                  enable_dnssec,
+       denial_of_existence_records_t * _Nullable * _Nonnull    denials_ptr) {
+
+       mDNSBool not_answer_but_required_for_dnssec = mDNSfalse;
+       mStatus error = mStatus_NoError;
+
+       require_quiet(enable_dnssec, exit);
+       require_quiet(record_denies_existence_of_dnssec_question(rr), exit);
+
+       if (*denials_ptr == mDNSNULL) {
+               *denials_ptr = create_denial_of_existence_records_t();
+               require_quiet(*denials_ptr != mDNSNULL, exit);
+       }
+
+       error = add_to_denial_of_existence_records_t(*denials_ptr, rr);
+       require_quiet(error == mStatus_NoError, exit);
+       not_answer_but_required_for_dnssec = mDNStrue;
+
+exit:
+       if (error != mStatus_NoError) {
+               if (*denials_ptr != mDNSNULL) destroy_denial_of_existence_records_t(*denials_ptr);
+               *denials_ptr = mDNSNULL;
+       }
+       return not_answer_but_required_for_dnssec;
+}
+
+//======================================================================================================================
+
+mDNSexport mDNSBool
+are_records_in_the_same_cache_set_for_dnssec(
+       const ResourceRecord * const _Nonnull left,
+       const ResourceRecord * const _Nonnull right) {
+
+       if (left->rrtype != kDNSType_RRSIG) {
+               return mDNStrue;
+       }
+
+       return rrsig_records_cover_the_same_record_type(left, right);
+}
+
+//======================================================================================================================
+
+// Check if the current record type belongs to the question that enables DNSSEC.
+mDNSexport mDNSBool
+record_type_answers_dnssec_question(const ResourceRecord * const _Nonnull record, const mDNSu16 qtype) {
+       mDNSBool result = mDNSfalse;
+
+       switch (record->rrtype) {
+               case kDNSType_CNAME:
+                       result          = mDNStrue;
+                       break;
+               case kDNSType_RRSIG: {
+                       mDNSu16 type_covered    = get_covered_type_of_dns_type_rrsig_t(record->rdata->u.data);
+
+                       if (qtype == kDNSType_RRSIG
+                               || type_covered == qtype
+                               || type_covered == kDNSType_CNAME) { // Returned RRSIG covers a CNAME for the current question.
+                               // RRSIG that covers NSEC/NSEC3 also answers question, but it provides non-existence proof.
+                               result = mDNStrue;
+                       }
+               }
+                       break;
+               // kDNSType_DS and kDNSType_DNSKEY also applies to the default case here.
+               default:
+                       if (record->rrtype == qtype) {
+                               result = mDNStrue;
+                       }
+                       // NSEC/NSEC3 or RRSIG that covers NSEC/NSEC3 also answers question, but they provides non-existence proof,
+                       // so they do not answer the question positively, and the function should return false.
+                       break;
+       }
+
+       return result;
+}
+
+//======================================================================================================================
+
+mDNSexport mDNSBool
+rrsig_records_cover_the_same_record_type(const ResourceRecord * const _Nonnull left, const ResourceRecord * const _Nonnull right) {
+       mDNSu16 type_covered_left       = get_covered_type_of_dns_type_rrsig_t(left->rdata->u.data);
+       mDNSu16 type_covered_right      = get_covered_type_of_dns_type_rrsig_t(right->rdata->u.data);
+
+       return type_covered_left == type_covered_right;
+}
+
+//======================================================================================================================
+
+// Used by mDNSCoreReceiveResponse, to check if the current NSEC/NSEC3 record belongs to the question that enables DNSSEC
+mDNSexport mDNSBool
+record_denies_existence_of_dnssec_question(const ResourceRecord * const _Nonnull record) {
+       const mDNSu16   rr_type                                                 = record->rrtype;
+       mDNSu16                 type_covered;
+       mDNSBool                acceptable_denial_of_existence  = mDNSfalse;
+
+       // Temporarily disbale NSEC validation, it should also check if it is NSEC(or the corresponding RRSIG covers NSEC)
+       if (rr_type == kDNSType_NSEC3) {
+               acceptable_denial_of_existence = mDNStrue;
+       } else if (rr_type == kDNSType_RRSIG) {
+               type_covered = get_covered_type_of_dns_type_rrsig_t(record->rdata->u.data);
+               // Same here, temporarily disbale NSEC validation.
+               if (type_covered == kDNSType_NSEC3) {
+                       acceptable_denial_of_existence = mDNStrue;
+               }
+       }
+
+       return acceptable_denial_of_existence;
+}
+
+//======================================================================================================================
+
+// The main DNSSEC callback function, it replaces the original user callback function, and becomes a middle layer
+// between the mDNSCore and user callback function, all the DNSSEC related operation happens here:
+// 1. Records retrieval
+// 2. Records validation
+mDNSexport void
+query_record_result_reply_with_dnssec(
+       mDNS *const                                             _Null_unspecified       m,
+       DNSQuestion *                                   _Null_unspecified       question,
+       const ResourceRecord * const    _Null_unspecified       const_answer,
+       QC_result                                                                                       add_record,
+       DNSServiceErrorType                                                                     dns_result_error,
+       void *                                                  _Null_unspecified       context) {
+
+       dnssec_context_t *                              dnssec_context          = (dnssec_context_t *)context;
+       QueryRecordClientRequest *              primary_request         = GET_PRIMARY_REQUEST(dnssec_context);
+       ResourceRecord * const                  answer                          = (ResourceRecord *)const_answer;
+       mDNSBool                                                anchor_reached          = mDNSfalse;
+       mDNSBool                                                stop_process            = mDNSfalse;
+       dnssec_retrieval_result_t               retrieval_result        = dnssec_retrieval_no_error;
+       dnssec_validation_result_t              validation_result;
+       mDNSu32                                                 request_id                      = primary_request->op.reqID;
+       returned_answers_t * const              returned_answers        = &dnssec_context->returned_answers;
+
+       switch (add_record) {
+               case QC_add:
+               case QC_rmv:
+               case QC_suppressed:
+                       break;
+               // QC_addnocache and QC_forceresponse are all cases where the returned resource record is not in the cache.
+               // We temporarily ignore those two cases.
+               case QC_addnocache:
+               case QC_forceresponse:
+               default:
+                       log_error("[R%u] QC_result other than add, remove, suppressed is returned; add_record=%d",
+                               request_id, add_record);
+                       return;
+       }
+
+       if (dns_result_error == kDNSServiceErr_NoError) {
+               retrieval_result = add_no_error_records(m, question, answer, add_record, dns_result_error, dnssec_context);
+       } else {
+               retrieval_result = add_denial_of_existence_records(m, question, answer, add_record, dns_result_error, dnssec_context);
+       }
+
+       do {
+               // handle any error case when addign records
+               stop_process = handle_retrieval_result(question, context, retrieval_result, dns_result_error, m);
+               // WARNING: If stop_process is set to true here, we should not touch anything including dnssec_context, because
+               // we might free the object related to the current dnssec request, and we would get memory fault if using it.
+
+               // If we have error when adding record, then we should not continue.
+               if (stop_process) {
+                       break;
+               }
+
+               // check if we could reach the trust anchor with the records we have currently
+               anchor_reached = trust_anchor_can_be_reached(dnssec_context);
+               if (anchor_reached) {
+                       // if so, validate the from the leaf to the root(trust anchor)
+                       validation_result = validate_dnssec(dnssec_context);
+
+                       // handle the validation result such as returning DNSSEC-secure answer to user, return error code to user
+                       stop_process = handle_validation_result(question, context, validation_result, dns_result_error, m);
+
+                       // If we already returned the answer/error to user, there is no more to do.
+                       if (stop_process) {
+                               break;
+                       }
+               } else if (returned_answers->error != kDNSServiceErr_Invalid) {
+                       // previous verified record set cannot establish trust chain, deliver rmv event for all returned records
+                       if (returned_answers->type != cname_response) { // Do not deliver RMV for CNAME records.
+                               // Since here we are returning the records on the behave of the primary request, the question being
+                               // returned should be the question from the primary request instead of the possible CNAME question that
+                               // is started by the DNSSEC handler itself.
+                               stop_process = deliver_remove_to_callback_with_all_returned_answers(dnssec_context, returned_answers, m,
+                                       GET_PRIMARY_QUESTION(dnssec_context), question);
+                               require_quiet(!stop_process, exit);
+                       }
+                       uninitialize_returned_answers_t(returned_answers);
+                       initialize_returned_answers_t(returned_answers, dnssec_indeterminate, kDNSServiceErr_Invalid);
+               }
+
+               // If the records we have currently is not enough to form a chian of trust, keep querying for more records until
+               // the trust anchor
+               retrieval_result = fetch_necessary_dnssec_records(dnssec_context, anchor_reached);
+
+               // handle the result of fetch_necessary_dnssec_records such as returning error to user if some error occurs when
+               // querying for more records
+               stop_process =  handle_retrieval_result(question, context, retrieval_result, dns_result_error, m);
+               if (stop_process) {
+                       break;
+               }
+       } while (retrieval_result == dnssec_retrieval_validate_again);
+
+exit:
+       return;
+}
+
+//======================================================================================================================
+
+mDNSexport void
+stop_dnssec_if_enable_dnssec(QueryRecordClientRequest * const _Nonnull request) {
+       DNSQuestion * const q = &request->op.q;
+       if (!q->DNSSECStatus.enable_dnssec) {
+               return;
+       }
+       stop_dnssec(request);
+}
+
+
+//======================================================================================================================
+
+mDNSexport void
+stop_dnssec(QueryRecordClientRequest * const _Nonnull request) {
+       DNSQuestion * const                     q                               = &request->op.q;
+       mDNSu32                                         request_id              = request->op.reqID;
+       if (!q->DNSSECStatus.enable_dnssec) {
+               goto exit;
+       }
+
+       dnssec_context_t * const        dnssec_context  = (dnssec_context_t *)q->DNSSECStatus.context;
+       list_t * const                          zones                   = &dnssec_context->zone_chain;
+       original_t * const                      original                = &dnssec_context->original;
+       const original_request_parameters_t * const param = &original->original_parameters;
+
+       log_default("[R%u] Stopping " PUB_S "DNSSEC request -- hostname: " PRI_DM_NAME ", type: " PUB_S, request_id,
+               dnssec_context->primary_dnssec_context == mDNSNULL ? "primary " : "sub-",
+               DM_NAME_PARAM(&param->question_name), DNSTypeName(param->question_type));
+
+       // stop and clean zone, dnssec_zone_t node will be deleted in destroy_dnssec_context_t
+       for (list_node_t * zone_node = list_get_first(zones); !list_has_ended(zones, zone_node); zone_node = list_next(zone_node)) {
+               dnssec_zone_t * zone = (dnssec_zone_t *)zone_node->data;
+               stop_and_clean_dnssec_zone_t(zone);
+       }
+
+       // Stop the sub CNAME request.
+       if (dnssec_context->subtask_dnssec_context != mDNSNULL) {
+               // Since we will not deliver RMV so there is no need to check if we should stop the request immediately because
+               // the client cancels the request in the callback.
+               stop_sub_cname_request_and_dnssec(q, dnssec_context, mDNSfalse, mDNSNULL);
+       }
+
+       // leave original->original_request to be released by QueryRecordClientRequestStop
+       uninitialize_originals_with_rrsig_t(&original->original_result_with_rrsig);
+
+       // undo create_dnssec_context_t
+       destroy_dnssec_context_t(dnssec_context);
+
+exit:
+       return;
+}
+
+//======================================================================================================================
+
+mDNSexport mDNSBool
+stop_sub_cname_request_and_dnssec(DNSQuestion * const question, dnssec_context_t * const _Nonnull dnssec_context,
+       const mDNSBool deliver_remove, mDNS * const _Nullable m) {
+
+       dnssec_context_t * const cname_dnssec_context = dnssec_context->subtask_dnssec_context;
+       DNSQuestion * const primary_question = GET_PRIMARY_QUESTION(dnssec_context);
+       mDNSBool stop_immediately = mDNSfalse;
+       require_quiet(cname_dnssec_context != mDNSNULL, exit);
+
+       // If we call this function because the CNAME reference chain has changed, all the answers that
+       // are returned to the client needs to be removed first.
+       if (deliver_remove) {
+               for (dnssec_context_t * context_i = cname_dnssec_context; context_i != mDNSNULL; context_i = context_i->subtask_dnssec_context) {
+                       // if it is not kDNSServiceErr_Invalid, it means that we have returned something.
+                       if (context_i->returned_answers.error == kDNSServiceErr_Invalid) {
+                               continue;
+                       }
+                       // Do not deliver RMV event for CNAME answer, since mDNSResponder never rewinds the CNAME chain, DNSSEC API
+                       // needs to follow the same behavior.
+                       if (context_i->returned_answers.type == cname_response) {
+                               continue;
+                       }
+                       stop_immediately = deliver_remove_to_callback_with_all_returned_answers(context_i,
+                               &context_i->returned_answers, m, primary_question, question);
+                       require_quiet(!stop_immediately, exit);
+               }
+       }
+
+       QueryRecordClientRequest * cname_request = cname_dnssec_context->me;
+       require_action_quiet(cname_request == &dnssec_context->request_to_follow_cname, exit, stop_immediately = mDNStrue;
+               log_debug("cname request does not points back to the request_to_follow_cname"));
+       QueryRecordOpStopForClientRequest(&cname_request->op);
+       stop_dnssec(cname_request);
+       dnssec_context->subtask_dnssec_context = mDNSNULL;
+
+exit:
+       return stop_immediately;
+}
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2.h b/mDNSMacOSX/dnssec_v2/dnssec_v2.h
new file mode 100644 (file)
index 0000000..7883182
--- /dev/null
@@ -0,0 +1,123 @@
+//
+//     dnssec_v2.h
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#ifndef DNSSEC_V2_H
+#define DNSSEC_V2_H
+
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include <os/feature_private.h>
+#include "dnssec_v2_embedded.h"
+#include "dnssec_v2_structs.h"
+#include "dnssec_v2_retrieval.h"
+#include "dnssec_v2_validation.h"
+#include "dnssec_v2_trust_anchor.h"
+#include "dnssec_v2_log.h"
+#include "base_n.h"
+#include "list.h"
+
+//======================================================================================================================
+//     Constants
+//======================================================================================================================
+
+#define EDNS0_SENDER_UDP_PAYLOAD_SIZE  512
+#define MAX_ZONES_ALLOWED                              10
+
+//======================================================================================================================
+//     Macros
+//======================================================================================================================
+
+#define FLAGS_CONTAIN_DNSOK_BIT(FLAGS) (((FLAGS) & kDNSServiceFlagsEnableDNSSEC) != 0)
+
+//======================================================================================================================
+//     functions
+//======================================================================================================================
+
+mDNSexport mDNSBool
+enables_dnssec_validation(const DNSQuestion * _Nonnull q);
+
+mDNSexport mDNSBool
+is_eligible_for_dnssec(const domainname * const _Nonnull name, mDNSu16 question_type);
+
+mDNSexport void
+get_denial_records_from_negative_cache_to_dnssec_context(
+       const mDNSBool                                                  enable_dnssec,
+       dnssec_context_t * const        _Nonnull        context,
+       CacheRecord * const                     _Nonnull        rr);
+
+mDNSexport void
+set_denial_records_in_cache_record(
+       CacheRecord * const                             _Nonnull                                cache_record,
+       denial_of_existence_records_t *         _Nullable *     _Nonnull        denial_records_ptr);
+
+mDNSexport void
+release_denial_records_in_cache_record(CacheRecord * const _Nonnull cache_record);
+
+mDNSexport void
+update_denial_records_in_cache_record(
+       CacheRecord * const                             _Nonnull                                cache_record,
+       denial_of_existence_records_t *         _Nullable *     _Nonnull        denial_records_ptr);
+
+mDNSexport mDNSBool
+adds_denial_records_in_cache_record(
+       const ResourceRecord *                  _Nonnull const                  rr,
+       const mDNSBool                                                                                  enable_dnssec,
+       denial_of_existence_records_t * _Nullable * _Nonnull    denials_ptr);
+
+mDNSexport mDNSBool
+are_records_in_the_same_cache_set_for_dnssec(
+       const ResourceRecord * const _Nonnull left,
+       const ResourceRecord * const _Nonnull right);
+
+mDNSexport mDNSBool
+record_type_answers_dnssec_question(const ResourceRecord * const _Nonnull record, const mDNSu16 qtype);
+
+mDNSexport mDNSBool
+rrsig_records_cover_the_same_record_type(const ResourceRecord * const _Nonnull left, const ResourceRecord * const _Nonnull right);
+
+mDNSexport mDNSBool
+record_denies_existence_of_dnssec_question(const ResourceRecord * const _Nonnull record);
+
+mDNSexport void
+query_record_result_reply_with_dnssec(
+       mDNS *const                                             _Null_unspecified       __unused m,
+       DNSQuestion *                                   _Null_unspecified       question,
+       const ResourceRecord * const    _Null_unspecified       answer,
+       QC_result                                                                                       add_record,
+       DNSServiceErrorType                                                                     dns_result_error,
+       void *                                                  _Null_unspecified       context);
+
+mDNSexport void
+stop_dnssec_if_enable_dnssec(QueryRecordClientRequest * const _Nonnull request);
+
+mDNSexport void
+stop_dnssec(QueryRecordClientRequest * const _Nonnull request);
+
+/*!
+ * @brief
+ *             Stops the sub request started by the current request, and also possibly delivers RMV events for all the returned answers.
+ *
+ * @param dnssec_context
+ *             A pointer to the DNSSEC context of the current request.
+ *
+ * @param deliver_remove
+ *             A boolean value to indicate if the function should deliver the RMV events for those records that have been returned to the client.
+ *
+ * @param m
+ *             A pointer to the mDNS structure.
+ *
+ * @return
+ *             A boolean value to indicate if the caller should stop all the work immediately. If it returns true, it means that the callback called by this function has canceled
+ *             the current request and its corresponding question, and the caller should assume that all the allocated memory it owns has already been freed, and it
+ *             should stop immediately to avoid invalid memory access.
+ */
+mDNSexport mDNSBool
+stop_sub_cname_request_and_dnssec(DNSQuestion * const _Nonnull question, dnssec_context_t * const _Nonnull dnssec_context,
+       const mDNSBool deliver_remove, mDNS * const _Nullable m);
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#endif // DNSSEC_V2_H
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2_client.c b/mDNSMacOSX/dnssec_v2/dnssec_v2_client.c
new file mode 100644 (file)
index 0000000..249aac5
--- /dev/null
@@ -0,0 +1,901 @@
+//
+//     dnssec_v2_client.c
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include "udns.h"
+#include "dnssec_v2.h"
+#include "dnssec_v2_client.h"
+#include "dnssec_v2_retrieval.h"
+#include "dnssec_v2_helper.h"
+
+//======================================================================================================================
+//     Local function prototype
+//======================================================================================================================
+
+// When calling remove_records_if_necessary/add_records_if_necessary, always check the boolean value stored in
+// out_stop_immediately. If the value is true, then the caller should stop all the work immediately and return, because
+// the functions might call the callback function which may cancel the current request and the question we are working
+// on.
+
+mDNSlocal mDNSBool
+remove_records_if_necessary(
+       dnssec_context_t * const        _Nonnull        dnssec_context,
+       const dnssec_result_t                                   dnssec_result,
+       mDNS * const                                                    mdnsresponder_context,
+       DNSQuestion * const                     _Nonnull        question,
+       mDNSBool *                                      _Nonnull        out_stop_immediately);
+
+mDNSlocal mDNSBool
+add_records_if_necessary(
+       dnssec_context_t * const        _Nonnull        dnssec_context,
+       const dnssec_result_t                                   dnssec_result,
+       const DNSServiceErrorType                               dns_error_from_core,
+       mDNS * const                                                    mdnsresponder_context,
+       DNSQuestion * const                     _Nonnull        question,
+       mDNSBool *                                      _Nonnull        out_stop_immediately);
+
+mDNSlocal mStatus
+handle_cname_response(dnssec_context_t * const _Nonnull context);
+
+// Always check the return value of return_answer_to_user. If the value is true, then the caller should stop all the
+// work immediately and return, because the functions may call the callback function which may cancel the current
+// request and the question we are working on.
+mDNSlocal mDNSBool
+return_answer_to_user(
+       QueryRecordResultHandler                                        user_handler,
+       void * const                                                            user_context,
+       dnssec_result_t                                                         result,
+       ResourceRecord * const                  _Nonnull        rr,
+       const QC_result                                                         add_or_remove,
+       const DNSServiceErrorType                                       dns_error,
+       mDNS * const                                    _Nonnull        m,
+       DNSQuestion * const                             _Nonnull        primary_question,
+       const DNSQuestion * const               _Nonnull        current_question);
+
+mDNSlocal mStatus
+add_record_to_be_returned_to_returned_answers_t(returned_answers_t * const returned_asnwers, const ResourceRecord * const rr);
+
+mDNSlocal mDNSBool
+contains_rr_in_returned_rrs(const ResourceRecord * const rr, const list_t * const returned_rrs/* list_t<ResourceRecord *> */);
+
+mDNSlocal DNSServiceErrorType
+get_dnsservice_error_type_from_originals(const original_t * const _Nonnull original, DNSServiceErrorType type_from_mDNSCore);
+
+#pragma mark - handle_validation_result
+
+mDNSlocal void
+handle_cname_retrieval_error(dnssec_context_t * const _Nonnull dnssec_context,
+       mDNS * const _Nonnull mdnsresponder_context);
+
+mDNSexport mDNSBool
+handle_retrieval_result(
+       DNSQuestion * const                     _Nonnull        question,
+       dnssec_context_t *                      _Nonnull        original_dnssec_context,
+       dnssec_retrieval_result_t                               retrieval_result,
+       const DNSServiceErrorType                               dnssec_error,
+       mDNS * const                            _Nonnull        mdnsresponder_context) {
+
+       mDNSBool                        stop                                    = mDNSfalse;
+       // We use a new variable here so that current_dnssec_context can be set to NULL when it is freed, to avoid corrupted
+       // memory access.
+       dnssec_context_t *      current_dnssec_context  = original_dnssec_context;
+       mDNSu32                         request_id                              = current_dnssec_context->original.original_parameters.request_id;
+       mDNSu16                         question_id                     = mDNSVal16(question->TargetQID);
+       dnssec_result_t         dnssec_result;
+       mDNSBool                        stop_immediately = mDNSfalse;
+
+       switch (retrieval_result) {
+               case dnssec_retrieval_no_error:
+               case dnssec_retrieval_waiting_for_records:      // We are waiting for the response for some active queries.
+               case dnssec_retrieval_validate_again:           // We have a trust anchor for the current root, and will use it to validate immediately.
+                       stop = mDNSfalse;
+                       break;
+
+               case dnssec_retrieval_no_new_change:
+                       stop = mDNStrue;
+                       break;
+
+               case dnssec_retrieval_no_rrsig: {                               // The returned answer is missing some necessary records to finish DNSSEC, it usually means the domain is not signed, thus it is an insecure zone.
+                       mDNSu16                 question_type = current_dnssec_context->original.original_parameters.question_type;
+                       response_type_t response_type = current_dnssec_context->original.original_result_with_rrsig.type;
+
+                       dnssec_result = dnssec_insecure;
+                       stop = mDNStrue;
+
+                       if (response_type == cname_response && question_type != kDNSServiceType_CNAME) {
+                               // After calling handle_cname_retrieval_error, dnssec_context will be freed, and should never be used.
+                               handle_cname_retrieval_error(current_dnssec_context, mdnsresponder_context);
+                               // Set current_dnssec_context to NULL to avoid any further access.
+                               current_dnssec_context = NULL;
+                               goto exit;
+                       }
+
+                       remove_records_if_necessary(current_dnssec_context, dnssec_result, mdnsresponder_context, question,
+                               &stop_immediately);
+                       require_quiet(!stop_immediately, exit);
+
+                       add_records_if_necessary(current_dnssec_context, dnssec_result, dnssec_error, mdnsresponder_context,
+                               question, &stop_immediately);
+                       require_quiet(!stop_immediately, exit);
+               }
+                       break;
+               case dnssec_retrieval_suppressed: {
+                       // Suppressed answers are generated by mDNSResponder itself, and we do not have a way to validate this
+                       // generated record, thus the DNSSEC result should be indeterminate.
+                       dnssec_result   = dnssec_indeterminate;
+                       stop                    = mDNStrue;
+
+                       remove_records_if_necessary(current_dnssec_context, dnssec_result, mdnsresponder_context, question,
+                               &stop_immediately);
+                       require_quiet(!stop_immediately, exit);
+                       add_records_if_necessary(current_dnssec_context, dnssec_result, dnssec_error, mdnsresponder_context,
+                               question, &stop_immediately);
+                       require_quiet(!stop_immediately, exit);
+               }
+                       break;
+               case dnssec_retrieval_cname_removed: {
+                       // if the CNAME is removed, then the sub request that follows this removed CNAME is no longer valid, and
+                       // we need to stop it. If the CNAME record does not exist anymore, there is nothing we could do to validate
+                       // the record(Maybe later we would get NSEC/NSEC3 records that denies the existence of this CNAME),
+                       // therefore, it is marked as indeterminate.
+                       dnssec_result = dnssec_indeterminate;
+                       stop = mDNStrue;
+
+                       remove_records_if_necessary(current_dnssec_context, dnssec_result, mdnsresponder_context, question,
+                               &stop_immediately);
+                       require_quiet(!stop_immediately, exit);
+
+                       require_action_quiet(current_dnssec_context->subtask_dnssec_context != mDNSNULL, exit, stop = mDNStrue;
+                               log_error("[R%u->Q%u] Get dnssec_retrieval_cname_removed error while the context of CNAME request is NULL",
+                                       request_id, question_id));
+
+                       stop_immediately = stop_sub_cname_request_and_dnssec(question, current_dnssec_context, mDNStrue, mdnsresponder_context);
+                       require_quiet(!stop_immediately, exit);
+                       // Since the previous CNAME we rely on is removed, there is nothing we can do next, so we should stop the
+                       // current processing.
+
+               }
+                       break;
+               default:
+                       stop = mDNStrue;
+                       log_error("[R%u->Q%u] handle_retrieval_result not handling this type of error; retrieval_result=%d",
+                               request_id, question_id, retrieval_result);
+                       break;
+       }
+
+exit:
+       return stop || stop_immediately;
+}
+
+//======================================================================================================================
+
+mDNSlocal void
+handle_cname_retrieval_error(dnssec_context_t * const _Nonnull dnssec_context,
+       mDNS * const _Nonnull mdnsresponder_context)
+{
+       dnssec_context_t * const primary_dnssec_context = GET_PRIMARY_DNSSEC_CONTEXT(dnssec_context);
+       QueryRecordClientRequest * const query_request = GET_PRIMARY_REQUEST(dnssec_context);
+       DNSQuestion * const q = &query_request->op.q;
+       QueryRecordResultHandler user_handler = primary_dnssec_context->original.original_parameters.user_handler;
+       void * const user_context = primary_dnssec_context->original.original_parameters.user_context;
+       cnames_with_rrsig_t *cname_ptr;
+       list_node_t *cname_node;
+       dnssec_cname_t * dnssec_cname;
+       ResourceRecord * rr_ptr;
+
+       // Get the CNAME that has no RRSIG
+       cname_ptr = &dnssec_context->original.original_result_with_rrsig.u.cname_with_rrsig;
+       cname_node = list_get_first(&cname_ptr->cname_records);
+       // Should never be NULL, but add checking here to avoid invalid memory access.
+       verify_action(cname_node != mDNSNULL, return);
+       dnssec_cname = (dnssec_cname_t *)cname_node->data;
+       rr_ptr = dnssec_cname->dnssec_rr.rr; // this pointer points to the cached resource record in mDNSCore.
+       // Should never be NULL, since when dnssec_cname_t is initialized in initialize_dnssec_cname_t, the rr has to be non-NULL.
+       verify_action(rr_ptr != mDNSNULL, return);
+
+       // After calling this function, dnssec_context has been freed, and should never be used.
+       stop_dnssec(query_request);
+
+       query_request->op.resultHandler = user_handler; // change the user handler back to original one
+       query_request->op.resultContext = user_context; // change the user context back to original one
+
+       // Have to grab the lock to avoid any call when we are restarting the query.
+       mDNS_Lock(mdnsresponder_context);
+       AnswerQuestionByFollowingCNAME(mdnsresponder_context, q, rr_ptr);
+       mDNS_Unlock(mdnsresponder_context);
+
+       // Disable DNSSEC for the current request.
+       q->DNSSECStatus.enable_dnssec                           = mDNSfalse;
+       q->DNSSECStatus.tried_dnssec_but_unsigned       = mDNStrue;
+       q->DNSSECStatus.context                                         = mDNSNULL;
+}
+
+//======================================================================================================================
+//     handle_validation_result
+//             When error happens or validation succeeds while we are validating records, handle it such as return the answer to user
+//======================================================================================================================
+
+#pragma mark - handle_validation_result
+
+mDNSlocal mStatus
+handle_cname_response(dnssec_context_t * const _Nonnull context);
+
+mDNSexport mDNSBool
+handle_validation_result(
+       DNSQuestion * const                     _Nonnull        question,
+       dnssec_context_t * const        _Nonnull        dnssec_context,
+       dnssec_validation_result_t                              validation_result,
+       const DNSServiceErrorType                               dnssec_error,
+       mDNS * const                            _Nonnull        mdnsresponder_context) {
+
+       mStatus                 error                           = mStatus_NoError;
+       mDNSBool                stop                            = mDNSfalse;
+       mDNSBool                stop_immediately        = mDNSfalse;
+       dnssec_result_t dnssec_result;
+
+       switch (validation_result) {
+               case dnssec_validation_trusted: {
+                       // return the secure answer to the user or continue the DNSSEC validation process if the verified answer is
+                       // CNAME and user is not querying for CNAME record
+                       mDNSu16                 question_type = dnssec_context->original.original_parameters.question_type;
+                       response_type_t response_type = dnssec_context->original.original_result_with_rrsig.type;
+                       dnssec_context_t * const cname_dnssec_context = dnssec_context->subtask_dnssec_context;
+                       dnssec_result = dnssec_secure;
+
+                       if (response_type == cname_response && question_type != kDNSType_CNAME) {
+                               if (cname_dnssec_context != mDNSNULL) {
+                                       // stop the old CNAME request, if the reference has changed
+                                       const list_t *                  cnames          = &dnssec_context->original.original_result_with_rrsig.u.cname_with_rrsig.cname_records;
+                                       const mDNSu8 * const    new_cname       = ((dnssec_cname_t *)list_get_first(cnames)->data)->cname;
+                                       const mDNSu8 * const    old_cname       = cname_dnssec_context->original.original_parameters.question_name.c;
+
+                                       if (DOMAIN_NAME_EQUALS(new_cname, old_cname)) {
+                                               // CNAME reference does not change
+                                               stop = mDNStrue;
+                                               goto exit;
+                                       }
+
+                                       // stop CNAME request and also deliver RMV event for those records that are returned to the client.
+                                       stop_immediately = stop_sub_cname_request_and_dnssec(question, dnssec_context, mDNStrue, mdnsresponder_context);
+                                       require_quiet(!stop_immediately, exit);
+                               }
+                               error = handle_cname_response(dnssec_context);
+                               if (error != mStatus_NoError) {
+                                       stop = mDNStrue;
+                                       goto exit;
+                               }
+                               stop = mDNStrue;
+                               // if the user requries CNAME refrence to be returned
+                               mDNSBool return_intermediates = ((dnssec_context->original.original_parameters.flags & kDNSServiceFlagsReturnIntermediates) != 0);
+                               if (!return_intermediates) {
+                                       goto exit;
+                               }
+                               // fall outside of it intentionally
+                       }
+                       stop = mDNStrue;
+                       remove_records_if_necessary(dnssec_context, dnssec_result, mdnsresponder_context, question,
+                               &stop_immediately);
+                       require_quiet(!stop_immediately, exit);
+                       add_records_if_necessary(dnssec_context, dnssec_result, dnssec_error, mdnsresponder_context, question,
+                               &stop_immediately);
+                       require_quiet(!stop_immediately, exit);
+                       goto exit;
+               }
+               case dnssec_validation_trust_anchor_does_not_macth: {
+                       const dnssec_zone_t * const zone = list_empty(&dnssec_context->zone_chain) ? mDNSNULL : (dnssec_zone_t *)list_get_last(&dnssec_context->zone_chain)->data;
+                       if (zone != mDNSNULL && is_root_domain(zone->domain_name.c) && !trust_anchor_contains_dnskey(zone->trust_anchor)) {
+                               // root DS trust anchor failed to validate the record, maybe update our trust anchor
+                               stop = mDNStrue;
+                               log_error("root trust anchor does not verifies the validation tree");
+                               break;
+                       } else {
+                               // tries to fetch records from the DNS server instead of using local trust anchor
+                               goto exit;
+                       }
+               }
+                       break;
+               default:
+                       stop = mDNStrue;
+                       log_error("handle_validation_result not hanlding this type of error; validation_result=%d", validation_result);
+                       break;
+       }
+
+exit:
+       return stop || stop_immediately;
+}
+
+
+#pragma mark handle_cname_response
+// follow the CNAME reference chain, create another DNSSEC request to finish the CNAME query
+mDNSlocal mStatus
+handle_cname_response(dnssec_context_t * const _Nonnull context) {
+       response_type_t                                 type = context->original.original_result_with_rrsig.type;
+       domainname *                                    old_question_name;
+       original_request_parameters_t * parameters;
+       domainname                                              new_question_name;
+       dnssec_context_t *                              new_context = mDNSNULL;
+       mStatus                                                 error           = mStatus_NoError;
+
+       // handle the cname referencing
+       list_t *cnames = &context->original.original_result_with_rrsig.u.cname_with_rrsig.cname_records;
+       verify(type == cname_response && list_count_node(cnames) == 1);
+       old_question_name = (domainname *)(((dnssec_cname_t *)list_get_first(cnames)->data)->cname);
+       AssignDomainName(&new_question_name, old_question_name);
+
+       // Create new dnssec_context_t for the CNAME
+       parameters = &context->original.original_parameters;
+       error = create_dnssec_context_t(mDNSNULL, parameters->request_id, &new_question_name, parameters->question_type,
+               parameters->question_class, parameters->interface_id, parameters->service_id, parameters->flags,
+               parameters->append_search_domains, parameters->pid, parameters->uuid, parameters->uid,
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+               parameters->has_peer_audit_token ? &parameters->peer_audit_token : mDNSNULL,
+               parameters->has_delegate_audit_token ? &parameters->delegate_audit_token : mDNSNULL,
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+               parameters->resolver_uuid, parameters->need_encryption, parameters->custom_id,
+#endif
+               parameters->user_handler, parameters->user_context,
+               context->primary_dnssec_context ? context->primary_dnssec_context : context,
+               &new_context);
+       require_action(error == mStatus_NoError, exit,
+               log_error("create_dnssec_context_t failed; error_description='%s'", mStatusDescription(error)));
+
+       new_context->me = &context->request_to_follow_cname;
+
+       // start a new dnssec request with new_question_name
+       error = QueryRecordOpStartForClientRequest(&new_context->me->op, parameters->request_id, &new_question_name,
+               parameters->question_type, parameters->question_class, parameters->interface_id, parameters->service_id,
+               parameters->flags, parameters->append_search_domains, parameters->pid, parameters->uuid, parameters->uid,
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+               parameters->has_peer_audit_token ? &parameters->peer_audit_token : mDNSNULL,
+               parameters->has_delegate_audit_token ? &parameters->delegate_audit_token : mDNSNULL,
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+               parameters->resolver_uuid, parameters->need_encryption, parameters->custom_id,
+#endif
+               query_record_result_reply_with_dnssec, new_context);
+       require_action(error == mStatus_NoError, exit,
+               log_error("QueryRecordOpStart failed; error_description='%s'", mStatusDescription(error)));
+
+       context->subtask_dnssec_context = new_context;
+
+exit:
+       if (error != mStatus_NoError) {
+               if (new_context != mDNSNULL) {
+                       destroy_dnssec_context_t(new_context);
+               }
+       }
+       return error;
+}
+
+#pragma mark remove_records_if_necessary
+
+mDNSlocal mDNSBool
+deliver_remove_for_returned_records(
+       dnssec_context_t * const        _Nonnull        dnssec_context,
+       const dnssec_result_t                                   dnssec_result,
+       mDNS * const                            _Nonnull        mdnsresponder_context,
+       DNSQuestion * const                     _Nonnull        question,
+       const response_type_t                                   type,
+       mDNSBool *                                      _Nonnull        out_stop_immediately);
+
+mDNSlocal mDNSBool
+remove_records_if_necessary(
+       dnssec_context_t * const        _Nonnull        dnssec_context,
+       const dnssec_result_t                                   dnssec_result,
+       mDNS * const                            _Nonnull        mdnsresponder_context,
+       DNSQuestion * const                     _Nonnull        question,
+       mDNSBool *                                      _Nonnull        out_stop_immediately) {
+
+       returned_answers_t * const              returned_answers                = &dnssec_context->returned_answers;
+       const original_t * const                original                                = &dnssec_context->original;
+       mDNSBool                                                delivered_remove_all    = mDNSfalse;
+       mDNSBool                                                delivered_remove_some   = mDNSfalse;
+
+       // check if we ever returned answers back to the user
+       if (returned_answers->error == kDNSServiceErr_Invalid) {
+               // there is no previous returned anwers(which means this is our first time to return the answer to the user)
+               goto exit;
+       }
+
+       // if we return No Error answer to the user
+       if (returned_answers->error == kDNSServiceErr_NoError) {
+               require_action_quiet(returned_answers->type == original_response || returned_answers->type == cname_response,
+                       exit, log_error("kDNSServiceErr_NoError must be matched with original_response or cname_response"));
+               if (original->original_result_with_rrsig.type == returned_answers->type && dnssec_result == returned_answers->dnssec_result) {
+                       // No Error response with no DNSSEC result change
+                       delivered_remove_some = deliver_remove_for_returned_records(dnssec_context, dnssec_result,
+                               mdnsresponder_context, question, returned_answers->type, out_stop_immediately);
+                       require_quiet(!(*out_stop_immediately), exit);
+               } else {
+                       // either the response is no longer the original response or the dnssec validation result has changed
+                       delivered_remove_all = mDNStrue;
+               }
+               goto exit;
+       }
+
+       // if we return No Such Name or No Such Record to deny the existence of some records
+       if (returned_answers->error == kDNSServiceErr_NoSuchName || returned_answers->error == kDNSServiceErr_NoSuchRecord) {
+               if (returned_answers->type == nsec_response) {
+                       if (original->original_result_with_rrsig.type == nsec_response && dnssec_result == dnssec_secure) {
+                               // unchanged NSEC response with no DNSSEC result change
+                               delivered_remove_some = deliver_remove_for_returned_records(dnssec_context, dnssec_result,
+                                       mdnsresponder_context, question, nsec_response, out_stop_immediately);
+                               require_quiet(!(*out_stop_immediately), exit);
+                       } else {
+                               delivered_remove_all = mDNStrue;
+                       }
+               } else if (returned_answers->type == nsec3_response) {
+                       if (original->original_result_with_rrsig.type == nsec3_response && dnssec_result == dnssec_secure) {
+                               // unchanged NSEC3 response with no DNSSEC result change
+                               delivered_remove_some = deliver_remove_for_returned_records(dnssec_context, dnssec_result,
+                                       mdnsresponder_context, question, nsec3_response, out_stop_immediately);
+                               require_quiet(!(*out_stop_immediately), exit);
+                       } else {
+                               delivered_remove_all = mDNStrue;
+                       }
+               } else if (returned_answers->type == original_response) {
+                       // The zone is unsigned
+                       if (original->original_result_with_rrsig.type == original_response && dnssec_result == dnssec_insecure) {
+                               // The zone is unsigned, and returns No Such Name or No Such Record, nothing has changed
+                               delivered_remove_some = deliver_remove_for_returned_records(dnssec_context, dnssec_result,
+                                       mdnsresponder_context, question, original_response, out_stop_immediately);
+                               require_quiet(!(*out_stop_immediately), exit);
+                       } else {
+                               delivered_remove_all = mDNStrue;
+                       }
+               } else {
+                       log_error("kDNSServiceErr_NoSuchName/kDNSServiceErr_NoSuchRecord must be matched with nsec_response/nsec3_response");
+                       goto exit;
+               }
+
+               goto exit;
+       }
+
+exit:
+       if (delivered_remove_all) {
+               *out_stop_immediately = deliver_remove_to_callback_with_all_returned_answers(dnssec_context, returned_answers,
+                       mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
+               // Check if the callback in deliver_remove_to_callback_with_all_returned_answers has already deleted the
+               // current request and question. If so we should not touch anything.
+               if (!(*out_stop_immediately)) {
+                       uninitialize_returned_answers_t(returned_answers);
+                       initialize_returned_answers_t(returned_answers, dnssec_indeterminate, kDNSServiceErr_Invalid);
+               }
+       }
+       return delivered_remove_all || delivered_remove_some;
+}
+
+#pragma mark return_answer_to_user
+// return_answer_to_user returns whether the caller should stop the DNSSEC-related processing. If it returns true,
+// it means that the callback has stopped the request and deleted the current question, and all the current work should
+// be stopped because the callback has stopped the request and deleted the current question. If we continue to process
+// more, we may access the memory that has already been freed.
+mDNSlocal mDNSBool
+return_answer_to_user(
+       QueryRecordResultHandler                                        user_handler,
+       void * const                                                            user_context,
+       dnssec_result_t                                                         result,
+       ResourceRecord * const                  _Nonnull        rr,
+       const QC_result                                                         add_or_remove,
+       const DNSServiceErrorType                                       dns_error,
+       mDNS * const                                    _Nonnull        m,
+       DNSQuestion * const                             _Nonnull        primary_question,
+       const DNSQuestion * const               _Nonnull        current_question) {
+
+       rr->dnssec_result = result;
+       user_handler(m, primary_question, rr, add_or_remove, dns_error, user_context);
+       rr->dnssec_result = dnssec_indeterminate;
+
+       return m->CurrentQuestion != current_question;
+}
+
+#pragma mark - add_records_if_necessary
+
+mDNSlocal mDNSBool
+add_records_if_necessary(
+       dnssec_context_t * const        _Nonnull        dnssec_context,
+       const dnssec_result_t                                   dnssec_result,
+       const DNSServiceErrorType                               dns_error_from_core,
+       mDNS * const                                                    mdnsresponder_context,
+       DNSQuestion * const                     _Nonnull        question,
+       mDNSBool *                                      _Nonnull        out_stop_immediately) {
+
+       originals_with_rrsig_t *        originals_with_rrsig    = &dnssec_context->original.original_result_with_rrsig;
+       returned_answers_t * const      returned_answers                = &dnssec_context->returned_answers;
+       list_t * const                          returned_rrs                    = &returned_answers->answers;
+       mDNSBool                                        add_records                             = mDNSfalse;
+       QueryRecordResultHandler        user_handler                    = dnssec_context->original.original_parameters.user_handler;
+       void * const                            user_context                    = dnssec_context->original.original_parameters.user_context;
+       DNSServiceErrorType                     dns_error                               = get_dnsservice_error_type_from_originals(&dnssec_context->original, dns_error_from_core);
+       mDNSBool                                        stop_immediately                = mDNSfalse;
+       mStatus                                         error;
+
+       if (returned_answers->error == kDNSServiceErr_Invalid) {
+               // It is our first time to return the response back to callback
+               initialize_returned_answers_t(returned_answers, dnssec_result, dns_error);
+       } else {
+               returned_answers->error = dns_error_from_core;
+       }
+
+       switch (originals_with_rrsig->type) {
+               case original_response: {
+                       list_t * const dnssec_originals = &originals_with_rrsig->u.original.original_records;
+                       if (originals_with_rrsig->u.original.negative_rr != mDNSNULL) {
+                               ResourceRecord * const rr = originals_with_rrsig->u.original.negative_rr;
+                               mDNSBool contained_in_returned_answer = contains_rr_in_returned_rrs(rr, returned_rrs);
+                               if (contained_in_returned_answer) {
+                                       break;
+                               }
+
+                               error = add_record_to_be_returned_to_returned_answers_t(&dnssec_context->returned_answers, rr);
+                               require_quiet(error == mStatus_NoError, exit);
+
+                               stop_immediately = return_answer_to_user(user_handler, user_context, dnssec_result, rr, QC_add,
+                                       dns_error, mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
+                               add_records = mDNStrue;
+                               require_quiet(!stop_immediately, exit);
+                               returned_answers->type = original_response;
+                       } else {
+                               for (const list_node_t * dnssec_original_node = list_get_first(dnssec_originals);
+                                       !list_has_ended(dnssec_originals, dnssec_original_node);
+                                       dnssec_original_node = list_next(dnssec_original_node)) {
+
+                                       dnssec_original_t * const dnssec_original = (dnssec_original_t *)dnssec_original_node->data;
+                                       ResourceRecord * const rr = dnssec_original->dnssec_rr.rr;
+                                       mDNSBool contained_in_returned_answer = contains_rr_in_returned_rrs(rr, returned_rrs);
+                                       if (contained_in_returned_answer) {
+                                               continue;
+                                       }
+
+                                       error = add_record_to_be_returned_to_returned_answers_t(returned_answers, rr);
+                                       require_quiet(error == mStatus_NoError, exit);
+
+                                       stop_immediately = return_answer_to_user(user_handler, user_context, dnssec_result, rr, QC_add,
+                                               dns_error, mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
+                                       add_records = mDNStrue;
+                                       require_quiet(!stop_immediately, exit);
+                               }
+                       }
+                       returned_answers->type = original_response;
+               }
+                       break;
+               case cname_response: {
+                       list_t * const dnssec_cnames = &originals_with_rrsig->u.cname_with_rrsig.cname_records;
+                       for (const list_node_t * dnssec_cname_node = list_get_first(dnssec_cnames);
+                               !list_has_ended(dnssec_cnames, dnssec_cname_node);
+                               dnssec_cname_node = list_next(dnssec_cname_node)) {
+
+                               dnssec_cname_t * const dnssec_cname             = (dnssec_cname_t *)dnssec_cname_node->data;
+                               ResourceRecord * const rr                               = dnssec_cname->dnssec_rr.rr;
+                               mDNSBool contained_in_returned_answer   = contains_rr_in_returned_rrs(rr, returned_rrs);
+                               if (contained_in_returned_answer) {
+                                       continue;
+                               }
+
+                               error = add_record_to_be_returned_to_returned_answers_t(returned_answers, rr);
+                               require_quiet(error == mStatus_NoError, exit);
+
+                               stop_immediately = return_answer_to_user(user_handler, user_context, dnssec_result, rr, QC_add,
+                                       dns_error, mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
+                               add_records = mDNStrue;
+                               require_quiet(!stop_immediately, exit);
+                       }
+                       returned_answers->type = cname_response;
+               }
+                       break;
+               case nsec_response: {
+                       ResourceRecord * const rr = originals_with_rrsig->u.nsecs_with_rrsig.negative_rr;
+                       mDNSBool contained_in_returned_answer = contains_rr_in_returned_rrs(rr, returned_rrs);
+                       if (contained_in_returned_answer) {
+                               break;
+                       }
+
+                       error = add_record_to_be_returned_to_returned_answers_t(&dnssec_context->returned_answers, rr);
+                       require_quiet(error == mStatus_NoError, exit);
+
+                       stop_immediately = return_answer_to_user(user_handler, user_context, dnssec_result, rr, QC_add, dns_error,
+                               mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
+                       add_records = mDNStrue;
+                       require_quiet(!stop_immediately, exit);
+                       returned_answers->type = nsec_response;
+               }
+                       break;
+               case nsec3_response: {
+                       ResourceRecord * const rr = originals_with_rrsig->u.nsec3s_with_rrsig.negative_rr;
+                       mDNSBool contained_in_returned_answer = contains_rr_in_returned_rrs(rr, returned_rrs);
+                       if (contained_in_returned_answer) {
+                               break;
+                       }
+
+                       error = add_record_to_be_returned_to_returned_answers_t(&dnssec_context->returned_answers, rr);
+                       require_quiet(error == mStatus_NoError, exit);
+
+                       stop_immediately = return_answer_to_user(user_handler, user_context, dnssec_result, rr, QC_add, dns_error,
+                               mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
+                       add_records = mDNStrue;
+                       require_quiet(!stop_immediately, exit);
+                       returned_answers->type = nsec3_response;
+               }
+                       break;
+               default:
+                       goto exit;
+       }
+
+exit:
+       if (out_stop_immediately != mDNSNULL) {
+               *out_stop_immediately = stop_immediately;
+       }
+       return add_records;
+}
+
+
+#pragma mark - returned_answers_t
+
+
+
+#pragma mark add_record_to_be_returned_to_returned_answers_t
+mDNSlocal mStatus
+add_record_to_be_returned_to_returned_answers_t(returned_answers_t * const returned_asnwers, const ResourceRecord * const rr) {
+       const ResourceRecord ** inserted_rr;
+       mStatus error;
+
+       require_action_quiet(rr != mDNSNULL, exit, error = mStatus_BadReferenceErr);
+
+       // No need to remember if we returned negative answer to the user since wo do not need to send RMV for negative record
+       if (rr->RecordType == kDNSRecordTypePacketNegative) {
+               error = mStatus_NoError;
+               goto exit;
+       }
+
+       error = list_append_uinitialized(&returned_asnwers->answers, sizeof(ResourceRecord *), (void **)&inserted_rr);
+       require_quiet(error == mStatus_NoError, exit);
+
+       *inserted_rr = rr;
+
+exit:
+       return error;
+}
+
+#pragma mark get_dnsservice_error_type_from_originals
+mDNSlocal DNSServiceErrorType
+get_dnsservice_error_type_from_originals(const original_t * const _Nonnull original, DNSServiceErrorType type_from_mDNSCore) {
+       response_type_t type            = original->original_result_with_rrsig.type;
+       DNSServiceErrorType error       = kDNSServiceErr_Invalid;
+
+       if (type == original_response) {
+               if (original->original_result_with_rrsig.u.original.negative_rr != mDNSNULL) {
+                       error = type_from_mDNSCore;
+               } else {
+                       error = kDNSServiceErr_NoError;
+               }
+       } else if (type == cname_response) {
+               error = kDNSServiceErr_NoError;
+       } else if (type == nsec_response) {
+               dnssec_validation_result_t validation_result = original->original_result_with_rrsig.u.nsecs_with_rrsig.nsec_result;
+               switch (validation_result) {
+                       case dnssec_validation_nsec_name_error:
+                               error = kDNSServiceErr_NoSuchName;
+                               break;
+                       case dnssec_validation_nsec_no_data:
+                               error = kDNSServiceErr_NoSuchRecord;
+                               break;
+                       case dnssec_validation_nsec_wildcard_answer:
+                       case dnssec_validation_nsec_wildcard_no_data:
+                               log_error("wildcard not handled");
+                       default:
+                               break;
+               }
+       } else if (type == nsec3_response) {
+               dnssec_validation_result_t validation_result = original->original_result_with_rrsig.u.nsec3s_with_rrsig.nsec3_result;
+               switch (validation_result) {
+                       case dnssec_validation_nsec3_name_error:
+                               error = kDNSServiceErr_NoSuchName;
+                               break;
+                       case dnssec_validation_nsec3_no_data_response:
+                               error = kDNSServiceErr_NoSuchRecord;
+                               break;
+                       case dnssec_validation_nsec3_wildcard_answer_response:
+                       case dnssec_validation_nsec3_wildcard_no_data:
+                               log_error("wildcard not handled");
+                               break;
+                       default:
+                               break;
+               }
+       } else {
+               log_error("Original response has type other than 'original_response', 'cname_response', 'nsec_response', 'nsec3_response'");
+       }
+
+       return error;
+}
+
+#pragma mark deliver_remove_to_callback_with_all_returned_answer
+// deliver_remove_to_callback_with_all_returned_answers returns whether the caller should stop the DNSSEC-related
+// processing. If it returns true, it means that the callback has stopped the request and deleted the current question,
+// and all the current work should be stopped because the callback has stopped the request and deleted the current
+// question. If we continue to process more, we may access the memory that has already been freed.
+mDNSexport mDNSBool
+deliver_remove_to_callback_with_all_returned_answers(
+       const dnssec_context_t * const          _Nonnull        context,
+       const returned_answers_t * const        _Nonnull        returned_answers,
+       mDNS * const                                            _Nonnull        m,
+       DNSQuestion * const                                     _Nonnull        primary_question,
+       const DNSQuestion * const                       _Nonnull        current_question) {
+
+       mDNSBool stop_immediately = mDNSfalse;
+
+       require_quiet(returned_answers->error != kDNSServiceErr_Invalid, exit);
+
+       const list_t * const            returned_rrs    = &returned_answers->answers;
+       QueryRecordResultHandler        user_handler    = context->original.original_parameters.user_handler;
+       void * const                            user_context    = context->original.original_parameters.user_context;
+
+       for (const list_node_t * rr_node = list_get_first(returned_rrs); !list_has_ended(returned_rrs, rr_node); rr_node = list_next(rr_node)) {
+               ResourceRecord * const * const rr_ptr = (ResourceRecord * const * const)rr_node->data;
+               ResourceRecord * const rr = *rr_ptr;
+               // 1. No need to deliver RMV for negative answer, such as No Such Record.
+               // 2. No need to deliver RMV for CNAME answer.
+               if (rr->RecordType == kDNSRecordTypePacketNegative || rr->rrtype == kDNSType_CNAME) {
+                       continue;
+               }
+               stop_immediately = return_answer_to_user(user_handler, user_context, returned_answers->dnssec_result, rr,
+                       QC_rmv, returned_answers->error, m, primary_question, current_question);
+               require_quiet(!stop_immediately, exit);
+       }
+
+exit:
+       return stop_immediately;
+}
+
+#pragma mark deliver_remove_for_returned_records
+
+mDNSlocal mDNSBool
+contains_rr_in_original_records(const ResourceRecord * const rr, const list_t * const original_records/* list_t<dnssec_original_t> */) {
+       mDNSBool contains = mDNSfalse;
+
+       for (const list_node_t * original_record_node = list_get_first(original_records);
+                       !list_has_ended(original_records, original_record_node);
+                       original_record_node = list_next(original_record_node)) {
+
+               const dnssec_original_t * const dnssec_original = (dnssec_original_t *)original_record_node->data;
+               if (dnssec_original->dnssec_rr.rr == rr) {
+                       contains = mDNStrue;
+                       goto exit;
+               }
+       }
+
+exit:
+       return contains;
+}
+
+mDNSlocal mDNSBool
+deliver_remove_for_returned_records(
+       dnssec_context_t * const        _Nonnull        dnssec_context,
+       const dnssec_result_t                                   dnssec_result,
+       mDNS * const                            _Nonnull        mdnsresponder_context,
+       DNSQuestion * const                     _Nonnull        question,
+       const response_type_t                                   type,
+       mDNSBool *                                      _Nonnull        out_stop_immediately) {
+
+       returned_answers_t * const                      returned_answers        = &dnssec_context->returned_answers;
+       list_t * const                                          returned_rrs            = &returned_answers->answers;
+       QueryRecordResultHandler                        user_handler            = dnssec_context->original.original_parameters.user_handler;
+       void * const                                            user_context            = dnssec_context->original.original_parameters.user_context;
+       originals_with_rrsig_t * const          originals_with_rrsig = &dnssec_context->original.original_result_with_rrsig;
+       mDNSu32                                                         request_id                      = dnssec_context->original.original_parameters.request_id;
+       mDNSu16                                                         question_id             = mDNSVal16(question->TargetQID);
+       mDNSBool                                                        remove_some                     = mDNSfalse;
+       mDNSBool                                                        stop_immediately        = mDNSfalse;
+
+       if (type == original_response) {
+               for (list_node_t * rr_node = list_get_first(returned_rrs), *next_node; !list_has_ended(returned_rrs, rr_node); rr_node = next_node) {
+                       ResourceRecord * const * const rr_ptr = (ResourceRecord * const * const)rr_node->data;
+                       ResourceRecord *rr = *rr_ptr;
+                       next_node = list_next(rr_node);
+                       mDNSBool contains = mDNStrue;
+                       if (returned_answers->error == kDNSServiceErr_NoError) {
+                               contains = contains_rr_in_original_records(rr, &originals_with_rrsig->u.original.original_records);
+                       } else if (returned_answers->error == kDNSServiceErr_NoSuchName || returned_answers->error == kDNSServiceErr_NoSuchRecord) {
+                               contains = (rr == originals_with_rrsig->u.original.negative_rr);
+                       } else {
+                               log_error("[R%u->Q%u] when the DNSSEC response is original response, only NoError, NoSuchName and NoSuchRecord are allowed"
+                                       " - response type: " PUB_S ", kDNSServiceErr type: %u",
+                                       request_id, question_id, response_type_value_to_string(type), returned_answers->error);
+                               goto exit;
+                       }
+                       if (!contains) {
+                               stop_immediately = return_answer_to_user(user_handler, user_context, dnssec_result, rr, QC_rmv,
+                                       returned_answers->error, mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
+                               remove_some = mDNStrue;
+                               require_quiet(!stop_immediately, exit);
+                               list_node_delete(rr_node);
+                       }
+               }
+       } else if (type == nsec_response) {
+               require_action_quiet(
+                       returned_answers->error == kDNSServiceErr_NoSuchName || returned_answers->error == kDNSServiceErr_NoSuchRecord,
+                       exit,
+                       log_error("[R%u->Q%u] when the NSEC response is original response, only NoSuchName and NoSuchRecord are allowed"
+                               " - response type: " PUB_S ", kDNSServiceErr type: %u",
+                               request_id, question_id, response_type_value_to_string(type), returned_answers->error)
+               );
+
+               require_action_quiet(list_count_node(returned_rrs) == 1, exit,
+                       log_error("[R%u->Q%u] Denail of existence answer returns more than one negative answer with NSEC proof - number_of_records: %u",
+                               request_id, question_id, list_count_node(returned_rrs)));
+
+               ResourceRecord * const * const rr_ptr = (ResourceRecord * const * const)(list_get_first(returned_rrs)->data);
+               ResourceRecord *rr = *rr_ptr;
+               if (rr != originals_with_rrsig->u.nsecs_with_rrsig.negative_rr) {
+                       stop_immediately = return_answer_to_user(user_handler, user_context, dnssec_result, rr, QC_rmv,
+                               returned_answers->error, mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
+                       remove_some = mDNStrue;
+                       require_quiet(!stop_immediately, exit);
+                       // Deletes the removed record.
+                       list_node_delete_all(returned_rrs);
+               }
+       } else if (type == nsec3_response) {
+               require_action_quiet(
+                       returned_answers->error == kDNSServiceErr_NoSuchName || returned_answers->error == kDNSServiceErr_NoSuchRecord,
+                       exit,
+                       log_error("[R%u->Q%u] when the NSEC3 response is original response, only NoSuchName and NoSuchRecord are allowed"
+                               " - response type: " PUB_S ", kDNSServiceErr type: %u",
+                               request_id, question_id, response_type_value_to_string(type), returned_answers->error)
+               );
+
+               require_action_quiet(list_count_node(returned_rrs) == 1, exit,
+                       log_error("[R%u->Q%u] Denail of existence answer returns more than one negative answer with NSEC3 proof - number_of_records: %u",
+                               request_id, question_id, list_count_node(returned_rrs)));
+
+               ResourceRecord * const * const rr_ptr = (ResourceRecord * const * const)(list_get_first(returned_rrs)->data);
+               ResourceRecord *rr = *rr_ptr;
+               if (rr != originals_with_rrsig->u.nsec3s_with_rrsig.negative_rr) {
+                       stop_immediately = return_answer_to_user(user_handler, user_context, dnssec_result, rr, QC_rmv,
+                               returned_answers->error, mdnsresponder_context, GET_PRIMARY_QUESTION(dnssec_context), question);
+                       remove_some = mDNStrue;
+                       require_quiet(!stop_immediately, exit);
+                       // Deletes the removed record.
+                       list_node_delete_all(returned_rrs);
+               }
+       } else {
+               // cname_response or other invalid value
+               log_error("[R%u->Q%u] Invalid returned answers response type - response type: " PUB_S, request_id, question_id,
+                       response_type_value_to_string(type));
+               goto exit;
+       }
+
+exit:
+       if (out_stop_immediately != mDNSNULL) {
+               *out_stop_immediately = stop_immediately;
+       }
+       return remove_some;
+}
+
+#pragma mark contains_rr_in_returned_rrs
+mDNSlocal mDNSBool
+contains_rr_in_returned_rrs(const ResourceRecord * const rr, const list_t * const returned_rrs/* list_t<ResourceRecord *> */) {
+       mDNSBool contains = mDNSfalse;
+
+       for (const list_node_t *rr_ptr_node = list_get_first(returned_rrs);
+                       !list_has_ended(returned_rrs, rr_ptr_node);
+                       rr_ptr_node = list_next(rr_ptr_node)) {
+
+               const ResourceRecord * const * const    rr_to_compare_ptr       = (const ResourceRecord * const * const)rr_ptr_node->data;
+               const ResourceRecord * const                    rr_to_compare           = *rr_to_compare_ptr;
+
+               if (rr == rr_to_compare) {
+                       contains = mDNStrue;
+                       goto exit;
+               }
+       }
+
+exit:
+       return contains;
+}
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2_client.h b/mDNSMacOSX/dnssec_v2/dnssec_v2_client.h
new file mode 100644 (file)
index 0000000..29aaa10
--- /dev/null
@@ -0,0 +1,71 @@
+//
+//     dnssec_v2_client.h
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#ifndef DNSSEC_V2_CLIENT_H
+#define DNSSEC_V2_CLIENT_H
+
+#include <stdio.h>
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include "dnssec_v2_structs.h"
+#include "dnssec_v2_retrieval.h"
+#include "dnssec_v2_validation.h"
+
+//======================================================================================================================
+//    function prototypes
+//======================================================================================================================
+
+mDNSexport mDNSBool
+handle_retrieval_result(
+       DNSQuestion * const                     _Nonnull        question,
+       dnssec_context_t *                      _Nonnull        original_dnssec_context,
+       dnssec_retrieval_result_t                               retrieval_result,
+       const DNSServiceErrorType                               dnssec_error,
+       mDNS * const                            _Nonnull        mdnsresponder_context);
+
+mDNSexport mDNSBool
+handle_validation_result(
+       DNSQuestion * const                     _Nonnull        question,
+       dnssec_context_t * const        _Nonnull        dnssec_context,
+       dnssec_validation_result_t                              validation_result,
+       const DNSServiceErrorType                               dnssec_error,
+       mDNS * const                            _Nonnull        mdnsresponder_context);
+
+/*!
+ * @brief
+ *             Delivers RMV events to the callback function for all the  returned answers.
+ *
+ * @param context
+ *             A pointer to the DNSSEC context of the current request.
+ *
+ * @param returned_answers
+ *             A pointer to the structure that stores all the answers returned to the callback.
+ *
+ * @param m
+ *             A pointer to the mDNS structure.
+ *
+ * @param primary_question
+ *             A pointer to the question created by the primary request, which is started by the client.
+ *
+ * @param current_question
+ *             A pointer to the question that we are currently working on, it is used to determine if the question has been deleted by the callback.
+ *
+ * @return
+ *             A boolean value to indicate if the caller should stop all the work immediately. If it returns true, it means that the callback called by this function has canceled
+ *             the current request and its corresponding question, and the caller should assume that all the allocated memory it owns has already been freed, and it
+ *             should stop immediately to avoid invalid memory access.
+ */
+mDNSexport mDNSBool
+deliver_remove_to_callback_with_all_returned_answers(
+       const dnssec_context_t * const          _Nonnull        context,
+       const returned_answers_t * const        _Nonnull        returned_answers,
+       mDNS * const                                            _Nonnull        m,
+       DNSQuestion * const                                     _Nonnull        primary_question,
+       const DNSQuestion * const                       _Nonnull        current_question);
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#endif // DNSSEC_V2_CLIENT_H
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2_crypto.c b/mDNSMacOSX/dnssec_v2/dnssec_v2_crypto.c
new file mode 100644 (file)
index 0000000..ead61cd
--- /dev/null
@@ -0,0 +1,675 @@
+//
+//     dnssec_v2_crypto.c
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include <stdio.h>
+#include <CommonCrypto/CommonDigestSPI.h>
+#include <Security/Security.h>
+#include <Security/SecKeyPriv.h>
+#include "dnssec_v2_crypto.h"
+#include "dnssec_v2_helper.h"
+#include "dnssec_v2_log.h"
+#include "base_n.h"
+
+mDNSlocal void
+parse_rsa_pubkey(
+       mDNSu8 * const          _Nonnull        public_key,
+       const mDNSu16                                   key_length,
+       uint8_t ** const        _Nonnull        out_modulus,
+       signed long * const _Nonnull    out_modulus_length,
+       uint8_t ** const        _Nonnull        out_exponent,
+       signed long * const _Nonnull    out_exponent_length);
+
+mDNSlocal void
+print_validation_progress(const mDNSu32 request_id, const dnssec_dnskey_t * const dnskey, const dnssec_rrsig_t * const rrsig);
+
+// The array index means the algorithm number, the array element value means the prefered order to use when there is
+// multiple algorithms avaliable, the order is determined by "The most secure, the better", 0 is the lowest priority.
+
+//======================================================================================================================
+//     get_priority_of_ds_digest
+//======================================================================================================================
+
+mDNSexport mDNSs16
+get_priority_of_ds_digest(mDNSu8 digest) {
+       // ref 1: https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml
+       // ref 2: https://tools.ietf.org/html/draft-ietf-dnsop-algorithm-update-10
+       mDNSs16 priority;
+
+       switch (digest) {
+               case DS_DIGEST_SHA_1:
+                       // Algorithm Number 1           SHA-1                           MUST
+                       priority = 0;
+                       break;
+               case DS_DIGEST_SHA_256:
+                       // Algorithm Number 2           SHA-256                         MUST
+                       priority = 1;
+                       break;
+               case DS_DIGEST_SHA_384:
+                       // Algorithm Number 4           SHA-384                         RECOMMENDED
+                       priority = 2;
+                       break;
+               default:
+                       // Algorithm Number 0           Reserved
+                       // Algorithm Number 3           GOST R 34.11-94         NOT SUPPORTED
+                       // Algorithm Number 5-255       Unassigned
+                       priority = -1;
+                       break;
+       }
+
+       return priority;
+}
+
+//======================================================================================================================
+//     get_priority_of_dnskey_algorithm
+//======================================================================================================================
+
+mDNSexport mDNSs16
+get_priority_of_dnskey_algorithm(mDNSu8 algorithm) {
+       // ref 1: https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xml
+       // ref 2: https://tools.ietf.org/html/draft-ietf-dnsop-algorithm-update-10
+
+       mDNSs16 priority;
+
+       switch (algorithm) {
+               case DNSKEY_ALGORITHM_RSASHA1:
+                       // Algorithm Number 5           RSA/SHA-1                                       MUST
+                       priority = 0;
+                       break;
+               case DNSKEY_ALGORITHM_RSASHA1_NSEC3_SHA1:
+                       // Algorithm Number 7           RSASHA1-NSEC3-SHA1                              MUST
+                       priority = 1;
+                       break;
+               case DNSKEY_ALGORITHM_RSASHA256:
+                       // Algorithm Number 8           RSA/SHA-256                                             MUST
+                       priority = 2;
+                       break;
+               case DNSKEY_ALGORITHM_RSASHA512:
+                       // Algorithm Number 10          RSA/SHA-512                                             MUST
+                       priority = 3;
+                       break;
+               case DNSKEY_ALGORITHM_ECDSAP256SHA256:
+                       // Algorithm Number 13          ECDSA Curve P-256 with SHA-256  RECOMMENDED
+                       priority = 4;
+                       break;
+               case DNSKEY_ALGORITHM_ECDSAP384SHA384:
+                       // Algorithm Number 14          ECDSA Curve P-384 with SHA-384  RECOMMENDED
+                       priority = 5;
+                       break;
+               default:
+                       // Algorithm Number 0           Delete                                                  N/A
+                       // Algorithm Number 1           RSA/MD5                                                 MUST NOT
+                       // Algorithm Number 2           Diffie-Hellman                                  NOT SUPPORTED
+                       // Algorithm Number 3           DSA/SHA1                                                MUST NOT
+                       // Algorithm Number 4           Reserved
+                       // Algorithm Number 6           DSA-NSEC3-SHA1                                  MUST NOT
+                       // Algorithm Number 9           Reserved
+                       // Algorithm Number 11          Reserved
+                       // Algorithm Number 12          GOST R 34.10-2001                               MAY
+                       // Algorithm Number 15          Ed25519                                                 RECOMMENDED, but NOT SUPPORTED
+                       // Algorithm Number 16          Ed448                                                   RECOMMENDED, but NOT SUPPORTED
+                       // Algorithm Number 17-122      Unassigned
+                       // Algorithm Number 123-251 Reserved
+                       // Algorithm Number 252         Reserved for Indirect Keys
+                       // Algorithm Number 253         private algorithm
+                       // Algorithm Number 254         private algorithm OID
+                       // Algorithm Number 255         Reserved
+                       priority = -1;
+                       break;
+       }
+
+       return priority;
+}
+
+//======================================================================================================================
+//     validate_signed_data_with_rrsig_and_dnskey
+//             the main function doing signature validation
+//======================================================================================================================
+
+mDNSexport mDNSBool
+validate_signed_data_with_rrsig_and_dnskey(
+       const mDNSu32                                                           request_id,
+       const mDNSu8 * const                    _Nonnull        signed_data,
+       const mDNSu32                                                           signed_data_length,
+       const dnssec_rrsig_t * const    _Nonnull        rrsig,
+       const dnssec_dnskey_t * const   _Nonnull        dnskey) {
+
+       mDNSBool                                valid                                           = mDNSfalse;
+       const mDNSu8 *                  data_or_digest_be_signed        = mDNSNULL;
+       mDNSu32                                 data_or_digest_be_signed_length;
+       SecKeyAlgorithm                 verify_algorithm;
+       const void *                    public_key_type;
+       CFErrorRef                              cf_error                                        = mDNSNULL;
+       CFDataRef                               data_to_verify_CFData           = mDNSNULL;
+       CFDataRef                               sig_to_match_CFData                     = mDNSNULL;
+       SecKeyRef                               key                                                     = mDNSNULL;
+       digest_type_t                   digest_type                                     = DIGEST_UNSUPPORTED;
+
+       // choose different signature validation algorithm and public key
+       switch (dnskey->algorithm) {
+               case DNSKEY_ALGORITHM_RSASHA1:
+                       verify_algorithm        = kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA1;
+                       public_key_type         = kSecAttrKeyTypeRSA;
+                       break;
+               case DNSKEY_ALGORITHM_RSASHA1_NSEC3_SHA1:
+                       verify_algorithm        = kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA1;
+                       public_key_type         = kSecAttrKeyTypeRSA;
+                       break;
+               case DNSKEY_ALGORITHM_RSASHA256:
+                       verify_algorithm        = kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA256;
+                       public_key_type         = kSecAttrKeyTypeRSA;
+                       break;
+               case DNSKEY_ALGORITHM_RSASHA512:
+                       verify_algorithm        = kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA512;
+                       public_key_type         = kSecAttrKeyTypeRSA;
+                       break;
+               case DNSKEY_ALGORITHM_ECDSAP256SHA256:
+                       verify_algorithm        = kSecKeyAlgorithmECDSASignatureRFC4754;
+                       public_key_type         = kSecAttrKeyTypeECSECPrimeRandom;
+                       digest_type                     = DIGEST_SHA_256;
+                       data_or_digest_be_signed_length = 32;
+                       break;
+               case DNSKEY_ALGORITHM_ECDSAP384SHA384:
+                       verify_algorithm        = kSecKeyAlgorithmECDSASignatureRFC4754;
+                       public_key_type         = kSecAttrKeyTypeECSECPrimeRandom;
+                       digest_type                     = DIGEST_SHA_384;
+                       data_or_digest_be_signed_length = 48;
+                       break;
+               default:
+                       log_error("Unsupported DNSKEY algorithm; algorithm=%d", dnskey->algorithm);
+                       goto exit;
+       }
+
+       // public key creation for RSA and ECDSA is different
+       if (public_key_type == kSecAttrKeyTypeRSA) {
+               // RSA
+               // The format of public key is not the standard PEM DER ASN.1 PKCS#1 RSA Public key format, so we need to parse
+               // the modulus and exponent explicitly.
+               SecRSAPublicKeyParams params;
+               parse_rsa_pubkey(dnskey->public_key, dnskey->public_key_length, &params.modulus, &params.modulusLength, &params.exponent, &params.exponentLength);
+               key = SecKeyCreateRSAPublicKey(kCFAllocatorDefault, (const uint8_t *)&params, sizeof(params), kSecKeyEncodingRSAPublicParams);
+               require_quiet(key != mDNSNULL, exit);
+
+               data_or_digest_be_signed                        = signed_data;
+               data_or_digest_be_signed_length         = signed_data_length;
+               // RSA uses original data to verify record.
+       } else if (public_key_type == kSecAttrKeyTypeECSECPrimeRandom) {
+               // ECDSA
+               const void *    public_key_options_key[]        = {kSecAttrKeyType,                                             kSecAttrKeyClass};
+               const void *    public_key_options_values[] = {kSecAttrKeyTypeECSECPrimeRandom,         kSecAttrKeyClassPublic};
+               CFDataRef               public_key_CFData                       = mDNSNULL;
+               CFDictionaryRef public_key_options                      = mDNSNULL;
+               CFNumberRef             key_size_CFNumber                       = mDNSNULL;
+               mDNSu8 *                ecdsa_key_bytes_encoding        = mDNSNULL;
+               mDNSu32                 ecdsa_key_length                        = dnskey->public_key_length + 1;
+               mDNSu8                  data_digest[MAX_HASH_OUTPUT_SIZE];
+
+               // create security framework readable public key format
+               ecdsa_key_bytes_encoding = malloc(ecdsa_key_length);
+               require_quiet(ecdsa_key_bytes_encoding != mDNSNULL, ecdsa_exit);
+
+               ecdsa_key_bytes_encoding[0] = 4;
+               memcpy(ecdsa_key_bytes_encoding + 1, dnskey->public_key, dnskey->public_key_length);
+
+               public_key_CFData = CFDataCreate(kCFAllocatorDefault, ecdsa_key_bytes_encoding, ecdsa_key_length);
+               require_quiet(public_key_CFData != NULL, ecdsa_exit);
+
+               public_key_options = CFDictionaryCreate(kCFAllocatorDefault, public_key_options_key,
+                       public_key_options_values, sizeof(public_key_options_key) / sizeof(void *),
+                       &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+               require_quiet(public_key_options != NULL, ecdsa_exit);
+
+               // create the public key
+               key = SecKeyCreateWithData(public_key_CFData, public_key_options, &cf_error);
+               require_action(key != mDNSNULL, ecdsa_exit, log_error("SecKeyCreateWithData failed: %@", CFErrorCopyDescription(cf_error)));
+
+               // ECDSA uses digest to verify record.
+               mDNSBool calculated = calculate_digest_for_data(signed_data, signed_data_length, digest_type, data_digest, sizeof(data_digest));
+               require_action(calculated, ecdsa_exit, log_error("calculate_digest_for_data failed to return the digest;"));
+               data_or_digest_be_signed = data_digest;
+               // data_be_signed_length is set in the previous switch statement.
+
+       ecdsa_exit:
+               if (public_key_CFData                   != NULL) CFRelease(public_key_CFData);
+               if (public_key_options                  != NULL) CFRelease(public_key_options);
+               if (key_size_CFNumber                   != NULL) CFRelease(key_size_CFNumber);
+               if (cf_error                                    != NULL) {
+                       CFRelease(cf_error);
+                       cf_error = mDNSNULL;
+               }
+               if (ecdsa_key_bytes_encoding    != mDNSNULL) free(ecdsa_key_bytes_encoding);
+       } else {
+               goto exit;
+       }
+
+       require_quiet(key != mDNSNULL, exit);
+
+       // create data and signature to verify
+       data_to_verify_CFData   = CFDataCreate(kCFAllocatorDefault, data_or_digest_be_signed, data_or_digest_be_signed_length);
+       sig_to_match_CFData             = CFDataCreate(kCFAllocatorDefault, rrsig->signature, rrsig->signature_length);
+
+       Boolean matches = SecKeyVerifySignature(key, verify_algorithm, data_to_verify_CFData, sig_to_match_CFData, &cf_error);
+       if (matches) {
+               print_validation_progress(request_id, dnskey, rrsig);
+       } else {
+               log_default("SecKeyVerifySignature error: %@", CFErrorCopyDescription(cf_error));
+       }
+
+       valid = matches ? mDNStrue : mDNSfalse;
+
+exit:
+       if (key                                         != mDNSNULL)    CFRelease(key);
+       if (data_to_verify_CFData       != mDNSNULL)    CFRelease(data_to_verify_CFData);
+       if (sig_to_match_CFData         != mDNSNULL)    CFRelease(sig_to_match_CFData);
+       if (cf_error                            != mDNSNULL)    CFRelease(cf_error);
+
+       return valid;
+}
+
+//======================================================================================================================
+//     Hash
+//======================================================================================================================
+
+//======================================================================================================================
+//     calculate_digest_for_data
+//             get the corresponding digest
+//======================================================================================================================
+
+mDNSexport mDNSBool
+calculate_digest_for_data(
+       const mDNSu8 * const    _Nonnull        data,
+       const mDNSu32                                           data_length,
+       const digest_type_t                                     digest_type,
+       mDNSu8 * const                  _Nonnull        digest_buffer,
+       mDNSu32                                                         buffer_size) {
+
+       CCDigestAlgorithm cc_digest_algorithm;
+       CCDigestCtx cc_digest_context;
+       mDNSBool calculated = mDNSfalse;
+
+       switch (digest_type) {
+               case DIGEST_SHA_1: // SHA-1
+                       require_quiet(buffer_size >= SHA1_OUTPUT_SIZE, exit); // SHA-1 produces 20 bytes
+#pragma clang diagnostic push // ignore the deprecation warning for SHA-1, since NSEC3 now only uses SHA-1 to get the digest.
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+                       cc_digest_algorithm = kCCDigestSHA1;
+#pragma clang diagnostic pop
+                       break;
+               case DIGEST_SHA_256: // SHA-256
+                       require_quiet(buffer_size >= SHA256_OUTPUT_SIZE, exit); // SHA-256 produces 256-bit(32-byte) digest
+                       cc_digest_algorithm = kCCDigestSHA256;
+                       break;
+               case DIGEST_SHA_384: // SHA-384
+                       require_quiet(buffer_size >= SHA384_OUTPUT_SIZE, exit); // SHA-384 produces 384-bit(48-byte) digest
+                       cc_digest_algorithm = kCCDigestSHA384;
+                       break;
+               case DIGEST_SHA_512: // SHA-512
+                       require_quiet(buffer_size >= SHA512_OUTPUT_SIZE, exit); // SHA-512 produces 512-bit(64-byte) digest
+                       cc_digest_algorithm = kCCDigestSHA512;
+                       break;
+               default:
+                       goto exit;
+       }
+
+       CCDigestInit(cc_digest_algorithm, &cc_digest_context);
+       CCDigestUpdate(&cc_digest_context, data, data_length);
+       CCDigestFinal(&cc_digest_context, digest_buffer);
+       calculated = mDNStrue;
+
+exit:
+       return calculated;
+}
+
+//======================================================================================================================
+//     calculate_b32_hash_for_nsec3
+//             get Base32 encoding from the digest of riginal data
+//======================================================================================================================
+
+mDNSexport mDNSu8 * _Nullable
+calculate_b32_hash_for_nsec3(
+       const mDNSu8 * const _Nonnull           name,
+       const mDNSu16                                           name_length,
+       const mDNSu8                                            hash_type,
+       const mDNSu8 * const    _Nullable       salt,
+       const mDNSu32                                           salt_length,
+       const mDNSu16                                           iterations) {
+
+       mDNSu8  name_hash[MAX_HASH_OUTPUT_SIZE];
+       mDNSu32 name_hash_length;
+       mDNSu8 *name_hash_b32 = mDNSNULL;
+
+       name_hash_length = get_hash_length_for_nsec3_hash_type(hash_type);
+
+       mDNSBool calculated = calculate_hash_for_nsec3(name_hash, sizeof(name_hash), hash_type, name, name_length, salt, salt_length, iterations);
+       require_quiet(calculated, exit);
+
+       name_hash_b32 = (mDNSu8 *)base_n_encode(DNSSEC_BASE_32_HEX, name_hash, name_hash_length);
+       require_quiet(name_hash_b32 != mDNSNULL, exit);
+
+exit:
+       return name_hash_b32;
+}
+
+//======================================================================================================================
+//     calculate_hash_for_nsec3
+//             get the hash value for nsec3 which includes salt and multiple iteration
+//======================================================================================================================
+
+mDNSexport mDNSBool
+calculate_hash_for_nsec3(
+       mDNSu8 * const                  _Nonnull        hash_buffer,
+       const mDNSu32                                           buffer_size,
+       const mDNSu8                                            hash_type,
+       const mDNSu8 * const    _Nonnull        name,
+       const mDNSu16                                           name_length,
+       const mDNSu8 * const    _Nullable       salt,
+       const mDNSu32                                           salt_length,
+       const mDNSu16                                           iterations) {
+
+       // data_to_be_hashed will be used to generate the hash, it should be big enough to hold 1) first hash iteration: name | salt, 2) the remaining iteration: hash_result_from_the_last_iteration | salt.
+       mDNSu8                  data_to_be_hashed[MAX(MAX_HASH_OUTPUT_SIZE, MAX_DOMAIN_NAME) + 256];
+       mDNSu32                 data_length;
+       mDNSu32                 hash_length;
+       digest_type_t   digest_type;
+       mDNSBool                calculated = mDNSfalse;
+
+       memcpy(data_to_be_hashed, name, name_length);
+       memcpy(data_to_be_hashed + name_length, salt, salt_length);
+       data_length = name_length + salt_length;
+
+       // choose correct hash algorithm to get digest
+       switch (hash_type) {
+               case NSEC3_HASH_ALGORITHM_SHA_1:
+                       digest_type             = DIGEST_SHA_1;
+                       hash_length             = 20;
+                       break;
+               default:
+                       goto exit;
+       }
+       calculated = calculate_digest_for_data(data_to_be_hashed, data_length, digest_type, hash_buffer, buffer_size);
+       require_quiet(calculated, exit);
+
+       // do iteration
+       for (mDNSs32 i = 0; i < iterations; i++) {
+               memcpy(data_to_be_hashed, hash_buffer, hash_length);
+               memcpy(data_to_be_hashed + hash_length, salt, salt_length);
+               data_length = hash_length + salt_length;
+
+               calculated = calculate_digest_for_data(data_to_be_hashed, data_length, hash_type, hash_buffer, buffer_size);
+               require_quiet(calculated, exit);
+       }
+
+       calculated = mDNStrue;
+exit:
+       return calculated;
+}
+
+//======================================================================================================================
+//     get_hash_length_for_nsec3_hash_type
+//======================================================================================================================
+
+mDNSexport mDNSu32
+get_hash_length_for_nsec3_hash_type(const nsec3_hash_algorithm_type_t nsec3_hash_type) {
+       digest_type_t digest_type;
+
+       switch (nsec3_hash_type) {
+               case NSEC3_HASH_ALGORITHM_SHA_1:
+                       digest_type = DIGEST_SHA_1;
+                       break;
+               default:
+                       digest_type = DIGEST_UNSUPPORTED;
+                       break;
+       }
+
+       return get_digest_length_for_digest_type(digest_type);
+}
+
+//======================================================================================================================
+//     get_digest_length_for_digest_type
+//======================================================================================================================
+
+mDNSexport mDNSu32
+get_digest_length_for_ds_digest_type(const ds_digest_type_t ds_digest_type) {
+       digest_type_t digest_type;
+
+       switch (ds_digest_type) {
+               case DS_DIGEST_SHA_1:
+                       digest_type = DIGEST_SHA_1;
+                       break;
+               case DS_DIGEST_SHA_256:
+                       digest_type = DIGEST_SHA_256;
+                       break;
+               case DS_DIGEST_SHA_384:
+                       digest_type = DIGEST_SHA_384;
+                       break;
+               default:
+                       digest_type = DIGEST_UNSUPPORTED;
+                       break;
+       }
+
+       return get_digest_length_for_digest_type(digest_type);
+}
+
+//======================================================================================================================
+//     get_digest_length_for_digest_type
+//======================================================================================================================
+
+mDNSexport mDNSu32
+get_digest_length_for_digest_type(const digest_type_t digest_type) {
+       mDNSu32 digest_length;
+
+       switch (digest_type) {
+               case DIGEST_SHA_1:
+                       digest_length = SHA1_OUTPUT_SIZE;
+                       break;
+               case DIGEST_SHA_256:
+                       digest_length = SHA256_OUTPUT_SIZE;
+                       break;
+               case DIGEST_SHA_384:
+                       digest_length = SHA384_OUTPUT_SIZE;
+                       break;
+               case DIGEST_SHA_512:
+                       digest_length = SHA512_OUTPUT_SIZE;
+                       break;
+               default:
+                       digest_length = 0;
+                       break;
+       }
+
+       return digest_length;
+}
+
+//======================================================================================================================
+//     parse_rsa_pubkey
+//             parse RSA key according to https://tools.ietf.org/html/rfc3110
+//======================================================================================================================
+
+mDNSlocal void
+parse_rsa_pubkey(
+       mDNSu8 * const          _Nonnull        public_key,
+       const mDNSu16                                   key_length,
+       uint8_t ** const        _Nonnull        out_modulus,
+       signed long * const _Nonnull    out_modulus_length,
+       uint8_t ** const        _Nonnull        out_exponent,
+       signed long * const _Nonnull    out_exponent_length) {
+
+       mDNSu8                  exponent_length_length;
+       if (public_key[0] != 0) {
+               *out_exponent_length = public_key[0];
+               exponent_length_length  = 1;
+       } else {
+               *out_exponent_length = (((uint32_t)public_key[1] << 8) | (uint32_t)public_key[2]);
+               exponent_length_length  = 3;
+       }
+
+       *out_exponent = public_key + exponent_length_length;
+
+       *out_modulus_length = key_length - (*out_exponent_length + exponent_length_length);
+
+       *out_modulus = public_key + exponent_length_length + *out_exponent_length;
+}
+
+//======================================================================================================================
+//     Canonical order and form
+//======================================================================================================================
+
+//======================================================================================================================
+//     canonical_form_name_length
+//======================================================================================================================
+
+mDNSexport mDNSu8
+canonical_form_name_length(const mDNSu8 * const _Nonnull name) {
+       // assume that domainname* is already in canonical form
+       return DOMAIN_NAME_LENGTH(name);
+}
+
+//======================================================================================================================
+//     compare_canonical_dns_name
+//             compare the domain name from the right most label, and compare label by label
+//======================================================================================================================
+
+mDNSexport mDNSs8
+compare_canonical_dns_name(const mDNSu8 * const _Nonnull left, const mDNSu8 * const _Nonnull right) {
+       const mDNSu16   left_len                        = DOMAIN_NAME_LENGTH(left);
+       const mDNSu8 *  left_limit                      = left + left_len - 1;
+       const mDNSu16   right_len                       = DOMAIN_NAME_LENGTH(right);
+       const mDNSu8 *  right_limit                     = right + right_len - 1;
+       mDNSBool                result;
+
+       const mDNSu8 *  left_label_length_ptrs[256];
+       mDNSu32                 left_ptrs_size = 0;
+       const mDNSu8 *  right_label_length_ptrs[256];
+       mDNSu32                 right_ptrs_size = 0;
+
+       // load the start of each label into an array
+       for (const mDNSu8 * left_ptr = left; left_ptr < left_limit; left_ptr += 1 + *left_ptr) {
+               left_label_length_ptrs[left_ptrs_size++] = left_ptr;
+               require_action(left_ptrs_size < sizeof(left_label_length_ptrs), exit,
+                       result = 0; log_error("domain name has more than 255 labels, returning 0"));
+       }
+
+       for (const mDNSu8 * right_ptr = right; right_ptr < right_limit; right_ptr += 1 + *right_ptr) {
+               right_label_length_ptrs[right_ptrs_size++] = right_ptr;
+               require_action(right_ptrs_size < sizeof(right_label_length_ptrs), exit,
+                       result = 0; log_error("domain name has more than 255 labels, returning 0"));
+       }
+
+       // start comparing
+       while (left_ptrs_size > 0 && right_ptrs_size > 0) {
+               const mDNSu8 *  left_ptr                        = left_label_length_ptrs[left_ptrs_size - 1];
+               const mDNSu8 *  right_ptr                       = right_label_length_ptrs[right_ptrs_size - 1];
+               const mDNSu8    left_label_length       = *left_ptr;
+               const mDNSu8    right_label_length      = *right_ptr;
+
+               mDNSs8 compare_label = compare_canonical_dns_label(left_ptr + 1, left_label_length, right_ptr + 1, right_label_length);
+               require_action_quiet(compare_label == 0, exit, result = compare_label);
+               left_ptrs_size--;
+               right_ptrs_size--;
+       }
+
+       // -1 -> <
+       //      0 -> =
+       // +1 -> >
+       if (left_ptrs_size == 0 && right_ptrs_size == 0) {
+               result = 0;
+       } else if (left_ptrs_size == 0) {
+               result = -1;
+       } else if (right_ptrs_size == 0) {
+               result = 1;
+       } else {
+               log_error("Impossible case here");
+               result = 0;
+       }
+
+exit:
+       return result;
+}
+
+//======================================================================================================================
+//     copy_canonical_name
+//             copy domain name to canonical form
+//======================================================================================================================
+
+mDNSexport mDNSu8
+copy_canonical_name(mDNSu8 * const _Nonnull dst, const mDNSu8 * const _Nonnull name) {
+       // assume that "name" is already fully expanded.
+       mDNSu16 name_length = DOMAIN_NAME_LENGTH(name);
+       mDNSu8 *ptr                     = mDNSNULL;
+       mDNSu32 bytes_convert = 0;
+
+       require_action_quiet(name_length > 0 && name_length <= MAX_DOMAIN_NAME, exit, bytes_convert = 0;
+               log_error("name is a malformed DNS name"));
+       memcpy(dst, name, name_length);
+
+       for (ptr = dst; *ptr != 0; ptr += *ptr + 1) {
+               mDNSu8          label_length    = *ptr;
+               mDNSu8 *        label_end               = ptr + label_length;
+               for (mDNSu8 *ch_ptr = ptr + 1; ch_ptr <= label_end; ch_ptr++) {
+                       char ch = *ch_ptr;
+                       if (IS_UPPER_CASE(ch)) {
+                               *ch_ptr = TO_LOWER_CASE(ch);
+                       }
+               }
+       }
+
+       bytes_convert = ptr + 1 - dst;
+       verify_action(bytes_convert == name_length,
+               log_error("convert more bytes than the actual name length; written=%u, name_length=%u", bytes_convert, name_length);
+               bytes_convert = 0);
+
+exit:
+       return bytes_convert;
+}
+
+//======================================================================================================================
+//     compare_canonical_dns_label
+//======================================================================================================================
+
+mDNSexport mDNSs8
+compare_canonical_dns_label(
+       const mDNSu8 * _Nonnull         left_label,
+       const mDNSu8                            left_label_length,
+       const mDNSu8 * _Nonnull         right_label,
+       const mDNSu8                            right_label_length) {
+
+       mDNSu8 length_limit = MIN(left_label_length, right_label_length);
+
+       for (mDNSu8 i = 0; i < length_limit; i++) {
+               mDNSu8 left_ch  = to_lowercase_if_char(*left_label);
+               mDNSu8 right_ch = to_lowercase_if_char(*right_label);
+               if (left_ch < right_ch)                 return -1;
+               else if (left_ch > right_ch)    return 1;
+               left_label++;
+               right_label++;
+       }
+
+       if (left_label_length < right_label_length) {
+               return -1;
+       } else if (left_label_length > right_label_length) {
+               return 1;
+       } else {
+               // left_label_length == right_label_length
+               return 0;
+       }
+}
+
+#ifdef UNIT_TEST
+mDNSexport mDNSu8
+copy_canonical_name_ut(mDNSu8 * const _Nonnull dst, const mDNSu8 * const _Nonnull name) {
+       return copy_canonical_name(dst, name);
+}
+#endif // UNIT_TEST
+
+mDNSlocal void
+print_validation_progress(const mDNSu32 request_id, const dnssec_dnskey_t * const dnskey, const dnssec_rrsig_t * const rrsig) {
+       log_default("[R%u] "PRI_DM_NAME ": DNSKEY (alg=%u, tag=%u, length=%u) -----> " PRI_DM_NAME ": " PUB_S, request_id,
+               DM_NAME_PARAM(&dnskey->dnssec_rr.name), dnskey->algorithm, dnskey->key_tag, dnskey->public_key_length,
+               DM_NAME_PARAM(&rrsig->dnssec_rr.name), DNS_TYPE_STR(rrsig->type_covered));
+}
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2_crypto.h b/mDNSMacOSX/dnssec_v2/dnssec_v2_crypto.h
new file mode 100644 (file)
index 0000000..0630edd
--- /dev/null
@@ -0,0 +1,147 @@
+//
+//     dnssec_v2_crypto.h
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#ifndef DNSSEC_V2_CRYPTO_H
+#define DNSSEC_V2_CRYPTO_H
+
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include <stdio.h>
+#include <corecrypto/ccsha1.h>
+#include "dnssec_v2_structs.h"
+
+#define SHA1_OUTPUT_SIZE               20
+#define SHA256_OUTPUT_SIZE             32
+#define SHA384_OUTPUT_SIZE             48
+#define SHA512_OUTPUT_SIZE             64
+#define MAX_HASH_OUTPUT_SIZE   SHA512_OUTPUT_SIZE // to ensure that the buffer has enough space to store digest
+
+// Taken from https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
+typedef enum dnskey_algorithm_type {
+       DNSKEY_ALGORITHM_DELETE                         = 0,
+       DNSKEY_ALGORITHM_RSAMD5                         = 1,
+       DNSKEY_ALGORITHM_DH                                     = 2,
+       DNSKEY_ALGORITHM_DSA                            = 3,
+       // Reserved 4
+       DNSKEY_ALGORITHM_RSASHA1                        = 5,
+       DNSKEY_ALGORITHM_DSA_NSEC3_SHA1         = 6,
+       DNSKEY_ALGORITHM_RSASHA1_NSEC3_SHA1 = 7,
+       DNSKEY_ALGORITHM_RSASHA256                      = 8,
+       // Reserved 9
+       DNSKEY_ALGORITHM_RSASHA512                      = 10,
+       // Reserved 11
+       DNSKEY_ALGORITHM_ECC_GOST                       = 12,
+       DNSKEY_ALGORITHM_ECDSAP256SHA256        = 13,
+       DNSKEY_ALGORITHM_ECDSAP384SHA384        = 14,
+       DNSKEK_ALGORITHM_ED25519                        = 15,
+       DNSKEY_ALGORITHM_ED448                          = 16,
+       // Unassigned 17 - 122
+       // Reserved 123 - 251
+       DNSKEY_ALGORITHM_INDIRECT                       = 252,
+       DNSKEY_ALGORITHM_PRIVATEDNS                     = 253,
+       DNSKEY_ALGORITHM_PRIVATEOID                     = 254
+       // Reserved 255
+} dnskey_algorithm_type_t;
+
+// Taken from https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml too
+typedef enum ds_digest_type {
+       // Reserved 0
+       DS_DIGEST_SHA_1                         = 1,
+       DS_DIGEST_SHA_256                       = 2,
+       DS_DIGEST_GOST_R_34_11_94       = 3,
+       DS_DIGEST_SHA_384                       = 4
+       // Reserved 5 - 255
+} ds_digest_type_t;
+
+typedef enum nsec3_hash_algorithm_type {
+       // Reserved 0
+       NSEC3_HASH_ALGORITHM_SHA_1      = 1
+       // Unassigned 2 - 255
+} nsec3_hash_algorithm_type_t;
+
+typedef enum digest_type {
+       DIGEST_UNSUPPORTED,
+       DIGEST_SHA_1,
+       DIGEST_SHA_256,
+       DIGEST_SHA_384,
+       DIGEST_SHA_512
+} digest_type_t;
+
+mDNSexport mDNSs16
+get_priority_of_ds_digest(mDNSu8 digest);
+
+mDNSexport mDNSs16
+get_priority_of_dnskey_algorithm(mDNSu8 algorithm);
+
+mDNSexport mDNSBool
+validate_signed_data_with_rrsig_and_dnskey(
+       const mDNSu32                                                           request_id,
+       const mDNSu8 * const                    _Nonnull        signed_data,
+       const mDNSu32                                                           signed_data_length,
+       const dnssec_rrsig_t * const    _Nonnull        rrsig,
+       const dnssec_dnskey_t * const   _Nonnull        dnskey);
+
+mDNSexport mDNSBool
+calculate_digest_for_data(
+       const mDNSu8 * const    _Nonnull        data,
+       const mDNSu32                                           data_length,
+       const digest_type_t                                     digest_type,
+       mDNSu8 * const                  _Nonnull        digest_buffer,
+       mDNSu32                                                         buffer_size);
+
+mDNSexport mDNSu8 * _Nullable
+calculate_b32_hash_for_nsec3(
+       const mDNSu8 * const _Nonnull           name,
+       const mDNSu16                                           name_length,
+       const mDNSu8                                            hash_type,
+       const mDNSu8 * const    _Nullable       salt,
+       const mDNSu32                                           salt_length,
+       const mDNSu16                                           iterations);
+
+mDNSexport mDNSBool
+calculate_hash_for_nsec3(
+       mDNSu8 * const                  _Nonnull        hash_buffer,
+       const mDNSu32                                           buffer_size,
+       const mDNSu8                                            hash_type,
+       const mDNSu8 * const    _Nonnull        name,
+       const mDNSu16                                           name_length,
+       const mDNSu8 * const    _Nullable       salt,
+       const mDNSu32                                           salt_length,
+       const mDNSu16                                           iterations);
+
+mDNSexport mDNSu32
+get_hash_length_for_nsec3_hash_type(const nsec3_hash_algorithm_type_t nsec3_hash_type);
+
+mDNSexport mDNSu32
+get_digest_length_for_ds_digest_type(const ds_digest_type_t ds_digest_type);
+
+mDNSexport mDNSu32
+get_digest_length_for_digest_type(const digest_type_t digest_type);
+
+mDNSexport mDNSu8
+canonical_form_name_length(const mDNSu8 * const _Nonnull name);
+
+mDNSexport mDNSs8
+compare_canonical_dns_name(const mDNSu8 * const _Nonnull left, const mDNSu8 * const _Nonnull right);
+
+mDNSexport mDNSs8
+compare_canonical_dns_label(
+       const mDNSu8 * _Nonnull         left_label,
+       const mDNSu8                            left_label_length,
+       const mDNSu8 * _Nonnull         right_label,
+       const mDNSu8                            right_label_length);
+
+mDNSexport mDNSu8
+copy_canonical_name(mDNSu8 * const _Nonnull dst, const mDNSu8 * const _Nonnull name);
+
+#ifdef UNIT_TEST
+mDNSexport mDNSu8
+copy_canonical_name_ut(mDNSu8 * const _Nonnull dst, const mDNSu8 * const _Nonnull name);
+#endif // UNIT_TEST
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#endif // DNSSEC_V2_CRYPTO_H
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2_embedded.h b/mDNSMacOSX/dnssec_v2/dnssec_v2_embedded.h
new file mode 100644 (file)
index 0000000..f11b80e
--- /dev/null
@@ -0,0 +1,28 @@
+//
+//  dnssec_v2_embedded.h
+//  mDNSResponder
+//
+//  Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#ifndef DNSSEC_v2_EMBEDDED_H
+#define DNSSEC_v2_EMBEDDED_H
+
+// These embedded structure is used in mDNSEmbedded.h, which is a base header file.
+#pragma mark - structures
+
+typedef struct dnssec_status dnssec_status_t;
+struct dnssec_status {
+       uint8_t enable_dnssec;                          // indicate if mDNSResponder should do DNSSEC validation for the current question
+       uint8_t tried_dnssec_but_unsigned;      // if a question does not enable DNSSEC but this boolean is set, it means the question that enables DNSSEC validation is restarted
+       void *  context;                                        // dnssec_context_t
+};
+
+typedef enum dnssec_result {
+       dnssec_indeterminate = 0,       // make dnssec_indeterminate as default so the uninitialized dnssec_result_t that usually has no data in it will yeild dnssec_indeterminate
+       dnssec_secure,                          // The answer returned to the user call back function is secure and validated through DNSSEC, and can be trusted.
+       dnssec_insecure,                        // The answer provided by the authority server is not signed by the zone, thus we are unable to validate, when it happens the unsigned answer will be returned with dnssec_insecure.
+       dnssec_bogus                            // The answer provided by the authority server has records to do the DNSSEC validation, but the validation fails for some reason, which may indicate an attack from network.
+} dnssec_result_t;
+
+#endif /* DNSSEC_v2_EMBEDDED_H */
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2_helper.c b/mDNSMacOSX/dnssec_v2/dnssec_v2_helper.c
new file mode 100644 (file)
index 0000000..c2e3a49
--- /dev/null
@@ -0,0 +1,285 @@
+//
+//     dnssec_v2_helper.c
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#pragma mark - Includes
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include "dnssec_v2_helper.h"
+#include "dnssec_v2_log.h"
+#include "dnssec_v2_crypto.h"
+#include <errno.h>
+#include <AssertMacros.h>
+
+#pragma mark - Functions
+
+
+
+#pragma mark - deep_copy_resource_record
+mDNSexport mStatus
+deep_copy_resource_record(ResourceRecord * const _Nonnull dst, const ResourceRecord * const _Nonnull src) {
+       mStatus error = mStatus_NoError;
+
+       RData * new_rdata = mDNSNULL;
+       mDNSu16 name_length;
+       mDNSu32 rdata_size;
+
+       require_action(dst != mDNSNULL, exit, error = mStatus_Invalid; log_fault("ResourceRecord dst is NULL, unable to copy to"));
+       memcpy(dst, src, sizeof(ResourceRecord));
+
+       dst->name       = mDNSNULL;
+       dst->rdata      = mDNSNULL;
+
+       // copy name
+       name_length = DOMAIN_NAME_LENGTH(src->name);
+       dst->name = malloc(name_length);
+       require_action(dst->name != mDNSNULL, exit, error = mStatus_NoMemoryErr;
+               log_fault("malloc failed; error_description='%s'", strerror(errno)));
+       memcpy((void *)dst->name, (void *)src->name, name_length);
+
+       // copy rdata
+       rdata_size              = MAX(src->rdlength, sizeof(RDataBody));
+       new_rdata               = malloc(sizeof(RData) - sizeof(RDataBody) + rdata_size);
+       require_action(new_rdata != mDNSNULL, exit, error = mStatus_NoMemoryErr;
+               log_fault("malloc failed; error_description='%s'", strerror(errno)));
+       new_rdata->MaxRDLength  = rdata_size;
+       memcpy(new_rdata->u.data, src->rdata->u.data, rdata_size);
+       dst->rdata = new_rdata;
+
+exit:
+       if (error != mStatus_NoError) {
+               if (dst != mDNSNULL) {
+                       if (dst->name != mDNSNULL)  free((void *)dst->name);
+                       if (dst->rdata != mDNSNULL) free(dst->rdata);
+               }
+       }
+       return error;
+}
+
+#pragma mark - free_resource_record_deep_copied
+mDNSexport void
+free_resource_record_deep_copied(ResourceRecord * const _Nonnull rr) {
+       if (rr->name != mDNSNULL) {
+               free((void *)rr->name);
+       }
+
+       if (rr->rdata != mDNSNULL) {
+               free(rr->rdata);
+       }
+}
+
+#pragma mark - is_root_domain
+mDNSexport mDNSBool
+is_root_domain(const mDNSu8 * const _Nonnull domain_name) {
+       return *domain_name == 0;
+}
+
+
+#pragma mark - is_a_subdomain_of_b
+mDNSexport mDNSBool
+is_a_subdomain_of_b(const mDNSu8 * const a_name, const mDNSu8 * const b_name) {
+       const mDNSu16 a_length = DOMAIN_NAME_LENGTH(a_name);
+       const mDNSu16 b_length = DOMAIN_NAME_LENGTH(b_name);
+
+       return memcmp(a_name + (a_length - b_length), b_name, b_length) == 0;
+}
+
+#pragma mark - resource_records_equal
+mDNSexport mDNSBool
+resource_records_equal(
+       const mDNSu16 rr_type_0,                                const mDNSu16 rr_type_1,
+       const mDNSu16 rr_class_0,                               const mDNSu16 rr_clasee_1,
+       const mDNSu16 rdata_length_0,                   const mDNSu16 rdata_length_1,
+       const mDNSu32 name_hash_0,                              const mDNSu32 name_hash_1,
+       const mDNSu32 rdata_hash_0,                             const mDNSu32 rdata_hash_1,
+       const mDNSu8 * const _Nonnull name_0,   const mDNSu8 * const _Nonnull name_1,
+       const mDNSu8 * const _Nonnull rdata_0,  const mDNSu8 * const _Nonnull rdata_1) {
+
+       if (rr_type_0           != rr_type_1)                                   return mDNSfalse;
+       if (rr_class_0          != rr_clasee_1)                                 return mDNSfalse;
+       if (rdata_length_0      != rdata_length_1)                              return mDNSfalse;
+       if (name_hash_0         != name_hash_1)                                 return mDNSfalse;
+       if (rdata_hash_0        != rdata_hash_1)                                return mDNSfalse;
+       if (!DOMAIN_NAME_EQUALS(name_0, name_1))                        return mDNSfalse;
+       if (memcmp(rdata_0, rdata_1, rdata_length_0) != 0)      return mDNSfalse;
+
+       return mDNStrue;
+}
+
+#pragma mark - dnssec_algorithm_value_to_string
+mDNSexport const char * _Nonnull
+dnssec_algorithm_value_to_string(const mDNSu8 algorithm) {
+       const char *dnskey_algorithm_desp = mDNSNULL;
+       switch (algorithm) {
+               case DNSKEY_ALGORITHM_DELETE:
+                       dnskey_algorithm_desp = "DELETE";
+                       break;
+               case DNSKEY_ALGORITHM_RSAMD5:
+                       dnskey_algorithm_desp = "RSAMD5";
+                       break;
+               case DNSKEY_ALGORITHM_DH:
+                       dnskey_algorithm_desp = "DH";
+                       break;
+               case DNSKEY_ALGORITHM_DSA:
+                       dnskey_algorithm_desp = "DSA";
+                       break;
+               case DNSKEY_ALGORITHM_RSASHA1:
+                       dnskey_algorithm_desp = "DSA";
+                       break;
+               case DNSKEY_ALGORITHM_DSA_NSEC3_SHA1:
+                       dnskey_algorithm_desp = "DSA_NSEC3_SHA1";
+                       break;
+               case DNSKEY_ALGORITHM_RSASHA1_NSEC3_SHA1:
+                       dnskey_algorithm_desp = "RSASHA1_NSEC3_SHA1";
+                       break;
+               case DNSKEY_ALGORITHM_RSASHA256:
+                       dnskey_algorithm_desp = "RSASHA256";
+                       break;
+               case DNSKEY_ALGORITHM_RSASHA512:
+                       dnskey_algorithm_desp = "RSASHA512";
+                       break;
+               case DNSKEY_ALGORITHM_ECC_GOST:
+                       dnskey_algorithm_desp = "ECC_GOST";
+                       break;
+               case DNSKEY_ALGORITHM_ECDSAP256SHA256:
+                       dnskey_algorithm_desp = "ECDSAP256SHA256";
+                       break;
+               case DNSKEY_ALGORITHM_ECDSAP384SHA384:
+                       dnskey_algorithm_desp = "ECDSAP384SHA384";
+                       break;
+               case DNSKEK_ALGORITHM_ED25519:
+                       dnskey_algorithm_desp = "ED25519";
+                       break;
+               case DNSKEY_ALGORITHM_ED448:
+                       dnskey_algorithm_desp = "ED448";
+                       break;
+               case DNSKEY_ALGORITHM_INDIRECT:
+                       dnskey_algorithm_desp = "INDIRECT";
+                       break;
+               case DNSKEY_ALGORITHM_PRIVATEDNS:
+                       dnskey_algorithm_desp = "PRIVATEDNS";
+                       break;
+               case DNSKEY_ALGORITHM_PRIVATEOID:
+                       dnskey_algorithm_desp = "PRIVATEOID";
+                       break;
+               default:
+                       dnskey_algorithm_desp = "UNKNOWN";
+                       break;
+       }
+
+       return dnskey_algorithm_desp;
+}
+
+#pragma mark - dnssec_digest_type_value_to_string
+mDNSexport const char * _Nonnull
+dnssec_digest_type_value_to_string(const mDNSu8 digest_type) {
+       const char * ds_digest_type_desp = mDNSNULL;
+       switch (digest_type) {
+               case DS_DIGEST_SHA_1:
+                       ds_digest_type_desp = "SHA_1";
+                       break;
+               case DS_DIGEST_SHA_256:
+                       ds_digest_type_desp = "SHA_256";
+                       break;
+               case DS_DIGEST_GOST_R_34_11_94:
+                       ds_digest_type_desp = "GOST_R_34_11_94";
+                       break;
+               case DS_DIGEST_SHA_384:
+                       ds_digest_type_desp = "SHA_384";
+                       break;
+               default:
+                       ds_digest_type_desp = "UNKNOWN";
+                       break;
+       }
+       return ds_digest_type_desp;
+}
+
+#pragma mark - dnssec_dnskey_flags_to_string
+mDNSexport const char * _Nonnull
+dnssec_dnskey_flags_to_string(const mDNSu16 flags, char * const _Nonnull buffer, const mDNSu32 buffer_size) {
+       char *  ptr                                     = buffer;
+       char *  limit                           = ptr + buffer_size;
+       int             num_of_char_write;
+
+       if ((flags & 0x100) != 0) { // bit 8
+               num_of_char_write = snprintf(ptr, limit - ptr, "ZONE ");
+               require(num_of_char_write + ptr < limit, exit);
+               ptr += num_of_char_write;
+       }
+
+       if ((flags & 0x80) != 0) { // bit 7
+               num_of_char_write = snprintf(ptr, limit - ptr, "REVOKE ");
+               require(num_of_char_write + ptr < limit, exit);
+               ptr += num_of_char_write;
+       }
+
+       if ((flags & 0x1) != 0) { // bit 15
+               num_of_char_write = snprintf(ptr, limit - ptr, "Secure_Entry_Point ");
+               require(num_of_char_write + ptr < limit, exit);
+               ptr += num_of_char_write;
+       }
+
+exit:
+       if (ptr > buffer) {
+               ptr--;
+               *ptr = '\0';
+       }
+       return buffer;
+}
+
+#pragma mark - dnssec_epoch_time_to_date_string
+mDNSexport const char * _Nonnull
+dnssec_epoch_time_to_date_string(const mDNSu32 epoch, char * const _Nonnull buffer, const mDNSu32 buffer_size) {
+       time_t          t = epoch;
+       struct tm       local_time;
+
+       localtime_r(&t, &local_time);
+       strftime(buffer, buffer_size, "%F %T%z", &local_time);
+
+       return buffer;
+}
+
+#pragma mark - dnssec_nsec3_flags_to_string
+mDNSexport const char * _Nonnull
+dnssec_nsec3_flags_to_string(const mDNSu8 flags, char * const _Nonnull buffer, const mDNSu32 buffer_size) {
+       char *  ptr             = buffer;
+       char *  limit   = buffer + buffer_size;
+       int             num_of_char_write;
+
+       if ((flags & 0x1) != 0) { // bit 0
+               num_of_char_write = snprintf(ptr, limit - ptr, "Opt-Out ");
+               require(num_of_char_write + ptr < limit, exit);
+               ptr += num_of_char_write;
+       }
+
+exit:
+       if (ptr > buffer) {
+               ptr--;
+               *ptr = '\0';
+       }
+       return buffer;
+}
+
+#pragma mark - get_number_of_labels
+mDNSexport mDNSu8
+get_number_of_labels(const mDNSu8 * _Nonnull name) {
+       mDNSu8 count = 0;
+
+       while (*name != 0) {
+               count++;
+               name += *name + 1;
+       }
+
+       return count;
+}
+
+#pragma mark - to_lowercase_if_char
+mDNSexport mDNSu8
+to_lowercase_if_char(const mDNSu8 ch) {
+       return IS_UPPER_CASE(ch) ? TO_LOWER_CASE(ch) : ch;
+}
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2_helper.h b/mDNSMacOSX/dnssec_v2/dnssec_v2_helper.h
new file mode 100644 (file)
index 0000000..49e2274
--- /dev/null
@@ -0,0 +1,218 @@
+//
+//     dnssec_v2_helper.h
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#ifndef DNSSEC_v2_HELPER_H
+#define DNSSEC_v2_HELPER_H
+
+#pragma mark - Includes
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
+#pragma mark - Macros
+
+// DNSSEC helper
+
+// Used to get the primary question from the current request, it is possible that the current request is started by
+// the DNSSEC handler to follow the CNAME, so the primary question should be get from primary_dnssec_context field.
+#define GET_PRIMARY_DNSSEC_CONTEXT(C)                  ((C)->primary_dnssec_context == mDNSNULL) ? \
+                                                                                                       (C) : ((C)->primary_dnssec_context)
+#define GET_PRIMARY_REQUEST(C)                                 (GET_PRIMARY_DNSSEC_CONTEXT(C))->me
+#define GET_PRIMARY_QUESTION(C)                                        (&((GET_PRIMARY_REQUEST(C))->op.q))
+#define GET_REQUEST_ID(C)                                              ((GET_PRIMARY_REQUEST(C))->op.reqID)
+#define GET_QUESTION(C)                                                        (&((C)->me->op.q))
+#define GET_QUESTION_ID_FROM_Q(Q)                              (mDNSVal16((Q)->TargetQID))
+#define GET_QUESTION_ID(C)                                             (GET_QUESTION_ID_FROM_Q(GET_QUESTION(C)))
+
+// string helper
+#define DOMAIN_NAME_EQUALS(A, B)                               SameDomainName((const domainname *)(A), (const domainname *)(B))
+#define DOMAIN_NAME_LENGTH(NAME)                               DomainNameLength((const domainname *)(NAME))
+#define IS_UPPER_CASE(CH)                                              ((CH) >= 'A') && ((CH) <= 'Z')
+#define IS_LOWER_CASE(CH)                                              ((CH) >= 'a') && ((CH) <= 'z')
+#define TO_LOWER_CASE(CH)                                              ((CH) + ('a' - 'A'))
+
+// print helper
+#define DNS_TYPE_STR(TYPE)                                             DNSTypeName((TYPE))
+#define NUM_OF_SPACES_PER_TAB                                  4
+#define TAB_STR                                                                        "%{public}*s"
+#define TAB_PARAM(NUM_OF_TABS)                                 ((NUM_OF_TABS) * (NUM_OF_SPACES_PER_TAB)), ""
+// BASE64_STR should be used with BASE64_PARAM
+#define BASE64_STR                                                             "%{private, mask.hash}.10s%s"
+#define BASE64_PARAM(BASE64)                                   (BASE64), strlen(BASE64) < 11 ? "" : "..."
+
+#pragma mark - Functions
+
+
+
+#pragma mark deep_copy_resource_record
+/*!
+ *     @brief
+ *             Deep copy the record including the name and the rdata.
+ *     @param dst
+ *             The copy destination.
+ *     @param src
+ *             The copy source.
+ *     @return
+ *             Returns mStatus_NoError if no error occurs, other error codes if error occurs
+ *     @discussion
+ *             Remember to free the malloced ResourceRecord->name and ResourceRecord->rdata.
+ */
+mDNSexport mStatus
+deep_copy_resource_record(ResourceRecord * const _Nonnull dst, const ResourceRecord * const _Nonnull src);
+
+#pragma mark free_resource_record_deep_copied
+/*!
+ *     @brief
+ *             Free the malloced memory in the ResourceRecord.
+ *     @param rr
+ *             The ResourceRecord that has malloced memory inside.
+ *     @discussion
+ *             It will only free malloced fields in ResourceRecord, remember to free ResourceRecord itself.
+ */
+mDNSexport void
+free_resource_record_deep_copied(ResourceRecord * const _Nonnull rr);
+
+#pragma mark is_root_domain
+/*!
+ *     @brief
+ *             Given a  DNS format DNS name, determine if it is root.
+ *     @param domain_name
+ *             The DNS format domain name.
+ *     @return
+ *             Returns true if the domain is root, otherwise return false.
+ */
+mDNSexport mDNSBool
+is_root_domain(const mDNSu8 * const _Nonnull domain_name);
+
+#pragma mark is_a_subdomain_of_b
+/*!
+ *     @brief
+ *             Determine if a_name is a sub domain of b_name
+ *     @param a_name
+ *             The child zone or sub domain.
+ *     @param b_name
+ *             The parent zone.
+ *     @return
+ *             Returns true if a_name is a subdomain of b_name
+ */
+mDNSexport mDNSBool
+is_a_subdomain_of_b(const mDNSu8 * const _Nonnull a_name, const mDNSu8 * const _Nonnull b_name);
+
+#pragma mark resource_records_equal
+/*!
+ *     @brief
+ *             Determine if two records are identical.
+ *     @return
+ *             Returns true if two records are identical, false if they are not equal.
+ */
+mDNSexport mDNSBool
+resource_records_equal(
+       const mDNSu16 rr_type_0,                                const mDNSu16 rr_type_1,
+       const mDNSu16 rr_class_0,                               const mDNSu16 rr_clasee_1,
+       const mDNSu16 rdata_length_0,                   const mDNSu16 rdata_length_1,
+       const mDNSu32 name_hash_0,                              const mDNSu32 name_hash_1,
+       const mDNSu32 rdata_hash_0,                             const mDNSu32 rdata_hash_1,
+       const mDNSu8 * const _Nonnull name_0,   const mDNSu8 * const _Nonnull name_1,
+       const mDNSu8 * const _Nonnull rdata_0,  const mDNSu8 * const _Nonnull rdata_1);
+
+#pragma mark dnssec_algorithm_value_to_string
+/*!
+ *     @brief
+ *             Convert DNSKEY type to string description.
+ *     @param algorithm
+ *             The DNSKEY type.
+ *     @return
+ *             The corresponding string description of the DNSKEY type.
+ */
+mDNSexport const char * _Nonnull
+dnssec_algorithm_value_to_string(const mDNSu8 algorithm);
+
+#pragma mark dnssec_dnskey_flags_to_string
+/*!
+ *     @brief
+ *             Convert DS hash type to string description.
+ *     @param digest_type
+ *             The DS digest type.
+ *     @return
+ *             The corresponding string description of the DS digest type.
+ */
+mDNSexport const char * _Nonnull
+dnssec_digest_type_value_to_string(const mDNSu8 digest_type);
+
+#pragma mark deep_copy_resource_record
+/*!
+ *     @brief
+ *             Convert DNSKEY flags to string description.
+ *     @param flags
+ *             The DNSKEY flags field
+ *     @param buffer
+ *             The string buffer that stores the converted string description.
+ *     @param buffer_size
+ *             The total size of the buffer.
+ *     @return
+ *             The pointer to the start of the buffer, aka the start of the description.
+ */
+mDNSexport const char * _Nonnull
+dnssec_dnskey_flags_to_string(const mDNSu16 flags, char * const _Nonnull buffer, const mDNSu32 buffer_size);
+
+#pragma mark dnssec_epoch_time_to_date_string
+/*!
+ *     @brief
+ *             Convert epoch time field in RRSIG to string description.
+ *     @param epoch
+ *             The epoch time.
+ *     @param buffer
+ *             The string buffer that stores the converted string description.
+ *     @param buffer_size
+ *             The total size of the buffer.
+ *     @return
+ *             The pointer to the start of the buffer, aka the start of the description.
+ */
+mDNSexport const char * _Nonnull
+dnssec_epoch_time_to_date_string(const mDNSu32 epoch, char * const _Nonnull buffer, const mDNSu32 buffer_size);
+
+#pragma mark dnssec_nsec3_flags_to_string
+/*!
+ *     @brief
+ *             Convert NSEC3 flags to string description.
+ *     @param flags
+ *             The DNSKEY flags field
+ *     @param buffer
+ *             The string buffer that stores the converted string description.
+ *     @param buffer_size
+ *             The total size of the buffer.
+ *     @return
+ *             The pointer to the start of the buffer, aka the start of the description.
+ */
+mDNSexport const char * _Nonnull
+dnssec_nsec3_flags_to_string(const mDNSu8 flags, char * const _Nonnull buffer, const mDNSu32 buffer_size);
+
+#pragma mark get_number_of_labels
+/*!
+ *     @brief
+ *             Count the number of labels for the given DNS format name.
+ *     @name
+ *             The DNS format name.
+ *     @return
+ *             The number of labels.
+ */
+mDNSexport mDNSu8
+get_number_of_labels(const mDNSu8 * _Nonnull name);
+
+#pragma mark to_lowercase_if_char
+/*!
+ *     @brief
+ *             Convert the character to lower string if it is alphbeta.
+ *     @param ch
+ *             The character.
+ *     @return
+ *             The converted character.
+ */
+mDNSexport mDNSu8
+to_lowercase_if_char(const mDNSu8 ch);
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#endif // DNSSEC_v2_HELPER_H
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2_log.h b/mDNSMacOSX/dnssec_v2/dnssec_v2_log.h
new file mode 100644 (file)
index 0000000..6148863
--- /dev/null
@@ -0,0 +1,22 @@
+//
+//     dnssec_v2_log.h
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#ifndef DNSSEC_V2_LOG_H
+#define DNSSEC_V2_LOG_H
+
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
+// short version logging for DNSSEC-related functionality
+#define log_debug(FORMAT, ...)         LogRedact(MDNS_LOG_CATEGORY_DNSSEC, MDNS_LOG_DEBUG, FORMAT, ## __VA_ARGS__)
+#define log_default(FORMAT, ...)       LogRedact(MDNS_LOG_CATEGORY_DNSSEC, MDNS_LOG_DEFAULT, FORMAT, ## __VA_ARGS__)
+#define log_error(FORMAT, ...)         LogRedact(MDNS_LOG_CATEGORY_DNSSEC, MDNS_LOG_ERROR, FORMAT, ## __VA_ARGS__)
+#define log_fault(FORMAT, ...)         LogRedact(MDNS_LOG_CATEGORY_DNSSEC, MDNS_LOG_FAULT, FORMAT, ## __VA_ARGS__)
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
+#endif // DNSSEC_V2_LOG_H
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2_retrieval.c b/mDNSMacOSX/dnssec_v2/dnssec_v2_retrieval.c
new file mode 100644 (file)
index 0000000..06397d9
--- /dev/null
@@ -0,0 +1,1741 @@
+//
+//     dnssec_v2_retrieval.c
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include <string.h>                                    // for strerror
+#include <errno.h>                                     // for errno
+#include "DNSCommon.h"
+#include "dnssec_v2.h"
+#include "dnssec_v2_helper.h"
+#include "dnssec_v2_retrieval.h"
+#include "dnssec_v2_client.h"
+
+
+//======================================================================================================================
+//     local functions prototypes
+//======================================================================================================================
+
+mDNSlocal response_type_t
+determine_response_type(mDNSu16 rr_type, const mDNSu8 * const _Nullable rdata, const mDNSu16 question_type);
+
+mDNSlocal mDNSBool
+domain_name_end_with(const mDNSu8 * const _Nonnull longer, const mDNSu8 * const _Nonnull shorter);
+
+mDNSlocal const mDNSu8 * _Nullable
+get_parent_zone_name(const list_t * const _Nonnull zones, originals_with_rrsig_t * const _Nonnull original);
+
+mDNSlocal mDNSBool
+nsec_nsec3_contains_rrsigs_with_same_signer(const list_t * const nsec_nsec3_list, mDNSu16 type);
+
+//======================================================================================================================
+//     functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_dnssec_status_t
+//======================================================================================================================
+
+mDNSexport mStatus
+initialize_dnssec_status_t(dnssec_status_t * const _Nonnull status, const domainname * const _Nonnull qname,
+       const mDNSu16 qtype, const mDNSu32 flags, void * const _Nonnull context) {
+
+    // Query ends with ".local." and query for RRSIG or ANY type cannot be validated by DNSSEC even if the user sets the
+    // kDNSServiceFlagsEnableDNSSEC flag.
+       mDNSBool enable_dnssec = FLAGS_CONTAIN_DNSOK_BIT(flags) && is_eligible_for_dnssec(qname, qtype);
+
+       if (enable_dnssec) {
+               status->enable_dnssec                           = mDNStrue;
+               status->tried_dnssec_but_unsigned       = mDNSfalse;
+               status->context                                         = context;
+       } else {
+               // if the question does not enable DNSSEC, only status->enable_dnssec is meaningful.
+               status->enable_dnssec                           = mDNSfalse;
+               status->tried_dnssec_but_unsigned       = mDNSfalse;
+               status->context                                         = mDNSNULL;
+       }
+
+       return mStatus_NoError;
+}
+
+//======================================================================================================================
+//     uninitialize_dnssec_status_t
+//======================================================================================================================
+
+mDNSexport mStatus
+uninitialize_dnssec_status_t(dnssec_status_t * const _Nonnull __unused status) {
+       status->enable_dnssec                           = mDNSfalse;
+       status->tried_dnssec_but_unsigned       = mDNSfalse;
+       status->context                                         = mDNSNULL;
+       return mStatus_NoError;
+}
+
+#pragma mark - dnssec_context_t functions
+
+
+
+#pragma mark create_dnssec_context_t
+mDNSexport mStatus
+create_dnssec_context_t(
+       QueryRecordClientRequest * const        _Nullable       request,
+       const mDNSu32                                                                   request_id,
+       const domainname * const                        _Nonnull        question_name,
+       const mDNSu16                                                                   question_type,
+       const mDNSu16                                                                   question_class,
+       const mDNSInterfaceID                           _Nullable       interface_id,
+       const mDNSs32                                                                   service_id,
+       const mDNSu32                                                                   flags,
+       const mDNSBool                                                                  append_search_domains,
+       const mDNSs32                                                                   pid,
+       const mDNSu8 *                                          _Nullable       uuid,
+       const mDNSs32                                                                   uid,
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+       const audit_token_t *                           _Nullable       peer_audit_token_ptr,
+       const audit_token_t *                           _Nullable       delegate_audit_token_ptr,
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+       const mDNSu8 *                                          _Nullable       resolver_uuid,
+       mDNSBool                                                                                need_encryption,
+       const mdns_dns_service_id_t                                             custom_id,
+#endif
+       const QueryRecordResultHandler          _Nonnull        result_handler,
+       void * const                                            _Nullable       result_context,
+       dnssec_context_t * const                        _Nullable       primary_dnssec_context,
+       dnssec_context_t * _Nullable * const    _Nonnull        out_dnssec_context) {
+
+       mStatus                         error                                                           = mStatus_NoError;
+       dnssec_context_t *      context                                                         = mDNSNULL;
+       mDNSBool                        context_created                                         = mDNSfalse;
+       original_request_parameters_t   *parameters;
+
+       context = calloc(1, sizeof(dnssec_context_t)); // must use calloc here to set context to all 0s
+       require_action(context != mDNSNULL, exit, error = mStatus_NoMemoryErr; log_debug("calloc failed; error_description='%s'", strerror(errno)));
+       context_created = mDNStrue;
+
+       context->me = request;
+
+       list_init(&context->zone_chain, sizeof(dnssec_zone_t));
+
+       // initialize original request fields
+       original_t * const original = &context->original;
+       original->original_result_with_rrsig.type = unknown_response;
+
+       parameters                                                      = &original->original_parameters;
+       parameters->request_id                          = request_id;
+       memcpy(parameters->question_name.c, question_name->c, DOMAIN_NAME_LENGTH(question_name->c));
+       parameters->question_name_hash          = DomainNameHashValue(&parameters->question_name);
+       parameters->question_type                       = question_type;
+       parameters->question_class                      = question_class;
+       parameters->interface_id                        =       interface_id;
+       parameters->service_id                          = service_id;
+       parameters->flags                                       = flags;
+       parameters->append_search_domains       = append_search_domains;
+       parameters->pid                                         = pid;
+       if (uuid != mDNSNULL) {
+               uuid_copy(parameters->uuid, uuid);
+       } else {
+               uuid_clear(parameters->uuid);
+       }
+       parameters->uid                                         = uid;
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+       if (peer_audit_token_ptr != mDNSNULL) {
+               parameters->peer_audit_token = *peer_audit_token_ptr;
+               parameters->has_peer_audit_token = mDNStrue;
+       } else {
+               parameters->has_peer_audit_token = mDNSfalse;
+       }
+       if (delegate_audit_token_ptr != mDNSNULL) {
+               parameters->delegate_audit_token = *delegate_audit_token_ptr;
+               parameters->has_delegate_audit_token = mDNStrue;
+       } else {
+               parameters->has_delegate_audit_token = mDNSfalse;
+       }
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+       if (resolver_uuid != mDNSNULL) {
+               uuid_copy(parameters->resolver_uuid, resolver_uuid);
+       } else {
+               uuid_clear(parameters->resolver_uuid);
+       }
+       parameters->need_encryption                     = need_encryption;
+       parameters->custom_id                           = custom_id;
+#endif
+       parameters->user_handler                        = result_handler;
+       parameters->user_context                        = result_context;
+
+       original->original_trust_anchor         = mDNSNULL;
+       original->last_time_add                         = INT_MIN;
+       original->last_time_rmv                         = INT_MIN;
+
+       // initialize returned_answers_t
+       initialize_returned_answers_t(&context->returned_answers, dnssec_indeterminate, kDNSServiceErr_Invalid);
+
+       // initialize denial of existence fields
+       context->denial_of_existence_records    = mDNSNULL;
+
+       context->primary_dnssec_context         = primary_dnssec_context;
+       context->subtask_dnssec_context         = mDNSNULL;
+
+       *out_dnssec_context = context;
+
+exit:
+       if (error != mStatus_NoError && context_created) free(context);
+       return error;
+}
+
+#pragma mark print_dnssec_context_t
+mDNSexport void
+print_dnssec_context_t(const dnssec_context_t * const _Nonnull context) {
+       mDNSu8 num_of_tabs = 0;
+
+       log_debug("\n");
+       log_debug(TAB_STR "DNSSEC Context:", TAB_PARAM(num_of_tabs));
+       print_original_request_parameters_t(&context->original.original_parameters, num_of_tabs + 1);
+       log_debug(TAB_STR "--------------------------------------------------", TAB_PARAM(num_of_tabs));
+
+       log_debug(TAB_STR "Original Response:", TAB_PARAM(num_of_tabs));
+       print_originals_with_rrsig_t(&context->original.original_result_with_rrsig, num_of_tabs + 1);
+       log_debug(TAB_STR "--------------------------------------------------", TAB_PARAM(num_of_tabs));
+
+       log_debug(TAB_STR "Zones:", TAB_PARAM(num_of_tabs));
+       for (list_node_t *node = list_get_first(&context->zone_chain);
+               !list_has_ended(&context->zone_chain, node);
+               node = list_next(node)) {
+               dnssec_zone_t *zone = (dnssec_zone_t *)node->data;
+               print_dnssec_zone_t(zone, num_of_tabs + 1);
+       }
+
+       log_debug(TAB_STR "Returned Response:", TAB_PARAM(num_of_tabs));
+       print_returned_answers_t(&context->returned_answers, num_of_tabs + 1);
+       log_debug(TAB_STR "--------------------------------------------------", TAB_PARAM(num_of_tabs));
+       log_debug("\n");
+}
+
+#pragma mark destroy_dnssec_context_t
+mDNSexport void
+destroy_dnssec_context_t(dnssec_context_t * const _Nonnull context) {
+       list_uninit(&context->zone_chain);
+       uninitialize_returned_answers_t(&context->returned_answers);
+       free(context);
+}
+
+#pragma mark - add_no_error_records
+
+mDNSlocal mDNSBool
+is_response_for_original_request(
+       const original_t * const                _Nonnull        original,
+       const DNSQuestion * const               _Nonnull        question);
+
+mDNSexport dnssec_retrieval_result_t
+add_no_error_records(
+       mDNS *const                                             _Nonnull        m,
+       DNSQuestion *                                   _Nonnull        question,
+       const ResourceRecord * const    _Nonnull        answer,
+       const QC_result                                                         add_record,
+       const DNSServiceErrorType                                       dns_result_error,
+       dnssec_context_t * const                _Nonnull        dnssec_context) {
+
+       dnssec_retrieval_result_t result;
+
+       dnssec_zone_t * const zone = find_dnssec_zone_t(&dnssec_context->zone_chain, question->qname.c);
+
+       if (is_response_for_original_request(&dnssec_context->original, question)) {
+               // original response requested by user
+               result = update_original_from_cache_for_no_error_response(m, question, answer, add_record, dns_result_error,
+                       dnssec_context);
+               require_quiet(result == dnssec_retrieval_no_error, exit);
+       }
+
+       // it is possible that user queries for A record for apple.com, and there is also a zone called "apple.com"
+       if (zone != mDNSNULL) {
+               // DS/DNSKEY response
+               result = update_dnssec_zone_t_from_cache_for_no_error_response(m, question, answer, add_record, zone);
+               require_quiet(result == dnssec_retrieval_no_error, exit);
+       }
+
+       result = dnssec_retrieval_no_error;
+exit:
+       return result;
+}
+
+#pragma mark is_response_for_original_request
+mDNSlocal mDNSBool
+is_response_for_original_request(
+       const original_t * const                _Nonnull        original,
+       const DNSQuestion * const               _Nonnull        question) {
+
+       mDNSBool                                                                        is_original_request = mDNSfalse;
+       const original_request_parameters_t * const parameters                  = &original->original_parameters;
+
+       if (parameters->question_name_hash != question->qnamehash) {
+               goto exit;
+       }
+
+       if (parameters->question_type != question->qtype) {
+               goto exit;
+       }
+
+       if (parameters->question_class != question->qclass) {
+               goto exit;
+       }
+
+       if (!DOMAIN_NAME_EQUALS(parameters->question_name.c, question->qname.c)) {
+               goto exit;
+       }
+
+       is_original_request = mDNStrue;
+exit:
+       return is_original_request;
+}
+
+#pragma mark - add_denial_of_existence_records
+mDNSexport dnssec_retrieval_result_t
+add_denial_of_existence_records(
+       const mDNS *const                               _Nonnull        m,
+       const DNSQuestion *                             _Nonnull        question,
+       ResourceRecord * const                  _Nonnull        answer,
+       const QC_result                                                         add_record,
+       const DNSServiceErrorType                                       dns_result_error,
+       dnssec_context_t * const                _Nonnull        dnssec_context) {
+
+       dnssec_retrieval_result_t result;
+
+       if (is_response_for_original_request(&dnssec_context->original, question)) {
+               result = update_original_from_cache_for_denial_of_existence_response(m, question, answer, add_record, dns_result_error, dnssec_context);
+               require_quiet(result == dnssec_retrieval_no_error, exit);
+       } else {
+               result = dnssec_retrieval_non_dnskey_ds_record_for_zone;
+               goto exit;
+       }
+
+exit:
+       return result;
+}
+
+//======================================================================================================================
+//     fetch_necessary_dnssec_records
+//======================================================================================================================
+
+mDNSexport dnssec_retrieval_result_t
+fetch_necessary_dnssec_records(dnssec_context_t * const _Nonnull context, mDNSBool anchor_reached) {
+       // if we reach here, it means we need at least 1 zone node to finish the validation process
+       // or the current top parent node has a trust anchor that does not pass the validation
+       mStatus                                                 error                           = mStatus_NoError;
+       dnssec_retrieval_result_t               retrieval_result        = dnssec_retrieval_no_error;
+       list_t *                                                zones                           = &context->zone_chain;
+       dnssec_zone_t *                                 zone                            = mDNSNULL;
+       original_request_parameters_t * params                          = &context->original.original_parameters;
+       const mDNSu8 *                                  parent_zone_name;
+       const mDNSu32 request_id                                                        = context->original.original_parameters.request_id;
+
+       zone = list_empty(zones) ? mDNSNULL : (dnssec_zone_t *)list_get_last(zones)->data;
+
+       mDNSBool is_root = (zone != mDNSNULL) ? (is_root_domain(zone->domain_name.c)) : mDNSfalse;
+       require_action_quiet(!is_root, exit, retrieval_result = dnssec_retrieval_waiting_for_records);
+
+       if (zone == mDNSNULL || zone->trust_anchor == mDNSNULL) {
+               // normal case, get new records from the "Signer Name"
+               parent_zone_name = get_parent_zone_name(zones, &context->original.original_result_with_rrsig);
+               require_action_quiet(parent_zone_name != mDNSNULL, exit, retrieval_result = dnssec_retrieval_waiting_for_records);
+
+               require_action(list_count_node(zones) < MAX_ZONES_ALLOWED, exit, retrieval_result = dnssec_retrieval_too_many_zones);
+
+               error = list_append_uinitialized(zones, sizeof(dnssec_zone_t), (void **)&zone);
+               require_action(error == mStatus_NoError, exit, retrieval_result = dnssec_retrieval_record_not_added;
+                       log_debug("list_add_front_uinitialized failed; error_description='%s'", mStatusDescription(error)));
+
+               initialize_dnssec_zone_t(zone, parent_zone_name);
+
+               if (trust_anchor_contains_dnskey(zone->trust_anchor)) {
+                       retrieval_result                                = dnssec_retrieval_validate_again;
+                       zone->dnskey_request_started    = mDNSfalse;
+                       zone->ds_request_started                = mDNSfalse;
+               } else if (trust_anchor_contains_ds(zone->trust_anchor)) {
+                       zone->dnskey_request_started    = mDNStrue;
+                       zone->ds_request_started                = mDNSfalse;
+               } else {
+                       zone->dnskey_request_started    = mDNStrue;
+                       zone->ds_request_started                = mDNStrue;
+               }
+
+               if (zone->dnskey_request_started) {
+                       error = QueryRecordOpStartForClientRequest(
+                               &zone->dnskey_request.op, params->request_id, (const domainname *)parent_zone_name, kDNSType_DNSKEY,
+                               params->question_class, params->interface_id, params->service_id, params->flags, params->append_search_domains,
+                               params->pid, params->uuid, params->uid,
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+                               params->has_peer_audit_token ? &params->peer_audit_token : mDNSNULL,
+                               params->has_delegate_audit_token ? &params->delegate_audit_token : mDNSNULL,
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                               params->resolver_uuid, params->need_encryption, params->custom_id,
+#endif
+                               query_record_result_reply_with_dnssec, context);
+                       require_action(error == mStatus_NoError, exit, retrieval_result = dnssec_retrieval_query_failed;
+                                                       log_debug("QueryRecordOpStart failed; error_description='%s'", mStatusDescription(error)));
+               }
+
+               if (zone->ds_request_started) {
+                       error = QueryRecordOpStartForClientRequest(
+                               &zone->ds_request.op, params->request_id, (const domainname *)parent_zone_name, kDNSType_DS,
+                               params->question_class, params->interface_id, params->service_id, params->flags, params->append_search_domains,
+                               params->pid, params->uuid, params->uid,
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+                               params->has_peer_audit_token ? &params->peer_audit_token : mDNSNULL,
+                               params->has_delegate_audit_token ? &params->delegate_audit_token : mDNSNULL,
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                               params->resolver_uuid, params->need_encryption, params->custom_id,
+#endif
+                               query_record_result_reply_with_dnssec, context);
+                       require_action(error == mStatus_NoError, exit, retrieval_result = dnssec_retrieval_query_failed;
+                                                       log_debug("QueryRecordOpStart failed; error_description='%s'", mStatusDescription(error)));
+               }
+       } else {
+               // special case where the trust anchor does not verify the records
+               require_action_quiet(anchor_reached, exit,
+                       retrieval_result = dnssec_retrieval_waiting_for_records; log_default("[R%u] still waiting for the response from child zones", request_id));
+
+               zone->trust_anchor = mDNSNULL;
+
+               if (!zone->dnskey_request_started) {
+                       error = QueryRecordOpStartForClientRequest(
+                               &zone->dnskey_request.op, params->request_id, &zone->domain_name, kDNSType_DNSKEY,
+                               params->question_class, params->interface_id, params->service_id, params->flags, params->append_search_domains,
+                               params->pid, params->uuid, params->uid,
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+                               params->has_peer_audit_token ? &params->peer_audit_token : mDNSNULL,
+                               params->has_delegate_audit_token ? &params->peer_audit_token : mDNSNULL,
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                               params->resolver_uuid, params->need_encryption, params->custom_id,
+#endif
+                               query_record_result_reply_with_dnssec, context);
+                       require_action(error == mStatus_NoError, exit, retrieval_result = dnssec_retrieval_query_failed;
+                                                       log_debug("QueryRecordOpStart failed; error_description='%s'", mStatusDescription(error)));
+                       zone->dnskey_request_started = mDNStrue;
+               }
+
+               if (!zone->ds_request_started && !is_root_domain(zone->domain_name.c)) {
+                       error = QueryRecordOpStartForClientRequest(
+                               &zone->ds_request.op, params->request_id, &zone->domain_name, kDNSType_DS,
+                               params->question_class, params->interface_id, params->service_id, params->flags, params->append_search_domains,
+                               params->pid, params->uuid, params->uid,
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+                               params->has_peer_audit_token ? &params->peer_audit_token : mDNSNULL,
+                               params->has_delegate_audit_token ? &params->delegate_audit_token : mDNSNULL,
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                               params->resolver_uuid, params->need_encryption, params->custom_id,
+#endif
+                               query_record_result_reply_with_dnssec, context);
+                       require_action(error == mStatus_NoError, exit, retrieval_result = dnssec_retrieval_query_failed;
+                                                       log_debug("QueryRecordOpStart failed; error_description='%s'", mStatusDescription(error)));
+                       zone->ds_request_started = mDNStrue;
+               }
+       }
+
+exit:
+       if (retrieval_result < 0) {
+               if (zone != mDNSNULL) {
+                       if (zone->dnskey_request_started || zone->ds_request_started) {
+                               // TODO: return correct error code to user and clean the dnssec related structure gracefully
+                       }
+                       uninitialize_dnssec_zone_t(zone);
+                       list_delete_node_with_data_ptr(zones, (void *)zone);
+               }
+       }
+
+       return retrieval_result;
+}
+
+//======================================================================================================================
+//     find_dnssec_zone_t
+//======================================================================================================================
+
+mDNSexport dnssec_zone_t * _Nullable
+find_dnssec_zone_t(const list_t * const _Nonnull zones, const mDNSu8 * const _Nonnull name) {
+       for (list_node_t *ptr = list_get_first(zones); !list_has_ended(zones, ptr); ptr = list_next(ptr)) {
+               dnssec_zone_t *zone = (dnssec_zone_t *)ptr->data;
+               if (DOMAIN_NAME_EQUALS(&zone->domain_name, name)) {
+                       return zone;
+               }
+       }
+
+       return mDNSNULL;
+}
+
+//======================================================================================================================
+//     add_to_cname_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport mStatus
+add_to_cname_with_rrsig_t(cnames_with_rrsig_t * const _Nonnull cnames_with_rrisg, ResourceRecord * const _Nonnull rr) {
+       mStatus error = mStatus_NoError;
+       list_t *                cname_records   = &cnames_with_rrisg->cname_records;
+       list_t *                rrsig_records   = &cnames_with_rrisg->rrsig_records;
+       dnssec_cname_t *cname                   = mDNSNULL;
+       dnssec_rrsig_t *rrsig                   = mDNSNULL;
+
+       if (rr->rrtype == kDNSType_CNAME) {
+               error = list_append_uinitialized(cname_records, sizeof(dnssec_cname_t), (void **)&cname);
+               require_action(error == mStatus_NoError, exit, log_debug("list_append_uinitialized failed; error_description='%s'", mStatusDescription(error)));
+
+               initialize_dnssec_cname_t(cname, rr);
+       } else {
+               mDNSBool is_rrsig_valid = mDNSfalse;
+
+               verify(rr->rrtype == kDNSType_RRSIG);
+               error = list_append_uinitialized(&cnames_with_rrisg->rrsig_records, sizeof(dnssec_rrsig_t), (void **)&rrsig);
+               require_action(error == mStatus_NoError, exit, log_debug("list_append_uinitialized failed; error_description='%s'", mStatusDescription(error)));
+
+               is_rrsig_valid = initialize_dnssec_rrsig_t(rrsig, rr);
+               require_action_quiet(is_rrsig_valid, exit, error = mStatus_BadParamErr;
+                       log_debug("When adding RRSIG for CNAME, RRSIG does not pass validation"));
+       }
+
+exit:
+       if (error != mStatus_NoError) {
+               if (rrsig != mDNSNULL)                  list_delete_node_with_data_ptr(rrsig_records, rrsig);
+               if (cname != mDNSNULL)                  list_delete_node_with_data_ptr(cname_records, cname);
+       }
+       return error;
+}
+
+//======================================================================================================================
+//     add_to_nsec_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport mStatus
+add_to_nsec_with_rrsig_t(nsecs_with_rrsig_t * const _Nonnull nsecs_with_rrisg, ResourceRecord * const _Nonnull rr) {
+       mStatus                                 error           = mStatus_NoError;
+       list_t * const                  nsec_list       = &nsecs_with_rrisg->nsec_and_rrsigs_same_name;
+       const mDNSu8 * const    owner_name      = rr->name->c;
+       const mDNSu32                   name_hash       = DomainNameHashValue(rr->name);
+       mDNSBool                                is_valid        = mDNSfalse;
+       const mDNSu8 *                  owner_name_to_compare;
+       mDNSu32                                 name_hash_to_compare;
+
+       if (rr->rrtype == kDNSType_NSEC) {
+               one_nsec_with_rrsigs_t * new_one_nsec = mDNSNULL;
+               for (list_node_t * nsec_node = list_get_first(nsec_list);
+                       !list_has_ended(nsec_list, nsec_node);
+                       nsec_node = list_next(nsec_node)) {
+                       one_nsec_with_rrsigs_t * const one_nsec = (one_nsec_with_rrsigs_t * const)nsec_node->data;
+                       if (one_nsec->owner_name != mDNSNULL) {
+                               owner_name_to_compare   = one_nsec->owner_name;
+                               name_hash_to_compare    = one_nsec->nsec_record.dnssec_rr.name_hash;
+
+                               require_action_quiet(name_hash_to_compare != name_hash || !DOMAIN_NAME_EQUALS(owner_name_to_compare, owner_name),
+                                       insert_nsec_exit, error = mStatus_BadParamErr;
+                                       log_debug("two NSEC records have the same owner name - owner name: " PRI_DM_NAME,
+                                               DM_NAME_PARAM((const domainname *)owner_name))
+                               );
+
+                       } else {
+                               require_action(!list_empty(&one_nsec->rrsig_records), insert_nsec_exit, error = mStatus_Invalid;
+                                       log_error("empty one_nsec_with_rrsigs_t created"));
+
+                               const dnssec_rrsig_t * const first_rrsig = (const dnssec_rrsig_t * const)(list_get_first(&one_nsec->rrsig_records)->data);
+                               owner_name_to_compare   = first_rrsig->dnssec_rr.name.c;
+                               name_hash_to_compare    = first_rrsig->dnssec_rr.name_hash;
+
+                               if (name_hash_to_compare != name_hash || !DOMAIN_NAME_EQUALS(owner_name_to_compare, owner_name)) {
+                                       continue;
+                               }
+
+                               is_valid = initialize_dnssec_nsec_t(&one_nsec->nsec_record, rr);
+                               require_action_quiet(is_valid, insert_nsec_exit, error = mStatus_BadParamErr;
+                                       log_debug("NSEC record initialization failed because of the malformated resource record"));
+                               one_nsec->owner_name = one_nsec->nsec_record.dnssec_rr.name.c;
+                               error = mStatus_NoError;
+                               goto insert_nsec_exit;
+                       }
+               }
+
+               // insert new one_nsec
+               error = list_append_uinitialized(nsec_list, sizeof(one_nsec_with_rrsigs_t), (void **)&new_one_nsec);
+               require_action(error == mStatus_NoError, insert_nsec_exit, log_error("list_append_uinitialized failed;"));
+
+               is_valid = initialize_one_nsec_with_rrsigs_t(new_one_nsec, rr);
+               require_action_quiet(is_valid, insert_nsec_exit, error = mStatus_BadParamErr;
+                       log_debug("One NSEC structure initialization failed because of malformated resource record - owner name: " PRI_DM_NAME,
+                               DM_NAME_PARAM(rr->name))
+               );
+
+               error = mStatus_NoError;
+       insert_nsec_exit:
+               if (error != mStatus_NoError) {
+                       if (new_one_nsec != mDNSNULL) {
+                               list_delete_node_with_data_ptr(nsec_list, new_one_nsec);
+                       }
+               }
+       } else if (rr->rrtype == kDNSType_RRSIG && get_covered_type_of_dns_type_rrsig_t(rr->rdata->u.data) == kDNSType_NSEC) {
+               list_t *                                        list_to_insert  = mDNSNULL;
+               one_nsec_with_rrsigs_t *        new_one_nsec    = mDNSNULL;
+               dnssec_rrsig_t *                        new_rrsig               = mDNSNULL;
+
+               for (list_node_t * nsec_node = list_get_first(&nsecs_with_rrisg->nsec_and_rrsigs_same_name);
+                       !list_has_ended(nsec_list, nsec_node);
+                       nsec_node = list_next(nsec_node)) {
+                       one_nsec_with_rrsigs_t * const one_nsec = (one_nsec_with_rrsigs_t * const)nsec_node->data;
+
+                       if (one_nsec->owner_name != mDNSNULL) {
+                               owner_name_to_compare   = one_nsec->owner_name;
+                               name_hash_to_compare    = one_nsec->nsec_record.dnssec_rr.name_hash;
+                       } else if (!list_empty(&one_nsec->rrsig_records)) {
+                               const dnssec_rrsig_t * const first_rrsig = (const dnssec_rrsig_t * const)(list_get_first(&one_nsec->rrsig_records)->data);
+                               owner_name_to_compare   = first_rrsig->dnssec_rr.name.c;
+                               name_hash_to_compare    = first_rrsig->dnssec_rr.name_hash;
+                       } else {
+                               error = mStatus_Invalid;
+                               log_error("empty one_nsec_with_rrsigs_t created - rr owner name: " PRI_DM_NAME, DM_NAME_PARAM(rr->name));
+                               goto insert_rrsig_exit;
+                       }
+
+                       if (name_hash_to_compare == name_hash && DOMAIN_NAME_EQUALS(owner_name_to_compare, owner_name)) {
+                               list_to_insert = &one_nsec->rrsig_records;
+                               break;
+                       }
+               }
+
+               if (list_to_insert == mDNSNULL) {
+                       // insert new one_nsec
+                       error = list_append_uinitialized(nsec_list, sizeof(one_nsec_with_rrsigs_t), (void **)&new_one_nsec);
+                       require_action(error == mStatus_NoError, insert_rrsig_exit, log_error("list_append_uinitialized failed;"));
+
+                       new_one_nsec->owner_name = mDNSNULL;
+                       list_init(&new_one_nsec->rrsig_records, sizeof(dnssec_rrsig_t));
+
+                       list_to_insert = &new_one_nsec->rrsig_records;
+               }
+
+               // insert new rrsig
+               error = list_append_uinitialized(list_to_insert, sizeof(dnssec_rrsig_t), (void **)&new_rrsig);
+               require_action(error == mStatus_NoError, insert_rrsig_exit, log_error("list_append_uinitialized failed;"));
+
+               is_valid = initialize_dnssec_rrsig_t(new_rrsig, rr);
+               require_action_quiet(is_valid, insert_rrsig_exit, error = mStatus_BadParamErr;
+                       log_debug("When adding RRSIG for NSEC, RRSIG does not pass validation"));
+
+       insert_rrsig_exit:
+               if (error != mStatus_NoError) {
+                       if (new_rrsig != mDNSNULL) {
+                               list_delete_node_with_data_ptr(list_to_insert, new_rrsig);
+                       }
+                       if (new_one_nsec != mDNSNULL) {
+                               list_delete_node_with_data_ptr(nsec_list, new_one_nsec);
+                       }
+               }
+       } else {
+               if (rr->rrtype != kDNSType_RRSIG) {
+                       // wildcard
+                       dnssec_rr_t *dnssec_rr = mDNSNULL;
+                       error = list_append_uinitialized(&nsecs_with_rrisg->wildcard_answers, sizeof(dnssec_rr_t), (void **)&dnssec_rr);
+                       require_action(error == mStatus_NoError, exit, log_error("list_append_uinitialized failed;"));
+
+                       initialize_dnssec_rr_t(dnssec_rr, rr);
+               } else {
+                       // RRSIG
+                       dnssec_rrsig_t *dnssec_rrsig = mDNSNULL;
+                       error = list_append_uinitialized(&nsecs_with_rrisg->wildcard_rrsigs, sizeof(dnssec_rrsig_t), (void **)&dnssec_rrsig);
+                       require_action(error == mStatus_NoError, exit, log_error("list_append_uinitialized failed;"));
+
+                       is_valid = initialize_dnssec_rrsig_t(dnssec_rrsig, rr);
+                       require_action_quiet(is_valid, insert_wildcard_rrsig_exit, error = mStatus_BadParamErr;
+                               log_debug("When adding RRSIG for wildcard answer, RRSIG does not pass validation"));
+
+               insert_wildcard_rrsig_exit:
+                       if (error != mStatus_NoError) {
+                               if (dnssec_rrsig != mDNSNULL) {
+                                       list_delete_node_with_data_ptr(&nsecs_with_rrisg->wildcard_rrsigs, dnssec_rrsig);
+                               }
+                       }
+               }
+       }
+
+exit:
+       return error;
+}
+
+//======================================================================================================================
+//     add_to_nsec3_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport mStatus
+add_to_nsec3_with_rrsig_t(nsec3s_with_rrsig_t * const _Nonnull nsec3s_with_rrisg, ResourceRecord * const _Nonnull rr) {
+       mStatus                                 error           = mStatus_NoError;
+       list_t * const                  nsec3_list      = &nsec3s_with_rrisg->nsec3_and_rrsigs_same_name;
+       const mDNSu8 * const    owner_name      = rr->name->c;
+       const mDNSu32                   name_hash       = DomainNameHashValue(rr->name);
+       mDNSBool                                is_valid        = mDNStrue;
+       const mDNSu8 *                  owner_name_to_compare;
+       mDNSu32                                 name_hash_to_compare;
+
+       if (rr->rrtype == kDNSType_NSEC3) {
+               one_nsec3_with_rrsigs_t * new_one_nsec3 = mDNSNULL;
+               for (list_node_t *nsec3_node = list_get_first(nsec3_list); !list_has_ended(nsec3_list, nsec3_node); nsec3_node = list_next(nsec3_node)) {
+                       one_nsec3_with_rrsigs_t * const one_nsec3 = (one_nsec3_with_rrsigs_t * const)nsec3_node->data;
+                       if (one_nsec3->owner_name != mDNSNULL) {
+                               owner_name_to_compare   = one_nsec3->owner_name;
+                               name_hash_to_compare    = one_nsec3->nsec3_record.dnssec_rr.name_hash;
+
+                               require_action_quiet(name_hash_to_compare != name_hash || !DOMAIN_NAME_EQUALS(owner_name_to_compare, owner_name),
+                                       insert_nsec3_exit, error = mStatus_BadParamErr;
+                                       log_debug("two NSEC3 records have the same owner name - owner name: " PRI_DM_NAME,
+                                               DM_NAME_PARAM((const domainname *)owner_name))
+                               );
+                       } else {
+                               require_action(!list_empty(&one_nsec3->rrsig_records), exit, error = mStatus_Invalid;
+                                       log_error("empty one_nsec3_with_rrsigs_t created"));
+
+                               const dnssec_rrsig_t * const first_rrsig = (const dnssec_rrsig_t * const)(list_get_first(&one_nsec3->rrsig_records)->data);
+                               owner_name_to_compare   = first_rrsig->dnssec_rr.name.c;
+                               name_hash_to_compare    = first_rrsig->dnssec_rr.name_hash;
+
+                               if (name_hash_to_compare != name_hash || !DOMAIN_NAME_EQUALS(owner_name_to_compare, owner_name)) {
+                                       continue;
+                               }
+
+                               is_valid = initialize_dnssec_nsec3_t(&one_nsec3->nsec3_record, rr);
+                               require_action_quiet(is_valid, insert_nsec3_exit, error = mStatus_BadParamErr;
+                                       log_debug("NSEC record initialization failed because of the malformated resource record"));
+                               one_nsec3->owner_name = one_nsec3->nsec3_record.dnssec_rr.name.c;
+                               error = mStatus_NoError;
+                               goto insert_nsec3_exit;
+                       }
+               }
+
+               // insert new one_nsec3
+               error = list_append_uinitialized(nsec3_list, sizeof(one_nsec3_with_rrsigs_t), (void **)&new_one_nsec3);
+               require_action(error == mStatus_NoError, insert_nsec3_exit, log_error("list_append_uinitialized failed;"));
+
+               is_valid = initialize_one_nsec3_with_rrsigs_t(new_one_nsec3, rr);
+               require_action_quiet(is_valid, insert_nsec3_exit, error = mStatus_BadParamErr;
+                       log_debug("One NSEC3 structure initialization failed because of malformated resource record - owner name: " PRI_DM_NAME,
+                               DM_NAME_PARAM(rr->name))
+               );
+
+       insert_nsec3_exit:
+               if (error != mStatus_NoError) {
+                       if (new_one_nsec3 != mDNSNULL) {
+                               list_delete_node_with_data_ptr(nsec3_list, new_one_nsec3);
+                       }
+               }
+       } else if (rr->rrtype == kDNSType_RRSIG && get_covered_type_of_dns_type_rrsig_t(rr->rdata->u.data) == kDNSType_NSEC3) {
+               list_t *                                        list_to_insert  = mDNSNULL;
+               one_nsec3_with_rrsigs_t *       new_one_nsec3   = mDNSNULL;
+               dnssec_rrsig_t *                        new_rrsig               = mDNSNULL;
+
+               for (list_node_t *nsec3_node = list_get_first(nsec3_list); !list_has_ended(nsec3_list, nsec3_node); nsec3_node = list_next(nsec3_node)) {
+                       one_nsec3_with_rrsigs_t * const one_nsec3 = (one_nsec3_with_rrsigs_t * const)nsec3_node->data;
+
+                       if (one_nsec3->owner_name != mDNSNULL) {
+                               owner_name_to_compare   = one_nsec3->owner_name;
+                               name_hash_to_compare    = one_nsec3->nsec3_record.dnssec_rr.name_hash;
+                       } else if (!list_empty(&one_nsec3->rrsig_records)) {
+                               const dnssec_rrsig_t * const first_rrsig = (const dnssec_rrsig_t * const)(list_get_first(&one_nsec3->rrsig_records)->data);
+                               owner_name_to_compare   = first_rrsig->dnssec_rr.name.c;
+                               name_hash_to_compare    = first_rrsig->dnssec_rr.name_hash;
+                       } else {
+                               error = mStatus_Invalid;
+                               log_error("empty one_nsec3_with_rrsigs_t created - rr owner name: " PRI_DM_NAME, DM_NAME_PARAM(rr->name));
+                               goto insert_rrsig_exit;
+                       }
+
+                       if (name_hash_to_compare == name_hash && DOMAIN_NAME_EQUALS(owner_name_to_compare, owner_name)) {
+                               list_to_insert = &one_nsec3->rrsig_records;
+                       }
+               }
+
+               if (list_to_insert == mDNSNULL) {
+                       // insert new one_nsec3
+                       error = list_append_uinitialized(nsec3_list, sizeof(one_nsec3_with_rrsigs_t), (void **)&new_one_nsec3);
+                       require_action(error == mStatus_NoError, insert_rrsig_exit, log_error("list_append_uinitialized failed"));
+
+                       new_one_nsec3->owner_name = mDNSNULL;
+                       list_init(&new_one_nsec3->rrsig_records, sizeof(dnssec_rrsig_t));
+
+                       list_to_insert = &new_one_nsec3->rrsig_records;
+               }
+
+               // insert new rrsig
+               error = list_append_uinitialized(list_to_insert, sizeof(dnssec_rrsig_t), (void **)&new_rrsig);
+               require_action(error == mStatus_NoError, insert_rrsig_exit, log_error("list_append_uinitialized failed;"));
+
+               is_valid = initialize_dnssec_rrsig_t(new_rrsig, rr);
+               require_action_quiet(is_valid, insert_rrsig_exit, error = mStatus_BadParamErr;
+                       log_debug("When adding RRSIG for NSEC3, RRSIG does not pass validation"));
+
+       insert_rrsig_exit:
+               if (error != mStatus_NoError) {
+                       if (new_rrsig != mDNSNULL) {
+                               list_delete_node_with_data_ptr(list_to_insert, new_rrsig);
+                       }
+                       if (new_one_nsec3 != mDNSNULL) {
+                               list_delete_node_with_data_ptr(nsec3_list, new_one_nsec3);
+                       }
+               }
+       } else {
+               if (rr->rrtype != kDNSType_RRSIG) {
+                       // wildcard
+                       dnssec_rr_t *dnssec_rr = mDNSNULL;
+                       error = list_append_uinitialized(&nsec3s_with_rrisg->wildcard_answers, sizeof(dnssec_rr_t), (void **)&dnssec_rr);
+                       require_action(error == mStatus_NoError, exit, log_error("list_append_uinitialized failed;"));
+
+                       initialize_dnssec_rr_t(dnssec_rr, rr);
+               } else {
+                       // RRSIG
+                       dnssec_rrsig_t *dnssec_rrsig = mDNSNULL;
+                       error = list_append_uinitialized(&nsec3s_with_rrisg->wildcard_rrsigs, sizeof(dnssec_rrsig_t), (void **)&dnssec_rrsig);
+                       require_action(error == mStatus_NoError, exit, log_error("list_append_uinitialized failed;"));
+
+                       is_valid = initialize_dnssec_rrsig_t(dnssec_rrsig, rr);
+                       require_action_quiet(is_valid, insert_wildcard_rrsig_exit, error = mStatus_BadParamErr;
+                               log_debug("When adding RRSIG for wildcard answer, RRSIG does not pass validation"));
+
+               insert_wildcard_rrsig_exit:
+                       if (error != mStatus_NoError) {
+                               if (dnssec_rrsig != mDNSNULL) {
+                                       list_delete_node_with_data_ptr(&nsec3s_with_rrisg->wildcard_rrsigs, dnssec_rrsig);
+                               }
+                       }
+               }
+       }
+
+exit:
+       return error;
+}
+
+//======================================================================================================================
+//     add_to_originals_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport mStatus
+add_to_originals_with_rrsig_t(
+       originals_with_rrsig_t * const  _Nonnull        originals_with_rrisg,
+       ResourceRecord * const  _Nonnull                        rr,
+       const mDNSBool                                                          answer_from_cache,
+       const DNSServiceErrorType                                       dns_error,
+       const QC_result                                                         qc_result) {
+
+       mStatus         error                   = mStatus_NoError;
+
+       if (originals_with_rrisg->type == original_response) {
+               dnssec_rrsig_t *        rrsig                                                   = mDNSNULL;
+               dnssec_original_t * original                                            = mDNSNULL;
+
+               if (rr->rrtype == kDNSType_RRSIG) {
+                       // the corresponding RRISG that covers the requested RR, and RRSIG cannot be the requested
+                       error = list_append_uinitialized(&originals_with_rrisg->u.original.rrsig_records, sizeof(dnssec_rrsig_t), (void **)&rrsig);
+                       require_action(error == mStatus_NoError, original_response_exit, log_debug("list_add_front_uinitialized failed; error_description='%s'", mStatusDescription(error)));
+
+                       mDNSBool is_rrsig_valid= initialize_dnssec_rrsig_t(rrsig, rr);
+                       require_action_quiet(is_rrsig_valid, original_response_exit, error = mStatus_BadParamErr;
+                               log_debug("When adding RRSIG for original response, RRSIG does not pass validation"));
+               } else {
+                       error = list_append_uinitialized(&originals_with_rrisg->u.original.original_records, sizeof(dnssec_original_t), (void **)&original);
+                       require_action(error == mStatus_NoError, original_response_exit, log_debug("list_add_front_uinitialized failed; error_description='%s'", mStatusDescription(error)));
+
+                       initialize_dnssec_original_t(original, rr, answer_from_cache, dns_error, qc_result);
+               }
+
+       original_response_exit:
+               if (error != mStatus_NoError) {
+                       if (original != mDNSNULL)                       list_delete_node_with_data_ptr(&originals_with_rrisg->u.original.original_records, (void *)original);
+                       if (rrsig != mDNSNULL)                          list_delete_node_with_data_ptr(&originals_with_rrisg->u.original.rrsig_records, (void *)rrsig);
+                       goto exit;
+               }
+       } else if (originals_with_rrisg->type == cname_response) {
+               error = add_to_cname_with_rrsig_t(&originals_with_rrisg->u.cname_with_rrsig, rr);
+               require_action(error == mStatus_NoError, exit, log_debug("add_to_cname_with_rrsig_t failed; error_description='%s'", mStatusDescription(error)));
+       } else if (originals_with_rrisg->type == nsec_response) {
+               error = add_to_nsec_with_rrsig_t(&originals_with_rrisg->u.nsecs_with_rrsig, rr);
+               require_action(error == mStatus_NoError, exit, log_debug("add_to_nsec_with_rrsig_t failed; error_description='%s'", mStatusDescription(error)));
+       } else if (originals_with_rrisg->type == nsec3_response) {
+               error = add_to_nsec3_with_rrsig_t(&originals_with_rrisg->u.nsec3s_with_rrsig, rr);
+               require_action(error == mStatus_NoError, exit, log_debug("add_to_nsec3_with_rrsig_t failed; error_description='%s'", mStatusDescription(error)));
+       } else {
+               verify(mDNSfalse);
+       }
+
+exit:
+       return error;
+}
+
+//======================================================================================================================
+//     dnskeys_with_rrsig_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     add_to_dnskeys_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport mStatus
+add_to_dnskeys_with_rrsig_t(dnskeys_with_rrsig_t * const _Nonnull dnskeys_with_rrsig, ResourceRecord * const _Nonnull rr) {
+       // dnskeys_with_rrsig != mDNSNULL && rr != mDNSNULL
+       mStatus error = mStatus_NoError;
+       mDNSBool is_valid = mDNStrue;
+
+       dnssec_dnskey_t *       dnskey                                          = mDNSNULL;
+       dnssec_rrsig_t *        rrsig                                           = mDNSNULL;
+
+       if (rr->rrtype == kDNSType_DNSKEY) {
+               error = list_append_uinitialized(&dnskeys_with_rrsig->dnskey_records, sizeof(dnssec_dnskey_t), (void **)&dnskey);
+               require_action(error == mStatus_NoError, original_response_exit, log_debug("list_append_uinitialized failed; error_description='%s'", mStatusDescription(error)));
+
+               is_valid = initialize_dnssec_dnskey_t(dnskey, rr);
+               require_action_quiet(is_valid, original_response_exit, error = mStatus_BadParamErr;
+                       log_debug("When adding DNSKEY rdata for DNSKEY, rdata does not pass validation and does not get added"));
+       } else {
+               verify(rr->rrtype == kDNSType_RRSIG);
+               error = list_append_uinitialized(&dnskeys_with_rrsig->rrsig_records, sizeof(dnssec_rrsig_t), (void **)&rrsig);
+               require_action(error == mStatus_NoError, original_response_exit, log_debug("list_append_uinitialized failed; error_description='%s'", mStatusDescription(error)));
+
+               is_valid = initialize_dnssec_rrsig_t(rrsig, rr);
+               require_action_quiet(is_valid, original_response_exit, error = mStatus_BadParamErr;
+                       log_debug("When adding RRSIG for DNSKEY, RRSIG does not pass validation and does not get added"));
+       }
+
+original_response_exit:
+       if (error != mStatus_NoError) {
+               if (dnskey != mDNSNULL)                         list_delete_node_with_data_ptr(&dnskeys_with_rrsig->dnskey_records, dnskey);
+               if (rrsig != mDNSNULL)                          list_delete_node_with_data_ptr(&dnskeys_with_rrsig->rrsig_records, rrsig);
+       }
+       return error;
+}
+
+//======================================================================================================================
+//     add_to_dses_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport mStatus
+add_to_dses_with_rrsig_t(dses_with_rrsig_t * const _Nonnull dses_with_rrsig, ResourceRecord * const _Nonnull rr) {
+       mStatus error = mStatus_NoError;
+       mDNSBool is_valid = mDNSfalse;
+
+       if (dses_with_rrsig->type == original_response) {
+               dnssec_ds_t *   ds                                                      = mDNSNULL;
+               dnssec_rrsig_t *rrsig                                           = mDNSNULL;
+
+               if (rr->rrtype == kDNSType_DS) {
+                       error = list_append_uinitialized(&dses_with_rrsig->u.original.ds_records, sizeof(dnssec_ds_t), (void **)&ds);
+                       require_action(error == mStatus_NoError, original_response_exit, log_debug("list_append_uinitialized failed; error_description='%s'", mStatusDescription(error)));
+
+                       is_valid = initialize_dnssec_ds_t(ds, rr);
+                       require_action_quiet(is_valid, original_response_exit, error = mStatus_BadParamErr;
+                               log_debug("When adding DS rdata for DS, the rdata does not pass validation and does not get added"));
+               } else {
+                       verify(rr->rrtype == kDNSType_RRSIG);
+                       error = list_append_uinitialized(&dses_with_rrsig->u.original.rrsig_records, sizeof(dnssec_rrsig_t), (void **)&rrsig);
+                       require_action(error == mStatus_NoError, original_response_exit, log_debug("list_append_uinitialized failed; error_description='%s'", mStatusDescription(error)));
+
+                       is_valid = initialize_dnssec_rrsig_t(rrsig, rr);
+                       require_action_quiet(is_valid, original_response_exit, error = mStatus_BadParamErr;
+                               log_debug("When adding RRSIG for DS, RRSIG does not pass validation and does not get added"));
+               }
+       original_response_exit:
+               if (error != mStatus_NoError) {
+                       if (ds != mDNSNULL)                             list_delete_node_with_data_ptr(&dses_with_rrsig->u.original.ds_records, (void *)ds);
+                       if (rrsig != mDNSNULL)                  list_delete_node_with_data_ptr(&dses_with_rrsig->u.original.rrsig_records, (void *)rrsig);
+                       goto exit;
+               }
+       }else if (dses_with_rrsig->type == nsec_response) {
+               error = add_to_nsec_with_rrsig_t(&dses_with_rrsig->u.nsecs_with_rrsig, rr);
+               require_action(error == mStatus_NoError, exit, log_debug("add_to_nsec_with_rrsig_t failed; error_description='%s'", mStatusDescription(error)));
+       } else if (dses_with_rrsig->type == nsec3_response) {
+               error = add_to_nsec3_with_rrsig_t(&dses_with_rrsig->u.nsec3s_with_rrsig, rr);
+               require_action(error == mStatus_NoError, exit, log_debug("add_to_nsec3_with_rrsig_t failed; error_description='%s'", mStatusDescription(error)));
+       } else{
+               error = mStatus_Invalid;
+               log_error("invalid response type for DS record");
+       }
+
+exit:
+       return error;
+}
+
+//======================================================================================================================
+//     initialize_denial_of_existence_records_t
+//======================================================================================================================
+
+mDNSexport denial_of_existence_records_t * _Nullable
+create_denial_of_existence_records_t(void) {
+       denial_of_existence_records_t *denial = malloc(sizeof(denial_of_existence_records_t));
+       require_quiet(denial != mDNSNULL, exit);
+
+       list_init(&denial->resource_records, sizeof(ResourceRecord));
+
+exit:
+       return denial;
+}
+
+//======================================================================================================================
+//     destroy_denial_of_existence_records_t
+//======================================================================================================================
+
+mDNSexport void
+destroy_denial_of_existence_records_t(denial_of_existence_records_t * const _Nonnull denial_of_existence_records) {
+       list_t *denial_rrs = &denial_of_existence_records->resource_records;
+       for (const list_node_t *rr_node = list_get_first(denial_rrs); !list_has_ended(denial_rrs, rr_node); rr_node = list_next(rr_node)) {
+               ResourceRecord * const rr = (ResourceRecord *)rr_node->data;
+               free_resource_record_deep_copied(rr);
+       }
+       list_uninit(denial_rrs);
+       free(denial_of_existence_records);
+}
+
+mDNSexport void
+destroy_denial_of_existence_records_t_if_nonnull(denial_of_existence_records_t * const _Nonnull denial_of_existence_records) {
+       if (denial_of_existence_records == mDNSNULL) {
+               return;
+       }
+
+       destroy_denial_of_existence_records_t(denial_of_existence_records);
+}
+
+//======================================================================================================================
+//     add_to_denial_of_existence_records_t
+//======================================================================================================================
+
+mDNSexport mStatus
+add_to_denial_of_existence_records_t(denial_of_existence_records_t * const _Nonnull denial_of_existence_records, const ResourceRecord * const _Nonnull rr) {
+       mStatus                 error                           = mStatus_NoError;
+       list_t *                resource_records        = &denial_of_existence_records->resource_records;
+       ResourceRecord *rr_copy;
+
+       error = list_append_uinitialized(resource_records, sizeof(ResourceRecord), (void **)&rr_copy);
+       require_action(error == mStatus_NoError, exit, log_debug("list_append_uinitialized failed; error_description='%s'", mStatusDescription(error)));
+
+       error = deep_copy_resource_record(rr_copy, rr);
+       require_action(error == mStatus_NoError, exit, log_error("initialize_dnssec_rr_t failed"));
+
+exit:
+       return error;
+}
+
+//======================================================================================================================
+//     add_to_dnssec_zone_t
+//======================================================================================================================
+
+mDNSexport mStatus
+add_to_dnssec_zone_t(
+       dnssec_zone_t * const                   _Nonnull        zone,
+       ResourceRecord * const                  _Nonnull        rr,
+       const mDNSu16                                                           question_type) {
+
+       mStatus         error = mStatus_NoError;
+
+       if (question_type == kDNSType_DNSKEY) {
+               error = add_to_dnskeys_with_rrsig_t(&zone->dnskeys_with_rrsig, rr);
+               require_action_quiet(error == mStatus_NoError, exit, log_debug("add_to_dnskeys_with_rrsig_t failed; error_description='%s'", mStatusDescription(error)));
+       } else if (question_type == kDNSType_DS) {
+               if (!zone->dses_initialized) {
+                       response_type_t type = determine_response_type(rr->rrtype, rr->rdata->u.data, question_type);
+                       require_action_quiet(type != unknown_response, exit, error = mStatus_Invalid;
+                               log_error("Unrelated response to current query; question_type=" PUB_S ", response_type=" PUB_S,
+                                       DNS_TYPE_STR(question_type), DNS_TYPE_STR(rr->rrtype)));
+                       initialize_dses_with_rrsig_t(&zone->dses_with_rrsig, type);
+                       zone->dses_initialized = mDNStrue;
+               }
+               error = add_to_dses_with_rrsig_t(&zone->dses_with_rrsig, rr);
+               require_action_quiet(error == mStatus_NoError, exit, log_debug("add_to_dses_with_rrsig_t failed; error_description='%s'", mStatusDescription(error)));
+       } else {
+               error = mStatus_Invalid;
+               log_error("Non DS/DNSKEY query created for dnssec zone; qtype=" PUB_S, DNS_TYPE_STR(question_type));
+       }
+
+exit:
+       return error;
+}
+
+#pragma mark - Update DNSSEC Records
+
+#pragma mark - update_dnssec_zone_t_from_cache_for_no_error_response
+
+mDNSlocal mDNSs32
+get_time_received_for_answer(
+       const CacheGroup * const                _Nonnull        cache_group,
+       const ResourceRecord * const    _Nonnull        answer);
+
+mDNSlocal mDNSu16
+get_updated_type_from_answer(const ResourceRecord * const _Nonnull answer);
+
+mDNSexport dnssec_retrieval_result_t
+update_dnssec_zone_t_from_cache_for_no_error_response(
+       const mDNS * const                              _Nonnull        m,
+       const DNSQuestion * const               _Nonnull        question,
+       const ResourceRecord * const    _Nonnull        answer,
+       const QC_result                                                         add_record,
+       dnssec_zone_t * const                   _Nonnull        zone) {
+
+       dnssec_retrieval_result_t       result          = dnssec_retrieval_unknown_error;
+       mStatus                                                 error;
+       const CacheGroup *                              cache_group;
+       mDNSs32                                                 last_time_received;
+       mDNSu16                                                 updated_type;
+       const dnssec_context_t * const  context         = question->DNSSECStatus.context;
+       mDNSu32                                                 request_id      = context->original.original_parameters.request_id;
+       mDNSu16                                                 question_id = mDNSVal16(question->TargetQID);
+
+       require_action_quiet(question->qtype == kDNSType_DS || question->qtype == kDNSType_DNSKEY, exit,
+               result = dnssec_retrieval_non_dnskey_ds_record_for_zone;
+                       log_error("Non DS/DNSKEY query created for dnssec zone; qtype=" PUB_S, DNS_TYPE_STR(question->qtype)));
+
+       cache_group = CacheGroupForName(m, question->qnamehash, &question->qname);
+       require_action_quiet(cache_group != mDNSNULL, exit,
+               log_error("The question deos not have any corresponding cache group; qname=" PRI_DM_NAME,
+                       DM_NAME_PARAM(&question->qname)));
+
+       last_time_received = get_time_received_for_answer(cache_group, answer);
+       require_action_quiet(last_time_received != 0, exit,
+               log_error("Did not find answer in the cache group; qname=" PRI_DM_NAME " answer_name=" PRI_DM_NAME,
+                       DM_NAME_PARAM(&question->qname), DM_NAME_PARAM(answer->name)));
+
+       updated_type = get_updated_type_from_answer(answer);
+       require_action_quiet(question->qtype == updated_type, exit, result = dnssec_retrieval_non_dnskey_ds_record_for_zone;
+               log_error("[R%u->%u] Record type is not what question asked for; qname=" PRI_DM_NAME ", qtype=" PUB_S ", rr_type=" PUB_S,
+                       request_id, question_id, DM_NAME_PARAM(&question->qname),
+                       DNS_TYPE_STR(question->qtype), DNS_TYPE_STR(updated_type)));
+
+       if (updated_type == kDNSType_DS) {
+               require_action_quiet(zone->ds_request_started, exit, result = dnssec_retrieval_invalid_internal_state;);
+
+               if (add_record == QC_add && zone->last_time_ds_add < last_time_received) {
+                       // having new records added into the response
+                       zone->last_time_ds_add = last_time_received;
+               } else if (add_record == QC_rmv && zone->last_time_ds_rmv < last_time_received) {
+                       // having old records removed from the response
+                       zone->last_time_ds_rmv = last_time_received;
+                       uninitialize_dses_with_rrsig_t(&zone->dses_with_rrsig);
+                       zone->dses_initialized = mDNSfalse;
+                       result = dnssec_retrieval_waiting_for_records;
+                       log_default("[R%u->Q%u] Removing DS record from the zone - hostname: " PRI_DM_NAME, request_id, question_id, DM_NAME_PARAM(&zone->domain_name));
+                       goto exit;
+               } else {
+                       result = dnssec_retrieval_no_new_change;
+                       goto exit;
+               }
+       } else if (updated_type == kDNSType_DNSKEY) {
+               require_action_quiet(zone->dnskey_request_started, exit, result = dnssec_retrieval_invalid_internal_state;);
+
+               if (add_record == QC_add && zone->last_time_dnskey_add < last_time_received) {
+                       // having new records added into the response
+                       zone->last_time_dnskey_add = last_time_received;
+               } else if (add_record == QC_rmv && zone->last_time_dnskey_rmv < last_time_received) {
+                       // having old records removed from the response
+                       zone->last_time_dnskey_rmv = last_time_received;
+                       // uninitialize and initialize to clear all the old contents in zone->dnskeys_with_rrsig
+                       uninitialize_dnskeys_with_rrsig_t(&zone->dnskeys_with_rrsig);
+                       initialize_dnskeys_with_rrsig_t(&zone->dnskeys_with_rrsig);
+                       result = dnssec_retrieval_waiting_for_records;
+                       log_default("[R%u->Q%u] Removing DNSKEY record from the zone - hostname: " PRI_DM_NAME, request_id, question_id, DM_NAME_PARAM(&zone->domain_name));
+                       goto exit;
+               } else {
+                       result = dnssec_retrieval_no_new_change;
+                       goto exit;
+               }
+       } else {
+               result = dnssec_retrieval_non_dnskey_ds_record_for_zone;
+               goto exit;
+       }
+
+       mDNSu32         now                                     = m->timenow;
+       mDNSBool        new_record_added        = mDNSfalse;
+       for (CacheRecord *cache_record = cache_group->members; cache_record != mDNSNULL; cache_record = cache_record->next) {
+               ResourceRecord * const rr = &cache_record->resrec;
+               mDNSBool cache_record_answers_question = SameNameCacheRecordAnswersQuestion(cache_record, question);
+               if (!cache_record_answers_question) {
+                       continue;
+               }
+
+               ssize_t remaining_ttl = (size_t)rr->rroriginalttl - (now - cache_record->TimeRcvd) / mDNSPlatformOneSecond;
+               if (remaining_ttl <= 0) {
+                       log_default("Ignoring record: name="PRI_DM_NAME ", rr_type=" PUB_S ", original_ttl=%d, remaining_ttl=%zd, rdlength=%d",
+                               DM_NAME_PARAM(rr->name), DNSTypeName(rr->rrtype), rr->rroriginalttl, remaining_ttl, rr->rdlength);
+                       continue;
+               }
+               log_default("[R%u->Q%u] Adding record: name="PRI_DM_NAME ", rr_type=" PUB_S ", original_ttl=%d, remaining_ttl=%zd, rdlength=%d",
+                       request_id, question_id,
+                       DM_NAME_PARAM(rr->name), DNSTypeName(rr->rrtype), rr->rroriginalttl, remaining_ttl, rr->rdlength);
+
+               error = add_to_dnssec_zone_t(zone, rr, question->qtype);
+               require_action_quiet(error == mStatus_NoError, exit, result = dnssec_retrieval_unknown_error);
+
+               if (!new_record_added) {
+                       new_record_added        = mDNStrue;
+               }
+               last_time_received      = MAX(last_time_received, cache_record->TimeRcvd);
+       }
+
+       require_action_quiet(new_record_added, exit, result = dnssec_retrieval_non_dnskey_ds_record_for_zone;
+               log_error("[R%u->Q%u] No new record is being added into validation tree, while the TimeRcvd field has a greater value than the last update time of zone - "
+                       "returned rr name: " PRI_DM_NAME ", rr type: " PUB_S ", rr existence type: 0x%X, QC result: %u",
+                       request_id, question_id, DM_NAME_PARAM(answer->name), DNSTypeName(answer->rrtype), answer->RecordType, add_record)
+       );
+
+       mDNSBool contains_rrsig = mDNSfalse;
+       if (updated_type == kDNSType_DS) {
+               if (new_record_added) {
+                       zone->dses_with_rrsig.set_completed = mDNStrue;
+                       zone->last_time_ds_add = last_time_received;
+               }
+               require_action_quiet(zone->dses_initialized, exit, result = dnssec_retrieval_invalid_internal_state;
+                       log_error("[R%u->Q%u] Have new records added into DS structure while the DS structure is not initialized"
+                               "returned rr name: " PRI_DM_NAME ", rr type: " PUB_S ", rr existence type: 0x%X, QC result: %u",
+                               request_id, question_id, DM_NAME_PARAM(answer->name), DNSTypeName(answer->rrtype), answer->RecordType,
+                               add_record)
+               );
+               contains_rrsig = contains_rrsig_in_dses_with_rrsig_t(&zone->dses_with_rrsig);
+       } else if (updated_type == kDNSType_DNSKEY) {
+               if (new_record_added) {
+                       zone->dnskeys_with_rrsig.set_completed = mDNStrue;
+                       zone->last_time_dnskey_add = last_time_received;
+               }
+               contains_rrsig = contains_rrsig_in_dnskeys_with_rrsig_t(&zone->dnskeys_with_rrsig);
+       } else {
+               result = dnssec_retrieval_non_dnskey_ds_record_for_zone;
+               log_error("Non DS/DNSKEY response for DNSSEC zone; rr_type=" PUB_S, DNS_TYPE_STR(updated_type));
+               goto exit;
+       }
+       require_action_quiet(contains_rrsig, exit, result = dnssec_retrieval_no_rrsig;
+               log_error("No RRSIG records returned for DNSSEC query; qname=" PRI_DM_NAME ", qtype=" PUB_S,
+                       DM_NAME_PARAM(&question->qname), DNS_TYPE_STR(question->qtype)));
+
+       result = dnssec_retrieval_no_error;
+exit:
+       return result;
+}
+
+#pragma mark get_time_received_for_answer
+mDNSlocal mDNSs32
+get_time_received_for_answer(
+       const CacheGroup * const                _Nonnull        cache_group,
+       const ResourceRecord * const    _Nonnull        answer) {
+
+       mDNSs32 last_time_received = 0;
+
+       for (CacheRecord *cache_record = cache_group->members; cache_record != mDNSNULL; cache_record = cache_record->next) {
+               if (answer != &cache_record->resrec) {
+                       continue;
+               }
+
+               last_time_received      = cache_record->TimeRcvd;
+               goto exit;
+       }
+
+exit:
+       return last_time_received;
+}
+
+#pragma mark - update_original_from_cache_for_no_error_response
+
+mDNSexport dnssec_retrieval_result_t
+update_original_from_cache_for_no_error_response(
+       mDNS * const                                    _Nonnull        m,
+       const DNSQuestion * const               _Nonnull        question,
+       const ResourceRecord * const    _Nonnull        answer,
+       const QC_result                                                         add_record,
+       const DNSServiceErrorType                                       dns_result_error,
+       dnssec_context_t * const                _Nonnull        dnssec_context) {
+
+       dnssec_retrieval_result_t       result                                  = dnssec_retrieval_unknown_error;
+       mStatus                                                 error                                   = mStatus_UnknownErr;
+       original_t * const                              original                                = &dnssec_context->original;
+       originals_with_rrsig_t * const  originals_with_rrsig    = &original->original_result_with_rrsig;
+       const CacheGroup *                              cache_group;
+       mDNSs32                                                 last_time_received;
+       mDNSu32                                                 request_id                              = dnssec_context->original.original_parameters.request_id;
+       mDNSu16                                                 question_id                     = mDNSVal16(question->TargetQID);
+
+       cache_group = CacheGroupForName(m, question->qnamehash, &question->qname);
+       require_action_quiet(cache_group != mDNSNULL, exit, result = dnssec_retrieval_invalid_internal_state;
+               log_error("The question deos not have any corresponding cache group; qname=" PRI_DM_NAME,
+                       DM_NAME_PARAM(&question->qname)));
+
+       last_time_received = get_time_received_for_answer(cache_group, answer);
+       require_action_quiet(last_time_received != 0, exit, result = dnssec_retrieval_invalid_internal_state;
+               log_error("Did not find answer in the cache group; qname=" PRI_DM_NAME " answer_name=" PRI_DM_NAME,
+                       DM_NAME_PARAM(&question->qname), DM_NAME_PARAM(answer->name)));
+
+       if (add_record == QC_add && original->last_time_add < last_time_received) {
+               // having new records added into the response
+               original->last_time_add = last_time_received;
+
+               if (originals_with_rrsig->type != unknown_response) {
+                       // the previous answer is NSEC, NSEC3 or suppressed fake negative cache
+                       uninitialize_originals_with_rrsig_t(&original->original_result_with_rrsig);
+               }
+       } else if (add_record == QC_rmv && original->last_time_rmv < last_time_received) {
+               // having old records removed from the response
+               response_type_t original_response_type = originals_with_rrsig->type;
+               original->last_time_rmv = last_time_received;
+               require_action_quiet(original_response_type != unknown_response, exit, result = dnssec_retrieval_invalid_internal_state);
+               uninitialize_originals_with_rrsig_t(&original->original_result_with_rrsig);
+
+               // check if there is still active sub CNAME request, if so, the CNAME request will be stopped by
+               // handle_retrieval_result later.
+               result = (original_response_type == cname_response) ?
+                       dnssec_retrieval_cname_removed : dnssec_retrieval_waiting_for_records;
+               goto exit;
+       } else {
+               result = dnssec_retrieval_no_new_change;
+               goto exit;
+       }
+
+       mDNSs32         now                                     = m->timenow;
+       mDNSBool        new_record_added        = mDNSfalse;
+       for (CacheRecord *cache_record = cache_group->members; cache_record != mDNSNULL; cache_record = cache_record->next) {
+               ResourceRecord * const rr = &cache_record->resrec;
+               mDNSBool cache_record_answers_question = SameNameCacheRecordAnswersQuestion(cache_record, question);
+               if (!cache_record_answers_question) {
+                       continue;
+               }
+
+               ssize_t remaining_ttl = (size_t)rr->rroriginalttl - (now - cache_record->TimeRcvd) / mDNSPlatformOneSecond;
+               if (remaining_ttl <= 0) {
+                       log_default("Ignoring record: name="PRI_DM_NAME ", rr_type=" PUB_S ", original_ttl=%d, remaining_ttl=%zd, rdlength=%d",
+                               DM_NAME_PARAM(rr->name), DNSTypeName(rr->rrtype), rr->rroriginalttl, remaining_ttl, rr->rdlength);
+                       continue;
+               }
+               log_default("[R%u->Q%u] Adding record: name="PRI_DM_NAME ", rr_type=" PUB_S ", original_ttl=%d, remaining_ttl=%zd, rdlength=%d",
+                       request_id, question_id,
+                       DM_NAME_PARAM(rr->name), DNSTypeName(rr->rrtype), rr->rroriginalttl, remaining_ttl, rr->rdlength);
+
+               if (originals_with_rrsig->type == unknown_response) {
+                       // this is our first time to add the records into the list
+                       response_type_t type = determine_response_type(rr->rrtype, rr->rdata->u.data, question->qtype);
+                       require_action_quiet(type != unknown_response, exit, result = dnssec_retrieval_invalid_internal_state;
+                               log_error("Unrelated response to current query; question_type=" PUB_S ", response_type=" PUB_S,
+                                       DNS_TYPE_STR(question->qtype), DNS_TYPE_STR(answer->rrtype)));
+                       initialize_originals_with_rrsig_t(&original->original_result_with_rrsig, type);
+
+                       if (type == cname_response) {
+                               QueryRecordClientRequest * const primary_request = GET_PRIMARY_REQUEST(dnssec_context);
+
+                               require_action_quiet(primary_request != mDNSNULL, exit,
+                                       result = dnssec_retrieval_invalid_internal_state;
+                                       log_error("[R%u] primary request has a NULL QueryRecordClientRequest", primary_request->op.reqID));
+
+                               // increment the referrals.
+                               primary_request->op.q.CNAMEReferrals++;
+                       }
+               }
+
+               error = add_to_originals_with_rrsig_t(originals_with_rrsig, rr, !question->InitialCacheMiss, dns_result_error, add_record);
+               require_action_quiet(error == mStatus_NoError, exit, result = dnssec_retrieval_unknown_error);
+               if (!new_record_added) {
+                       new_record_added        = mDNStrue;
+               }
+               last_time_received      = MAX(last_time_received, cache_record->TimeRcvd);
+       }
+
+       if (new_record_added) {
+               original->last_time_add                                                         = last_time_received;
+       }
+       mDNSBool contains_rrsig = mDNSfalse;
+       contains_rrsig = contains_rrsig_in_originals_with_rrsig_t(&original->original_result_with_rrsig);
+       require_action_quiet(contains_rrsig, exit, result = dnssec_retrieval_no_rrsig;
+               log_error("No RRSIG records returned for DNSSEC query; qname=" PRI_DM_NAME ", qtype=" PUB_S,
+                       DM_NAME_PARAM(&question->qname), DNS_TYPE_STR(question->qtype)));
+
+       result = dnssec_retrieval_no_error;
+exit:
+       return result;
+}
+
+#pragma mark get_updated_type_from_answer
+mDNSlocal mDNSu16
+get_updated_type_from_answer(const ResourceRecord * const _Nonnull answer) {
+       mDNSu16 type = kDNSQType_ANY;
+
+       if (answer->rrtype == kDNSType_RRSIG) {
+               type = get_covered_type_of_dns_type_rrsig_t(answer->rdata->u.data);
+       } else {
+               type = answer->rrtype;
+       }
+
+       return type;
+}
+
+#pragma mark - update_original_from_cache_for_denial_of_existence_response
+
+mDNSexport dnssec_retrieval_result_t
+update_original_from_cache_for_denial_of_existence_response(
+       const mDNS *const                               _Nonnull        m,
+       const DNSQuestion *                             _Nonnull        question,
+       ResourceRecord * const                  _Nonnull        answer,
+       const QC_result                                                         add_record,
+       const DNSServiceErrorType                                       dns_result_error,
+       dnssec_context_t * const                _Nonnull        dnssec_context) {
+
+       dnssec_retrieval_result_t               result = dnssec_retrieval_unknown_error;
+       mStatus                                                 error;
+       original_t * const                              original                                = &dnssec_context->original;
+       originals_with_rrsig_t * const  originals_with_rrsig    = &original->original_result_with_rrsig;
+       const list_t *                                  denial_rrs; // list_t<dnssec_rr>
+       const ResourceRecord *                  first_rr                                = mDNSNULL;
+       const CacheGroup *                              cache_group;
+       mDNSs32                                                 last_time_received;
+       response_type_t                                 type;
+       mDNSu32                                                 request_id                              = dnssec_context->original.original_parameters.request_id;
+       mDNSu16                                                 question_id                     = mDNSVal16(question->TargetQID);
+       mDNSBool                                                suppressed                              = add_record == QC_suppressed;
+
+       if (dnssec_context->denial_of_existence_records != mDNSNULL) {
+               denial_rrs = &dnssec_context->denial_of_existence_records->resource_records;
+               require_action_quiet(!list_empty(denial_rrs), exit, result = dnssec_retrieval_invalid_internal_state);
+       } else {
+               denial_rrs = mDNSNULL;
+       }
+
+       cache_group = CacheGroupForName(m, question->qnamehash, &question->qname);
+       require_action_quiet(cache_group != mDNSNULL || suppressed,
+               exit, result = dnssec_retrieval_invalid_internal_state;
+               log_error("The question deos not have any corresponding cache group; qname=" PRI_DM_NAME,
+                       DM_NAME_PARAM(&question->qname)));
+
+       if (cache_group != mDNSNULL) {
+               last_time_received = get_time_received_for_answer(cache_group, answer);
+               require_action_quiet(last_time_received != 0, exit, result = dnssec_retrieval_invalid_internal_state;
+                       log_error("Did not find answer in the cache group; qname=" PRI_DM_NAME " answer_name=" PRI_DM_NAME,
+                               DM_NAME_PARAM(&question->qname), DM_NAME_PARAM(answer->name)));
+       } else {
+               last_time_received = INT_MIN;
+       }
+
+       if ((add_record == QC_add && original->last_time_add < last_time_received) || add_record == QC_suppressed) {
+               // having new records added into the response, since negative answer is mutual exclusive, the previous answer
+               // must be removed
+               original->last_time_add = last_time_received;
+               if (originals_with_rrsig->type != unknown_response) {
+                       uninitialize_originals_with_rrsig_t(&original->original_result_with_rrsig);
+                       original->last_time_rmv = last_time_received;
+               }
+       } else {
+               result = dnssec_retrieval_no_new_change;
+               goto exit;
+       }
+
+       require_action_quiet(add_record == QC_add || suppressed,
+               exit, result = dnssec_retrieval_invalid_internal_state);
+
+       if (denial_rrs != mDNSNULL && !list_empty(denial_rrs)) {
+               first_rr = (ResourceRecord *)(list_get_first(denial_rrs)->data);
+               type = determine_response_type(first_rr->rrtype, first_rr->rdata->u.data, question->qtype);
+               require_action_quiet(type != unknown_response, exit, result = dnssec_retrieval_invalid_internal_state;
+                       log_error("Unrelated response to current query; question_type=" PUB_S ", response_type=" PUB_S,
+                               DNS_TYPE_STR(question->qtype), DNS_TYPE_STR(first_rr->rrtype)));
+       } else {
+               type = original_response;
+       }
+
+       initialize_originals_with_rrsig_t(originals_with_rrsig, type);
+
+       if (type == nsec_response) {
+               original->original_result_with_rrsig.u.nsecs_with_rrsig.negative_rr = answer;
+               log_default("[R%u->Q%u] Adding negative answer verified by NSEC record; name=" PRI_DM_NAME ", rr_type=" PUB_S ", original_ttl=%d, rdlength=%d",
+                       request_id, question_id,
+                       DM_NAME_PARAM(answer->name), DNSTypeName(answer->rrtype), answer->rroriginalttl, answer->rdlength);
+       } else if (type == nsec3_response) {
+               original->original_result_with_rrsig.u.nsec3s_with_rrsig.negative_rr = answer;
+               log_default("[R%u->Q%u] Adding negative answer verified by NSEC3 record; name=" PRI_DM_NAME ", rr_type=" PUB_S ", original_ttl=%d, rdlength=%d",
+                       request_id, question_id,
+                       DM_NAME_PARAM(answer->name), DNSTypeName(answer->rrtype), answer->rroriginalttl, answer->rdlength);
+       } else if (type == original_response) {
+               original->original_result_with_rrsig.u.original.negative_rr = answer;
+               original->original_result_with_rrsig.u.original.suppressed_response = suppressed;
+               if (!suppressed) {
+                       log_default("[R%u->Q%u] Adding negative answer not verified by any DNSSEC record; name=" PRI_DM_NAME ", rr_type=" PUB_S ", original_ttl=%d, rdlength=%d",
+                               request_id, question_id,
+                               DM_NAME_PARAM(answer->name), DNSTypeName(answer->rrtype), answer->rroriginalttl, answer->rdlength);
+               } else {
+                       log_default("[R%u->Q%u] Adding negative answer suppressed by mDNSResponder; name=" PRI_DM_NAME ", rr_type=" PUB_S ", original_ttl=%d, rdlength=%d",
+                               request_id, question_id,
+                               DM_NAME_PARAM(answer->name), DNSTypeName(answer->rrtype), answer->rroriginalttl, answer->rdlength);
+                       print_dnssec_context_t(dnssec_context);
+               }
+       } else {
+               result = dnssec_retrieval_invalid_internal_state;
+               goto exit;
+       }
+
+       if (denial_rrs != mDNSNULL) {
+               for (const list_node_t *rr_node = list_get_first(denial_rrs); !list_has_ended(denial_rrs, rr_node); rr_node = list_next(rr_node)) {
+                       ResourceRecord * const rr = (ResourceRecord *)rr_node->data;
+
+                       log_default("[R%u->Q%u] Adding denial of existence record: name=" PRI_DM_NAME ", rr_type=" PUB_S ", original_ttl=%d, rdlength=%d",
+                               request_id, question_id,
+                               DM_NAME_PARAM(rr->name), DNSTypeName(rr->rrtype), rr->rroriginalttl, rr->rdlength);
+
+                       error = add_to_originals_with_rrsig_t(&original->original_result_with_rrsig, rr, !question->InitialCacheMiss, dns_result_error, add_record);
+                       require_action_quiet(error == mStatus_NoError, exit, result = dnssec_retrieval_unknown_error);
+               }
+       }
+
+       mDNSBool contains_rrsig = mDNSfalse;
+       contains_rrsig = contains_rrsig_in_originals_with_rrsig_t(&original->original_result_with_rrsig);
+       require_action_quiet(contains_rrsig || suppressed, exit, result = dnssec_retrieval_no_rrsig;
+               log_error("No RRSIG records returned for DNSSEC query; qname=" PRI_DM_NAME ", rr_type=" PUB_S,
+                       DM_NAME_PARAM(&question->qname),
+                       (first_rr != mDNSNULL) ? DNS_TYPE_STR(first_rr->rrtype) : DNS_TYPE_STR(question->qtype)));
+
+       // add wildcard answer
+       mDNSs32 now = m->timenow;
+       if (answer->RecordType != kDNSRecordTypePacketNegative && cache_group != mDNSNULL) {
+               for (CacheRecord *cache_record = cache_group->members; cache_record != mDNSNULL; cache_record = cache_record->next) {
+                       ResourceRecord * const rr = &cache_record->resrec;
+                       mDNSBool cache_record_answers_question = SameNameCacheRecordAnswersQuestion(cache_record, question);
+                       if (!cache_record_answers_question) {
+                               continue;
+                       }
+
+                       ssize_t remaining_ttl = (size_t)rr->rroriginalttl - (now - cache_record->TimeRcvd) / mDNSPlatformOneSecond;
+                       if (remaining_ttl <= 0) {
+                               log_default("Ignoring record: name="PRI_DM_NAME ", rr_type=" PUB_S ", original_ttl=%d, remaining_ttl=%zd, rdlength=%d",
+                                       DM_NAME_PARAM(rr->name), DNSTypeName(rr->rrtype), rr->rroriginalttl, remaining_ttl, rr->rdlength);
+                               continue;
+                       }
+                       log_default("[R%u->Q%u] Adding record: name="PRI_DM_NAME ", rr_type=" PUB_S ", original_ttl=%d, remaining_ttl=%zd, rdlength=%d",
+                               request_id, question_id,
+                               DM_NAME_PARAM(rr->name), DNSTypeName(rr->rrtype), rr->rroriginalttl, remaining_ttl, rr->rdlength);
+
+                       error = add_to_originals_with_rrsig_t(&original->original_result_with_rrsig, rr, !question->InitialCacheMiss, dns_result_error, add_record);
+                       require_action_quiet(error == mStatus_NoError, exit, result = dnssec_retrieval_record_not_added);
+               }
+       }
+
+       result = suppressed ? dnssec_retrieval_suppressed : dnssec_retrieval_no_error;
+exit:
+       return result;
+}
+
+
+//======================================================================================================================
+//     local function
+//======================================================================================================================
+
+//======================================================================================================================
+//     determine_response_type
+//======================================================================================================================
+
+mDNSlocal response_type_t
+determine_response_type(mDNSu16 rr_type, const mDNSu8 * const _Nullable rdata, const mDNSu16 question_type) {
+       response_type_t response_type = unknown_response;
+
+       switch (rr_type) {
+               case kDNSType_CNAME:
+                       if (question_type != kDNSType_CNAME)    response_type   = cname_response;
+                       else                                                                    response_type   = original_response;
+                       break;
+               case kDNSType_DS:
+                       verify(rr_type == question_type);
+                       response_type   = original_response;
+                       break;
+               case kDNSType_RRSIG: {
+                       dns_type_rrsig_t *      rrsig_rdata             = (dns_type_rrsig_t *)rdata;
+                       mDNSu16                         type_covered    = ntohs(rrsig_rdata->type_covered);
+                       require_action(type_covered != kDNSType_RRSIG, exit, response_type = unknown_response; log_error("Malformed RRSIG that covers RRSIG;"));
+                       response_type   = determine_response_type(type_covered, mDNSNULL, question_type);
+               }
+                       break;
+               case kDNSType_NSEC:
+                       response_type   = nsec_response;
+                       break;
+               case kDNSType_DNSKEY:
+                       verify(rr_type == question_type);
+                       response_type   = original_response;
+                       break;
+               case kDNSType_NSEC3:
+                       response_type   = nsec3_response;
+                       break;
+               default:
+                       if (rr_type == question_type) {
+                               response_type = original_response;
+                       } else {
+                               response_type   = unknown_response;
+                       }
+                       break;
+       }
+
+exit:
+       return response_type;
+}
+
+//======================================================================================================================
+//     domain_name_end_with
+//======================================================================================================================
+
+mDNSlocal mDNSBool
+domain_name_end_with(const mDNSu8 * const _Nonnull longer, const mDNSu8 * const _Nonnull shorter) {
+       mDNSu32 longer_length   = DOMAIN_NAME_LENGTH(longer);
+       mDNSu32 shorter_length  = DOMAIN_NAME_LENGTH(shorter);
+       const mDNSu8 *longer_ptr;
+       const mDNSu8 *shorter_ptr;
+
+       if (longer_length < shorter_length) {
+               return mDNSfalse;
+       }
+
+       longer_ptr      = longer + longer_length - 1;
+       shorter_ptr = shorter + shorter_length - 1;
+
+       for (mDNSu32 limit = shorter_length; limit > 0; limit--, longer_ptr--, shorter_ptr--) {
+               if (*longer_ptr != *shorter_ptr) {
+                       return mDNSfalse;
+               }
+       }
+
+       return mDNStrue;
+}
+
+//======================================================================================================================
+//     get_parent_zone_name
+//======================================================================================================================
+
+mDNSlocal const mDNSu8 * _Nullable
+get_parent_zone_name(const list_t * const _Nonnull zones, originals_with_rrsig_t * const _Nonnull original) {
+       const list_t *                          rrsig_records           = mDNSNULL;
+       const dnssec_rrsig_t *          rrsig                           = mDNSNULL;
+       const mDNSu8 *                          parent_zone_name        = mDNSNULL;
+
+       if (list_empty(zones)) {
+               switch (original->type) {
+                       case original_response:
+                               rrsig_records = &original->u.original.rrsig_records;
+                               break;
+                       case cname_response:
+                               rrsig_records = &original->u.cname_with_rrsig.rrsig_records;
+                               break;
+                       case nsec_response: {
+                               const list_t * const nsec_list = &original->u.nsecs_with_rrsig.nsec_and_rrsigs_same_name;
+                               const one_nsec_with_rrsigs_t * const one_nsec = (one_nsec_with_rrsigs_t *)list_get_first(nsec_list)->data;
+                               rrsig_records = &one_nsec->rrsig_records;
+                               verify_action(nsec_nsec3_contains_rrsigs_with_same_signer(nsec_list, kDNSType_NSEC), return mDNSNULL);
+                               break;
+                       }
+                       case nsec3_response: {
+                               const list_t * const nsec3_list = &original->u.nsec3s_with_rrsig.nsec3_and_rrsigs_same_name;
+                               const one_nsec3_with_rrsigs_t * const one_nsec3 = (one_nsec3_with_rrsigs_t *)list_get_first(nsec3_list)->data;
+                               rrsig_records = &one_nsec3->rrsig_records;
+                               verify_action(nsec_nsec3_contains_rrsigs_with_same_signer(nsec3_list, kDNSType_NSEC3), return mDNSNULL);
+                               break;
+                       }
+                       default:
+                               break;
+               }
+
+               if (rrsig_records != mDNSNULL && !list_empty(rrsig_records)) {
+                       rrsig = (dnssec_rrsig_t *)list_get_first(rrsig_records)->data;
+               }
+
+       } else {
+               dnssec_zone_t *                 last_zone                       = (dnssec_zone_t *)(list_get_last(zones)->data);
+               dses_with_rrsig_t *             dses_with_rrsig         = &last_zone->dses_with_rrsig;
+
+               if (last_zone->dses_initialized) {
+                       switch (dses_with_rrsig->type) {
+                               case original_response:
+                                       rrsig_records = &dses_with_rrsig->u.original.rrsig_records;
+                                       break;
+                               case nsec_response: {
+                                       const list_t * const nsec_list = &dses_with_rrsig->u.nsecs_with_rrsig.nsec_and_rrsigs_same_name;
+                                       const one_nsec_with_rrsigs_t * const one_nsec = (one_nsec_with_rrsigs_t *)list_get_first(nsec_list)->data;
+                                       rrsig_records = &one_nsec->rrsig_records;
+                                       break;
+                               }
+                               case nsec3_response: {
+                                       const list_t * const nsec3_list = &dses_with_rrsig->u.nsec3s_with_rrsig.nsec3_and_rrsigs_same_name;
+                                       const one_nsec3_with_rrsigs_t * const one_nsec3 = (one_nsec3_with_rrsigs_t *)list_get_first(nsec3_list)->data;
+                                       rrsig_records = &one_nsec3->rrsig_records;
+                                       break;
+                               }
+                               default:
+                                       break;
+                       }
+               }
+
+               if (rrsig_records != mDNSNULL && !list_empty(rrsig_records)) {
+                       rrsig = (dnssec_rrsig_t *)list_get_first(rrsig_records)->data;
+               }
+       }
+
+       if (rrsig != mDNSNULL && domain_name_end_with(rrsig->dnssec_rr.name.c, rrsig->signer_name)) {
+               parent_zone_name = rrsig->signer_name;
+       }
+
+       return parent_zone_name;
+}
+
+//======================================================================================================================
+//     nsec_nsec3_contains_rrsigs_with_same_signer
+//======================================================================================================================
+
+mDNSlocal mDNSBool
+nsec_nsec3_contains_rrsigs_with_same_signer(const list_t * const nsec_nsec3_list, mDNSu16 type)
+{
+       mDNSBool contains_the_same_signer = mDNSfalse;
+       const list_t * first_rrsig_list = mDNSNULL;
+       const dnssec_rrsig_t * first_rrsig = mDNSNULL;
+       const mDNSu8 * signer_name = mDNSNULL;
+
+       require_action_quiet(type == kDNSType_NSEC || type == kDNSType_NSEC3, exit, contains_the_same_signer = mDNSfalse;
+               log_debug("NSEC/NSEC3 list contains records other than NSEC/NSEC3 - Type: " PUB_S, DNSTypeName(type)));
+
+       require_action_quiet(!list_empty(nsec_nsec3_list), exit, contains_the_same_signer = mDNSfalse;
+               log_debug("NSEC/NSEC3 list is empty, which should never happens"));
+
+       if (type == kDNSType_NSEC) {
+               const one_nsec_with_rrsigs_t * const first_one_nsec     = (one_nsec_with_rrsigs_t * )(list_get_first(nsec_nsec3_list)->data);
+               first_rrsig_list = &first_one_nsec->rrsig_records;
+       } else {
+               // type == kDNSType_NSEC3
+               const one_nsec3_with_rrsigs_t * const first_one_nsec3 = (one_nsec3_with_rrsigs_t * )(list_get_first(nsec_nsec3_list)->data);
+               first_rrsig_list = &first_one_nsec3->rrsig_records;
+       }
+
+       require_action_quiet(!list_empty(first_rrsig_list), exit, contains_the_same_signer = mDNSfalse;
+               log_debug("The RRSIG list of " PUB_S " is empty, such record should never be added into the list",
+                       DNSTypeName(type)));
+
+       first_rrsig = (dnssec_rrsig_t *)(list_get_first(first_rrsig_list)->data);
+       signer_name = first_rrsig->signer_name;
+
+       for (const list_node_t * one_nsec_nsec3_node = list_get_first(nsec_nsec3_list);
+                !list_has_ended(nsec_nsec3_list, one_nsec_nsec3_node);
+                one_nsec_nsec3_node = list_next(one_nsec_nsec3_node)) {
+
+               const list_t * rrsig_list = mDNSNULL;
+               if (type == kDNSType_NSEC) {
+                       const one_nsec_with_rrsigs_t * const one_nsec = (one_nsec_with_rrsigs_t *)one_nsec_nsec3_node->data;
+                       rrsig_list = &one_nsec->rrsig_records;
+               } else { // type == kDNSType_NSEC3
+                       const one_nsec3_with_rrsigs_t * const one_nsec3 = (one_nsec3_with_rrsigs_t *)one_nsec_nsec3_node->data;
+                       rrsig_list = &one_nsec3->rrsig_records;
+               }
+
+               for (const list_node_t * rrsig_node = list_get_first(rrsig_list);
+                        !list_has_ended(rrsig_list, rrsig_node);
+                        rrsig_node = list_next(rrsig_node)) {
+
+                       const dnssec_rrsig_t * const dnssec_rrsig = (dnssec_rrsig_t *)(rrsig_node->data);
+                       const mDNSu8 * const signer_name_to_compare = dnssec_rrsig->signer_name;
+
+                       require_action_quiet(DOMAIN_NAME_EQUALS(signer_name, signer_name_to_compare), exit,
+                               contains_the_same_signer = mDNSfalse;
+                               log_debug("RRSIGs do not have the same signer name - Signer name 1: " PRI_DM_NAME ", Signer name 2: " PRI_DM_NAME,
+                                       DM_NAME_PARAM((domainname *)signer_name), DM_NAME_PARAM((domainname *)signer_name_to_compare))
+                       );
+               }
+       }
+
+       contains_the_same_signer = mDNStrue;
+exit:
+       return contains_the_same_signer;
+}
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2_retrieval.h b/mDNSMacOSX/dnssec_v2/dnssec_v2_retrieval.h
new file mode 100644 (file)
index 0000000..ecf225b
--- /dev/null
@@ -0,0 +1,209 @@
+//
+//     dnssec_v2_retrieval.h
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#ifndef DNSSEC_v2_RETRIEVAL_H
+#define DNSSEC_v2_RETRIEVAL_H
+
+#include "mDNSEmbeddedAPI.h"   //      for mStatus
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include "ClientRequests.h"            // QueryRecordOp
+#include "dnssec_v2_embedded.h" //     for dnssec_status_t
+#include "dnssec_v2_structs.h"
+
+typedef enum dnssec_retrieval_result {
+       // normal
+       dnssec_retrieval_no_error                                               = 0,
+       dnssec_retrieval_waiting_for_records                    = 1,
+       dnssec_retrieval_validate_again                                 = 2,
+       dnssec_retrieval_no_new_change                                  = 3,
+       dnssec_retrieval_suppressed                                             = 4,
+       dnssec_retrieval_cname_removed                                  = 5,
+
+       // error
+       dnssec_retrieval_no_rrsig                                               = -65537,
+       dnssec_retrieval_zone_not_found                                 = -65538,
+       dnssec_retrieval_invalid_qtype                                  = -65539,
+       dnssec_retrieval_record_not_added                               = -65540,
+       dnssec_retrieval_no_record                                              = -65541,
+       dnssec_retrieval_not_qc_add                                             = -65542,
+       dnssec_retrieval_too_many_zones                                 = -65543,
+       dnssec_retrieval_query_failed                                   = -65544,
+       dnssec_retrieval_unknown_error                                  = -65545,
+       dnssec_retrieval_invalid_wildcard                               = -65546,
+       dnssec_retrieval_invalid_internal_state                 = -65547,
+       dnssec_retrieval_non_dnskey_ds_record_for_zone  = -65548
+} dnssec_retrieval_result_t;
+
+//======================================================================================================================
+//     function prototypes
+//======================================================================================================================
+
+// dnssec_status_t
+mDNSexport mStatus
+initialize_dnssec_status_t(dnssec_status_t * const _Nonnull status, const domainname * const _Nonnull qname,
+       const mDNSu16 qtype, const mDNSu32 flags, void * const _Nonnull context);
+
+mDNSexport mStatus
+uninitialize_dnssec_status_t(dnssec_status_t * const _Nonnull status);
+
+#pragma mark - dnssec_context_t function prototypes
+mDNSexport mStatus
+create_dnssec_context_t(
+       QueryRecordClientRequest * const        _Nullable       request,
+       const mDNSu32                                                                   request_id,
+       const domainname * const                        _Nonnull        question_name,
+       const mDNSu16                                                                   question_type,
+       const mDNSu16                                                                   question_class,
+       const mDNSInterfaceID                           _Nullable       interface_id,
+       const mDNSs32                                                                   service_id,
+       const mDNSu32                                                                   flags,
+       const mDNSBool                                                                  append_search_domains,
+       const mDNSs32                                                                   pid,
+       const mDNSu8 *                                          _Nullable       uuid,
+       const mDNSs32                                                                   uid,
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+       const audit_token_t *                           _Nullable       peer_audit_token_ptr,
+       const audit_token_t *                           _Nullable       delegate_audit_token_ptr,
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+       const mDNSu8 *                                          _Nullable       resolver_uuid,
+       mDNSBool                                                                                need_encryption,
+       const mdns_dns_service_id_t                                             custom_id,
+#endif
+       const QueryRecordResultHandler          _Nonnull        result_handler,
+       void * const                                            _Nullable       result_context,
+       dnssec_context_t * const                        _Nullable       primary_dnssec_context,
+       dnssec_context_t * _Nullable * const    _Nonnull        out_dnssec_context);
+
+mDNSexport void
+print_dnssec_context_t(const dnssec_context_t * const _Nonnull context);
+
+mDNSexport void
+destroy_dnssec_context_t(dnssec_context_t * const _Nonnull context);
+
+mDNSexport dnssec_retrieval_result_t
+add_no_error_records(
+       mDNS *const                                             _Nonnull        m,
+       DNSQuestion *                                   _Nonnull        question,
+       const ResourceRecord * const    _Nonnull        answer,
+       const QC_result                                                         add_record,
+       const DNSServiceErrorType                                       dns_result_error,
+       dnssec_context_t * const                _Nonnull        dnssec_context);
+
+mDNSexport dnssec_retrieval_result_t
+add_denial_of_existence_records(
+       const mDNS *const                               _Nonnull        m,
+       const DNSQuestion *                             _Nonnull        question,
+       ResourceRecord * const                  _Nonnull        answer,
+       const QC_result                                                         add_record,
+       const DNSServiceErrorType                                       dns_result_error,
+       dnssec_context_t * const                _Nonnull        dnssec_context);
+
+mDNSexport dnssec_retrieval_result_t
+fetch_necessary_dnssec_records(dnssec_context_t * const _Nonnull context, mDNSBool anchor_reached);
+
+// list_t<dnssec_zone_t>
+mDNSexport dnssec_zone_t * _Nullable
+find_dnssec_zone_t(const list_t * const _Nonnull zones, const mDNSu8 * const _Nonnull name);
+
+// cnames_with_rrsig_t
+mDNSexport mStatus
+add_to_cname_with_rrsig_t(cnames_with_rrsig_t * const _Nonnull cnames_with_rrisg, ResourceRecord * const _Nonnull rr);
+
+mDNSexport mDNSBool
+remove_from_cname_with_rrsig_t(cnames_with_rrsig_t * const _Nonnull cnames_with_rrisg, const ResourceRecord * const _Nonnull rr);
+
+// nsecs_with_rrsig_t
+mDNSexport mStatus
+add_to_nsec_with_rrsig_t(nsecs_with_rrsig_t * const _Nonnull nsecs_with_rrisg, ResourceRecord * const _Nonnull rr);
+
+mDNSexport mDNSBool
+remove_from_nsec_with_rrsig_t(nsecs_with_rrsig_t * const _Nonnull nsecs_with_rrisg, const ResourceRecord * const _Nonnull rr);
+
+// nsec3s_with_rrsig_t
+mDNSexport mStatus
+add_to_nsec3_with_rrsig_t(nsec3s_with_rrsig_t * const _Nonnull nsec3s_with_rrisg, ResourceRecord * const _Nonnull rr);
+
+mDNSexport mDNSBool
+remove_from_nsec3_with_rrsig_t(nsec3s_with_rrsig_t * const _Nonnull nsec3s_with_rrisg, const ResourceRecord * const _Nonnull rr);
+
+// originals_with_rrsig_t
+mDNSexport mStatus
+add_to_originals_with_rrsig_t(
+       originals_with_rrsig_t * const  _Nonnull        originals_with_rrisg,
+       ResourceRecord * const                  _Nonnull        rr,
+       const mDNSBool                                                          answer_from_cache,
+       const DNSServiceErrorType                                       dns_error,
+       const QC_result                                                         qc_result);
+
+mDNSexport void
+remove_from_originals_with_rrsig_t(
+       originals_with_rrsig_t * const  _Nonnull        originals_with_rrisg,
+       const ResourceRecord * const    _Nonnull        rr);
+
+// dnskeys_with_rrsig_t
+mDNSexport mStatus
+add_to_dnskeys_with_rrsig_t(dnskeys_with_rrsig_t * const _Nonnull dnskeys_with_rrsig, ResourceRecord * const _Nonnull rr);
+
+mDNSexport void
+remove_from_dnskeys_with_rrsig_t(dnskeys_with_rrsig_t * const _Nonnull dnskeys_with_rrsig, const ResourceRecord * const _Nonnull rr);
+
+// dses_with_rrsig_t
+mDNSexport mStatus
+add_to_dses_with_rrsig_t(dses_with_rrsig_t * const _Nonnull dses_with_rrsig, ResourceRecord * const _Nonnull rr);
+
+mDNSexport void
+remove_from_dses_with_rrsig_t(dses_with_rrsig_t * const _Nonnull dses_with_rrsig, const ResourceRecord * const _Nonnull rr);
+
+// denial_of_existence_records_t
+mDNSexport denial_of_existence_records_t * _Nullable
+create_denial_of_existence_records_t(void);
+
+mDNSexport void
+destroy_denial_of_existence_records_t(denial_of_existence_records_t * const _Nonnull denial_of_existence_records);
+
+mDNSexport void
+destroy_denial_of_existence_records_t_if_nonnull(denial_of_existence_records_t * const _Nonnull denial_of_existence_records);
+
+mDNSexport mStatus
+add_to_denial_of_existence_records_t(denial_of_existence_records_t * const _Nonnull denial_of_existence_records, const ResourceRecord * const _Nonnull rr);
+
+// dnssec_zone_t
+mDNSexport mStatus
+add_to_dnssec_zone_t(
+       dnssec_zone_t * const                   _Nonnull        zone,
+       ResourceRecord * const  _Nonnull        rr,
+       const mDNSu16                                                           question_type);
+
+mDNSexport dnssec_retrieval_result_t
+update_dnssec_zone_t_from_cache_for_no_error_response(
+       const mDNS * const                              _Nonnull        m,
+       const DNSQuestion * const               _Nonnull        question,
+       const ResourceRecord * const    _Nonnull        answer,
+       const QC_result                                                         add_record,
+       dnssec_zone_t * const                   _Nonnull        zone);
+
+mDNSexport dnssec_retrieval_result_t
+update_original_from_cache_for_no_error_response(
+       mDNS * const                                    _Nonnull        m,
+       const DNSQuestion * const               _Nonnull        question,
+       const ResourceRecord * const    _Nonnull        answer,
+       const QC_result                                                         add_record,
+       const DNSServiceErrorType                                       dns_result_error,
+       dnssec_context_t * const                _Nonnull        dnssec_context);
+
+mDNSexport dnssec_retrieval_result_t
+update_original_from_cache_for_denial_of_existence_response(
+       const mDNS *const                               _Nonnull        m,
+       const DNSQuestion *                             _Nonnull        question,
+       ResourceRecord * const                  _Nonnull        answer,
+       const QC_result                                                         add_record,
+       const DNSServiceErrorType                                       dns_result_error,
+       dnssec_context_t * const                _Nonnull        dnssec_context);
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#endif /* DNSSEC_v2_RETRIEVAL_H */
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2_structs.c b/mDNSMacOSX/dnssec_v2/dnssec_v2_structs.c
new file mode 100644 (file)
index 0000000..3c13d89
--- /dev/null
@@ -0,0 +1,1663 @@
+//
+//     dnssec_v2_structs.c
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include <string.h>                                    // for strerror
+#include <errno.h>                                     // for errno
+#include "DNSCommon.h"
+#include "dnssec_v2_helper.h"
+#include "dnssec_v2_structs.h"
+#include "dnssec_v2_log.h"
+#include "dnssec_v2_validation.h"
+#include "dnssec_v2_trust_anchor.h"
+#include "base_n.h"
+
+//======================================================================================================================
+//     Local functions
+//======================================================================================================================
+
+mDNSlocal char *
+type_bit_map_to_cstring(
+       const mDNSu8 * const _Nonnull   bit_map,
+       const mDNSu16                                   map_length,
+       char *                                                  buffer,
+       mDNSu32                                                 buffer_length);
+
+//======================================================================================================================
+//     functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     dns_type_*_t parse functions
+//======================================================================================================================
+
+mDNSexport void
+parsse_dns_type_cname_t(const void * const _Nonnull rdata, mDNSu8 * _Nullable * const _Nonnull out_cname) {
+       // rdata != mDNSNULL
+       dns_type_cname_t *cname_struct = (dns_type_cname_t *)rdata;
+
+       if (out_cname != mDNSNULL) *out_cname = cname_struct->cname;
+}
+
+mDNSexport mDNSBool
+parse_dns_type_ds_t(
+       const void * const      _Nonnull        rdata,
+       const mDNSu16                                   rdata_length,
+       mDNSu16 * const         _Nullable       out_key_tag,
+       mDNSu8 * const          _Nullable       out_algorithm,
+       mDNSu8 * const          _Nullable       out_digest_type,
+       mDNSu16 * const         _Nullable       out_digest_length,
+       const mDNSu8 * _Nonnull * const _Nullable       out_digest) {
+
+       mDNSBool is_valid = mDNSfalse;
+       dns_type_ds_t *ds = (dns_type_ds_t *)rdata;
+
+       require_action_quiet(rdata_length > offsetof(dns_type_ds_t, digest), exit, is_valid = mDNSfalse;
+               log_debug("DS record parsing failed because of incorrect rdata length - rdata length: %u", rdata_length));
+
+       if (out_key_tag != mDNSNULL)            *out_key_tag            = ntohs(ds->key_tag);
+       if (out_algorithm != mDNSNULL)          *out_algorithm          = ds->algorithm;
+       if (out_digest_type != mDNSNULL)        *out_digest_type        = ds->digest_type;
+       if (out_digest != mDNSNULL)                     *out_digest                     = ds->digest;
+       if (out_digest_length != mDNSNULL)      *out_digest_length      = rdata_length - offsetof(dns_type_ds_t, digest);
+
+       is_valid = mDNStrue;
+exit:
+       return is_valid;
+}
+
+mDNSexport mDNSBool
+parse_dns_type_dnskey_t(
+       const void * const      _Nonnull        rdata,
+       const mDNSu16                                   rdata_length,
+       mDNSu16 * const         _Nullable       out_flags,
+       mDNSu8 * const          _Nullable       out_protocol,
+       mDNSu8 * const          _Nullable       out_algorithm,
+       mDNSu16 * const         _Nullable       out_public_key_length,
+       mDNSu8 * _Nonnull * const _Nullable out_public_key) {
+
+       mDNSBool is_valid = mDNSfalse;
+       dns_type_dnskey_t *dnskey = (dns_type_dnskey_t *)rdata;
+       require_action_quiet(rdata_length > offsetof(dns_type_dnskey_t, public_key), exit, is_valid = mDNSfalse;
+               log_debug("DNSKEY record parsing failed because of incorrect rdata length - rdata length: %u", rdata_length));
+
+       if (out_flags != mDNSNULL)                              *out_flags                              = ntohs(dnskey->flags);
+       if (out_protocol != mDNSNULL)                   *out_protocol                   = dnskey->protocol;
+       if (out_algorithm != mDNSNULL)                  *out_algorithm                  = dnskey->algorithm;
+       if (out_public_key != mDNSNULL)                 *out_public_key                 = dnskey->public_key;
+       if (out_public_key_length != mDNSNULL)  *out_public_key_length  = rdata_length - offsetof(dns_type_dnskey_t, public_key);
+
+       is_valid = mDNStrue;
+exit:
+       return is_valid;
+}
+
+mDNSexport mDNSBool
+parse_dns_type_rrsig_t(
+       const void * const      _Nonnull        rdata,
+       const mDNSu16                                   rdata_length,
+       mDNSu16 * const         _Nullable       out_type_covered,
+       mDNSu8 * const          _Nullable       out_algorithm,
+       mDNSu8 * const          _Nullable       out_labels,
+       mDNSu32 * const         _Nullable       out_original_ttl,
+       mDNSu32 * const         _Nullable       out_signature_expiration,
+       mDNSu32 * const         _Nullable       out_signature_inception,
+       mDNSu16 * const         _Nullable       out_key_tag,
+       mDNSu16 * const         _Nullable       out_signature_length,
+       mDNSu8 * _Nonnull * const _Nullable out_signer_name,
+       mDNSu8 * _Nonnull * const _Nullable out_signature) {
+
+       mDNSBool                        is_valid                        = mDNSfalse;
+       dns_type_rrsig_t *      rrsig                           = (dns_type_rrsig_t *)rdata;
+       mDNSu16                         signer_name_length      = DomainNameLengthLimit((const domainname *)rrsig->signer_name, rdata + rdata_length);
+       require_action_quiet(signer_name_length != MAX_DOMAIN_NAME + 1, exit, is_valid = mDNSfalse;
+               log_debug("RRSIG record parsing failed, because the signer name length goes out of RRSIG rdata"));
+       require_action_quiet(rdata_length > offsetof(dns_type_rrsig_t, signer_name) + signer_name_length, exit,
+               is_valid = mDNSfalse;
+               log_debug("RRSIG record parsing failed because of incorrect rdata length - rdata length: %u", rdata_length));
+
+       if (out_type_covered != mDNSNULL)                       *out_type_covered                       = ntohs(rrsig->type_covered);
+       if (out_algorithm != mDNSNULL)                          *out_algorithm                          = rrsig->algorithm;
+       if (out_labels != mDNSNULL)                                     *out_labels                                     = rrsig->labels;
+       if (out_original_ttl != mDNSNULL)                       *out_original_ttl                       = ntohl(rrsig->original_TTL);
+       if (out_signature_expiration != mDNSNULL)       *out_signature_expiration       = ntohl(rrsig->signature_expiration);
+       if (out_signature_inception != mDNSNULL)        *out_signature_inception        = ntohl(rrsig->signature_inception);
+       if (out_key_tag != mDNSNULL)                            *out_key_tag                            = ntohs(rrsig->key_tag);
+       if (out_signer_name != mDNSNULL)                        *out_signer_name                        = rrsig->signer_name;
+       if (out_signature != mDNSNULL)                          *out_signature                          = (mDNSu8 * const)rdata + offsetof(dns_type_rrsig_t, signer_name) + signer_name_length;
+       if (out_signature_length != mDNSNULL)           *out_signature_length           = rdata_length - offsetof(dns_type_rrsig_t, signer_name) - signer_name_length;
+
+       is_valid = mDNStrue;
+exit:
+       return is_valid;
+}
+
+mDNSexport mDNSBool
+parse_dns_type_nsec_t(
+       const void * const      _Nonnull        rdata,
+       const mDNSu16                                   rdata_length,
+       mDNSu16 * const         _Nonnull        out_type_bit_maps_length,
+       mDNSu8 * _Nonnull * const _Nullable out_next_domain_name,
+       mDNSu8 * _Nonnull * const _Nullable out_type_bit_maps) {
+
+       mDNSBool is_valid                               = mDNSfalse;
+       dns_type_nsec_t *nsec                   = (dns_type_nsec_t *)rdata;
+       mDNSu16 next_domain_name_length = DomainNameLengthLimit((const domainname *)nsec->next_domain_name, rdata + rdata_length);
+       require_action_quiet(next_domain_name_length != MAX_DOMAIN_NAME + 1 && rdata_length > next_domain_name_length, exit,
+               is_valid = mDNSfalse;
+               log_debug("NSEC record parsing failed because of incorrect rdata length - rdata length: %u", rdata_length));
+
+       if (out_next_domain_name != mDNSNULL)           *out_next_domain_name           = nsec->next_domain_name;
+       if (out_type_bit_maps != mDNSNULL)                      *out_type_bit_maps                      = (mDNSu8 *)rdata + next_domain_name_length;
+       if (out_type_bit_maps_length != mDNSNULL)       *out_type_bit_maps_length       = rdata_length - next_domain_name_length;
+
+       is_valid = mDNStrue;
+exit:
+       return is_valid;
+}
+
+mDNSexport mDNSBool
+parse_dns_type_nsec3_t(
+       const void * const      _Nonnull        rdata,
+       const mDNSu16                                   rdata_length,
+       mDNSu8 * const          _Nullable       out_hash_algorithm,
+       mDNSu8 * const          _Nullable       out_flags,
+       mDNSu16 * const         _Nullable       out_iterations,
+       mDNSu8 * const          _Nullable       out_salt_length,
+       mDNSu8 * const          _Nullable       out_hash_length,
+       mDNSu16 * const         _Nullable       out_type_bit_maps_length,
+       mDNSu8 * _Nonnull * const _Nullable out_salt,
+       mDNSu8 * _Nonnull * const _Nullable out_next_hashed_owner_name,
+       mDNSu8 * _Nonnull * const _Nullable out_type_bit_maps) {
+
+       mDNSBool is_valid = mDNSfalse;
+
+       dns_type_nsec3_t *nsec3 = (dns_type_nsec3_t *)rdata;
+
+       if (out_hash_algorithm != mDNSNULL)                     *out_hash_algorithm                     = nsec3->hash_algorithm;
+       if (out_flags != mDNSNULL)                                      *out_flags                                      = nsec3->flags;
+       if (out_iterations != mDNSNULL)                         *out_iterations                         = ntohs(nsec3->iterations);
+       if (out_salt_length != mDNSNULL)                        *out_salt_length                        = nsec3->salt_length;
+       if (out_salt != mDNSNULL)                                       *out_salt                                       = nsec3->salt;
+       require_action_quiet(rdata_length > offsetof(dns_type_nsec3_t, salt) + nsec3->salt_length, exit, is_valid = mDNSfalse);
+       if (out_hash_length != mDNSNULL)                        *out_hash_length                        = *((mDNSu8 *)rdata + offsetof(dns_type_nsec3_t, salt) + nsec3->salt_length);
+       require_action_quiet(rdata_length > offsetof(dns_type_nsec3_t, salt) + nsec3->salt_length + *out_hash_length + 1, exit, is_valid = mDNSfalse);
+       if (out_next_hashed_owner_name != mDNSNULL) *out_next_hashed_owner_name = (mDNSu8 *)rdata + offsetof(dns_type_nsec3_t, salt) + nsec3->salt_length + 1;
+       if (out_type_bit_maps_length != mDNSNULL)       *out_type_bit_maps_length       = rdata_length - (offsetof(dns_type_nsec3_t, salt) + nsec3->salt_length + *out_hash_length + 1);
+       if (out_type_bit_maps != mDNSNULL)                      *out_type_bit_maps                      = (mDNSu8 *)rdata + offsetof(dns_type_nsec3_t, salt) + nsec3->salt_length + *out_hash_length + 1;
+
+       is_valid = mDNStrue;
+exit:
+       if (!is_valid) {
+               log_debug("NSEC3 record parsing failed because of incorrect rdata length - rdata length: %u", rdata_length);
+       }
+       return is_valid;
+}
+
+mDNSexport mDNSu16
+get_covered_type_of_dns_type_rrsig_t(const void * const _Nonnull rdata) {
+       mDNSu16 type_covered = ntohs(((dns_type_rrsig_t *)rdata)->type_covered);
+       return type_covered;
+}
+
+//======================================================================================================================
+//     dnssec_rr_t parse functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_dnssec_rr_t
+//======================================================================================================================
+
+mDNSexport void
+initialize_dnssec_rr_t(dnssec_rr_t * const _Nonnull dnssec_rr, ResourceRecord * const _Nonnull rr) {
+       dnssec_rr->rr_type              = rr->rrtype;
+       dnssec_rr->rr_class             = rr->rrclass;
+       dnssec_rr->rdata_length = rr->rdlength;
+       dnssec_rr->name_hash    = rr->namehash;
+       dnssec_rr->rdata_hash   = rr->rdatahash;
+
+       memcpy(dnssec_rr->name.c, rr->name->c, DomainNameLength(rr->name));
+
+       dnssec_rr->rdata = rr->rdata->u.data;
+       dnssec_rr->rr = rr;
+}
+
+//======================================================================================================================
+//     unintialize_dnssec_rr_t
+//======================================================================================================================
+
+mDNSexport void
+uninitialize_dnssec_rr_t(dnssec_rr_t * const _Nonnull dnssec_rr) {
+       (void)dnssec_rr;
+}
+
+//======================================================================================================================
+//     equal_dnssec_rr_t
+//======================================================================================================================
+
+mDNSexport mDNSBool
+equal_dnssec_rr_t(const dnssec_rr_t * const _Nonnull left, const dnssec_rr_t * const _Nonnull right) {
+       return resource_records_equal(left->rr_type, right->rr_type, left->rr_class, right->rr_class,
+                               left->rdata_length, right->rdata_length, left->name_hash, right->name_hash, left->rdata_hash, right->rdata_hash,
+                               left->name.c, right->name.c, left->rdata, right->rdata);
+}
+
+//======================================================================================================================
+//     print_dnssec_rr_t
+//======================================================================================================================
+
+mDNSexport void
+print_dnssec_rr_t(const dnssec_rr_t * const _Nonnull dnssec_rr, mDNSu8 num_of_tabs) {
+       char *  rdata_base64    = mDNSNULL;
+
+       log_debug(TAB_STR PRI_DM_NAME " " PUB_S ":", TAB_PARAM(num_of_tabs),
+               DM_NAME_PARAM(&dnssec_rr->name), DNS_TYPE_STR(dnssec_rr->rr_type));
+
+       num_of_tabs += 1;
+       rdata_base64 = base_n_encode(DNSSEC_BASE_64, dnssec_rr->rdata, dnssec_rr->rdata_length);
+
+       log_debug(TAB_STR "Name Hash: %u, Rdata Hash: %u, Rdata Length: %u, Rdata: " BASE64_STR, TAB_PARAM(num_of_tabs),
+               dnssec_rr->name_hash, dnssec_rr->rdata_hash, dnssec_rr->rdata_length, BASE64_PARAM(rdata_base64));
+
+       free(rdata_base64);
+}
+
+//======================================================================================================================
+//     dnssec_original_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_dnssec_original_t
+//======================================================================================================================
+
+mDNSexport void
+initialize_dnssec_original_t(
+       dnssec_original_t * const               _Nonnull        original,
+       ResourceRecord * const                  _Nonnull        rr,
+       const mDNSBool                                                          answer_from_cache,
+       const DNSServiceErrorType                                       dns_error,
+       const QC_result                                                         qc_result) {
+
+       initialize_dnssec_rr_t(&original->dnssec_rr, rr);
+
+       original->answer_from_cache = answer_from_cache;
+       original->dns_error                     = dns_error;
+       original->qc_result                     = qc_result;
+}
+
+//======================================================================================================================
+//     uninitialize_dnssec_original_t
+//======================================================================================================================
+
+mDNSexport void
+uninitialize_dnssec_original_t(dnssec_original_t * const _Nonnull original) {
+       uninitialize_dnssec_rr_t(&original->dnssec_rr);
+}
+
+//======================================================================================================================
+//     print_dnssec_original_t
+//======================================================================================================================
+
+mDNSexport void
+print_dnssec_original_t(const dnssec_original_t * const _Nonnull original, mDNSu8 num_of_tabs) {
+       log_debug(TAB_STR PUB_S
+               "DNS Error: " PUB_S
+               ", QC Result: %u",
+               TAB_PARAM(num_of_tabs),
+               original->answer_from_cache ? "Answer from cache, " : "",
+               mStatusDescription(original->dns_error),
+               original->qc_result);
+
+       print_dnssec_rr_t(&original->dnssec_rr, num_of_tabs);
+}
+
+//======================================================================================================================
+//     dnssec_ds_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_dnssec_cname_t
+//======================================================================================================================
+
+mDNSexport void
+initialize_dnssec_cname_t(dnssec_cname_t * const _Nonnull cname, ResourceRecord * const _Nonnull rr) {
+       initialize_dnssec_rr_t(&cname->dnssec_rr, rr);
+       parsse_dns_type_cname_t(rr->rdata->u.data, &cname->cname);
+}
+
+//======================================================================================================================
+//     uninitialize_dnssec_cname_t
+//======================================================================================================================
+
+mDNSexport void
+uninitialize_dnssec_cname_t(dnssec_cname_t * const _Nonnull cname) {
+       uninitialize_dnssec_rr_t(&cname->dnssec_rr);
+}
+
+//======================================================================================================================
+//     print_dnssec_cname_t
+//======================================================================================================================
+
+mDNSexport void
+print_dnssec_cname_t(const dnssec_cname_t * const _Nonnull cname, mDNSu8 num_of_tabs) {
+       log_debug(TAB_STR "CNAME: " PRI_DM_NAME, TAB_PARAM(num_of_tabs), DM_NAME_PARAM((domainname *)cname->cname));
+}
+
+//======================================================================================================================
+//     dnssec_ds_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_dnssec_ds_t
+//======================================================================================================================
+
+mDNSexport mDNSBool
+initialize_dnssec_ds_t(dnssec_ds_t * const _Nonnull ds, ResourceRecord * const _Nonnull rr) {
+       mDNSBool is_valid = mDNSfalse;
+
+       initialize_dnssec_rr_t(&ds->dnssec_rr, rr);
+       is_valid = parse_dns_type_ds_t(ds->dnssec_rr.rdata, ds->dnssec_rr.rdata_length, &ds->key_tag, &ds->algorithm, &ds->digest_type,
+               &ds->digest_length, &ds->digest);
+
+       return is_valid;
+}
+
+//======================================================================================================================
+//     initialize_dnssec_ds_t
+//======================================================================================================================
+mDNSexport mDNSBool
+equals_dnssec_ds_t(const dnssec_ds_t * const left, const dnssec_ds_t * const right) {
+       if (left->key_tag               != right->key_tag)                      return mDNSfalse;
+       if (left->algorithm             != right->algorithm)            return mDNSfalse;
+       if (left->digest_type   != right->digest_type)          return mDNSfalse;
+       if (left->digest_length != right->digest_length)        return mDNSfalse;
+       if (memcmp(left->digest, right->digest, left->digest_length) != 0) return mDNSfalse;
+
+       return mDNStrue;
+}
+
+//======================================================================================================================
+//     uninitialize_dnssec_ds_t
+//======================================================================================================================
+
+mDNSexport void
+uninitialize_dnssec_ds_t(dnssec_ds_t * const _Nonnull ds) {
+       uninitialize_dnssec_rr_t(&ds->dnssec_rr);
+}
+
+//======================================================================================================================
+//     print_dnssec_ds_t
+//======================================================================================================================
+
+mDNSexport void
+print_dnssec_ds_t(const dnssec_ds_t * const _Nonnull ds, mDNSu8 num_of_tabs) {
+       char *digest_base64 = base_n_encode(DNSSEC_BASE_64, ds->digest, ds->digest_length);
+
+       log_debug(TAB_STR "Key Tag: %u, Algorithm: " PUB_S ", Digest Type: " PUB_S ", Digest Length: %u, Digest: " BASE64_STR, TAB_PARAM(num_of_tabs),
+               ds->key_tag,
+               dnssec_algorithm_value_to_string(ds->algorithm),
+               dnssec_digest_type_value_to_string(ds->digest_type),
+               ds->digest_length,
+               BASE64_PARAM(digest_base64));
+
+       free(digest_base64);
+}
+
+//======================================================================================================================
+//     dnssec_dnskey_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_dnssec_dnskey_t
+//======================================================================================================================
+
+mDNSexport mDNSBool
+initialize_dnssec_dnskey_t(dnssec_dnskey_t * const _Nonnull dnskey, ResourceRecord * const _Nonnull rr) {
+       mDNSBool is_valid = mDNSfalse;
+
+       initialize_dnssec_rr_t(&dnskey->dnssec_rr, rr);
+
+       is_valid = parse_dns_type_dnskey_t(dnskey->dnssec_rr.rdata, dnskey->dnssec_rr.rdata_length, &dnskey->flags,
+               &dnskey->protocol, &dnskey->algorithm, &dnskey->public_key_length, &dnskey->public_key);
+       require_quiet(is_valid, exit);
+
+       dnskey->key_tag = calculate_key_tag(dnskey->dnssec_rr.rdata, dnskey->dnssec_rr.rdata_length, dnskey->algorithm);
+
+       is_valid = mDNStrue;
+exit:
+       return is_valid;
+}
+
+//======================================================================================================================
+//     uninitialize_dnssec_dnskey_t
+//======================================================================================================================
+
+mDNSexport void
+uninitialize_dnssec_dnskey_t(dnssec_dnskey_t * const _Nonnull dnskey) {
+       uninitialize_dnssec_rr_t(&dnskey->dnssec_rr);
+}
+
+//======================================================================================================================
+//     uninitialize_dnssec_dnskey_t
+//======================================================================================================================
+
+mDNSexport mDNSBool
+equals_dnssec_dnskey_t(const dnssec_dnskey_t * const left, const dnssec_dnskey_t * const right) {
+       if (left->flags                         != right->flags)                                return mDNSfalse;
+       if (left->protocol                      != right->protocol)                             return mDNSfalse;
+       if (left->algorithm                     != right->algorithm)                    return mDNSfalse;
+       if (left->key_tag                       != right->key_tag)                              return mDNSfalse;
+       if (left->public_key_length != right->public_key_length)        return mDNSfalse;
+       if (memcmp(left->public_key, right->public_key, left->public_key_length) != 0)  return mDNSfalse;
+
+       return mDNStrue;
+}
+
+//======================================================================================================================
+//     print_dnssec_dnskey_t
+//======================================================================================================================
+
+mDNSexport void
+print_dnssec_dnskey_t(const dnssec_dnskey_t * const _Nonnull dnskey, mDNSu8 num_of_tabs) {
+       char *public_key_base64 = base_n_encode(DNSSEC_BASE_64, dnskey->public_key, dnskey->public_key_length);
+       char flags_string[64]; // 64 is big enough to hold all flags
+
+       log_debug(TAB_STR "Flags: " PUB_S ", Protocol: %u, Algorithm: " PUB_S ", Ket Tag: %u" ", Public Key Length: %u, Public Key: " BASE64_STR , TAB_PARAM(num_of_tabs),
+               dnssec_dnskey_flags_to_string(dnskey->flags, flags_string, sizeof(flags_string)), dnskey->protocol,
+               dnssec_algorithm_value_to_string(dnskey->algorithm), dnskey->key_tag, dnskey->public_key_length, BASE64_PARAM(public_key_base64));
+
+       free(public_key_base64);
+}
+
+//======================================================================================================================
+//     dnssec_rrsig_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_dnssec_rrsig_t
+//======================================================================================================================
+
+mDNSexport mDNSBool
+initialize_dnssec_rrsig_t(dnssec_rrsig_t * const _Nonnull rrsig, ResourceRecord * const _Nonnull rr) {
+       mDNSBool is_valid = mDNSfalse;
+
+       initialize_dnssec_rr_t(&rrsig->dnssec_rr, rr);
+       is_valid = parse_dns_type_rrsig_t(rrsig->dnssec_rr.rdata, rrsig->dnssec_rr.rdata_length, &rrsig->type_covered,
+               &rrsig->algorithm, &rrsig->labels, &rrsig->original_TTL, &rrsig->signature_expiration,
+               &rrsig->signature_inception, &rrsig->key_tag, &rrsig->signature_length, &rrsig->signer_name, &rrsig->signature);
+
+       return is_valid;
+}
+
+//======================================================================================================================
+//     uninitialize_dnssec_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+uninitialize_dnssec_rrsig_t(dnssec_rrsig_t * const _Nonnull rrsig) {
+       // rrsig != mDNSNULL;
+       uninitialize_dnssec_rr_t(&rrsig->dnssec_rr);
+}
+
+//======================================================================================================================
+//     print_dnssec_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+print_dnssec_rrsig_t(const dnssec_rrsig_t * const _Nonnull rrsig, mDNSu8 num_of_tabs) {
+       char    expiration_date_string[32]; // 32 is big enough to hold "1970-01-01 00:00:00-0800"
+       char    inception_date_string[32];
+       char *  signature_base64 = base_n_encode(DNSSEC_BASE_64, rrsig->signature, rrsig->signature_length);
+
+       log_debug(TAB_STR
+               "Type Covered: " PUB_S
+               ", Algorithm: " PUB_S
+               ", Labels: %u"
+               ", Original TTL: %u"
+               ", Signature Expiration: " PRI_S
+               ", Signature Inception: " PRI_S
+               ", Key Tag: %u"
+               ", Signature Length: %u"
+               ", Signer Name: " PRI_DM_NAME
+               ", Signature: " BASE64_STR,
+               TAB_PARAM(num_of_tabs),
+               DNS_TYPE_STR(rrsig->type_covered),
+               dnssec_algorithm_value_to_string(rrsig->algorithm),
+               rrsig->labels,
+               rrsig->original_TTL,
+               dnssec_epoch_time_to_date_string(rrsig->signature_expiration, expiration_date_string, sizeof(expiration_date_string)),
+               dnssec_epoch_time_to_date_string(rrsig->signature_inception, inception_date_string, sizeof(inception_date_string)),
+               rrsig->key_tag,
+               rrsig->signature_length,
+               DM_NAME_PARAM((domainname *)rrsig->signer_name),
+               BASE64_PARAM(signature_base64));
+
+       free(signature_base64);
+}
+
+//======================================================================================================================
+//     dnssec_nsec_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_dnssec_nsec_t
+//======================================================================================================================
+
+mDNSexport mDNSBool
+initialize_dnssec_nsec_t(dnssec_nsec_t * const _Nonnull nsec, ResourceRecord * const _Nonnull rr) {
+       mDNSBool is_valid = mDNSfalse;
+
+       initialize_dnssec_rr_t(&nsec->dnssec_rr, rr);
+       is_valid = parse_dns_type_nsec_t(nsec->dnssec_rr.rdata, nsec->dnssec_rr.rdata_length, &nsec->type_bit_maps_length, &nsec->next_domain_name, &nsec->type_bit_maps);
+       require_quiet(is_valid, exit);
+       nsec->exist_domain_name = nsec->dnssec_rr.name.c;
+
+       is_valid = mDNStrue;
+exit:
+       return is_valid;
+}
+
+//======================================================================================================================
+//     uninitialize_dnssec_nsec_t
+//======================================================================================================================
+
+mDNSexport void
+uninitialize_dnssec_nsec_t(dnssec_nsec_t * const _Nonnull nsec) {
+       uninitialize_dnssec_rr_t(&nsec->dnssec_rr);
+}
+
+//======================================================================================================================
+//     print_dnssec_nsec_t
+//======================================================================================================================
+
+mDNSexport void
+print_dnssec_nsec_t(const dnssec_nsec_t * const _Nonnull nsec, mDNSu8 num_of_tabs) {
+       char string_buffer[1024];
+
+       log_debug(TAB_STR
+               "Domain Name: " PRI_DM_NAME
+               ", Next Domain Name: " PRI_DM_NAME
+               ", Type Bit Maps Length: %u"
+               ", Type Bit Maps: " PUB_S,
+               TAB_PARAM(num_of_tabs),
+               DM_NAME_PARAM((domainname *)nsec->exist_domain_name),
+               DM_NAME_PARAM((domainname *)nsec->next_domain_name),
+               nsec->type_bit_maps_length,
+               type_bit_map_to_cstring(nsec->type_bit_maps, nsec->type_bit_maps_length, string_buffer, sizeof(string_buffer)));
+}
+
+mDNSlocal char *
+type_bit_map_to_cstring(
+       const mDNSu8 * const _Nonnull   bit_map,
+       const mDNSu16                                   map_length,
+       char *                                                  buffer,
+       mDNSu32                                                 buffer_length) {
+
+       const mDNSu8 *                  ptr                                             = bit_map;
+       const mDNSu8 * const    ptr_limit                               = ptr + map_length;
+       char *                                  buffer_ptr                              = buffer;
+       const char * const              buffer_limit                    = buffer + buffer_length - 4;
+
+       for (; ptr < ptr_limit; ptr += 2 + *(ptr + 1)) {
+               const mDNSu8    window_index                    = *ptr;
+               const mDNSu8    block_bit_map_length    = *(ptr + 1);
+               const mDNSu32   bit_count                               = block_bit_map_length * 8;
+               const mDNSu8 *  current_block                   = ptr + 2;
+
+               for (mDNSu32 i = 0; i < bit_count; i++) {
+                       const mDNSu8 mask       = 1 << (7 - (i % 8));
+                       mDNSBool bit_set        = (current_block[i / 8] & mask) != 0;
+                       if (bit_set) {
+                               const char * const dns_type_cstring = DNS_TYPE_STR(window_index * 256 + i);
+                               const mDNSs32 bytes_will_be_written = snprintf(buffer_ptr, buffer_limit - buffer_ptr, "%s ", dns_type_cstring);
+                               if (bytes_will_be_written + buffer_ptr >= buffer_limit) {
+                                       snprintf(buffer_ptr, buffer_limit - buffer_ptr + 4, "...");
+                                       return buffer;
+                               }
+                               buffer_ptr += bytes_will_be_written;
+                       }
+               }
+       }
+
+       return buffer;
+}
+
+//======================================================================================================================
+//     dnssec_nsec3_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_dnssec_nsec3_t
+//======================================================================================================================
+
+mDNSexport mDNSBool
+initialize_dnssec_nsec3_t(dnssec_nsec3_t * const _Nonnull nsec3, ResourceRecord * const _Nonnull rr) {
+       mDNSBool is_valid = mDNSfalse;
+
+       initialize_dnssec_rr_t(&nsec3->dnssec_rr, rr);
+       is_valid = parse_dns_type_nsec3_t(nsec3->dnssec_rr.rdata, nsec3->dnssec_rr.rdata_length, &nsec3->hash_algorithm, &nsec3->flags, &nsec3->iterations,
+               &nsec3->salt_length, &nsec3->hash_length, &nsec3->type_bit_maps_length, &nsec3->salt,
+               &nsec3->next_hashed_owner_name, &nsec3->type_bit_maps);
+       require_action_quiet(is_valid, exit, is_valid = mDNSfalse);
+
+       nsec3->next_hashed_owner_name_b32 = base_n_encode(DNSSEC_BASE_32_HEX, nsec3->next_hashed_owner_name, nsec3->hash_length);
+       nsec3->next_hashed_owner_name_b32_length = strlen(nsec3->next_hashed_owner_name_b32);
+
+       is_valid = mDNStrue;
+exit:
+       return is_valid;
+}
+
+//======================================================================================================================
+//     uninitialize_dnssec_nsec3_t
+//======================================================================================================================
+
+mDNSexport void
+uninitialize_dnssec_nsec3_t(dnssec_nsec3_t * const _Nonnull nsec3) {
+       uninitialize_dnssec_rr_t(&nsec3->dnssec_rr);
+       free(nsec3->next_hashed_owner_name_b32);
+}
+
+//======================================================================================================================
+//     print_dnssec_nsec3_t
+//======================================================================================================================
+
+mDNSexport void
+print_dnssec_nsec3_t(const dnssec_nsec3_t * const _Nonnull nsec3, mDNSu8 num_of_tabs) {
+       char    flags_string[32]; // 32 is big enough to hold the flag string
+       char *  salt_base64 = base_n_encode(DNSSEC_BASE_64, nsec3->salt, nsec3->salt_length);
+       char    string_buffer[1024];
+
+       log_debug(TAB_STR
+               "Hash Algorithm: " PUB_S
+               ", Flags: " PUB_S
+               ", Iterations: %u"
+               ", Salt Length: %u"
+               ", Hash Length: %u"
+               ", Type Bit Maps Length: %u"
+               ", Salt: " BASE64_STR
+               ", Next Hash Owner Name: " PRI_S
+               ", Type Bit Maps: " PUB_S,
+               TAB_PARAM(num_of_tabs),
+               dnssec_algorithm_value_to_string(nsec3->hash_algorithm),
+               dnssec_nsec3_flags_to_string(nsec3->flags, flags_string, sizeof(flags_string)),
+               nsec3->iterations,
+               nsec3->salt_length,
+               nsec3->hash_length,
+               nsec3->type_bit_maps_length,
+               BASE64_PARAM(salt_base64),
+               nsec3->next_hashed_owner_name_b32,
+               type_bit_map_to_cstring(nsec3->type_bit_maps, nsec3->type_bit_maps_length, string_buffer, sizeof(string_buffer)));
+
+       free(salt_base64);
+}
+
+//======================================================================================================================
+//     nsecs_with_rrsig_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_nsecs_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport mStatus
+initialize_nsecs_with_rrsig_t(nsecs_with_rrsig_t * const _Nonnull nsecs) {
+       list_init(&nsecs->nsec_and_rrsigs_same_name, sizeof(one_nsec_with_rrsigs_t));
+       list_init(&nsecs->wildcard_answers, sizeof(dnssec_rr_t));
+       list_init(&nsecs->wildcard_rrsigs, sizeof(dnssec_rrsig_t));
+       nsecs->negative_rr = mDNSNULL;
+
+       return mStatus_NoError;
+}
+
+//======================================================================================================================
+//     uninitialize_nsecs_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+uninitialize_nsecs_with_rrsig_t(nsecs_with_rrsig_t * const _Nonnull nsecs) {
+       for (list_node_t * one_nsec_with_rrsigs_node = list_get_first(&nsecs->nsec_and_rrsigs_same_name);
+                !list_has_ended(&nsecs->nsec_and_rrsigs_same_name, one_nsec_with_rrsigs_node);
+                one_nsec_with_rrsigs_node = list_next(one_nsec_with_rrsigs_node)) {
+
+               one_nsec_with_rrsigs_t * one_nsec_with_rrsigs = (one_nsec_with_rrsigs_t *)one_nsec_with_rrsigs_node->data;
+               uninitialize_one_nsec_with_rrsigs_t(one_nsec_with_rrsigs);
+       }
+       list_uninit(&nsecs->nsec_and_rrsigs_same_name);
+       for (list_node_t * dnssec_rr_node = list_get_first(&nsecs->wildcard_answers);
+                !list_has_ended(&nsecs->wildcard_answers, dnssec_rr_node);
+                dnssec_rr_node = list_next(dnssec_rr_node)) {
+
+               dnssec_rr_t * dnssec_rr = (dnssec_rr_t *)dnssec_rr_node->data;
+               uninitialize_dnssec_rr_t(dnssec_rr);
+       }
+       list_uninit(&nsecs->wildcard_answers);
+       for (list_node_t * dnssec_rrsig_node = list_get_first(&nsecs->wildcard_rrsigs);
+                !list_has_ended(&nsecs->wildcard_rrsigs, dnssec_rrsig_node);
+                dnssec_rrsig_node = list_next(dnssec_rrsig_node)) {
+
+               dnssec_rrsig_t * dnssec_rrsig = (dnssec_rrsig_t *)dnssec_rrsig_node->data;
+               uninitialize_dnssec_rrsig_t(dnssec_rrsig);
+       }
+       list_uninit(&nsecs->wildcard_rrsigs);
+       nsecs->negative_rr = mDNSNULL;
+}
+
+//======================================================================================================================
+//     print_nsecs_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+print_nsecs_with_rrsig_t(const nsecs_with_rrsig_t * const _Nonnull nsecs, mDNSu8 num_of_tabs) {
+
+       const list_t * list_ptr;
+
+       list_ptr = &nsecs->wildcard_answers;
+       log_debug(TAB_STR "Wildcard:", TAB_PARAM(num_of_tabs));
+       for (list_node_t *node = list_get_first(list_ptr); !list_has_ended(list_ptr, node); node = list_next(node)) {
+               const dnssec_rr_t * const dnssec_rr = (dnssec_rr_t *) node->data;
+               print_dnssec_rr_t(dnssec_rr, num_of_tabs + 1);
+       }
+
+       list_ptr = &nsecs->wildcard_rrsigs;
+       log_debug(TAB_STR "RRSIG:", TAB_PARAM(num_of_tabs));
+       for (list_node_t *node = list_get_first(list_ptr); !list_has_ended(list_ptr, node); node = list_next(node)) {
+               const dnssec_rrsig_t * const dnssec_rrsig = (dnssec_rrsig_t *) node->data;
+               print_dnssec_rrsig_t(dnssec_rrsig, num_of_tabs + 1);
+       }
+
+       list_ptr = &nsecs->nsec_and_rrsigs_same_name;
+       for (list_node_t *node = list_get_first(list_ptr); !list_has_ended(list_ptr, node); node = list_next(node)) {
+               const one_nsec_with_rrsigs_t * const one_nsec = (const one_nsec_with_rrsigs_t * const)node->data;
+               log_debug(TAB_STR "Owner Name:" PRI_DM_NAME, TAB_PARAM(num_of_tabs), DM_NAME_PARAM((const domainname *)one_nsec->owner_name));
+
+               log_debug(TAB_STR "NSEC:", TAB_PARAM(num_of_tabs));
+               print_dnssec_nsec_t(&one_nsec->nsec_record, num_of_tabs + 1);
+
+               log_debug(TAB_STR "RRSIG:", TAB_PARAM(num_of_tabs));
+               for (list_node_t *rrsig_node = list_get_first(&one_nsec->rrsig_records);
+                        !list_has_ended(&one_nsec->rrsig_records, rrsig_node);
+                        rrsig_node = list_next(rrsig_node)) {
+                       const dnssec_rrsig_t * const rrsig = (const dnssec_rrsig_t * const)rrsig_node->data;
+                       print_dnssec_rrsig_t(rrsig, num_of_tabs + 1);
+               }
+       }
+}
+
+# pragma mark - one_nsec_with_rrsigs_t functions
+
+
+
+# pragma mark initialize_one_nsec_with_rrsigs_t
+mDNSexport mDNSBool
+initialize_one_nsec_with_rrsigs_t(one_nsec_with_rrsigs_t * const one_nsec_with_rrsigs, ResourceRecord * const rr) {
+       mDNSBool is_valid = mDNSfalse;
+
+       is_valid = initialize_dnssec_nsec_t(&one_nsec_with_rrsigs->nsec_record, rr);
+       require_quiet(is_valid, exit);
+       one_nsec_with_rrsigs->owner_name = one_nsec_with_rrsigs->nsec_record.dnssec_rr.name.c;
+       list_init(&one_nsec_with_rrsigs->rrsig_records, sizeof(dnssec_rrsig_t));
+
+       is_valid = mDNStrue;
+exit:
+       return is_valid;
+}
+
+# pragma mark uninitialize_one_nsec_with_rrsigs_t
+mDNSexport void
+uninitialize_one_nsec_with_rrsigs_t(one_nsec_with_rrsigs_t * const     _Nonnull one_nsec_with_rrsigs) {
+       for (list_node_t * dnssec_rrsig_node = list_get_first(&one_nsec_with_rrsigs->rrsig_records);
+                !list_has_ended(&one_nsec_with_rrsigs->rrsig_records, dnssec_rrsig_node);
+                dnssec_rrsig_node = list_next(dnssec_rrsig_node)) {
+
+               dnssec_rrsig_t * dnssec_rrsig = (dnssec_rrsig_t *)dnssec_rrsig_node->data;
+               uninitialize_dnssec_rrsig_t(dnssec_rrsig);
+       }
+       list_uninit(&one_nsec_with_rrsigs->rrsig_records);
+       uninitialize_dnssec_nsec_t(&one_nsec_with_rrsigs->nsec_record);
+}
+//======================================================================================================================
+//     nsec3s_with_rrsig_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_nsec3s_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport mStatus
+initialize_nsec3s_with_rrsig_t(nsec3s_with_rrsig_t * const _Nonnull nsec3s) {
+       list_init(&nsec3s->nsec3_and_rrsigs_same_name, sizeof(one_nsec3_with_rrsigs_t));
+       list_init(&nsec3s->wildcard_answers, sizeof(dnssec_rr_t));
+       list_init(&nsec3s->wildcard_rrsigs, sizeof(dnssec_rrsig_t));
+       nsec3s->negative_rr = mDNSNULL;
+
+       return mStatus_NoError;
+}
+
+//======================================================================================================================
+//     uninitialize_nsec3s_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+uninitialize_nsec3s_with_rrsig_t(nsec3s_with_rrsig_t * const _Nonnull nsec3s) {
+       for (list_node_t * one_nsec3_with_rrsigs_node = list_get_first(&nsec3s->nsec3_and_rrsigs_same_name);
+                !list_has_ended(&nsec3s->nsec3_and_rrsigs_same_name, one_nsec3_with_rrsigs_node);
+                one_nsec3_with_rrsigs_node = list_next(one_nsec3_with_rrsigs_node)) {
+
+               one_nsec3_with_rrsigs_t * one_nsec3_with_rrsigs = (one_nsec3_with_rrsigs_t *)one_nsec3_with_rrsigs_node->data;
+               uninitialize_one_nsec3_with_rrsigs_t(one_nsec3_with_rrsigs);
+       }
+       list_uninit(&nsec3s->nsec3_and_rrsigs_same_name);
+       for (list_node_t * dnssec_rr_node = list_get_first(&nsec3s->wildcard_answers);
+                !list_has_ended(&nsec3s->wildcard_answers, dnssec_rr_node);
+                dnssec_rr_node = list_next(dnssec_rr_node)) {
+
+               dnssec_rr_t * dnssec_rr = (dnssec_rr_t *)dnssec_rr_node->data;
+               uninitialize_dnssec_rr_t(dnssec_rr);
+       }
+       list_uninit(&nsec3s->wildcard_answers);
+       for (list_node_t * dnssec_rrsig_node = list_get_first(&nsec3s->wildcard_rrsigs);
+                !list_has_ended(&nsec3s->wildcard_rrsigs, dnssec_rrsig_node);
+                dnssec_rrsig_node = list_next(dnssec_rrsig_node)) {
+
+               dnssec_rrsig_t * dnssec_rrsig = (dnssec_rrsig_t *)dnssec_rrsig_node->data;
+               uninitialize_dnssec_rrsig_t(dnssec_rrsig);
+       }
+       list_uninit(&nsec3s->wildcard_rrsigs);
+       nsec3s->negative_rr = mDNSNULL;
+}
+
+//======================================================================================================================
+//     print_nsec3s_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+print_nsec3s_with_rrsig_t(const nsec3s_with_rrsig_t * const _Nonnull nsec3s, mDNSu8 num_of_tabs) {
+       const list_t * list_ptr;
+
+       list_ptr = &nsec3s->wildcard_answers;
+       log_debug(TAB_STR "Wildcard:", TAB_PARAM(num_of_tabs));
+       for (list_node_t *node = list_get_first(list_ptr); !list_has_ended(list_ptr, node); node = list_next(node)) {
+               const dnssec_rr_t * const dnssec_rr = (dnssec_rr_t *) node->data;
+               print_dnssec_rr_t(dnssec_rr, num_of_tabs + 1);
+       }
+
+       list_ptr = &nsec3s->wildcard_rrsigs;
+       log_debug(TAB_STR "RRSIG:", TAB_PARAM(num_of_tabs));
+       for (list_node_t *node = list_get_first(list_ptr); !list_has_ended(list_ptr, node); node = list_next(node)) {
+               const dnssec_rrsig_t * const dnssec_rrsig = (dnssec_rrsig_t *) node->data;
+               print_dnssec_rrsig_t(dnssec_rrsig, num_of_tabs + 1);
+       }
+
+       list_ptr = &nsec3s->nsec3_and_rrsigs_same_name;
+       for (list_node_t *node = list_get_first(list_ptr); !list_has_ended(list_ptr, node); node = list_next(node)) {
+               const one_nsec3_with_rrsigs_t * const one_nsec3 = (const one_nsec3_with_rrsigs_t * const)node->data;
+               log_debug(TAB_STR "Owner Name:" PRI_DM_NAME, TAB_PARAM(num_of_tabs), DM_NAME_PARAM((const domainname *)one_nsec3->owner_name));
+
+               log_debug(TAB_STR "NSEC3:", TAB_PARAM(num_of_tabs));
+               print_dnssec_nsec3_t(&one_nsec3->nsec3_record, num_of_tabs + 1);
+
+               log_debug(TAB_STR "RRSIG:", TAB_PARAM(num_of_tabs));
+               for (list_node_t *rrsig_node = list_get_first(&one_nsec3->rrsig_records);
+                        !list_has_ended(&one_nsec3->rrsig_records, rrsig_node);
+                        rrsig_node = list_next(rrsig_node)) {
+                       const dnssec_rrsig_t * const rrsig = (const dnssec_rrsig_t * const)rrsig_node->data;
+                       print_dnssec_rrsig_t(rrsig, num_of_tabs + 1);
+               }
+       }
+}
+
+# pragma mark - one_nsec_with_rrsigs_t functions
+
+
+
+# pragma mark initialize_one_nsec3_with_rrsigs_t
+mDNSexport mDNSBool
+initialize_one_nsec3_with_rrsigs_t(
+       one_nsec3_with_rrsigs_t * const _Nonnull        one_nsec3_with_rrsigs,
+       ResourceRecord * const                  _Nonnull        rr) {
+
+       mDNSBool is_valid = mDNSfalse;
+
+       is_valid = initialize_dnssec_nsec3_t(&one_nsec3_with_rrsigs->nsec3_record, rr);
+       require_action_quiet(is_valid, exit, is_valid = mDNSfalse);
+       one_nsec3_with_rrsigs->owner_name = one_nsec3_with_rrsigs->nsec3_record.dnssec_rr.name.c;
+       list_init(&one_nsec3_with_rrsigs->rrsig_records, sizeof(dnssec_rrsig_t));
+
+       is_valid = mDNStrue;
+exit:
+       return is_valid;
+}
+
+# pragma mark uninitialize_one_nsec3_with_rrsigs_t
+mDNSexport void
+uninitialize_one_nsec3_with_rrsigs_t(one_nsec3_with_rrsigs_t * const _Nonnull one_nsec3_with_rrsigs) {
+       for (list_node_t * dnssec_rrsig_node = list_get_first(&one_nsec3_with_rrsigs->rrsig_records);
+                !list_has_ended(&one_nsec3_with_rrsigs->rrsig_records, dnssec_rrsig_node);
+                dnssec_rrsig_node = list_next(dnssec_rrsig_node)) {
+
+               dnssec_rrsig_t * dnssec_rrsig = (dnssec_rrsig_t *)dnssec_rrsig_node->data;
+               uninitialize_dnssec_rrsig_t(dnssec_rrsig);
+       }
+       list_uninit(&one_nsec3_with_rrsigs->rrsig_records);
+       uninitialize_dnssec_nsec3_t(&one_nsec3_with_rrsigs->nsec3_record);
+}
+
+//======================================================================================================================
+//     cnames_with_rrsig_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_cname_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+initialize_cname_with_rrsig_t(cnames_with_rrsig_t * const _Nonnull cname) {
+       list_init(&cname->cname_records, sizeof(dnssec_cname_t));
+       list_init(&cname->rrsig_records, sizeof(dnssec_rrsig_t));
+}
+
+//======================================================================================================================
+//     uninitialize_cname_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+uninitialize_cname_with_rrsig_t(cnames_with_rrsig_t * const _Nonnull cname) {
+       for (list_node_t * dnssec_cname_node = list_get_first(&cname->cname_records);
+                !list_has_ended(&cname->cname_records, dnssec_cname_node);
+                dnssec_cname_node = list_next(dnssec_cname_node)) {
+
+               dnssec_cname_t * dnssec_cname = (dnssec_cname_t *)dnssec_cname_node->data;
+               uninitialize_dnssec_cname_t(dnssec_cname);
+       }
+       list_uninit(&cname->cname_records);
+       for (list_node_t *dnssec_rrsig_node = list_get_first(&cname->rrsig_records);
+                !list_has_ended(&cname->rrsig_records, dnssec_rrsig_node);
+                dnssec_rrsig_node = list_next(dnssec_rrsig_node)) {
+
+               dnssec_rrsig_t * dnssec_rrsig = (dnssec_rrsig_t *)dnssec_rrsig_node->data;
+               uninitialize_dnssec_rrsig_t(dnssec_rrsig);
+       }
+       list_uninit(&cname->rrsig_records);
+}
+
+//======================================================================================================================
+//     print_cname_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+print_cname_with_rrsig_t(const cnames_with_rrsig_t * const _Nonnull cnames, mDNSu8 num_of_tabs) {
+       const list_t * const cname_records = &cnames->cname_records;
+       const list_t * const rrsig_records = &cnames->rrsig_records;
+
+
+       log_debug(TAB_STR "CNAME:", TAB_PARAM(num_of_tabs));
+       for (list_node_t *node = list_get_first(cname_records); !list_has_ended(cname_records, node); node = list_next(node)) {
+               dnssec_cname_t *cname = (dnssec_cname_t *)node->data;
+               print_dnssec_cname_t(cname, num_of_tabs + 1);
+       }
+
+       log_debug(TAB_STR "RRSIG:", TAB_PARAM(num_of_tabs));
+       for (list_node_t *node = list_get_first(rrsig_records); !list_has_ended(rrsig_records, node); node = list_next(node)) {
+               dnssec_rrsig_t *rrsig = (dnssec_rrsig_t *)node->data;
+               print_dnssec_rrsig_t(rrsig, num_of_tabs + 1);
+       }
+}
+
+//======================================================================================================================
+//     response_type_t functions
+//======================================================================================================================
+
+mDNSexport const char * _Nonnull
+response_type_value_to_string(response_type_t type) {
+       static const char * const string_table[] =
+       {
+               "Unknown Response",             // 0
+               "Original Response",    // 1
+               "CNAME Response",               // 2
+               "NSEC Response",                // 3
+               "NSEC3 Response"                // 4
+       };
+       if (type >= sizeof(string_table) / sizeof(char *)) {
+               type = 0;
+       }
+
+       return string_table[type];
+}
+
+//======================================================================================================================
+//     originals_with_rrsig_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_originals_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+initialize_originals_with_rrsig_t(originals_with_rrsig_t * const _Nonnull original, const response_type_t type) {
+       original->type                  = type;
+
+       switch (type) {
+               case original_response:
+                       list_init(&original->u.original.original_records, sizeof(dnssec_original_t));
+                       list_init(&original->u.original.rrsig_records, sizeof(dnssec_rrsig_t));
+                       original->u.original.negative_rr = mDNSNULL;
+                       original->u.original.suppressed_response = mDNSfalse;
+                       break;
+               case cname_response:
+                       initialize_cname_with_rrsig_t(&original->u.cname_with_rrsig);
+                       break;
+               case nsec_response:
+                       initialize_nsecs_with_rrsig_t(&original->u.nsecs_with_rrsig);
+                       break;
+               case nsec3_response:
+                       initialize_nsec3s_with_rrsig_t(&original->u.nsec3s_with_rrsig);
+                       break;
+               default:
+                       verify(mDNSfalse);
+       }
+}
+
+//======================================================================================================================
+//     uninitialize_originals_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+uninitialize_originals_with_rrsig_t(originals_with_rrsig_t * const _Nonnull original) {
+       switch (original->type) {
+               case original_response:
+                       for (list_node_t * dnssec_original_node = list_get_first(&original->u.original.original_records);
+                                !list_has_ended(&original->u.original.original_records, dnssec_original_node);
+                                dnssec_original_node = list_next(dnssec_original_node)) {
+
+                               dnssec_original_t * dnssec_original = (dnssec_original_t *)dnssec_original_node->data;
+                               uninitialize_dnssec_original_t(dnssec_original);
+                       }
+                       list_uninit(&original->u.original.original_records);
+                       for (list_node_t * dnssec_rrsig_node = list_get_first(&original->u.original.rrsig_records);
+                                !list_has_ended(&original->u.original.rrsig_records, dnssec_rrsig_node);
+                                dnssec_rrsig_node = list_next(dnssec_rrsig_node)) {
+
+                               dnssec_rrsig_t * dnssec_rrsig = (dnssec_rrsig_t *)dnssec_rrsig_node->data;
+                               uninitialize_dnssec_rrsig_t(dnssec_rrsig);
+                       }
+                       list_uninit(&original->u.original.rrsig_records);
+                       original->u.original.negative_rr                        = mDNSNULL;
+                       original->u.original.suppressed_response        = mDNSfalse;
+                       break;
+               case cname_response:
+                       uninitialize_cname_with_rrsig_t(&original->u.cname_with_rrsig);
+                       break;
+               case nsec_response:
+                       uninitialize_nsecs_with_rrsig_t(&original->u.nsecs_with_rrsig);
+                       break;
+               case nsec3_response:
+                       uninitialize_nsec3s_with_rrsig_t(&original->u.nsec3s_with_rrsig);
+                       break;
+               default:
+                       // it is possible that the DNSSEC rquest is stopped we receive any response
+                       break;
+       }
+       original->type                  = unknown_response;
+}
+
+//======================================================================================================================
+//     contains_rrsig_in_nsecs_with_rrsig_t
+//======================================================================================================================
+
+mDNSlocal mDNSBool
+contains_rrsig_in_nsecs_with_rrsig_t(const nsecs_with_rrsig_t * const _Nonnull nsecs) {
+       const list_t * const nsec_list = &nsecs->nsec_and_rrsigs_same_name;
+       mDNSBool contains_non_empty_rrsig_list = mDNSfalse;
+       for (list_node_t *nsec_node = list_get_first(nsec_list);
+                !list_has_ended(nsec_list, nsec_node);
+                nsec_node = list_next(nsec_node)) {
+               const one_nsec_with_rrsigs_t * const    one_nsec        = (const one_nsec_with_rrsigs_t * const)nsec_node->data;
+               const list_t * const                                    rrsig_list      = &one_nsec->rrsig_records;
+               if (!list_empty(rrsig_list)) {
+                       contains_non_empty_rrsig_list = mDNStrue;
+                       break;
+               }
+       }
+       if (!contains_non_empty_rrsig_list) {
+               return mDNSfalse;
+       }
+
+       return mDNStrue;
+}
+
+//======================================================================================================================
+//     contains_rrsig_in_nsecs_with_rrsig_t
+//======================================================================================================================
+
+mDNSlocal mDNSBool
+contains_rrsig_in_nsec3s_with_rrsig_t(const nsec3s_with_rrsig_t * const _Nonnull nsec3s) {
+       const list_t * const nsec3_list = &nsec3s->nsec3_and_rrsigs_same_name;
+       mDNSBool contains_non_empty_rrsig_list = mDNSfalse;
+       for (list_node_t *nsec3_node = list_get_first(nsec3_list);
+                !list_has_ended(nsec3_list, nsec3_node);
+                nsec3_node = list_next(nsec3_node)) {
+               const one_nsec3_with_rrsigs_t * const   one_nsec3       = (const one_nsec3_with_rrsigs_t * const)nsec3_node->data;
+               const list_t * const                                    rrsig_list      = &one_nsec3->rrsig_records;
+               if (!list_empty(rrsig_list)) {
+                       contains_non_empty_rrsig_list = mDNStrue;
+                       break;
+               }
+       }
+       if (!contains_non_empty_rrsig_list) {
+               return mDNSfalse;
+       }
+
+       return mDNStrue;
+}
+
+//======================================================================================================================
+//     contains_rrsig_in_originals_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport mDNSBool
+contains_rrsig_in_originals_with_rrsig_t(const originals_with_rrsig_t * const _Nonnull original) {
+       const list_t *rrsig_list;
+       const response_type_t type = original->type;
+
+       if (type == original_response || type == cname_response) {
+               if (type == original_response) {
+                       rrsig_list = &original->u.original.rrsig_records;
+               } else { // type == cname_response
+                       rrsig_list = &original->u.cname_with_rrsig.rrsig_records;
+               }
+
+               if (rrsig_list == mDNSNULL) {
+                       log_error("originals_with_rrsig_t has unknown response, it should never heppen");
+                       return mDNSfalse;
+               }
+
+               if (list_empty(rrsig_list)) {
+                       return mDNSfalse;
+               }
+       } else if (type == nsec_response) {
+               if (!contains_rrsig_in_nsecs_with_rrsig_t(&original->u.nsecs_with_rrsig)) {
+                       return mDNSfalse;
+               }
+       } else if (type == nsec3_response) {
+               if (!contains_rrsig_in_nsec3s_with_rrsig_t(&original->u.nsec3s_with_rrsig)) {
+                       return mDNStrue;
+               }
+       } else {
+               log_error("originals_with_rrsig_t has unknown response, it should never heppen");
+               return mDNSfalse;
+       }
+
+       return mDNStrue;
+}
+
+//======================================================================================================================
+//     print_originals_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+print_originals_with_rrsig_t(const originals_with_rrsig_t * const _Nonnull original, mDNSu8 num_of_tabs) {
+       log_debug(TAB_STR "Response Type: " PUB_S, TAB_PARAM(num_of_tabs),
+                         response_type_value_to_string(original->type));
+
+       switch (original->type) {
+               case original_response: {
+                       const list_t * const original_records   = &original->u.original.original_records;
+                       const list_t * const rrsig_records              = &original->u.original.rrsig_records;
+
+                       log_debug(TAB_STR "Original:", TAB_PARAM(num_of_tabs));
+                       for (list_node_t *node = list_get_first(original_records); !list_has_ended(original_records, node); node = list_next(node)) {
+                               dnssec_original_t *dnssec_original = (dnssec_original_t *)node->data;
+                               print_dnssec_original_t(dnssec_original, num_of_tabs + 1);
+                       }
+
+                       log_debug(TAB_STR "RRSIG:", TAB_PARAM(num_of_tabs));
+                       for (list_node_t *node = list_get_first(rrsig_records); !list_has_ended(rrsig_records, node); node = list_next(node)) {
+                               dnssec_rrsig_t *dnssec_rrsig = (dnssec_rrsig_t *)node->data;
+                               print_dnssec_rrsig_t(dnssec_rrsig, num_of_tabs + 1);
+                       }
+               }
+                       break;
+               case cname_response:
+                       print_cname_with_rrsig_t(&original->u.cname_with_rrsig, num_of_tabs + 1);
+                       break;
+               case nsec_response:
+                       print_nsecs_with_rrsig_t(&original->u.nsecs_with_rrsig, num_of_tabs + 1);
+                       break;
+               case nsec3_response:
+                       print_nsec3s_with_rrsig_t(&original->u.nsec3s_with_rrsig, num_of_tabs + 1);
+                       break;
+               case unknown_response:
+                       log_debug(TAB_STR "Unknown Response", TAB_PARAM(num_of_tabs));
+                       break;
+               default:
+                       log_debug(TAB_STR "Invalid", TAB_PARAM(num_of_tabs));
+                       break;
+       }
+}
+
+//======================================================================================================================
+//     dses_with_rrsig_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_dses_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+initialize_dses_with_rrsig_t(dses_with_rrsig_t * const _Nonnull ds, const response_type_t type) {
+       ds->type                        = type;
+       ds->set_completed       = mDNSfalse;
+
+       switch (type) {
+               case original_response:
+                       list_init(&ds->u.original.ds_records, sizeof(dnssec_ds_t));
+                       list_init(&ds->u.original.rrsig_records, sizeof(dnssec_rrsig_t));
+                       break;
+               case nsec_response:
+                       initialize_nsecs_with_rrsig_t(&ds->u.nsecs_with_rrsig);
+                       break;
+               case nsec3_response:
+                       initialize_nsec3s_with_rrsig_t(&ds->u.nsec3s_with_rrsig);
+                       break;
+               default:
+                       break;
+       }
+}
+
+//======================================================================================================================
+//     uninitialize_dses_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+uninitialize_dses_with_rrsig_t(dses_with_rrsig_t * const _Nonnull ds) {
+       switch (ds->type) {
+               case original_response: {
+                       list_t * ds_records     = &ds->u.original.ds_records;
+                       list_t * rrsig_records  = &ds->u.original.rrsig_records;
+                       for (list_node_t * dnssec_ds_node = list_get_first(ds_records);
+                                !list_has_ended(ds_records, dnssec_ds_node);
+                                dnssec_ds_node = list_next(dnssec_ds_node)) {
+
+                               dnssec_ds_t * dnssec_ds = (dnssec_ds_t *)dnssec_ds_node->data;
+                               uninitialize_dnssec_ds_t(dnssec_ds);
+                       }
+                       list_uninit(ds_records);
+                       for (list_node_t * dnssec_rrsig_node = list_get_first(rrsig_records);
+                                !list_has_ended(rrsig_records, dnssec_rrsig_node);
+                                dnssec_rrsig_node = list_next(dnssec_rrsig_node)) {
+
+                               dnssec_rrsig_t * dnssec_rrsig = (dnssec_rrsig_t *)dnssec_rrsig_node->data;
+                               uninitialize_dnssec_rrsig_t(dnssec_rrsig);
+                       }
+                       list_uninit(rrsig_records);
+                       break;
+               }
+               case nsec_response:
+                       uninitialize_nsecs_with_rrsig_t(&ds->u.nsecs_with_rrsig);
+                       break;
+               case nsec3_response:
+                       uninitialize_nsec3s_with_rrsig_t(&ds->u.nsec3s_with_rrsig);
+                       break;
+               default:
+                       break;
+       }
+
+       ds->type                        = unknown_response;
+       ds->set_completed       = mDNSfalse;
+}
+
+//======================================================================================================================
+//     contains_rrsig_in_dses_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport mDNSBool
+contains_rrsig_in_dses_with_rrsig_t(const dses_with_rrsig_t * const _Nonnull ds) {
+       const response_type_t type = ds->type;
+       mDNSBool contains = mDNStrue;
+
+       if (!ds->set_completed) {
+               log_error("contains_rrsig_in_dses_with_rrsig_t called with incompleted ds records set");
+               contains = mDNSfalse;
+               goto exit;
+       }
+
+       if (type == original_response) {
+               if (list_empty(&ds->u.original.rrsig_records)) {
+                       contains = mDNSfalse;
+                       goto exit;
+               }
+       } else if (type == nsec_response) {
+               if (!contains_rrsig_in_nsecs_with_rrsig_t(&ds->u.nsecs_with_rrsig)) {
+                       contains = mDNSfalse;
+                       goto exit;
+               }
+       } else if (type == nsec3_response) {
+               if (!contains_rrsig_in_nsec3s_with_rrsig_t(&ds->u.nsec3s_with_rrsig)) {
+                       contains = mDNSfalse;
+                       goto exit;
+               }
+       } else {
+               log_error("dses_with_rrsig_t has unknown response, it should never heppen");
+               contains = mDNSfalse;
+               goto exit;
+       }
+
+exit:
+       return contains;
+}
+
+//======================================================================================================================
+//     print_dses_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+print_dses_with_rrsig_t(const dses_with_rrsig_t * const _Nonnull ds, mDNSu8 num_of_tabs) {
+       log_debug(TAB_STR "Response Type: " PUB_S PUB_S, TAB_PARAM(num_of_tabs),
+                         response_type_value_to_string(ds->type), ds->set_completed ? ", Set Completed" : "");
+
+       switch (ds->type) {
+               case original_response: {
+                       const list_t * const ds_records         = &ds->u.original.ds_records;
+                       const list_t * const rrsig_records      = &ds->u.original.rrsig_records;
+
+                       log_debug(TAB_STR "DS:", TAB_PARAM(num_of_tabs));
+                       for (list_node_t *node = list_get_first(ds_records); !list_has_ended(ds_records, node); node = list_next(node)) {
+                               dnssec_ds_t *dnssec_ds = (dnssec_ds_t *)node->data;
+                               print_dnssec_ds_t(dnssec_ds, num_of_tabs + 1);
+                       }
+
+                       log_debug(TAB_STR "RRSIG:", TAB_PARAM(num_of_tabs));
+                       for (list_node_t *node = list_get_first(rrsig_records); !list_has_ended(rrsig_records, node); node = list_next(node)) {
+                               dnssec_rrsig_t *dnssec_rrsig = (dnssec_rrsig_t *)node->data;
+                               print_dnssec_rrsig_t(dnssec_rrsig, num_of_tabs + 1);
+                       }
+               }
+                       break;
+               case nsec_response:
+                       print_nsecs_with_rrsig_t(&ds->u.nsecs_with_rrsig, num_of_tabs + 1);
+                       break;
+               case nsec3_response:
+                       print_nsec3s_with_rrsig_t(&ds->u.nsec3s_with_rrsig, num_of_tabs + 1);
+                       break;
+               case unknown_response:
+                       log_debug(TAB_STR "Unknown Response", TAB_PARAM(num_of_tabs));
+                       break;
+               default:
+                       log_debug(TAB_STR "Invalid", TAB_PARAM(num_of_tabs));
+                       break;
+       }
+}
+
+//======================================================================================================================
+//     dnskeys_with_rrsig_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_dnskeys_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+initialize_dnskeys_with_rrsig_t(dnskeys_with_rrsig_t * const _Nonnull dnskey) {
+       dnskey->set_completed = mDNSfalse;
+
+       list_init(&dnskey->dnskey_records, sizeof(dnssec_dnskey_t));
+       list_init(&dnskey->rrsig_records, sizeof(dnssec_rrsig_t));
+}
+
+//======================================================================================================================
+//     uninitialize_dnskeys_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+uninitialize_dnskeys_with_rrsig_t(dnskeys_with_rrsig_t * const _Nonnull dnskey) {
+       for (list_node_t * dnssec_dnskey_node = list_get_first(&dnskey->dnskey_records);
+                !list_has_ended(&dnskey->dnskey_records, dnssec_dnskey_node);
+                dnssec_dnskey_node = list_next(dnssec_dnskey_node)) {
+
+               dnssec_dnskey_t * dnssec_dnskey = (dnssec_dnskey_t *)dnssec_dnskey_node->data;
+               uninitialize_dnssec_dnskey_t(dnssec_dnskey);
+       }
+       list_uninit(&dnskey->dnskey_records);
+       for (list_node_t * dnssec_rrsig_node = list_get_first(&dnskey->rrsig_records);
+                !list_has_ended(&dnskey->rrsig_records, dnssec_rrsig_node);
+                dnssec_rrsig_node = list_next(dnssec_rrsig_node)) {
+
+               dnssec_rrsig_t * dnssec_rrsig = (dnssec_rrsig_t *)dnssec_rrsig_node->data;
+               uninitialize_dnssec_rrsig_t(dnssec_rrsig);
+       }
+       list_uninit(&dnskey->rrsig_records);
+       dnskey->set_completed = mDNSfalse;
+}
+
+//======================================================================================================================
+//     contains_rrsig_in_dnskeys_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport mDNSBool
+contains_rrsig_in_dnskeys_with_rrsig_t(const dnskeys_with_rrsig_t * const _Nonnull dnskey) {
+       mDNSBool                contains        = mDNSfalse;
+
+       verify_action(dnskey->set_completed,
+               log_error("contains_rrsig_in_dnskeys_with_rrsig_t called with incompleted dnskey records set"); return mDNSfalse);
+
+       if (list_empty(&dnskey->rrsig_records)) {
+               goto exit;
+       }
+
+       contains = mDNStrue;
+exit:
+       return contains;
+}
+
+//======================================================================================================================
+//     print_dnskeys_with_rrsig_t
+//======================================================================================================================
+
+mDNSexport void
+print_dnskeys_with_rrsig_t(const dnskeys_with_rrsig_t * const _Nonnull dnskey, mDNSu8 num_of_tabs) {
+       log_debug(TAB_STR PUB_S, TAB_PARAM(num_of_tabs),
+               dnskey->set_completed ? "Set Completed" : "");
+
+       const list_t * const dnskey_records     = &dnskey->dnskey_records;
+       const list_t * const rrsig_records      = &dnskey->rrsig_records;
+
+       log_debug(TAB_STR "DNSKEY:", TAB_PARAM(num_of_tabs));
+       for (list_node_t *node = list_get_first(dnskey_records); !list_has_ended(dnskey_records, node); node = list_next(node)) {
+               dnssec_dnskey_t *dnssec_dnskey = (dnssec_dnskey_t *)node->data;
+               print_dnssec_dnskey_t(dnssec_dnskey, num_of_tabs + 1);
+       }
+
+       log_debug(TAB_STR "RRSIG:", TAB_PARAM(num_of_tabs));
+       for (list_node_t *node = list_get_first(rrsig_records); !list_has_ended(rrsig_records, node); node = list_next(node)) {
+               dnssec_rrsig_t *dnssec_rrsig = (dnssec_rrsig_t *)node->data;
+               print_dnssec_rrsig_t(dnssec_rrsig, num_of_tabs + 1);
+       }
+}
+
+//======================================================================================================================
+//     original_request_parameters_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     print_original_request_parameters_t
+//======================================================================================================================
+
+mDNSexport void
+print_original_request_parameters_t(const original_request_parameters_t * const _Nonnull parameters, mDNSu8 num_of_tabs) {
+       char uuid_hex[UUID_SIZE * 4];
+       char *ptr       = uuid_hex;
+       char *limit = ptr + sizeof(uuid_hex);
+
+       for (size_t i = 0 ; i < sizeof(parameters->uuid); i++) {
+               mDNSu32 num_of_char_write = snprintf(ptr, limit - ptr, "%x", parameters->uuid[i]);
+               if (num_of_char_write + ptr > limit) {
+                       break;
+               }
+               ptr += num_of_char_write;
+       }
+
+       log_debug(TAB_STR "Original Parameters:", TAB_PARAM(num_of_tabs));
+       log_debug(TAB_STR
+               "Rquest ID: %u"
+               ", Q Name: " PRI_DM_NAME
+               ", Q Type: " PUB_S
+               ", Q Class: %u"
+               ", Interface ID: %p"
+               ", Service ID: %d"
+               ", Flags: %#04x"
+               PUB_S
+               ", PID: %d"
+               ", UUID: " PRI_S
+               ", UID: %d"
+               ", Handler: %p"
+               ", Context: %p",
+               TAB_PARAM(num_of_tabs),
+               parameters->request_id,
+               DM_NAME_PARAM(&parameters->question_name),
+               DNS_TYPE_STR(parameters->question_type),
+               parameters->question_class,
+               parameters->interface_id,
+               parameters->service_id,
+               parameters->flags,
+               parameters->append_search_domains ? "Append Search Domains, " : "",
+               parameters->pid,
+               uuid_hex,
+               parameters->uid,
+               parameters->user_handler,
+               parameters->user_context);
+}
+
+//======================================================================================================================
+//     dnssec_zone_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_dnssec_zone_t
+//======================================================================================================================
+
+mDNSexport void
+initialize_dnssec_zone_t(
+       dnssec_zone_t * const   _Nonnull        zone,
+       const mDNSu8 * const    _Nonnull        domain_name) {
+
+       memcpy(zone->domain_name.c, domain_name, DOMAIN_NAME_LENGTH(domain_name));
+       zone->name_hash = DomainNameHashValue((domainname *)domain_name);
+
+       bzero(&zone->ds_request,                sizeof(QueryRecordClientRequest));
+       bzero(&zone->dnskey_request,    sizeof(QueryRecordClientRequest));
+       zone->ds_request_started                = mDNSfalse;
+       zone->dses_initialized                  = mDNSfalse;
+       zone->last_time_ds_add                  = INT_MIN;
+       zone->last_time_ds_rmv                  = INT_MIN;
+       zone->dnskey_request_started    = mDNSfalse;
+       zone->last_time_dnskey_add              = INT_MIN;
+       zone->last_time_dnskey_rmv              = INT_MIN;
+       zone->trust_anchor                              = get_trust_anchor_with_name(domain_name);
+
+       initialize_dnskeys_with_rrsig_t(&zone->dnskeys_with_rrsig);
+}
+
+//======================================================================================================================
+//     uninitialize_dnssec_zone_t
+//======================================================================================================================
+
+mDNSexport void
+uninitialize_dnssec_zone_t(dnssec_zone_t * const _Nonnull zone) {
+       if (zone->dses_initialized) {
+               uninitialize_dses_with_rrsig_t(&zone->dses_with_rrsig);
+       }
+       uninitialize_dnskeys_with_rrsig_t(&zone->dnskeys_with_rrsig);
+}
+
+mDNSexport void
+stop_and_clean_dnssec_zone_t(dnssec_zone_t * const _Nonnull zone) {
+       if (zone->dnskey_request_started) {
+               QueryRecordOpStopForClientRequest(&zone->dnskey_request.op);
+       }
+       if (zone->ds_request_started) {
+               QueryRecordOpStopForClientRequest(&zone->ds_request.op);
+       }
+
+       uninitialize_dnssec_zone_t(zone);
+}
+
+//======================================================================================================================
+//     print_dnssec_zone_t
+//======================================================================================================================
+
+mDNSexport void
+print_dnssec_zone_t(const dnssec_zone_t * const _Nonnull zone, mDNSu8 num_of_tabs) {
+       log_debug(TAB_STR
+               "Name: " PRI_DM_NAME
+               ", Name Hash: %u"
+               ,
+               TAB_PARAM(num_of_tabs),
+               DM_NAME_PARAM(&zone->domain_name),
+               zone->name_hash);
+
+       log_debug(TAB_STR "DNSKEYs:", TAB_PARAM(num_of_tabs));
+       print_dnskeys_with_rrsig_t(&zone->dnskeys_with_rrsig, num_of_tabs + 1);
+
+       if (zone->dses_initialized) {
+               log_debug(TAB_STR "DSs:", TAB_PARAM(num_of_tabs));
+               print_dses_with_rrsig_t(&zone->dses_with_rrsig, num_of_tabs + 1);
+       }
+
+       if (zone->trust_anchor != mDNSNULL) {
+               log_debug(TAB_STR "Trust Anchors:", TAB_PARAM(num_of_tabs));
+               print_trust_anchors_t(zone->trust_anchor, num_of_tabs + 1);
+       }
+
+       log_debug(TAB_STR "--------------------------------------------------", TAB_PARAM(num_of_tabs));
+}
+
+#pragma mark - returned_answers_t
+
+
+
+#pragma mark initialize_returned_answers_t
+mDNSexport void
+initialize_returned_answers_t(
+       returned_answers_t * const      _Nonnull        returned_answers,
+       const dnssec_result_t                                   dnssec_result,
+       const DNSServiceErrorType                               error) {
+
+       list_init(&returned_answers->answers, sizeof(ResourceRecord *));
+       returned_answers->dnssec_result         = dnssec_result;
+       returned_answers->error                         = error;
+}
+
+#pragma mark uninitialize_returned_answers_t
+mDNSexport void
+uninitialize_returned_answers_t(returned_answers_t * const _Nonnull returned_answers) {
+       list_uninit(&returned_answers->answers);
+}
+
+#pragma mark print_returned_answers_t
+mDNSexport void
+print_returned_answers_t(const returned_answers_t * const _Nonnull returned_answers, mDNSu8 num_of_tabs) {
+
+       log_debug(TAB_STR "dnssec_result:%d", TAB_PARAM(num_of_tabs), returned_answers->dnssec_result);
+       log_debug(TAB_STR "error:%d", TAB_PARAM(num_of_tabs), returned_answers->error);
+       log_debug(TAB_STR "type:%d", TAB_PARAM(num_of_tabs), returned_answers->type);
+
+       log_debug(TAB_STR "Resource Record:", TAB_PARAM(num_of_tabs));
+       dnssec_rr_t dnssec_rr;
+       const list_t * const answers = &returned_answers->answers;
+       for (list_node_t * rr_node = list_get_first(answers); !list_has_ended(answers, rr_node); rr_node = list_next(rr_node)) {
+               ResourceRecord **       rr_ptr  = (ResourceRecord **)rr_node->data;
+               ResourceRecord *        rr              = *rr_ptr;
+               initialize_dnssec_rr_t(&dnssec_rr, rr);
+               print_dnssec_rr_t(&dnssec_rr, num_of_tabs + 1);
+               uninitialize_dnssec_rr_t(&dnssec_rr);
+       }
+}
+
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2_structs.h b/mDNSMacOSX/dnssec_v2/dnssec_v2_structs.h
new file mode 100644 (file)
index 0000000..b9915a4
--- /dev/null
@@ -0,0 +1,801 @@
+//
+//     dnssec_v2_structs.h
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#ifndef DNSSEC_v2_STRUCTS_H
+#define DNSSEC_v2_STRUCTS_H
+
+#pragma mark - Includes
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include "list.h"
+#include "mDNSEmbeddedAPI.h"
+#include "ClientRequests.h"
+
+#pragma mark - Enums
+
+
+
+#pragma mark dnssec_validation_result_t
+typedef enum dnssec_validation_result {
+       // chain of trust validation error
+       dnssec_validation_invalid,
+       dnssec_validation_valid,
+       dnssec_validation_not_trusted,
+       dnssec_validation_trusted,
+       dnssec_validation_invalid_internal_state,
+       dnssec_validation_non_dnskey_ds_record_chain,
+       dnssec_validation_no_matching_key_tag,
+
+       // DNSSEC record general error
+       dnssec_validation_algorithm_number_not_equal,
+
+       // DS validation error
+       dnssec_validation_ds_digest_not_supported,
+
+       // DNSKEY validation error
+       dnssec_validation_dnskey_algorithm_not_supported,
+       dnssec_validation_dnskey_invalid_flags,
+       dnssec_validation_dnskey_wrong_protocol,
+
+       // RRSIG validation error
+       dnssec_validation_rrsig_use_before_inception,
+       dnssec_validation_rrsig_use_after_expiration,
+
+       // NSEC validation error
+       dnssec_validation_nsec_invalid_nsec_result,
+       dnssec_validation_nsec_malformated_record,
+       // Verifiable NSEC error
+       dnssec_validation_nsec_name_error,
+       dnssec_validation_nsec_no_data,
+       dnssec_validation_nsec_wildcard_answer,
+       dnssec_validation_nsec_wildcard_no_data,
+
+       // NSEC3 validation error
+       dnssec_validation_nsec3_invalid_hash_iteration,
+       dnssec_validation_nsec3_unsupported_hash_algorithm,
+       dnssec_validation_nsec3_unsupported_flag,
+       dnssec_validation_nsec3_different_hash_iteration_salt,
+       dnssec_validation_nsec3_provable_closest_encloser,
+       dnssec_validation_nsec3_nsec3_not_from_the_zone,
+       dnssec_validation_nsec3_malformated_record,
+       // Verifiable NSEC3 error
+       dnssec_validation_nsec3_name_error,
+       dnssec_validation_nsec3_wildcard_no_data,
+       dnssec_validation_nsec3_no_data_response,
+       dnssec_validation_nsec3_no_data_response_opt_out,
+       dnssec_validation_nsec3_wildcard_answer_response,
+
+       // path validation error
+       dnssec_validation_path_invalid_node_type,
+       dnssec_validation_path_unmatched_owner_name,
+       dnssec_validation_path_unmatched_class,
+       dnssec_validation_path_unmatched_type_covered,
+       dnssec_validation_path_invalid_label_count,
+
+       // trust anchor error
+       dnssec_validation_trust_anchor_not_available,
+       dnssec_validation_trust_anchor_does_not_macth,
+
+       // general error
+       dnssec_validation_validating,
+       dnssec_validation_no_memory,
+       dnssec_validation_bogus
+} dnssec_validation_result_t;
+
+#pragma mark - Structures
+
+
+
+#pragma mark - DNSSEC-related records wire format
+
+
+
+#pragma mark dns_type_cname_t
+// DNS CNAME record data fields (see <https://tools.ietf.org/html/rfc1035#section-3.3.1>)
+typedef struct dns_type_cname dns_type_cname_t;
+struct dns_type_cname {
+       mDNSu8          cname[0];                               // To CNAME rdata.
+};
+
+#pragma mark dns_type_ds_t
+typedef struct dns_type_ds dns_type_ds_t;
+// DNS DS record data fields (see <https://tools.ietf.org/html/rfc4034#section-5.1>)
+struct dns_type_ds {
+       mDNSu16         key_tag;                                // key tag that identifies a specific DNSKEY.
+       mDNSu8          algorithm;                              // The DNSKEY algorithm that is used to sign the data.
+       mDNSu8          digest_type;                    // The type of the DS digest.
+       mDNSu8          digest[0];                              // To digest rdata.
+};
+
+#pragma mark dns_type_dnskey_t
+// DNS DNSKEY record data fields (see <https://tools.ietf.org/html/rfc4034#section-2.1>)
+typedef struct dns_type_dnskey dns_type_dnskey_t;
+struct dns_type_dnskey {
+       mDNSu16         flags;                                  // The DNSKEY flags.
+       mDNSu8          protocol;                               // Protocol that identifies DNSKEY as a key used in DNSSEC, it should always be 3.
+       mDNSu8          algorithm;                              // The DNSKEY algorithm that is used to sign the data.
+       mDNSu8          public_key[0];                  // To public rdata.
+};
+
+#pragma mark dns_type_rrsig_t
+// DNS RRSIG record data fields (see <https://tools.ietf.org/html/rfc4034#section-3.1>)
+typedef struct dns_type_rrsig dns_type_rrsig_t;
+struct dns_type_rrsig {
+       mDNSu16         type_covered;                   // Indicates which DNS type RRSIG covers
+       mDNSu8          algorithm;                              // The DNSKEY algorithm that is used to sign the data.
+       mDNSu8          labels;                                 // The number of labels in the RRSIG owner name, it is used to check wild matching.
+       mDNSu32         original_TTL;                   // The original TTL of the records that are covered by the RRSIG, it is used to reconstruct the signed data.
+       mDNSu32         signature_expiration;   // The epoch time when the RRSIG expires.
+       mDNSu32         signature_inception;    // The epoch time when the RRSIG should start to be valid to validate.
+       mDNSu16         key_tag;                                // The key tag that identifies which DNSKEY it uses to generate the current RRSIG.
+       mDNSu8          signer_name[0];                 // To the signer name.
+       // mDNSu8       signature[0];                   // To the signature rdata.
+};
+
+#pragma mark dns_type_nsec_t
+// DNS NSEC record data fields (see <https://tools.ietf.org/html/rfc4034#section-2.1>)
+typedef struct dns_type_nsec dns_type_nsec_t;
+struct dns_type_nsec {
+       mDNSu8          next_domain_name[0];    // The next domain that exists in the zone
+       // mDNSu8       type_bit_maps[0];               // To the type bit map that indicates what DNS types are covered by the current NSEC record.
+};
+
+#pragma mark dns_type_nsec3_t
+// DNS NSEC3 record data fields (see <https://tools.ietf.org/html/rfc5155#section-3.1>)
+typedef struct dns_type_nsec3 dns_type_nsec3_t;
+struct dns_type_nsec3 {
+       mDNSu8          hash_algorithm;                 // Which hash algorithm that NSEC3 uses to generate the hash.
+       mDNSu8          flags;                                  // The NSEC3 flags
+       mDNSu16         iterations;                             // The iterations of hash operation on the data
+       mDNSu8          salt_length;
+       mDNSu8          salt[0];                                // varied-size array
+       // mDNSu8       hash_length;
+       // mDNSu8       next_hashed_owner_name[0];// The next hashed domain that exists in the zone
+       // mDNSu8 * type_bit_maps;                      // To the type bit map that indicates what DNS types are covered by the current NSEC3 record.
+};
+
+#pragma mark - DNSSEC records in memory
+
+#pragma mark dnssec_rr_t
+typedef struct dnssec_rr dnssec_rr_t;
+struct dnssec_rr {                                                     // Represents a resource record
+       mDNSu16                                         rr_type;                                // The DNS type of the resource record.
+       mDNSu16                                         rr_class;                               // The internet class of the record, should be IN
+       mDNSu16                                         rdata_length;                   // The length of the rdata
+       mDNSu32                                         name_hash;                              // The hash of the owner name that is used to compare name quickly
+       mDNSu32                                         rdata_hash;                             // The hash of the rdata that is used to compare rdata quickly
+       domainname                                      name;                                   // The owner name of the record
+       mDNSu8 *        _Nullable               rdata;                                  // The rdata
+       ResourceRecord * _Nullable      rr;                                             // Points to ResourceRecord in the mDNSCore cache
+};
+
+#pragma mark dnssec_original_t
+typedef struct dnssec_original dnssec_original_t;
+struct dnssec_original {                                               // The response that answers user's question.
+       mDNSBool                                        answer_from_cache;              // Indicates If we get the answer from the cache, instead of refresh response from DNS server.
+       DNSServiceErrorType                     dns_error;                              // DNSServiceErrorType when mDNSCore returns the answer.
+       QC_result                                       qc_result;                              // Event type for the current resource record including QC_add, QC_rmv, QC_supressed.
+       dnssec_rr_t                                     dnssec_rr;                              // Store the answer returned from mDNSCore.
+};
+
+#pragma mark dnssec_cname_t
+typedef struct dnssec_cname dnssec_cname_t;
+struct dnssec_cname {                                                  // The response that does not answer user's question, but provides a CNAME to continue the query
+       mDNSu8 *        _Nullable               cname;                                  // The CNAME rdata.
+       dnssec_rr_t                                     dnssec_rr;                              // Store the answer returned from mDNSCore.
+};
+
+#pragma mark dnssec_ds_t
+typedef struct dnssec_ds dnssec_ds_t;
+struct dnssec_ds {                                                     // The response that answers DS query from mDNSResponder
+       mDNSu16                                         key_tag;                                // ID that identifies the DNSKEY that the current DS verifies.
+       mDNSu8                                          algorithm;                              // The algorithm of the DNSKEY that the current DS verifies.
+       mDNSu8                                          digest_type;                    // The digest type that is used to caluclate the current DS from the DNSKEY.
+       mDNSu16                                         digest_length;                  // The length of the digest.
+       const mDNSu8 * _Nullable        digest;                                 // The DS rdata.
+       dnssec_rr_t                                     dnssec_rr;                              // Store the answer returned from mDNSCore.
+};
+
+// DNSKEY flag bits
+#define DNSKEY_FLAG_ZONE_KEY                           0x100
+#define DNSKEY_FLAG_SECURITY_ENTRY_POINT       0x1
+
+#pragma mark dnssec_dnskey_t
+typedef struct dnssec_dnskey dnssec_dnskey_t;
+struct dnssec_dnskey {                                         // The response that answeres DNSKEY query from mDNSResponder.
+       mDNSu16                                         flags;                                  // The bit flags for DNSKEY.
+       mDNSu8                                          protocol;                               // Should always be value 3.
+       mDNSu8                                          algorithm;                              // The type of crypoto algorithm that the current DNSKEY applies to.
+       mDNSu16                                         key_tag;                                // ID that identifies the current DNSKEY
+       mDNSu16                                         public_key_length;              // The length of the DNSKEY.
+       mDNSu8 * _Nullable                      public_key;                             // The DNSKEY rdata
+       dnssec_rr_t                                     dnssec_rr;                              // Store the answer returned from mDNSCore.
+};
+
+#pragma mark dnssec_rrsig_t
+typedef struct dnssec_rrsig dnssec_rrsig_t;
+struct dnssec_rrsig {
+       mDNSu16                                         type_covered;                   // The DNS type that is covered by the current RRSIG
+       mDNSu8                                          algorithm;                              // The algorithm of DNSKEY that is used to generate the current RRSIG
+       mDNSu8                                          labels;                                 // The number of labels of RRSIG's owner, used for wildcard matching
+       mDNSu32                                         original_TTL;                   // The original TTL of the records that are used to generate the current RRSIG, used to reconstruct the signed data
+       mDNSu32                                         signature_expiration;   // The epoch time when the RRSIG expires.
+       mDNSu32                                         signature_inception;    // The epoch time when the RRSIG should start to be valid to validate.
+       mDNSu16                                         key_tag;                                // The key tag that identifies which DNSKEY it uses to generate the current RRSIG.
+       mDNSu16                                         signature_length;               // The length of the signature.
+       mDNSu8 *        _Nullable               signer_name;                    // The name of signer that signs the covered records
+       mDNSu8 *        _Nullable               signature;                              // The signature rdata
+       dnssec_rr_t                                     dnssec_rr;                              // Store the answer returned from mDNSCore.
+};
+
+#pragma mark dnssec_nsec_t
+typedef struct dnssec_nsec dnssec_nsec_t;
+struct dnssec_nsec {
+       mDNSu8 *        _Nullable               exist_domain_name;              // The owner name of records that exist before next_domain_name by canonical order in the zone.
+       mDNSu8 *        _Nullable               next_domain_name;               // The owner name of records that exist after next_domain_name by canonical order in the zone.
+       mDNSu8 *        _Nullable               type_bit_maps;                  // The type bit map that indicates what DNS types are covered by the current NSEC record.
+       mDNSu16                                         type_bit_maps_length;   // The length of the type bit map.
+       dnssec_rr_t                                     dnssec_rr;                              // Store the answer returned from mDNSCore.
+};
+
+#pragma mark dnssec_nsec3_t
+typedef struct dnssec_nsec3 dnssec_nsec3_t;
+struct dnssec_nsec3 {
+       mDNSu8                                          hash_algorithm;                 // The hash algorithm that NSEC3 uses to generate the hash
+       mDNSu8                                          flags;                                  // The NSEC3 flags
+       mDNSu16                                         iterations;                             // The iterations of hash operation on the data
+       mDNSu8                                          salt_length;                    // The length of the salt added when doing hash
+       mDNSu8                                          hash_length;                    // The length of the final hash result
+       mDNSu16                                         type_bit_maps_length;   // The length of the type bit map.
+       mDNSu8 *        _Nullable               salt;                                   // The salt added to the hash result for every iteration
+       mDNSu8 *        _Nullable               next_hashed_owner_name; // The binary-format hashed owner name of records that exist after the owner name of current NSEC3 record by canonical order in the zone.
+       mDNSu8 *        _Nullable               type_bit_maps;                  // The type bit map that indicates what DNS types are covered by the current NSEC3 record.
+       char *          _Nullable               next_hashed_owner_name_b32;                     // The b32-format string of hashed owner name
+       mDNSu32                                         next_hashed_owner_name_b32_length;      // The length of next_hashed_owner_name_b32 string
+       dnssec_rr_t                                     dnssec_rr;                              // Store the answer returned from mDNSCore.
+};
+
+#pragma mark - Validation tree structures
+
+
+
+#pragma mark nsecs_with_rrsig_t
+typedef struct nsecs_with_rrsig nsecs_with_rrsig_t;
+struct nsecs_with_rrsig {                                                                      // The NSEC response from DNS server that indicates the non-existence of the record in the query.
+       list_t                                                  nsec_and_rrsigs_same_name;              // list_t<one_nsec_with_rrsigs_t>, the list of one_nsec_with_rrsigs_t structure, each one_nsec_with_rrsigs_t represents one NSEC record with its corresponding RRSIG.
+       list_t                                                  wildcard_answers;                               // list_t<dnssec_rr_t>, the list of dnssec_rr_t structure, each dnssec_rr_t represents one wildcard record.
+       list_t                                                  wildcard_rrsigs;                                // list_t<dnssec_rrsig_t>, the list of dnssec_rrsig_t structure, each dnssec_rrsig_t represents one RRSIG that is used to validate the wildcard answer in wildcard_answers.
+       ResourceRecord *        _Nullable       negative_rr;                                    // The negative answer generated by mDNSResponder, that NSEC records try to prove.
+       dnssec_validation_result_t              nsec_result;                                    // The validation result after validating the current NSEC records, it is only meaningful after we finish the validation.
+};
+
+#pragma mark one_nsec_with_rrsigs_t
+typedef struct one_nsec_with_rrsigs one_nsec_with_rrsigs_t;
+struct one_nsec_with_rrsigs {                                                          // One NSEC record that form a complete NSEC response with other NSEC records.
+       const mDNSu8 * _Nullable        owner_name;                                                     // The onwer name of NSEC record, also indicates that this name does exist in the zone.
+       dnssec_nsec_t                           nsec_record;                                            // The dnssec_nsec_t that holds the NSEC record.
+       list_t                                          rrsig_records;                                          // list_t<dnssec_rrsig_t>, the RRSIGs that generated from the current NSEC record.
+};
+
+#pragma mark nsec3s_with_rrsig_t
+typedef struct nsec3s_with_rrsig nsec3s_with_rrsig_t;
+struct nsec3s_with_rrsig {                                                             // The NSEC3 response from DNS server that indicates the non-existence of the record in the query.
+       list_t                                                          nsec3_and_rrsigs_same_name; // list_t<one_nsec3_with_rrsigs_t>, the list of one_nsec3_with_rrsigs_t structure, each one_nsec3_with_rrsigs_t represents one NSEC3 record with its corresponding RRSIG.
+       list_t                                                          wildcard_answers;                       // list_t<dnssec_rr_t>, the list of dnssec_rr_t structure, each dnssec_rr_t represents one wildcard record.
+       list_t                                                          wildcard_rrsigs;                        // list_t<dnssec_rrsig_t>, the list of dnssec_rrsig_t structure, each dnssec_rrsig_t represents one RRSIG that is used to validate the wildcard answer in wildcard_answers.
+       ResourceRecord *        _Nullable               negative_rr;                            // The negative answer generated by mDNSResponder, that NSEC3 records try to prove.
+       dnssec_validation_result_t                      nsec3_result;                           // The validation result after validating the current NSEC3 records, it is only meaningful after we finish the validation.
+};
+
+#pragma mark one_nsec3_with_rrsigs_t
+typedef struct one_nsec3_with_rrsigs one_nsec3_with_rrsigs_t;
+struct one_nsec3_with_rrsigs {                                                 // One NSEC3 record that form a complete NSEC response with other NSEC records.
+       const mDNSu8 * _Nullable        owner_name;                                                     // The onwer name of NSEC3 record, also indicates that this name does exist in the zone.
+       dnssec_nsec3_t                          nsec3_record;                                           // The dnssec_nsec_t that holds the NSEC3 record.
+       list_t                                          rrsig_records;                                          // list_t<dnssec_rrsig_t>, the RRSIGs that generated from the current NSEC3 record.
+};
+
+#pragma mark cnames_with_rrsig_t
+typedef struct cnames_with_rrsig cnames_with_rrsig_t;
+struct cnames_with_rrsig {                     // The CNAME response from DNS server that indicates the current query name is a alias of another name.
+       list_t                                          cname_records;  // list_t<dnssec_cname_t>, the list of dnssec_cname_t structure, each dnssec_cname_t represents one CNAME, in fact there should only be one CNAME in the list.
+       list_t                                          rrsig_records;  // list_t<dnssec_rrsig_t>, the list of dnssec_rrsig_t structure, each dnssec_rrsig_t represents one RRSIG that i sused to validate the CNAME record in cname_records.
+};
+
+#pragma mark trust_anchor_t
+typedef struct trust_anchors trust_anchors_t;
+struct trust_anchors {                 // The trust anchor structure that mDNSResponder loads during initialization, it stays unchanged during the life time of mDNSResponder.
+       domainname      name;                                   // The owner name of the trust anchor
+       mDNSu32         name_hash;                              // The hash of the owner name, used to speed up name comparsion.
+       list_t          dnskey_trust_anchors;   // list_t<dnssec_dnskey_t>, the list of dnssec_dnskey_t structures, the trust anchor could be a DNSKEY record. When mDNSResponder can use this trusted DNSKEY to validate the record, then the entire validation chain rooted in the validated record could be trusted.
+       list_t          ds_trust_anchors;               // list_t<dnssec_ds_t>, the list of dnssec_ds_t structures, the trust anchor could also be a DS record. When mDNSResponder can use this trusted DS to match DNSKEY record, then the entire validation chain rooted in the validated DNSKEY record could be trusted.
+};
+
+#pragma mark response_type_t
+typedef enum response_type {                   // The response type for any DNS query in DNSSEC environment.
+       unknown_response,                       // The initial state, which means we have not received any response.
+       original_response,                      // The DNS server returns what the user expects, thus it is a original response for the query.
+       cname_response,                         // The DNS server returns a CNAME for user's NOn-CNAME query
+       nsec_response,                          // The DNS server returns a NSEC response trying to prove that the name or the record type does not exist.
+       nsec3_response                          // The DNS server returns a NSEC3 response trying to prove that the name or the record type does not exist.
+} response_type_t;
+
+#pragma mark originals_with_rrsig_t
+typedef struct originals_with_rrsig originals_with_rrsig_t;
+struct originals_with_rrsig {                                  // This structure holds the response that will be returned to the user after validation.
+       union {                                                                                         // The response can only be original_response/cname_response/nsec_response/nsec3_response.
+               struct {
+                       list_t                          original_records;               // list_t<dnssec_original_t>, the list of dnssec_original_t structures, each dnssec_original_t holds one response that user expects.
+                       list_t                          rrsig_records;                  // list_t<dnssec_rrsig_t>, the list of dnssec_rrsig_t structures, each dnssec_rrsig_t holds one RRSIG generated from the dnssec_original_t above.
+                       ResourceRecord * _Nullable negative_rr;         // The query may be suppressed by mDNSResponder, it is a denial of existence response that does not need NSEC/NSEC3 to validate, just put it in original.
+                       mDNSBool                        suppressed_response;    // Indicates if the original response is a suppressed one.
+               } original;                                                                             // Used when the type is original_response.
+               cnames_with_rrsig_t             cname_with_rrsig;               // Used when the type is cname_response.
+                                                                                                               // NSEC and NSEC3 RRs can not co-exist in a zone.
+               nsecs_with_rrsig_t              nsecs_with_rrsig;               // Used when the type is nsec_response.
+               nsec3s_with_rrsig_t             nsec3s_with_rrsig;              // Used when the type is nsec3_response.
+       } u;
+       response_type_t                         type;                                   // original_response/cname_response/nsec_response/nsec3_response.
+};
+
+#pragma mark dses_with_rrsig_t
+typedef struct dses_with_rrsig dses_with_rrsig_t;
+struct dses_with_rrsig {                                               // This structure holds the DS response that DNSSEC handler queries for.
+       union {                                                                                         // The response can only be original_response/nsec_response/nsec3_response.
+               struct {
+                       list_t                          ds_records;                             // list_t<dnssec_ds_t>, the list of dnssec_ds_t structure, each dnssec_ds_t holds one DS record that could be used to verifies one DNSKEY record
+                       list_t                          rrsig_records;                  // list_t<dnssec_rrsig_t>, the list of dnssec_rrsig_t structure, each dnssec_rrsig_t holds one RRSIG generated from the dnssec_ds_t above.
+               } original;
+                                                                                                               // NSEC and NSEC3 RRs can not co-exist in a zone.
+               nsecs_with_rrsig_t              nsecs_with_rrsig;               // Used when the type is nsec_response.
+               nsec3s_with_rrsig_t             nsec3s_with_rrsig;              // Used when the type is nsec3_response.
+       } u;
+       response_type_t                         type;                                   // original_response/nsec_response/nsec3_response, CNAME can only be the leaf node, thus there should never be CNAME for DS query.
+       mDNSBool                                        set_completed;                  // Indicates if we have already get the DS records.
+};
+
+#pragma mark dnskeys_with_rrsig_t
+typedef struct dnskeys_with_rrsig dnskeys_with_rrsig_t;
+struct dnskeys_with_rrsig {                                    // This structure holds the DNSKEY response that DNSSEC handler queries for. The response can only be original_response, because both NSEC and NSEC3 indicate that DNSKEY does exist in order to validate the NSEC/NSEC3.
+       list_t                          dnskey_records;                                 // list_t<dnssec_dnskey_t>, the list of dnssec_dnskey_t structures, each dnssec_dnskey_t holds one DNSKEY that could be used to validate signer's records with the corresponding RRSIG.
+       list_t                          rrsig_records;                                  // list_t<dnssec_rrsig_t>, the list of dnssec_rrsig_t structures, each dnssec_rrsig_t holds one RRSIG generated from the dnssec_dnskey_t above.
+       mDNSBool                        set_completed;                                  // Indicates if we have already get the DNSKEY records.
+};
+
+#pragma mark dnssec_zone_t
+// This structure represents one zone node in the validation tree. For example, if the user queries for the AAAA record
+// of "www.internetsociety.org.", there will be 3 zone nodes at last:
+// Root zone node:     "."                                                     DNSKEY
+// Zone node:          "org."                                          DNSKEY/DS
+// Zone node:          "internetsociety.org."          DNSKEY/DS
+// Response:           "www.internetsociety.org."      AAAA
+// Each node contains 2 kinds of records: DNSKEY and DS records
+typedef struct dnssec_zone dnssec_zone_t;
+struct dnssec_zone {
+       // general field that is used to fecth records
+       domainname                                      domain_name;                    // DNS representation of the zone name.
+       mDNSu32                                         name_hash;                              // The name hash to speed up name comparison.
+
+       // dnssec resource records
+       // DS record
+       QueryRecordClientRequest        ds_request;                             // The DS request handler
+       mDNSBool                                        ds_request_started;             // Indicates if we issued the DS request, sometimes we do not want to issue query such as we are in root "." node, and root node does not have DS record.
+       mDNSBool                                        dses_initialized;               // Indicate if the DS list is initialized
+       dses_with_rrsig_t                       dses_with_rrsig;                // list_t<dses_with_rrsig_t>, the list of dses_with_rrsig_t structures, each dses_with_rrsig_t holds the DS records and the corresponding RRSIG records for the current zone.
+       mDNSs32                                         last_time_ds_add;               // last time that DS records are added
+       mDNSs32                                         last_time_ds_rmv;               // last time that DS records are removed
+
+       // DNSKEY record
+       QueryRecordClientRequest        dnskey_request;                 // The DNSKEY request handler
+       mDNSBool                                        dnskey_request_started; // Indicates if we issue the DNSKEY request, sometimes we do not issue the query because the current zone has a trust anchor for DNSKEY.
+       dnskeys_with_rrsig_t            dnskeys_with_rrsig;             // list_t<dnssec_dnskey_t>, the list of dnskeys_with_rrsig structures, each dnssec_dnskey_t holds the DNSKEY records and the corresponding RRSIG records for the current zone.
+       mDNSs32                                         last_time_dnskey_add;   // last time that DNSKEY records are added
+       mDNSs32                                         last_time_dnskey_rmv;   // last time that DNSKEY records are removed
+
+       // The current zone may have trust anchor installed in the system
+       const trust_anchors_t * _Nullable       trust_anchor;   // list_t<trust_anchor_t>, the list of trust_anchor_t structures, each trust_anchor_t represent one trust anchor.
+};
+
+#pragma mark original_request_parameters_t
+typedef struct original_request_parameters original_request_parameters_t;
+struct original_request_parameters {                   // This structure contains the original request paramters set by the user.
+       mDNSu32                                         request_id;
+       domainname                                      question_name;
+       mDNSu32                                         question_name_hash;
+       mDNSu16                                         question_type;
+       mDNSu16                                         question_class;
+       mDNSInterfaceID _Nullable       interface_id;
+       mDNSs32                                         service_id;
+       mDNSu32                                         flags;
+       mDNSBool                                        append_search_domains;
+       mDNSs32                                         pid;
+       mDNSu8                                          uuid[UUID_SIZE];
+       mDNSs32                                         uid;
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+       audit_token_t                           peer_audit_token;
+       mDNSBool                                        has_peer_audit_token;
+       audit_token_t                           delegate_audit_token;
+       mDNSBool                                        has_delegate_audit_token;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+       mDNSu8                                          resolver_uuid[UUID_SIZE];
+       mDNSBool                                        need_encryption;
+       mdns_dns_service_id_t           custom_id;
+#endif
+       QueryRecordResultHandler _Nullable      user_handler;
+       void *  _Nullable                       user_context;
+};
+
+#pragma mark denial_of_existence_records_t
+typedef struct denial_of_existence_records denial_of_existence_records_t;
+struct denial_of_existence_records {
+       list_t                                          resource_records;                       //list_t<ResourceRecord>;
+};
+
+#pragma mark original_t
+typedef struct original original_t;
+struct original {                                                                                                      // This structure contains all the useful information about the user's original request.
+       original_request_parameters_t                           original_parameters;            // The original paramters getting from the user
+       originals_with_rrsig_t                                          original_result_with_rrsig;     // The original response that will be returned to the user.
+       const trust_anchors_t *                 _Nullable       original_trust_anchor;          // It is possible that the returned original response is a trust anchor installed.
+       mDNSs32                                                                         last_time_add;                          // Last time that originals_with_rrsig_t records are added.
+       mDNSs32                                                                         last_time_rmv;                          // Last time that originals_with_rrsig_t records are removed.
+};
+
+#pragma mark returned_answers_t
+typedef struct returned_answers returned_answers_t;
+struct returned_answers {                      // This structure contains all the records that are returned to the user, these information is tracked to properly deliver ADD/RMV event to the user
+       list_t                                  answers;                // list_t<ResourceRecord *>, the list of "ResourceRecord *" pointers, each pointer points to a ResourceRecord in the cache that has been returned to the user.
+       dnssec_result_t                 dnssec_result;  // The dnssec_result_t that has been returned to the user.
+       DNSServiceErrorType             error;                  // The returned DNSServiceErrorType
+       response_type_t                 type;                   // The type of the returned answer, it could be original_response(including suppressed case)/nsec_response/nsec3_response
+};
+
+#pragma mark dnssec_context_t
+// This structure contains the DNSSEC context that is needed to track additional information that is not provided by
+// mDNSCore, each DNSSEC-enabled DNS request would have a seperated DNSSEC context.
+typedef struct dnssec_context dnssec_context_t;
+struct dnssec_context {
+       // Necessary request
+       QueryRecordClientRequest *              _Nonnull        me;                                                             // The request of the question that we are currently working on.
+       QueryRecordClientRequest                                        request_to_follow_cname;                // An idle request unless there is a need to follow the CNAME reference, and start a sub request.
+
+       // Zone records that could be used to validate records.
+       list_t                                                                          zone_chain;                                             // list_t<dnssec_zone_t>, the validation tree consists of zone nodes from root to leaf.
+
+       // original request fields
+       original_t original;                                                                                                            // Information about the user's original request.
+
+       // denial of existence fields
+       denial_of_existence_records_t * _Nullable       denial_of_existence_records;    // It is a temporary field that is used to pass the NSEC/NSEC3 records to the DNSSEC handler, will be cleared after running DNSSEC handler.
+
+       // save the records that are returned to the user
+       returned_answers_t                                                      returned_answers;                               // The records that have been returned to the user.
+
+       // DNSSEC context pointer
+       dnssec_context_t *                              _Nullable       primary_dnssec_context;                 // This points to the initial DNSSEC context of the first query coming from the user, i.e. the first name in the CNAME chain.
+       dnssec_context_t *                              _Nullable       subtask_dnssec_context;                 // If the DNSSEC-enabled DNS query has CNAMEs, this field is used to create another new DNSSEC context that resolves and validates the new CNAME.
+};
+
+
+#pragma mark - Functions
+
+
+
+#pragma mark - dns_type_*_t records parsing
+
+
+
+#pragma mark parsse_dns_type_cname_t
+mDNSexport void
+parsse_dns_type_cname_t(const void * const _Nonnull rdata, mDNSu8 * _Nullable * const _Nonnull out_cname);
+
+#pragma mark parse_dns_type_ds_t
+mDNSexport mDNSBool
+parse_dns_type_ds_t(
+       const void * const      _Nonnull        rdata,
+       const mDNSu16                                   rdata_length,
+       mDNSu16 * const         _Nullable       out_key_tag,
+       mDNSu8 * const          _Nullable       out_algorithm,
+       mDNSu8 * const          _Nullable       out_digest_type,
+       mDNSu16 * const         _Nullable       out_digest_length,
+       const mDNSu8 * _Nonnull * const _Nullable       out_digest);
+
+#pragma mark parse_dns_type_dnskey_t
+mDNSexport mDNSBool
+parse_dns_type_dnskey_t(
+       const void * const      _Nonnull        rdata,
+       const mDNSu16                                   rdata_length,
+       mDNSu16 * const         _Nullable       out_flags,
+       mDNSu8 * const          _Nullable       out_protocol,
+       mDNSu8 * const          _Nullable       out_algorithm,
+       mDNSu16 * const         _Nullable       out_public_key_length,
+       mDNSu8 * _Nonnull * const _Nullable out_public_key);
+
+#pragma mark parse_dns_type_rrsig_t
+mDNSexport mDNSBool
+parse_dns_type_rrsig_t(
+       const void * const      _Nonnull        rdata,
+       const mDNSu16                                   rdata_length,
+       mDNSu16 * const         _Nullable       out_type_covered,
+       mDNSu8 * const          _Nullable       out_algorithm,
+       mDNSu8 * const          _Nullable       out_labels,
+       mDNSu32 * const         _Nullable       out_original_ttl,
+       mDNSu32 * const         _Nullable       out_signature_expiration,
+       mDNSu32 * const         _Nullable       out_signature_inception,
+       mDNSu16 * const         _Nullable       out_key_tag,
+       mDNSu16 * const         _Nullable       out_signature_length,
+       mDNSu8 * _Nonnull * const _Nullable out_signer_name,
+       mDNSu8 * _Nonnull * const _Nullable out_signature);
+
+#pragma mark parse_dns_type_nsec_t
+mDNSexport mDNSBool
+parse_dns_type_nsec_t(
+       const void * const      _Nonnull        rdata,
+       const mDNSu16                                   rdata_length,
+       mDNSu16 * const         _Nonnull        out_type_bit_maps_length,
+       mDNSu8 * _Nonnull * const _Nullable out_next_domain_name,
+       mDNSu8 * _Nonnull * const _Nullable out_type_bit_maps);
+
+#pragma mark parse_dns_type_nsec3_t
+mDNSexport mDNSBool
+parse_dns_type_nsec3_t(
+       const void * const      _Nonnull        rdata,
+       const mDNSu16                                   rdata_length,
+       mDNSu8 * const          _Nullable       out_hash_algorithm,
+       mDNSu8 * const          _Nullable       out_flags,
+       mDNSu16 * const         _Nullable       out_iterations,
+       mDNSu8 * const          _Nullable       out_salt_length,
+       mDNSu8 * const          _Nullable       out_hash_length,
+       mDNSu16 * const         _Nullable       out_type_bit_maps_length,
+       mDNSu8 * _Nonnull * const _Nullable out_salt,
+       mDNSu8 * _Nonnull * const _Nullable out_next_hashed_owner_name,
+       mDNSu8 * _Nonnull * const _Nullable out_type_bit_maps);
+
+#pragma mark get_covered_type_of_dns_type_rrsig_t
+mDNSexport mDNSu16
+get_covered_type_of_dns_type_rrsig_t(const void * const _Nonnull rdata);
+
+// dnssec_rr_t function prototypes
+#pragma mark - dnssec_rr_t functions
+
+mDNSexport void
+initialize_dnssec_rr_t(dnssec_rr_t * const _Nonnull dnssec_rr, ResourceRecord * const _Nonnull rr);
+
+mDNSexport void
+uninitialize_dnssec_rr_t(dnssec_rr_t * const _Nonnull dnssec_rr);
+
+mDNSexport mDNSBool
+equal_dnssec_rr_t(const dnssec_rr_t * const _Nonnull left, const dnssec_rr_t * const _Nonnull right);
+
+mDNSexport void
+print_dnssec_rr_t(const dnssec_rr_t * const _Nonnull dnssec_rr, mDNSu8 num_of_tabs);
+
+// dnssec_original_t function prototypes
+#pragma mark - dnssec_original_t functions
+
+mDNSexport void
+initialize_dnssec_original_t(
+       dnssec_original_t * const               _Nonnull        original,
+       ResourceRecord * const                  _Nonnull        rr,
+       const mDNSBool                                                          answer_from_cache,
+       const DNSServiceErrorType                                       dns_error,
+       const QC_result                                                         qc_result);
+
+mDNSexport void
+uninitialize_dnssec_original_t(dnssec_original_t * const _Nonnull original);
+
+mDNSexport void
+print_dnssec_original_t(const dnssec_original_t * const _Nonnull original, mDNSu8 num_of_tabs);
+
+#pragma mark - dnssec_cname_t functions
+
+mDNSexport void
+initialize_dnssec_cname_t(dnssec_cname_t * const _Nonnull cname, ResourceRecord * const _Nonnull rr);
+
+mDNSexport void
+uninitialize_dnssec_cname_t(dnssec_cname_t * const _Nonnull cname);
+
+mDNSexport void
+print_dnssec_cname_t(const dnssec_cname_t * const _Nonnull cname, mDNSu8 num_of_tabs);
+
+#pragma mark - dnssec_ds_t functions
+
+mDNSexport mDNSBool
+initialize_dnssec_ds_t(dnssec_ds_t * const _Nonnull ds, ResourceRecord * const _Nonnull rr);
+
+mDNSexport mDNSBool
+equals_dnssec_ds_t(const dnssec_ds_t * const _Nonnull left, const dnssec_ds_t * const _Nonnull right);
+
+mDNSexport void
+uninitialize_dnssec_ds_t(dnssec_ds_t * const _Nonnull ds);
+
+mDNSexport void
+print_dnssec_ds_t(const dnssec_ds_t * const _Nonnull ds, mDNSu8 num_of_tabs);
+
+#pragma mark - dnssec_dnskey_t functions
+
+mDNSexport mDNSBool
+initialize_dnssec_dnskey_t(dnssec_dnskey_t * const _Nonnull dnskey, ResourceRecord * const _Nonnull rr);
+
+mDNSexport mDNSBool
+equals_dnssec_dnskey_t(const dnssec_dnskey_t * const _Nonnull left, const dnssec_dnskey_t * const _Nonnull right);
+
+mDNSexport void
+uninitialize_dnssec_dnskey_t(dnssec_dnskey_t * const _Nonnull dnskey);
+
+mDNSexport void
+print_dnssec_dnskey_t(const dnssec_dnskey_t * const _Nonnull dnskey, mDNSu8 num_of_tabs);
+
+#pragma mark - dnssec_rrsig_t functions
+
+mDNSexport mDNSBool
+initialize_dnssec_rrsig_t(dnssec_rrsig_t * const _Nonnull rrsig, ResourceRecord * const _Nonnull rr);
+
+mDNSexport void
+uninitialize_dnssec_rrsig_t(dnssec_rrsig_t * const _Nonnull rrsig);
+
+mDNSexport void
+print_dnssec_rrsig_t(const dnssec_rrsig_t * const _Nonnull rrsig, mDNSu8 num_of_tabs);
+
+#pragma mark - dnssec_nsec_t functions
+
+mDNSexport mDNSBool
+initialize_dnssec_nsec_t(dnssec_nsec_t * const _Nonnull nsec, ResourceRecord * const _Nonnull rr);
+
+mDNSexport void
+uninitialize_dnssec_nsec_t(dnssec_nsec_t * const _Nonnull nsec);
+
+mDNSexport void
+print_dnssec_nsec_t(const dnssec_nsec_t * const _Nonnull nsec, mDNSu8 num_of_tabs);
+
+#pragma mark - dnssec_nsec3_t functions
+
+mDNSexport mDNSBool
+initialize_dnssec_nsec3_t(dnssec_nsec3_t * const _Nonnull nsec3, ResourceRecord * const _Nonnull rr);
+
+mDNSexport void
+uninitialize_dnssec_nsec3_t(dnssec_nsec3_t * const _Nonnull nsec3);
+
+mDNSexport void
+print_dnssec_nsec3_t(const dnssec_nsec3_t * const _Nonnull nsec3, mDNSu8 num_of_tabs);
+
+#pragma mark - nsecs_with_rrsig_t functions
+
+mDNSexport mStatus
+initialize_nsecs_with_rrsig_t(nsecs_with_rrsig_t * const _Nonnull nsecs);
+
+mDNSexport void
+uninitialize_nsecs_with_rrsig_t(nsecs_with_rrsig_t * const _Nonnull nsecs);
+
+mDNSexport void
+print_nsecs_with_rrsig_t(const nsecs_with_rrsig_t * const _Nonnull nsecs, mDNSu8 num_of_tabs);
+
+# pragma mark - one_nsec_with_rrsigs_t functions
+
+mDNSexport mDNSBool
+initialize_one_nsec_with_rrsigs_t(
+       one_nsec_with_rrsigs_t * const  _Nonnull        one_nsec_with_rrsigs,
+       ResourceRecord * const                  _Nonnull        rr);
+
+mDNSexport void
+uninitialize_one_nsec_with_rrsigs_t(one_nsec_with_rrsigs_t * const     _Nonnull one_nsec_with_rrsigs);
+
+# pragma mark - one_nsec3_with_rrsigs_t functions
+
+mDNSexport mDNSBool
+initialize_one_nsec3_with_rrsigs_t(
+       one_nsec3_with_rrsigs_t * const _Nonnull        one_nsec3_with_rrsigs,
+       ResourceRecord * const                  _Nonnull        rr);
+
+mDNSexport void
+uninitialize_one_nsec3_with_rrsigs_t(one_nsec3_with_rrsigs_t * const _Nonnull one_nsec3_with_rrsigs);
+
+#pragma mark - nsec3s_with_rrsig_t functions
+
+mDNSexport mStatus
+initialize_nsec3s_with_rrsig_t(nsec3s_with_rrsig_t * const _Nonnull nsec3s);
+
+mDNSexport void
+uninitialize_nsec3s_with_rrsig_t(nsec3s_with_rrsig_t * const _Nonnull nsec3s);
+
+mDNSexport void
+print_nsec3s_with_rrsig_t(const nsec3s_with_rrsig_t * const _Nonnull nsec3s, mDNSu8 num_of_tabs);
+
+#pragma mark - cnames_with_rrsig_t
+
+mDNSexport void
+initialize_cname_with_rrsig_t(cnames_with_rrsig_t * const _Nonnull cname);
+
+mDNSexport void
+uninitialize_cname_with_rrsig_t(cnames_with_rrsig_t * const _Nonnull cname);
+
+mDNSexport void
+print_cname_with_rrsig_t(const cnames_with_rrsig_t * const _Nonnull cname, mDNSu8 num_of_tabs);
+
+#pragma mark - response_type_t functions
+
+mDNSexport const char * _Nonnull
+response_type_value_to_string(response_type_t type);
+
+#pragma mark - originals_with_rrsig_t
+
+mDNSexport void
+initialize_originals_with_rrsig_t(originals_with_rrsig_t * const _Nonnull original, const response_type_t type);
+
+mDNSexport void
+uninitialize_originals_with_rrsig_t(originals_with_rrsig_t * const _Nonnull original);
+
+mDNSexport mDNSBool
+contains_rrsig_in_originals_with_rrsig_t(const originals_with_rrsig_t * const _Nonnull original);
+
+mDNSexport void
+print_originals_with_rrsig_t(const originals_with_rrsig_t * const _Nonnull original, mDNSu8 num_of_tabs);
+
+#pragma mark - dses_with_rrsig_t functions
+
+mDNSexport void
+initialize_dses_with_rrsig_t(dses_with_rrsig_t * const _Nonnull ds, const response_type_t type);
+
+mDNSexport void
+uninitialize_dses_with_rrsig_t(dses_with_rrsig_t * const _Nonnull ds);
+
+mDNSexport mDNSBool
+contains_rrsig_in_dses_with_rrsig_t(const dses_with_rrsig_t * const _Nonnull ds);
+
+mDNSexport void
+print_dses_with_rrsig_t(const dses_with_rrsig_t * const _Nonnull ds, mDNSu8 num_of_tabs);
+
+#pragma mark - dses_with_rrsig_t functions
+
+mDNSexport void
+initialize_dnskeys_with_rrsig_t(dnskeys_with_rrsig_t * const _Nonnull dnskey);
+
+mDNSexport void
+uninitialize_dnskeys_with_rrsig_t(dnskeys_with_rrsig_t * const _Nonnull dnskey);
+
+mDNSexport mDNSBool
+contains_rrsig_in_dnskeys_with_rrsig_t(const dnskeys_with_rrsig_t * const _Nonnull dnskey);
+
+mDNSexport void
+print_dnskeys_with_rrsig_t(const dnskeys_with_rrsig_t * const _Nonnull dnskey, mDNSu8 num_of_tabs);
+
+mDNSexport void
+print_original_request_parameters_t(const original_request_parameters_t * const _Nonnull parameters, mDNSu8 num_of_tabs);
+
+#pragma mark - dnssec_zone_t functions
+
+mDNSexport void
+initialize_dnssec_zone_t(
+       dnssec_zone_t * const   _Nonnull        zone,
+       const mDNSu8 * const    _Nonnull        domain_name);
+
+mDNSexport void
+uninitialize_dnssec_zone_t(dnssec_zone_t * const _Nonnull zone);
+
+mDNSexport void
+stop_and_clean_dnssec_zone_t(dnssec_zone_t * const _Nonnull zone);
+
+mDNSexport void
+print_dnssec_zone_t(const dnssec_zone_t * const _Nonnull zone, mDNSu8 num_of_tabs);
+
+#pragma mark - returned_answers_t
+
+mDNSexport void
+initialize_returned_answers_t(
+       returned_answers_t * const      _Nonnull        returned_answers,
+       const dnssec_result_t                                   dnssec_result,
+       const DNSServiceErrorType                               error);
+
+mDNSexport void
+uninitialize_returned_answers_t(returned_answers_t * const _Nonnull returned_answers);
+
+mDNSexport void
+print_returned_answers_t(const returned_answers_t * const _Nonnull returned_answers, mDNSu8 num_of_tabs);
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#endif // DNSSEC_v2_STRUCTS_H
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2_trust_anchor.c b/mDNSMacOSX/dnssec_v2/dnssec_v2_trust_anchor.c
new file mode 100644 (file)
index 0000000..8453ce0
--- /dev/null
@@ -0,0 +1,360 @@
+//
+//     dnssec_v2_trust_anchor.c
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include "DNSCommon.h"
+#include "dnssec_v2_trust_anchor.h"
+#include "dnssec_v2_crypto.h"
+#include "dnssec_v2_trust_anchor.h"
+#include "dnssec_v2_helper.h"
+#include "dnssec_v2_log.h"
+
+static list_t trust_anchors; // list_t<trust_anchor_t>
+
+// trust anchors egtting from https://www.iana.org/dnssec/files
+typedef struct trust_anchor_ds trust_anchor_ds_t;
+struct trust_anchor_ds {
+       domainname      name;
+       mDNSu16         key_tag;
+       mDNSu8          algorithm;
+       mDNSu8          digest_type;
+       mDNSu16         digest_length;
+       mDNSu8          digest[MAX_HASH_OUTPUT_SIZE];
+};
+
+// hard code root trust anchor
+static const trust_anchor_ds_t trusted_trust_anchor[] = {
+       {
+               .name                   = {
+                       .c      = {
+                               0
+                       }
+               },
+               .key_tag                = 19036,
+               .algorithm              = 8,
+               .digest_type    = 2,
+               .digest_length  = 32,
+               .digest                 = {
+                       0x49, 0xAA, 0xC1, 0x1D, 0x7B, 0x6F, 0x64, 0x46, 0x70, 0x2E, 0x54, 0xA1, 0x60, 0x73, 0x71, 0x60, 0x7A, 0x1A,
+                       0x41, 0x85, 0x52, 0x00, 0xFD, 0x2C, 0xE1, 0xCD, 0xDE, 0x32, 0xF2, 0x4E, 0x8F, 0xB5
+               }
+       },
+       {
+               .name                   = {
+                       .c      = {
+                               0
+                       }
+               },
+               .key_tag                = 20326,
+               .algorithm              = 8,
+               .digest_type    = 2,
+               .digest_length  = 32,
+               .digest                 = {
+                       0xE0, 0x6D, 0x44, 0xB8, 0x0B, 0x8F, 0x1D, 0x39, 0xA9, 0x5C, 0x0B, 0x0D, 0x7C, 0x65, 0xD0, 0x84, 0x58, 0xE8,
+                       0x80, 0x40, 0x9B, 0xBC, 0x68, 0x34, 0x57, 0x10, 0x42, 0x37, 0xC7, 0xF8, 0xEC, 0x8D
+               }
+       },
+       // This dnssec.test. trust anchor is set to run local DNSSEC test by using dnssdutil server
+       {
+               .name                   = {
+                       .c      = {
+                               6, 'd', 'n', 's', 's', 'e', 'c', 4, 't', 'e', 's' ,'t', 0
+                       }
+               },
+               .key_tag                = 36815,
+               .algorithm              = 14,
+               .digest_type    = 2,
+               .digest_length  = 32,
+               .digest                 = {
+                       0x23, 0x51, 0xA5, 0x30, 0x3C, 0xD1, 0x68, 0x26, 0x70, 0x64, 0xF1, 0xED, 0x82, 0x53, 0x59, 0x82, 0x05, 0xE7,
+                       0xDF, 0xBE, 0xE1, 0x8E, 0xBA, 0xA9, 0x40, 0xDD, 0x1F, 0x3F, 0x49, 0x97, 0xE3, 0x20
+               }
+       }
+};
+
+//======================================================================================================================
+//     trust_anchors_t functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     initialize_trust_anchors_t
+//======================================================================================================================
+
+mDNSexport void
+initialize_trust_anchors_t(trust_anchors_t * const _Nonnull anchor, const mDNSu8 *const _Nonnull zone_name) {
+       memcpy(anchor->name.c, zone_name, DOMAIN_NAME_LENGTH(zone_name));
+       anchor->name_hash = DomainNameHashValue(&anchor->name);
+       list_init(&anchor->dnskey_trust_anchors, sizeof(dnssec_dnskey_t));
+       list_init(&anchor->ds_trust_anchors, sizeof(dnssec_ds_t));
+}
+
+//======================================================================================================================
+//     uninitialize_trust_anchors_t
+//======================================================================================================================
+
+mDNSexport void
+uninitialize_trust_anchors_t(trust_anchors_t * const _Nonnull anchor) {
+       list_uninit(&anchor->dnskey_trust_anchors);
+       list_uninit(&anchor->ds_trust_anchors);
+}
+
+//======================================================================================================================
+//     print_trust_anchors_t
+//======================================================================================================================
+
+mDNSexport void
+print_trust_anchors_t(const trust_anchors_t * const _Nonnull anchor, mDNSu8 num_of_tabs) {
+       log_debug(TAB_STR "Name: " PRI_DM_NAME, TAB_PARAM(num_of_tabs), DM_NAME_PARAM(&anchor->name));
+       log_debug(TAB_STR "Name Hash: %u", TAB_PARAM(num_of_tabs), anchor->name_hash);
+
+       const list_t * const dnskey_records = &anchor->dnskey_trust_anchors;
+       const list_t * const ds_records         = &anchor->ds_trust_anchors;
+
+       log_debug(TAB_STR "DNSKEY Trust Anchor:", TAB_PARAM(num_of_tabs));
+       for (list_node_t *node = list_get_first(dnskey_records); !list_has_ended(dnskey_records, node); node = list_next(node)) {
+               const dnssec_dnskey_t * const dnssec_dnskey = (dnssec_dnskey_t *)node->data;
+               print_dnssec_dnskey_t(dnssec_dnskey, num_of_tabs + 1);
+       }
+
+       log_debug(TAB_STR "DS Trust Anchor:", TAB_PARAM(num_of_tabs));
+       for (list_node_t *node = list_get_first(ds_records); !list_has_ended(ds_records, node); node = list_next(node)) {
+               const dnssec_ds_t * const dnssec_ds = (dnssec_ds_t *)node->data;
+               print_dnssec_ds_t(dnssec_ds, num_of_tabs + 1);
+       }
+}
+
+//======================================================================================================================
+//     init_and_load_trust_anchors
+//             load trust anchor when mDNSResponder initializes
+//======================================================================================================================
+
+mDNSexport mStatus
+init_and_load_trust_anchors(void) {
+       mStatus                         error;
+       list_t *                        trust_anchor_list                       = &trust_anchors;
+
+       list_init(trust_anchor_list, sizeof(trust_anchors_t));
+
+       for (int i = 0, limit = sizeof(trusted_trust_anchor) / sizeof(trust_anchor_ds_t); i < limit; i++) {
+               const trust_anchor_ds_t * const hardcoded_trust_anchor_ds               = &trusted_trust_anchor[i];
+               trust_anchors_t *                               trust_anchors_from_same_zone    = get_trust_anchor_with_name(hardcoded_trust_anchor_ds->name.c);
+               trust_anchors_t *                               new_trust_anchors_initialized   = mDNSNULL;
+               dnssec_ds_t *                                   ds_to_insert;
+               if (trust_anchors_from_same_zone == mDNSNULL) {
+                       error = list_append_uinitialized(trust_anchor_list, sizeof(trust_anchors_t), (void **)&trust_anchors_from_same_zone);
+                       require_quiet(error == mStatus_NoError, for_loop_exit);
+
+                       initialize_trust_anchors_t(trust_anchors_from_same_zone, hardcoded_trust_anchor_ds->name.c);
+                       new_trust_anchors_initialized = trust_anchors_from_same_zone;
+               }
+
+               error = list_append_uinitialized(&trust_anchors_from_same_zone->ds_trust_anchors, sizeof(dnssec_ds_t), (void **)&ds_to_insert);
+               require_quiet(error == mStatus_NoError, for_loop_exit);
+
+               ds_to_insert->key_tag           = hardcoded_trust_anchor_ds->key_tag;
+               ds_to_insert->algorithm         = hardcoded_trust_anchor_ds->algorithm;
+               ds_to_insert->digest_type       = hardcoded_trust_anchor_ds->digest_type;
+               ds_to_insert->digest_length = hardcoded_trust_anchor_ds->digest_length;
+               ds_to_insert->digest            = hardcoded_trust_anchor_ds->digest;
+
+               ds_to_insert->dnssec_rr.rr_type                 = kDNSType_DS;
+               ds_to_insert->dnssec_rr.rr_class                = 1; // IN_CLASS
+               ds_to_insert->dnssec_rr.rdata_length    = 36; // 4 + 32
+               ds_to_insert->dnssec_rr.name_hash               = DomainNameHashValue(&trust_anchors_from_same_zone->name);
+               memcpy(ds_to_insert->dnssec_rr.name.c, trust_anchors_from_same_zone->name.c, DomainNameLength(&trust_anchors_from_same_zone->name));
+               ds_to_insert->dnssec_rr.rdata_hash              = 0; // fake value
+               ds_to_insert->dnssec_rr.rdata                   = mDNSNULL; // fake value
+
+       for_loop_exit:
+               if (error != mStatus_NoError) {
+                       if (new_trust_anchors_initialized != mDNSNULL) {
+                               list_delete_node_with_data_ptr(trust_anchor_list, new_trust_anchors_initialized);
+                       }
+                       break;
+               }
+       }
+
+       return error;
+}
+
+//======================================================================================================================
+//     get_trust_anchor_with_name
+//             get the trust anchor with the corresponding zone name
+//======================================================================================================================
+
+mDNSexport trust_anchors_t * _Nullable
+get_trust_anchor_with_name(const mDNSu8 * _Nonnull const name) {
+       list_t *trust_anchor_list = &trust_anchors;
+       mDNSu32 name_hash = DomainNameHashValue((domainname *)name);
+
+       for (list_node_t *trust_anchor_node = list_get_first(trust_anchor_list);
+               !list_has_ended(trust_anchor_list, trust_anchor_node);
+               trust_anchor_node = list_next(trust_anchor_node)) {
+
+               trust_anchors_t * trust_anchor = (trust_anchors_t *)trust_anchor_node->data;
+
+               if (trust_anchor->name_hash == name_hash && DOMAIN_NAME_EQUALS(name, trust_anchor->name.c)) {
+                       return trust_anchor;
+               }
+       }
+
+       return mDNSNULL;
+}
+
+//======================================================================================================================
+//     unint_trust_anchors
+//             free the trust anchor list when mDNSResponder exits
+//======================================================================================================================
+
+mDNSexport void
+uninit_trust_anchors(void) { // list_t <trust_anchors_t>
+       list_t *trust_anchor_list = &trust_anchors;
+       for (list_node_t *trust_anchor_node = list_get_first(trust_anchor_list);
+                       !list_has_ended(trust_anchor_list, trust_anchor_node);
+                       trust_anchor_node = list_next(trust_anchor_node)) {
+
+               trust_anchors_t * trust_anchor = (trust_anchors_t *)trust_anchor_node->data;
+               uninitialize_trust_anchors_t(trust_anchor);
+       }
+
+       list_uninit(trust_anchor_list);
+}
+
+//======================================================================================================================
+//     trust_anchor_can_be_reached
+//             check if there is a path in validation tree from leaf(the expected answer/NSEC/NSEC3) to the trust anchor.
+//     If so, we can start the validation process.
+//======================================================================================================================
+
+mDNSexport mDNSBool
+trust_anchor_can_be_reached(dnssec_context_t * const _Nonnull context) {
+       list_t *                                        zones           = &context->zone_chain;
+       dnssec_zone_t *                         zone            = mDNSNULL;
+       originals_with_rrsig_t *        original        = &context->original.original_result_with_rrsig;
+       const list_node_t *                     last_node;
+       mDNSu32                                         request_id = context->original.original_parameters.request_id;
+
+       // If the original response has trust anchor, just match itto see if it is trusted
+       if (context->original.original_trust_anchor != mDNSNULL) {
+               log_default("[R%u] trust_anchor_can_be_reached? Yes, trust anchor found for original response", request_id);
+               return mDNStrue;
+       }
+
+       // Suppressed response comes from mDNSResponder intenal policy, it shold always be trusted
+       if (original->type == original_response && original->u.original.suppressed_response) {
+               log_default("[R%u] trust_anchor_can_be_reached? Yes, suppressed answer is always trusted", request_id);
+               return mDNStrue;
+       }
+
+       if (original->type == unknown_response) {
+               log_default("[R%u] trust_anchor_can_be_reached? No, did not get any original response", request_id);
+               return mDNSfalse;
+       }
+
+       // needs at leaset one zone to reach the trust anchor
+       if (list_empty(zones)) {
+               log_default("[R%u] trust_anchor_can_be_reached? No, zone list is empty", request_id);
+               return mDNSfalse;
+       }
+
+       // The first zone is leaf, the last one is root, the root must have trust anchor to be validated.
+       last_node = list_get_last(zones);
+       zone = (dnssec_zone_t *)last_node->data;
+       if (zone->trust_anchor == mDNSNULL) {
+               log_default("[R%u] trust_anchor_can_be_reached? No, no trust anchor found in the current root", request_id);
+               return mDNSfalse;
+       }
+
+       // starting from the first zone node(which is leaf) to the last node(which is root), to see if they could be connected
+       // through chain of trust
+       for (list_node_t *node = list_get_first(zones); !list_has_ended(zones, node); node = list_next(node)) {
+               zone = (dnssec_zone_t *)node->data;
+
+               if (zone->trust_anchor == mDNSNULL) { // nodes below the root have no trust anchor
+                       verify(node != last_node);
+
+                       // if the data structure that caches the necessary records is not initialized, then the chain is not completed
+                       if (!zone->dses_initialized) {
+                               log_default("[R%u] trust_anchor_can_be_reached? No, not receiving DS reocrds; qname=" PRI_DM_NAME,
+                                       request_id, DM_NAME_PARAM(&zone->domain_name));
+                               return mDNSfalse;
+                       }
+
+                       // if the response is not completely returned to the DNSSEC handler, then the chain is not completed
+                       if (!zone->dnskeys_with_rrsig.set_completed) {
+                               log_default("[R%u] trust_anchor_can_be_reached? No, DNSKEY Set is not completed; qname=" PRI_DM_NAME,
+                                       request_id, DM_NAME_PARAM(&zone->domain_name));
+                               return mDNSfalse;
+                       }
+
+                       if (!contains_rrsig_in_dnskeys_with_rrsig_t(&zone->dnskeys_with_rrsig)) {
+                               log_default("[R%u] trust_anchor_can_be_reached? No, DNSKEY Set does not have any RRSIG; qname=" PRI_DM_NAME,
+                                       request_id, DM_NAME_PARAM(&zone->domain_name));
+                               return mDNSfalse;
+                       }
+
+                       // if the response is not completely returned to the DNSSEC handler, then the chain is not completed
+                       if (!zone->dses_with_rrsig.set_completed) {
+                               log_default("[R%u] trust_anchor_can_be_reached? No, DS Set is not completed; qname=" PRI_DM_NAME,
+                                       request_id, DM_NAME_PARAM(&zone->domain_name));
+                               return mDNSfalse;
+                       }
+
+                       if (!contains_rrsig_in_dses_with_rrsig_t(&zone->dses_with_rrsig)) {
+                               log_default("[R%u] trust_anchor_can_be_reached? No, DS Set does not have any RRSIG; qname=" PRI_DM_NAME,
+                                       request_id, DM_NAME_PARAM(&zone->domain_name));
+                               return mDNSfalse;
+                       }
+
+               } else {
+                       // zone->trust_anchor != mDNSNULL
+                       verify(node == last_node);
+
+                       // If the trust anchor is saved as DNSKEY, then we could use it to validate the records directly instead of
+                       // querying for it
+                       if (trust_anchor_contains_dnskey(zone->trust_anchor)) {
+                               // has DNSKEY trust anchor, can be used to establish chain of trust
+                               log_default("[R%u] trust_anchor_can_be_reached? Yes, it is DNSKEY trust anchor; qname=" PRI_DM_NAME, request_id, DM_NAME_PARAM(&zone->domain_name));
+                               return mDNStrue;
+                       }
+
+                       // If the trust anchor is saved as DS, we should wait for the DNSKEY response come back before doing validation
+                       if (trust_anchor_contains_ds(zone->trust_anchor) && zone->dnskeys_with_rrsig.set_completed) {
+                               log_default("[R%u] trust_anchor_can_be_reached? Yes, it is DS trust anchor; qname=" PRI_DM_NAME, request_id, DM_NAME_PARAM(&zone->domain_name));
+                               return mDNStrue;
+                       }
+
+                       // does not get enough dnskey records to verify
+                       log_default("[R%u] trust_anchor_can_be_reached? No, DNSKEY set is not complete; qname=" PRI_DM_NAME, request_id, DM_NAME_PARAM(&zone->domain_name));
+                       return mDNSfalse;
+               }
+       }
+
+       log_error("should never reach here");
+       return mDNSfalse;
+}
+
+//======================================================================================================================
+//     trust_anchor_contains_deskey
+//======================================================================================================================
+
+mDNSexport mDNSBool
+trust_anchor_contains_dnskey(const trust_anchors_t * const anchor) {
+       return anchor ? (!list_empty(&anchor->dnskey_trust_anchors)) : mDNSfalse;
+}
+
+//======================================================================================================================
+//     trust_anchor_contains_ds
+//======================================================================================================================
+
+mDNSexport mDNSBool
+trust_anchor_contains_ds(const trust_anchors_t * const anchor) {
+       return anchor ? (!list_empty(&anchor->ds_trust_anchors)) : mDNSfalse;
+}
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2_trust_anchor.h b/mDNSMacOSX/dnssec_v2/dnssec_v2_trust_anchor.h
new file mode 100644 (file)
index 0000000..ffca59e
--- /dev/null
@@ -0,0 +1,44 @@
+//
+//     dnssec_v2_trust_anchor.h
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#ifndef DNSSEC_V2_TRUST_ANCHOR_H
+#define DNSSEC_V2_TRUST_ANCHOR_H
+
+#include <stdio.h>
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include "dnssec_v2_structs.h"
+
+mDNSexport void
+initialize_trust_anchors_t(trust_anchors_t * const _Nonnull anchor, const mDNSu8 *const _Nonnull zone_name);
+
+mDNSexport void
+uninitialize_trust_anchors_t(trust_anchors_t * const _Nonnull anchor);
+
+mDNSexport void
+print_trust_anchors_t(const trust_anchors_t * const _Nonnull anchor, mDNSu8 num_of_tabs);
+
+mDNSexport mStatus
+init_and_load_trust_anchors(void);
+
+mDNSexport trust_anchors_t * _Nullable
+get_trust_anchor_with_name(const mDNSu8 * _Nonnull const name);
+
+mDNSexport void
+uninit_trust_anchors(void);
+
+mDNSexport mDNSBool
+trust_anchor_can_be_reached(dnssec_context_t * const _Nonnull context);
+
+mDNSexport mDNSBool
+trust_anchor_contains_dnskey(const trust_anchors_t * const _Nonnull anchor);
+
+mDNSexport mDNSBool
+trust_anchor_contains_ds(const trust_anchors_t * const _Nonnull anchor);
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#endif // DNSSEC_V2_TRUST_ANCHOR_H
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2_validation.c b/mDNSMacOSX/dnssec_v2/dnssec_v2_validation.c
new file mode 100644 (file)
index 0000000..f917512
--- /dev/null
@@ -0,0 +1,3211 @@
+//
+//     dnssec_v2_validation.c
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include <string.h>                                    // for strerror
+#include <errno.h>                                     // for errno
+#include "DNSCommon.h"                         // DomainNameHashValue
+#include "dnssec_v2_structs.h"
+#include "dnssec_v2_validation.h"
+#include "dnssec_v2_retrieval.h"
+#include "dnssec_v2_crypto.h"
+#include "dnssec_v2_trust_anchor.h"
+#include "dnssec_v2_log.h"
+#include "dnssec_v2_helper.h"
+#include "base_n.h"
+
+//======================================================================================================================
+// MARK: - macros
+//======================================================================================================================
+
+#define NSEC3_FLAG_OPT_OUT_BIT 1
+#define NSEC3_FLAG_SET                 NSEC3_FLAG_OPT_OUT_BIT
+
+//======================================================================================================================
+// MARK: - validator type define
+//======================================================================================================================
+typedef struct dnssec_validator_node dnssec_validator_node_t;
+struct dnssec_validator_node {
+       union {
+               struct {
+                       response_type_t                                 rr_response_type;
+               } rr;
+               struct {
+                       const dnssec_dnskey_t *         _Nullable       key;
+                       const dnssec_rrsig_t *          _Nullable       sig;
+               } zsk;
+               struct {
+                       const dnssec_dnskey_t * _Nullable       key;
+                       const dnssec_rrsig_t *  _Nullable       sig;
+                       const dnssec_ds_t *             _Nullable       ds;
+               } ksk;
+               struct {
+                       const dnssec_nsec_t     *       _Nullable       nsec;
+               } nsec;
+               struct {
+                       const dnssec_nsec3_t    * _Nullable     nsec3;
+               } nsec3;
+       }                                                                                       u;
+       dnssec_validator_node_type_t                            type;
+       const mDNSu8 *                                  _Nonnull        name;
+       // type: resource_records, list_t<dnssec_original_t>; type: zone_signing_key, list<dnssec_dnskey_t>; type: key_signing_key, list<dnssec_dnskey_t>;
+       // type: nsec, list_t<one_nsec_with_rrsigs>; type: nsec3, list_t<one_nsec3_wtih_rrsigs>
+       const list_t *                                  _Nullable       siblings;
+       const list_t *                                  _Nonnull        rrssigs_covering_it;
+       mDNSBool                                                                        trusted;
+};
+
+//======================================================================================================================
+// MARK: - local functions prototype
+//======================================================================================================================
+
+
+// MARK: NSEC validation
+
+mDNSlocal dnssec_validation_result_t
+validate_nsec_response(
+       const mDNSu32                                                                   qname_hash,
+       const mDNSu8 * const                            _Nonnull        qname,
+       const mDNSu16                                                                   qclass,
+       const mDNSu16                                                                   qtype,
+       const nsecs_with_rrsig_t * const        _Nonnull        nsecs_with_rrsig,
+       const dnssec_nsec_t * _Nullable *       _Nonnull        out_nsec_1,
+       const list_t * _Nullable                *       _Nonnull        out_rrsigs_1,
+       const dnssec_nsec_t * _Nullable *       _Nonnull        out_nsec_2,
+       const list_t * _Nullable                *       _Nonnull        out_rrsigs_2);
+
+mDNSlocal mDNSBool
+nsec_proves_no_data(
+       const mDNSu32                                                                   qname_hash,
+       const mDNSu8 * const                            _Nonnull        qname,
+       const mDNSu16                                                                   qclass,
+       const mDNSu16                                                                   qtype,
+       const list_t * const                            _Nonnull        nsecs,
+       const dnssec_nsec_t * _Nullable *       _Nonnull        out_nsec_no_data,
+       const list_t * _Nullable *                      _Nonnull        out_rrsigs);
+
+mDNSlocal mDNSBool
+nsec_proves_name_error(
+       const mDNSu8 * const                            _Nonnull        qname,
+       const list_t * const                            _Nonnull        nsecs,
+       const dnssec_nsec_t * _Nullable *       _Nonnull        out_nsec_no_exact_match,
+       const list_t * _Nullable                *       _Nonnull        out_rrsigs_no_exact_match,
+       const dnssec_nsec_t * _Nullable *       _Nonnull        out_nsec_no_wildcard,
+       const list_t * _Nullable                *       _Nonnull        out_rrsigs_no_wildcard);
+
+mDNSlocal mDNSBool
+nsec_proves_wildcard_answer(
+       const mDNSu8 * const                            _Nonnull        qname,
+       const mDNSu16                                                                   qtype,
+       const list_t * const                            _Nonnull        nsecs,
+       const list_t * const                            _Nonnull        wildcard_answer,
+       const list_t * const                            _Nonnull        wildcard_rrsig,
+       const dnssec_nsec_t * _Nullable *       _Nonnull        out_nsec_no_exact_match,
+       const list_t * _Nullable                *       _Nonnull        out_rrsigs_no_exact_match);
+
+mDNSlocal mDNSBool
+nsec_proves_wildcard_no_data(
+       const mDNSu8 * const                            _Nonnull        qname,
+       const mDNSu16                                                                   qclass,
+       const mDNSu16                                                                   qtype,
+       const list_t * const                            _Nonnull        nsecs,
+       const dnssec_nsec_t * _Nullable *       _Nonnull        out_nsec_no_exact_match,
+       const list_t * _Nullable                *       _Nonnull        out_rrsigs_no_exact_match,
+       const dnssec_nsec_t * _Nullable *       _Nonnull        out_nsec_no_matching_stype,
+       const list_t * _Nullable                *       _Nonnull        out_rrsigs_no_matching_stype);
+
+// MARK: NSEC3 validation
+
+mDNSlocal mDNSBool
+is_nsec3_iteration_valid(const dnssec_context_t * const context);
+
+mDNSlocal mDNSu16
+get_maximum_nsec3_iteration_for_dnskey_length(mDNSu16 key_length);
+
+mDNSlocal dnssec_validation_result_t
+get_initial_children_to_validate(
+       dnssec_context_t * const                        _Nonnull        context,
+       dnssec_validator_node_t * const         _Nonnull        child,
+       mDNSu8 * const                                          _Nonnull        out_child_size);
+
+mDNSlocal dnssec_validation_result_t
+validate_nsec3_response(
+       const mDNSu8 * const                            _Nonnull        qname,
+       const mDNSu16                                                                   qtype,
+       const nsec3s_with_rrsig_t * const       _Nonnull        nsec3s_with_rrsig,
+       const dnssec_nsec3_t * _Nullable *      _Nonnull        out_nsec3_1,
+       const list_t * _Nullable *                      _Nonnull        out_rrsig_1,
+       const dnssec_nsec3_t * _Nullable *      _Nonnull        out_nsec3_2,
+       const list_t * _Nullable *                      _Nonnull        out_rrsig_2,
+       const dnssec_nsec3_t * _Nullable *      _Nonnull        out_nsec3_3,
+       const list_t * _Nullable *                      _Nonnull        out_rrsig_3);
+
+mDNSlocal dnssec_validation_result_t
+nsec3_proves_closest_encloser(
+       const mDNSu8 * const                    _Nonnull                        name,
+       const list_t * const                    _Nonnull                        nsec3s,
+       const mDNSu8 * const                    _Nonnull                        zone_name,
+       mDNSu8                                                                                          canonical_name[MAX_DOMAIN_NAME],
+       const dnssec_nsec3_t *  _Nullable * const _Nonnull      out_nsec3_closest_encloser_proof,
+       const list_t *                  _Nullable * const _Nonnull      out_rrsig_closest_encloser_proof,
+       const dnssec_nsec3_t *  _Nullable * const _Nonnull      out_nsec3_next_closer_proof,
+       const list_t *                  _Nullable * const _Nonnull      out_rrsig_next_closer_proof,
+       const mDNSu8 *                  _Nullable * const _Nonnull      out_closest_encloser_name,
+       const mDNSu8 *                  _Nullable * const _Nonnull      out_next_closer_name);
+
+mDNSlocal mDNSBool
+nsec3_contains_different_hash_iteration_salt(const list_t * const nsec3s);
+
+mDNSlocal mDNSBool
+ignore_this_nsec3_record(const dnssec_nsec3_t * const _Nonnull dnssec_nsec3);
+
+// MARK: NSEC/NSEC3 helper function
+
+mDNSlocal mDNSBool
+bit_map_contain_dns_type(const mDNSu8 * const _Nonnull bit_maps, const mDNSu16 bit_maps_length, const mDNSu16 type);
+
+// MARK: validator initializer
+
+mDNSlocal void
+initialize_validator_node_with_rr(
+       dnssec_validator_node_t * const _Nonnull        node,
+       const mDNSu8 * const                    _Nonnull        name,
+       const list_t * const                    _Nonnull        siblings,
+       const list_t * const                    _Nonnull        rrsigs_covering_it, // list_t<dnssec_rrsig_t>
+       response_type_t                                                 response_type);         // list_t<dnssec_rrsig_t>
+
+mDNSlocal void
+initialize_validator_node_with_nsec(
+       dnssec_validator_node_t * const _Nonnull        node,
+       const dnssec_nsec_t     *                       _Nullable       nsec,
+       const mDNSu8 * const                    _Nonnull        name,
+       const list_t * const                    _Nonnull        rrsig_covering_it );    // list_t<dnssec_rrsig_t>
+
+mDNSlocal void
+initialize_validator_node_with_nsec3(
+       dnssec_validator_node_t * const _Nonnull        node,
+       const dnssec_nsec3_t    *               _Nullable       nsec3,
+       const mDNSu8 * const                    _Nonnull        name,
+       const list_t * const                    _Nonnull        rrsig_covering_it);             // list_t<dnssec_rrsig_t>
+
+mDNSlocal void
+initialize_validator_node_with_zsk(
+       dnssec_validator_node_t * const _Nonnull        node,
+       const dnssec_dnskey_t * const   _Nonnull        key,
+       const dnssec_rrsig_t * const    _Nonnull        sig,
+       const mDNSu8 * const                    _Nonnull        name,
+       const list_t * const                    _Nonnull        siblings,                       // list<dnssec_dnskey_t>
+       const list_t * const                    _Nonnull        rrsig_covering_it,      // list_t<dnssec_rrsig_t>
+       mDNSBool                                                                        trusted);
+
+mDNSlocal void
+initialize_validator_node_with_ksk(
+       dnssec_validator_node_t * const _Nonnull        node,
+       const dnssec_dnskey_t * const   _Nonnull        key,
+       const dnssec_rrsig_t * const    _Nonnull        sig,
+       const dnssec_ds_t * const               _Nullable       ds,
+       const mDNSu8 * const                    _Nonnull        name,
+       const list_t * const                    _Nonnull        siblings,                               // list<dnssec_ds_t>
+       const list_t * const                    _Nonnull        rrsig_covering_it,              // list_t<dnssec_rrsig_t>
+       mDNSBool                                                                        trusted);
+
+mDNSlocal void
+uninitialize_validator_node(dnssec_validator_node_t * const _Nonnull node);
+
+// MARK: validation function
+
+mDNSlocal dnssec_validation_result_t
+build_trust_from_ksk_to_zsk(
+       const mDNSu32                                                           request_id,
+       const dnssec_zone_t *   const   _Nonnull        zone,
+       const list_t *                  const   _Nonnull        dnskeys,
+       const list_t *                  const   _Nonnull        dses,
+       const list_t * const                    _Nonnull        rrsigs_covering_dnskey,
+       dnssec_validator_node_t *               _Nonnull        children,
+       const mDNSu8                                                            child_size,
+       dnssec_validator_node_t *               _Nonnull        parents,
+       mDNSu8 * const                                  _Nonnull        out_parent_size);
+
+mDNSlocal dnssec_validation_result_t
+build_trust_from_zsk(
+       const mDNSu32                                                           request_id,
+       const dnssec_zone_t *   const   _Nonnull        zone,
+       const list_t *                  const   _Nonnull        dnskey_list,
+       const list_t *                  const   _Nonnull        rrsig_list_covering_dnskey,
+       dnssec_validator_node_t *               _Nonnull        children,
+       const mDNSu8                                                            child_size,
+       dnssec_validator_node_t *               _Nonnull        parents,
+       mDNSu8 * const                                  _Nonnull        out_parent_size);
+
+mDNSlocal dnssec_validation_result_t
+validate_validator_node(const dnssec_validator_node_t * const _Nonnull nodes, const mDNSu8 nodes_count);
+
+mDNSlocal dnssec_validation_result_t
+validate_validator_path_between_parents_and_children(
+       const mDNSu32                                                   request_id,
+       dnssec_validator_node_t *       _Nonnull        children,
+       dnssec_validator_node_t *       _Nonnull        parents,
+       mDNSu8 * const                          _Nonnull        out_parent_size);
+
+mDNSlocal dnssec_validation_result_t
+validate_validator_path(
+       const mDNSu32                                                                   request_id,
+       const dnssec_validator_node_t * const _Nonnull  child,
+       const dnssec_validator_node_t * const _Nonnull  parent);
+
+mDNSlocal dnssec_validation_result_t
+check_trust_validator_node(const dnssec_validator_node_t * const _Nonnull node);
+
+mDNSlocal void
+dedup_validator_with_the_same_siblings(
+       dnssec_validator_node_t *       _Nonnull        parents,
+       mDNSu8 * const                          _Nonnull        out_parent_size);
+
+mDNSlocal void
+print_ds_validation_progress(const dnssec_validator_node_t * const _Nonnull nodes, const mDNSu8 nodes_count);
+
+mDNSlocal dnssec_validation_result_t
+validate_zone_records_type(const dnssec_zone_t * const _Nonnull zone);
+
+mDNSlocal dnssec_validation_result_t
+validate_ds(const dnssec_ds_t * const _Nonnull ds);
+
+mDNSlocal dnssec_validation_result_t
+validate_dnskey(const dnssec_dnskey_t * const _Nonnull dnskey, mDNSBool security_entry_point);
+
+mDNSlocal dnssec_validation_result_t
+validate_rrsig(const dnssec_rrsig_t * const _Nonnull rrsig);
+
+mDNSlocal dnssec_validation_result_t
+validate_nsec(const dnssec_nsec_t * const _Nonnull nsec);
+
+mDNSlocal dnssec_validation_result_t
+validate_nsec3(const dnssec_nsec3_t * const _Nonnull nsec3);
+
+mDNSlocal dnssec_validation_result_t
+check_if_ds_ksk_matches(const dnssec_ds_t * const _Nonnull ds, const dnssec_dnskey_t * const _Nonnull ksk);
+
+mDNSlocal dnssec_validation_result_t
+validate_path_from_zsk_to_rr(
+       const mDNSu32                                                                           request_id,
+       const dnssec_validator_node_t * const   _Nonnull        parent,
+       const list_t * const                                    _Nonnull        originals /* list_t<dnssec_original_t> */,
+       response_type_t                                                         response_type);
+
+mDNSlocal dnssec_validation_result_t
+validate_path_from_ksk_to_zsk(
+       const mDNSu32                                                                           request_id,
+       const dnssec_validator_node_t * const   _Nonnull        parent,
+       const list_t * const                                    _Nonnull        zsks /* list_t<dnssec_dnskey_t> */);
+
+mDNSlocal dnssec_validation_result_t
+validate_path_from_zsk_to_ds(
+       const mDNSu32                                                                           request_id,
+       const dnssec_validator_node_t * const   _Nonnull        parent,
+       const list_t * const                                    _Nonnull        dses /* list_t<dnssec_dses_t> */);
+
+mDNSlocal dnssec_validation_result_t
+validate_path_from_zsk_to_nsec(
+       const mDNSu32                                                                           request_id,
+       const dnssec_validator_node_t * const   _Nonnull        parent,
+       const dnssec_validator_node_t * const   _Nonnull        child);
+
+mDNSlocal dnssec_validation_result_t
+validate_path_from_zsk_to_nsec3(
+       const mDNSu32                                                                           request_id,
+       const dnssec_validator_node_t * const   _Nonnull        parent,
+       const dnssec_validator_node_t * const   _Nonnull        child);
+
+mDNSlocal dnssec_validation_result_t
+check_rrsig_validity_with_dnssec_rr(
+       const dnssec_rrsig_t * const    _Nonnull        rrsig,
+       const dnssec_rr_t * const               _Nonnull        rr);
+
+mDNSlocal dnssec_validation_result_t
+check_rrsig_validity_with_rrs(
+       const dnssec_rrsig_t * const    _Nonnull        rrsig,
+       const list_t * const                    _Nonnull        list_to_check,
+       response_type_t                                                 response_type_in_list,
+       const mDNSu16                                                           record_type_in_list);
+
+// MARK: reconstruct signed data
+
+mDNSlocal void *
+reconstruct_signed_data_with_rrs(
+       const list_t * const                    _Nonnull        rr_set,
+       const dnssec_rrsig_t * const    _Nonnull        dnssec_rrsig,
+       const response_type_t                                   response_type,
+       const mDNSu16                                                           record_type,
+       mDNSu32 * const                                 _Nonnull        out_signed_data_length);
+
+mDNSlocal void *
+reconstruct_signed_data_with_one_dnssec_rr(
+       const dnssec_rr_t * const               _Nonnull        dnssec_rr,
+       const dnssec_rrsig_t * const    _Nonnull        dnssec_rrsig,
+       mDNSu32 * const                                 _Nonnull        out_signed_data_length);
+
+mDNSlocal void *
+reconstruct_signed_data_internal(
+       const dnssec_rr_t * const                               rr_array[],
+       const mDNSu8                                                    rr_count,
+       const dnssec_rrsig_t * const _Nonnull   dnssec_rrsig,
+       mDNSu32 * const _Nonnull                                out_signed_data_length);
+
+mDNSlocal mStatus
+calculate_signed_data_length(
+       const dnssec_rr_t * const                                       rr_array[_Nonnull],
+       const mDNSu8                                                            rr_count,
+       const dnssec_rrsig_t * const    _Nonnull        dnssec_rrsig,
+       mDNSu32 * const                                 _Nonnull        out_length);
+
+mDNSlocal mDNSs16
+calculate_name_length_in_signed_data(const mDNSu8 * const _Nonnull name, const mDNSu8 rrsig_labels);
+
+mDNSlocal mDNSu16
+calculate_rdata_length_in_signed_data(const dnssec_rr_t * const _Nonnull dnssec_rr);
+
+mDNSlocal const mDNSu8 *
+get_wildcard_name(const mDNSu8 * const _Nonnull name, mDNSu8 * const _Nonnull buffer, const mDNSu16 buffer_length);
+
+mDNSlocal mDNSu32
+copy_rr_for_signed_data(
+       mDNSu8 *                                                _Nonnull        dst,
+       const dnssec_rr_t * const               _Nonnull        rr,
+       const dnssec_rrsig_t * const    _Nonnull        rrsig);
+
+mDNSlocal mDNSu8
+copy_name_in_rr_for_signed_data(
+       mDNSu8 * const                                  _Nonnull        dst,
+       const mDNSu8 * const                    _Nonnull        name,
+       const dnssec_rrsig_t * const    _Nonnull        dnssec_rrsig);
+
+mDNSlocal mDNSu16
+copy_rdata_in_rr(mDNSu8 * const _Nonnull dst, const mDNSu8 * const rdata, const mDNSu16 rdata_length, const mDNSu8 rr_type);
+
+// MARK: sort function
+
+mDNSlocal void
+sort_records_with_algorithm(dnssec_context_t * const _Nonnull context);
+
+mDNSlocal mDNSs8
+dnssec_ds_t_comparator(const list_node_t * _Nonnull const left, const list_node_t * _Nonnull const right);
+
+mDNSlocal mDNSs8
+dnssec_rrsig_t_comparator(const list_node_t * _Nonnull const left, const list_node_t * _Nonnull const right);
+
+mDNSlocal void
+sort_rr_array_canonically(const dnssec_rr_t * rr_array[_Nonnull], const mDNSu8 rr_count);
+
+mDNSlocal mDNSs8
+dnssec_rr_t_comparator(const dnssec_rr_t * const _Nonnull left, const dnssec_rr_t * const _Nonnull right);
+
+mDNSlocal mDNSBool
+rr_array_dedup(const dnssec_rr_t * rr_array[_Nonnull], const mDNSu8 rr_count);
+
+
+//======================================================================================================================
+// MARK: - function definations
+//======================================================================================================================
+
+
+// MARK: validate_dnssec
+
+mDNSexport dnssec_validation_result_t
+validate_dnssec(dnssec_context_t * const _Nonnull context) {
+       dnssec_validator_node_t                 child_node[4] = {0};    // 4 is big enough to hold: nsec3_1, nsec3_2, nsec3_3, wildcard
+       dnssec_validator_node_t                 parent_node[4] = {0};   // the same reason as the above
+       dnssec_validator_node_t *               child = child_node;
+       dnssec_validator_node_t *               parent = parent_node;
+       mDNSu8                                                  child_size = 0;
+       mDNSu8                                                  parent_size = 0;
+       const mDNSu32                                   request_id = context->original.original_parameters.request_id;
+
+       dnssec_validator_node_t *               temp_validator          = mDNSNULL;
+       mDNSu8                                                  temp_size                       = 0;
+       dnssec_validation_result_t      validation_result       = dnssec_validation_valid;
+       mDNSBool                                                nsec3_iteration_valid;
+
+       nsec3_iteration_valid = is_nsec3_iteration_valid(context);
+       require_action_quiet(nsec3_iteration_valid, exit, validation_result = dnssec_validation_nsec3_invalid_hash_iteration);
+
+       // sort records with most secure algorithm comes first
+       sort_records_with_algorithm(context);
+
+       // get the original records out
+       validation_result = get_initial_children_to_validate(context, child, &child_size);
+       require_quiet(validation_result == dnssec_validation_valid && child_size != 0, exit);
+
+       // check if the original RRSET is valid
+       validation_result = validate_validator_node(child, child_size);
+       require_quiet(validation_result == dnssec_validation_valid, exit);
+
+       // check if the original RRSET is trusted by our trusted anchor, it is only possbile when the user queryies for
+       // DNSKEY record since trust anchor is used to trust DNSKEY
+       validation_result = check_trust_validator_node(child);
+       if (validation_result == dnssec_validation_trusted) {
+               goto exit;
+       }
+
+       // Check the validation through the entire chain of trust
+       for (list_node_t *zone_node = list_get_first(&context->zone_chain);
+               !list_has_ended(&context->zone_chain, zone_node);
+               zone_node = list_next(zone_node)) {
+
+               // check for every zone
+               dnssec_zone_t * zone                    = (dnssec_zone_t *)zone_node->data;
+               list_t *                dnskeys                 = &zone->dnskeys_with_rrsig.dnskey_records;
+               list_t *                dnskey_rrsigs   = &zone->dnskeys_with_rrsig.rrsig_records;
+               list_t *                dses                    = &zone->dses_with_rrsig.u.original.ds_records;
+               list_t *                ds_rrsigs               = &zone->dses_with_rrsig.u.original.rrsig_records;
+
+               // It is possible that some records in the critical path of trust chain is missing(NSEC/NSEC3) or redirected(CNAME)
+               validation_result = validate_zone_records_type(zone);
+               require_quiet(validation_result == dnssec_validation_valid, exit);
+
+               //      Validate the previous records(DS or RR) with Zone Signing Key(ZSK)
+               validation_result = build_trust_from_zsk(request_id, zone, dnskeys, dnskey_rrsigs, child, child_size, parent, &parent_size);
+
+               if (validation_result != dnssec_validation_valid) {
+                       goto exit;
+               }
+
+               if (parent_size == 0) {
+                       validation_result = dnssec_validation_trusted;
+                       goto exit;
+               }
+
+               // validate Zone Signing Key(ZSK) with Key Signing Key(KSK)
+               temp_validator  = parent;
+               parent                  = child;
+               child                   = temp_validator;
+               temp_size               = parent_size;
+               parent_size             = child_size;
+               child_size              = temp_size;
+               // TODO: test got NSEC/NSEC3 in the middle
+
+               validation_result = build_trust_from_ksk_to_zsk(request_id, zone, dnskeys, dses, ds_rrsigs, child, child_size, parent, &parent_size);
+               if (validation_result != dnssec_validation_valid) {
+                       goto exit;
+               }
+
+               // print_ds_validation_progress(parent, parent_size);
+
+               if (parent_size == 0) {
+                       validation_result = dnssec_validation_trusted;
+                       goto exit;
+               }
+
+               // repeat the validation process until reaching trust anchor
+               temp_validator  = parent;
+               parent                  = child;
+               child                   = temp_validator;
+               temp_size               = parent_size;
+               parent_size             = child_size;
+               child_size              = temp_size;
+       }
+
+exit:
+       return validation_result;
+}
+
+mDNSlocal dnssec_validation_result_t
+validate_nsec_response(
+       const mDNSu32                                                                   qname_hash,
+       const mDNSu8 * const                            _Nonnull        qname,
+       const mDNSu16                                                                   qclass,
+       const mDNSu16                                                                   qtype,
+       const nsecs_with_rrsig_t * const        _Nonnull        nsecs_with_rrsig,
+       const dnssec_nsec_t * _Nullable *       _Nonnull        out_nsec_1,
+       const list_t * _Nullable                *       _Nonnull        out_rrsigs_1,
+       const dnssec_nsec_t * _Nullable *       _Nonnull        out_nsec_2,
+       const list_t * _Nullable                *       _Nonnull        out_rrsigs_2) {
+
+       const list_t * const nsecs = &nsecs_with_rrsig->nsec_and_rrsigs_same_name;
+
+       // dnssec_validation_nsec_no_data
+       if (nsec_proves_no_data(qname_hash, qname, qclass, qtype, nsecs, out_nsec_1, out_rrsigs_1)) {
+               return dnssec_validation_nsec_no_data;
+       }
+
+       // dnssec_validation_nsec_name_error
+       if (nsec_proves_name_error(qname, nsecs, out_nsec_1, out_rrsigs_1, out_nsec_2, out_rrsigs_2)) {
+               return dnssec_validation_nsec_name_error;
+       }
+
+       // dnssec_validation_nsec_wildcard_answer
+       if (nsec_proves_wildcard_answer(qname, qtype, nsecs, &nsecs_with_rrsig->wildcard_answers, &nsecs_with_rrsig->wildcard_rrsigs, out_nsec_1, out_rrsigs_2)) {
+               return dnssec_validation_nsec_wildcard_answer;
+       }
+
+       // dnssec_validation_nsec_wildcard_no_data
+       if (nsec_proves_wildcard_no_data(qname, qclass, qtype, nsecs, out_nsec_1, out_rrsigs_1, out_nsec_2, out_rrsigs_2)) {
+               return dnssec_validation_nsec_wildcard_no_data;
+       }
+
+       return dnssec_validation_nsec_invalid_nsec_result;
+}
+
+// MARK: validate_dnssec
+// According to https://tools.ietf.org/html/rfc5155#section-10.3 , the iteration field of NSEC3 should have an upper bound
+// to prevent denial-of-service attacks
+mDNSlocal mDNSBool
+is_nsec3_iteration_valid(const dnssec_context_t * const context) {
+       mDNSBool valid = mDNStrue;
+       // check original response
+       const originals_with_rrsig_t * const originals_with_rrsig = &context->original.original_result_with_rrsig;
+
+       if (originals_with_rrsig->type == nsec3_response) {
+               const nsec3s_with_rrsig_t * const       nsec3s_with_rrsig                       = &originals_with_rrsig->u.nsec3s_with_rrsig;
+               const list_t * const                            nsec3_and_rrsigs_same_name      = &nsec3s_with_rrsig->nsec3_and_rrsigs_same_name; // list_t<one_nsec3_with_rrsigs_t>
+               const dnssec_zone_t *                           zone_with_dnskey;
+               const dnskeys_with_rrsig_t *            dnskeys_with_rrsig;
+               const list_t *                                          dnskeys;
+
+               require_action_quiet(!list_empty(nsec3_and_rrsigs_same_name), exit, valid = mDNSfalse);
+
+               require_quiet(!list_empty(&context->zone_chain), exit);
+               zone_with_dnskey        = (dnssec_zone_t *)list_get_first(&context->zone_chain)->data;
+               dnskeys_with_rrsig      = (dnskeys_with_rrsig_t *)&zone_with_dnskey->dnskeys_with_rrsig;
+
+               dnskeys = &dnskeys_with_rrsig->dnskey_records;
+               require_action_quiet(!list_empty(dnskeys), exit, valid = mDNSfalse);
+
+               for (const list_node_t *one_nsec3_node = list_get_first(nsec3_and_rrsigs_same_name);
+                               !list_has_ended(nsec3_and_rrsigs_same_name, one_nsec3_node);
+                               one_nsec3_node = list_next(one_nsec3_node)) {
+
+                       const one_nsec3_with_rrsigs_t * const   one_nsec3               = (one_nsec3_with_rrsigs_t *)one_nsec3_node->data;
+                       const list_t * const                                    rrsigs                  = &one_nsec3->rrsig_records; // list_t<dnssec_rrsig_t>
+                       const mDNSu16                                                   nsec3_iteration = one_nsec3->nsec3_record.iterations;
+
+                       for (const list_node_t * rrsig_node = list_get_first(rrsigs);
+                                       !list_has_ended(rrsigs, rrsig_node);
+                                       rrsig_node = list_next(rrsig_node)) {
+
+                               const dnssec_rrsig_t * const    dnssec_rrsig            = (dnssec_rrsig_t *)rrsig_node->data;
+                               const mDNSu16                                   key_tag_from_rrsig      = dnssec_rrsig->key_tag;
+
+                               for (const list_node_t * dnskey_node = list_get_first(dnskeys);
+                                               !list_has_ended(dnskeys, dnskey_node);
+                                               dnskey_node = list_next(dnskey_node)) {
+
+                                       const dnssec_dnskey_t * const   dnssec_dnskey           = (dnssec_dnskey_t *)dnskey_node->data;
+                                       const mDNSu16                                   key_tag_from_dnskey = dnssec_dnskey->key_tag;
+                                       mDNSu16                                                 max_iteration;
+
+                                       if (key_tag_from_rrsig != key_tag_from_dnskey) {
+                                               continue;
+                                       }
+
+                                       max_iteration = get_maximum_nsec3_iteration_for_dnskey_length(dnssec_dnskey->public_key_length);
+                                       require_action_quiet(nsec3_iteration <= max_iteration, exit, valid = mDNSfalse);
+                               }
+                       }
+               }
+       }
+
+exit:
+       return valid;
+}
+
+mDNSlocal mDNSu16
+get_maximum_nsec3_iteration_for_dnskey_length(mDNSu16 key_length) {
+       mDNSu16 max;
+       mDNSs32 rounded_key_length_in_bits = key_length * 8;
+       static const mDNSs32 fixed_key_size[] = {
+               1024, 2048, 4096
+       };
+       static const mDNSs32 max_iteration_for_fixed_key_size[] = {
+               150, 500, 2500
+       };
+       // use "sizeof(fixed_key_size) == sizeof(max_iteration_for_fixed_key_size) ? sizeof(fixed_key_size) / sizeof(mDNSu32) : -1" to check if two array have matched elements.
+       mDNSu32 distance_to_fixed_key_size[sizeof(fixed_key_size) == sizeof(max_iteration_for_fixed_key_size) ? sizeof(fixed_key_size) / sizeof(mDNSu32) : -1];
+
+       // get the closest key size from 102, 2048, 4096
+       for (size_t i = 0; i < sizeof(fixed_key_size) / sizeof(mDNSu32); i++) {
+               distance_to_fixed_key_size[i] = abs(fixed_key_size[i] - rounded_key_length_in_bits);
+       }
+
+       mDNSu32 min_distance            = UINT_MAX;
+       size_t  min_distance_index      = -1;
+       for (size_t i = 0; i < sizeof(fixed_key_size) / sizeof(mDNSu32); i++) {
+               if (min_distance < distance_to_fixed_key_size[i]) {
+                       continue;
+               }
+               min_distance            = distance_to_fixed_key_size[i];
+               min_distance_index      = i;
+       }
+
+       switch (fixed_key_size[min_distance_index]) {
+               case 1024:
+                       max = 150;
+                       break;
+               case 2048:
+                       max = 500;
+                       break;
+               case 4096:
+                       max = 2500;
+                       break;
+               default:
+                       max = 0;
+                       break;
+       }
+
+       return max;
+}
+
+mDNSlocal dnssec_validation_result_t
+get_initial_children_to_validate(
+       dnssec_context_t * const                        _Nonnull                        context,
+       dnssec_validator_node_t * const _Nonnull                        child,
+       mDNSu8 * const                                          _Nonnull                        out_child_size) {
+
+       const list_t *                                  original_siblings;
+       mDNSu8                                                  child_size                      = 0;
+       dnssec_validation_result_t              validation_result       = dnssec_validation_valid;
+       const response_type_t                   type                            = context->original.original_result_with_rrsig.type;
+       const mDNSu8 *                                  qname                           = context->original.original_parameters.question_name.c;
+       const mDNSu32                                   qname_hash                      = DomainNameHashValue((domainname *)qname);
+       const mDNSu16                                   qclass                          = context->original.original_parameters.question_class;
+       const mDNSu16                                   qtype                           = context->original.original_parameters.question_type;
+       const trust_anchors_t * const   orig_trust_anchor       = context->original.original_trust_anchor;
+       mDNSu32                                                 request_id                      = GET_REQUEST_ID(context);
+       mDNSu32                                                 question_id                     = GET_QUESTION_ID(context);
+
+       switch (type) {
+               case original_response: {
+                       // check if the trust anchor installed for the question name already trusts all the answers
+                       mDNSBool                                trusted_original_response       = mDNStrue;
+                       const list_t * const    original_rr_list                        = &context->original.original_result_with_rrsig.u.original.original_records;
+                       if (qtype == kDNSType_DNSKEY && trust_anchor_contains_dnskey(orig_trust_anchor)) {
+                               const list_t * const dnskey_trust_anchors       = &orig_trust_anchor->dnskey_trust_anchors;
+                               dnssec_dnskey_t left;
+
+                               for (list_node_t * dnssec_original_node = list_get_first(original_rr_list);
+                                               !list_has_ended(original_rr_list, dnssec_original_node);
+                                               dnssec_original_node = list_next(dnssec_original_node)) {
+
+                                       const dnssec_original_t * const original = (dnssec_original_t *)dnssec_original_node->data;
+                                       parse_dns_type_dnskey_t(original->dnssec_rr.rdata, original->dnssec_rr.rdata_length,
+                                                                                       &left.flags, &left.protocol, &left.algorithm, &left.public_key_length, &left.public_key);
+
+                                       mDNSBool trust_anchor_matches = mDNSfalse;
+                                       for (const list_node_t * dnskey_node = list_get_first(dnskey_trust_anchors);
+                                                       !list_has_ended(dnskey_trust_anchors, dnskey_node);
+                                                       dnskey_node = list_next(dnskey_node)) {
+                                               const dnssec_dnskey_t * const right_ptr = (dnssec_dnskey_t *)dnskey_node->data;
+                                               if (equals_dnssec_dnskey_t(&left, right_ptr)) {
+                                                       trust_anchor_matches = mDNStrue;
+                                                       break;
+                                               }
+                                       }
+
+                                       if (!trust_anchor_matches) {
+                                               trusted_original_response = mDNSfalse;
+                                               break;
+                                       }
+                               }
+                       } else if (qtype == kDNSType_DS && trust_anchor_contains_ds(orig_trust_anchor)) {
+                               const list_t * const ds_trust_anchors   = &orig_trust_anchor->ds_trust_anchors;
+                               dnssec_ds_t left;
+                               mDNSBool is_valid = mDNSfalse;
+
+                               for (list_node_t * dnssec_original_node = list_get_first(original_rr_list);
+                                               !list_has_ended(original_rr_list, dnssec_original_node);
+                                               dnssec_original_node = list_next(dnssec_original_node)) {
+
+                                       const dnssec_original_t * const original = (dnssec_original_t *)dnssec_original_node->data;
+                                       is_valid = parse_dns_type_ds_t(original->dnssec_rr.rdata, original->dnssec_rr.rdata_length,
+                                               &left.key_tag, &left.algorithm, &left.digest_type, &left.digest_length, &left.digest);
+                                       verify_action(is_valid,
+                                               log_debug("[R%u->Q%u] The returned DS records are malformated", request_id, question_id);
+                                               continue
+                                       );
+
+                                       mDNSBool trust_anchor_matches = mDNSfalse;
+                                       for (const list_node_t * ds_node = list_get_first(ds_trust_anchors);
+                                                       !list_has_ended(ds_trust_anchors, ds_node);
+                                                       ds_node = list_next(ds_node)) {
+                                               const dnssec_ds_t * const right_ptr = (dnssec_ds_t *)ds_node->data;
+                                               if (equals_dnssec_ds_t(&left, right_ptr)) {
+                                                       trust_anchor_matches = mDNStrue;
+                                                       break;
+                                               }
+                                       }
+
+                                       if (!trust_anchor_matches) {
+                                               trusted_original_response = mDNSfalse;
+                                               break;
+                                       }
+                               }
+                       } else {
+                               trusted_original_response = mDNSfalse;
+                       }
+
+                       if (trusted_original_response) {
+                               validation_result = dnssec_validation_trusted;
+                               goto exit;
+                       }
+               } // fall into case cname_response
+               case cname_response: {
+                       const list_t *                                  rrsigs; // list_t<dnssec_rrsig_t>
+                       if (type == original_response) {
+                               original_siblings       = &context->original.original_result_with_rrsig.u.original.original_records;
+                               rrsigs                          = &context->original.original_result_with_rrsig.u.original.rrsig_records;
+                       } else if (type == cname_response) {
+                               original_siblings       = &context->original.original_result_with_rrsig.u.cname_with_rrsig.cname_records;
+                               rrsigs                          = &context->original.original_result_with_rrsig.u.cname_with_rrsig.rrsig_records;
+                       } else {
+                               validation_result = dnssec_validation_invalid_internal_state;
+                               goto exit;
+                       }
+                       initialize_validator_node_with_rr(&child[child_size], qname, original_siblings, rrsigs, type);
+                       child_size++;
+                       break;
+               }
+               case nsec_response: {
+                       // check the meaning of NSEC response: No Name, No Data, Wildcard Answer, Wildcard No Data
+                       const dnssec_nsec_t *   nsec_to_verify_1        = mDNSNULL;
+                       const list_t *                  rrsigs_to_verify_1      = mDNSNULL;
+                       const dnssec_nsec_t *   nsec_to_verify_2        = mDNSNULL;
+                       const list_t *                  rrsigs_to_verify_2      = mDNSNULL;
+                       nsecs_with_rrsig_t *    nsecs_with_rrsig        = &context->original.original_result_with_rrsig.u.nsecs_with_rrsig;
+
+                       nsecs_with_rrsig->nsec_result = validate_nsec_response(qname_hash, qname, qclass, qtype, nsecs_with_rrsig,
+                                                                                                                                       &nsec_to_verify_1, &rrsigs_to_verify_1, &nsec_to_verify_2, &rrsigs_to_verify_2);
+
+                       require_action_quiet(
+                                                                       (nsecs_with_rrsig->nsec_result == dnssec_validation_nsec_no_data
+                                                                       || nsecs_with_rrsig->nsec_result == dnssec_validation_nsec_name_error
+                                                                       || nsecs_with_rrsig->nsec_result == dnssec_validation_nsec_wildcard_answer
+                                                                       || nsecs_with_rrsig->nsec_result == dnssec_validation_nsec_wildcard_no_data)
+                                                                       && rrsigs_to_verify_1 != mDNSNULL
+                                                                       , exit, validation_result = nsecs_with_rrsig->nsec_result); // When the assertion holds, rrsigs_to_verify_1 must be nonnull.
+
+                       initialize_validator_node_with_nsec(&child[child_size], nsec_to_verify_1, qname, rrsigs_to_verify_1);
+                       child_size++;
+
+                       if (nsec_to_verify_2 != mDNSNULL) {
+                               initialize_validator_node_with_nsec(&child[child_size], nsec_to_verify_2, qname, rrsigs_to_verify_2);
+                               child_size++;
+                       }
+
+                       if (!list_empty(&nsecs_with_rrsig->wildcard_answers)) {
+                               // and nsecs_with_rrsig->wildcard_rrsigs is not empty either
+                               initialize_validator_node_with_rr(&child[child_size], qname, &nsecs_with_rrsig->wildcard_answers,
+                                                                                                       &nsecs_with_rrsig->wildcard_rrsigs, original_response);
+                               child_size++;
+                       }
+                       break;
+               }
+               case nsec3_response: {
+                       const dnssec_nsec3_t *  nsec3_to_verify_1       = mDNSNULL;
+                       const list_t *                  rrsigs_to_verify_1      = mDNSNULL;
+                       const dnssec_nsec3_t *  nsec3_to_verify_2       = mDNSNULL;
+                       const list_t *                  rrsigs_to_verify_2      = mDNSNULL;
+                       const dnssec_nsec3_t *  nsec3_to_verify_3       = mDNSNULL;
+                       const list_t *                  rrsigs_to_verify_3      = mDNSNULL;
+                       nsec3s_with_rrsig_t *   nsec3s_with_rrsig       = &context->original.original_result_with_rrsig.u.nsec3s_with_rrsig;
+
+                       nsec3s_with_rrsig->nsec3_result = validate_nsec3_response(qname, qtype, nsec3s_with_rrsig,
+                                                                                                                                               &nsec3_to_verify_1, &rrsigs_to_verify_1, &nsec3_to_verify_2, &rrsigs_to_verify_2, &nsec3_to_verify_3, &rrsigs_to_verify_3);
+
+                       require_action_quiet(
+                                                                       nsec3s_with_rrsig->nsec3_result == dnssec_validation_nsec3_no_data_response
+                                                                       || nsec3s_with_rrsig->nsec3_result == dnssec_validation_nsec3_no_data_response
+                                                                       || nsec3s_with_rrsig->nsec3_result == dnssec_validation_nsec3_no_data_response_opt_out
+                                                                       || nsec3s_with_rrsig->nsec3_result == dnssec_validation_nsec3_wildcard_no_data
+                                                                       || nsec3s_with_rrsig->nsec3_result == dnssec_validation_nsec3_wildcard_answer_response
+                                                                       || nsec3s_with_rrsig->nsec3_result == dnssec_validation_nsec3_name_error
+                                                                       , exit, validation_result = nsec3s_with_rrsig->nsec3_result);
+
+                       initialize_validator_node_with_nsec3(&child[child_size], nsec3_to_verify_1, qname,      rrsigs_to_verify_1);
+                       child_size++;
+
+                       if (nsec3_to_verify_2 != mDNSNULL) {
+                               initialize_validator_node_with_nsec3(&child[child_size], nsec3_to_verify_2, qname, rrsigs_to_verify_2);
+                               child_size++;
+                       }
+
+                       if (nsec3_to_verify_3 != mDNSNULL) {
+                               initialize_validator_node_with_nsec3(&child[child_size], nsec3_to_verify_3, qname, rrsigs_to_verify_3);
+                               child_size++;
+                       }
+
+                       if (!list_empty(&nsec3s_with_rrsig->wildcard_answers)) {
+                               // and nsecs_with_rrsig->wildcard_rrsigs is not empty either
+                               initialize_validator_node_with_rr(&child[child_size], qname, &nsec3s_with_rrsig->wildcard_answers,
+                                                                                                       &nsec3s_with_rrsig->wildcard_rrsigs, rr_validator);
+                               child_size++;
+                       }
+                       break;
+               }
+               default:
+                       log_error("DNSSEC validation starts with unknown orginal resource record;");
+                       validation_result = dnssec_validation_invalid_internal_state;
+                       goto exit;
+       }
+
+exit:
+       *out_child_size = child_size;
+       return validation_result;
+}
+
+mDNSlocal dnssec_validation_result_t
+validate_nsec3_response(
+       const mDNSu8 * const                            _Nonnull        qname,
+       const mDNSu16                                                                   qtype,
+       const nsec3s_with_rrsig_t * const       _Nonnull        nsec3s_with_rrsig,
+       const dnssec_nsec3_t * _Nullable *      _Nonnull        out_nsec3_1,
+       const list_t * _Nullable *                      _Nonnull        out_rrsig_1,
+       const dnssec_nsec3_t * _Nullable *      _Nonnull        out_nsec3_2,
+       const list_t * _Nullable *                      _Nonnull        out_rrsig_2,
+       const dnssec_nsec3_t * _Nullable *      _Nonnull        out_nsec3_3,
+       const list_t * _Nullable *                      _Nonnull        out_rrsig_3) {
+
+       dnssec_validation_result_t                              validation_result       = dnssec_validation_validating;
+       dnssec_validation_result_t                              error;
+       const list_t * const                                    nsec3s                          = &nsec3s_with_rrsig->nsec3_and_rrsigs_same_name;
+       const one_nsec3_with_rrsigs_t * const   first_one_nsec3         = (one_nsec3_with_rrsigs_t *)(list_get_first(nsec3s)->data);
+       const dnssec_nsec3_t * const                    first_dnssec_nsec3      = &first_one_nsec3->nsec3_record;
+       const mDNSu8                                                    hash_algorithm          = first_dnssec_nsec3->hash_algorithm;
+       const mDNSu8 * const                                    salt                            = first_dnssec_nsec3->salt;
+       const mDNSu8                                                    salt_length                     = first_dnssec_nsec3->salt_length;
+       const mDNSu16                                                   iterations                      = first_dnssec_nsec3->iterations;
+       mDNSu8 *                                                                qname_hash_b32          = mDNSNULL;
+       mDNSu16                                                                 qname_length            = DOMAIN_NAME_LENGTH(qname);
+       mDNSu8                                                                  canonical_name[MAX_DOMAIN_NAME];
+
+       require_action(!list_empty(nsec3s), exit, validation_result = dnssec_validation_bogus;
+               log_default("nsec3 list is empty"));
+
+       require_action(!nsec3_contains_different_hash_iteration_salt(nsec3s), exit, validation_result = dnssec_validation_nsec3_different_hash_iteration_salt;
+               log_default("NSEC3s with different algorithm, salt or iteration in the same response"));
+
+       // check if there is any wildcard response
+       if (!list_empty(&nsec3s_with_rrsig->wildcard_answers)) {
+               // Wildcard Answer Responses
+               const list_t * const    wildcard_answers = &nsec3s_with_rrsig->wildcard_answers; // list_t<dnssec_rr_t>
+               const mDNSu8 *                  closest_encloser_name;
+               mDNSu8                                  closest_encloser_name_length;
+               const dnssec_nsec3_t *  nsec3_closest_encloser_proof;
+               const list_t *                  rrsig_closest_encloser_proof;
+               const mDNSu8 *                  next_closer;
+               const dnssec_nsec3_t *  nsec3_next_closer_proof;
+               const list_t *                  rrsig_next_closer_proof;
+
+               error = nsec3_proves_closest_encloser(qname, nsec3s, qname, canonical_name, &nsec3_closest_encloser_proof,
+                       &rrsig_closest_encloser_proof, &nsec3_next_closer_proof, &rrsig_next_closer_proof, &closest_encloser_name,
+                       &next_closer);
+               require_action(error == dnssec_validation_nsec3_provable_closest_encloser, exit,
+                       validation_result = dnssec_validation_bogus;log_default("Cannot find closest encloser;"));
+               closest_encloser_name_length = DOMAIN_NAME_LENGTH(closest_encloser_name);
+
+               // make sure that this closest encloser is the immediate ancestor to the generating wildcard
+               for (list_node_t *node = list_get_first(wildcard_answers); !list_has_ended(wildcard_answers, node); node = list_next(node)) {
+                       const dnssec_rr_t * const       dnssec_rr       = (dnssec_rr_t *)node->data;
+                       const mDNSu8 *                          name            = dnssec_rr->name.c;
+
+                       mDNSBool matches = memcmp(name + 1 + *name, closest_encloser_name,
+                               MIN(DOMAIN_NAME_LENGTH(name), closest_encloser_name_length));
+
+                       require_action(matches, exit, validation_result = dnssec_validation_bogus);
+               }
+
+               *out_nsec3_1            = nsec3_closest_encloser_proof;
+               *out_rrsig_1            = rrsig_closest_encloser_proof;
+               *out_nsec3_2            = nsec3_next_closer_proof;
+               *out_rrsig_2            = rrsig_next_closer_proof;
+               validation_result       = dnssec_validation_nsec3_wildcard_answer_response;
+
+               goto exit;
+       } else {
+               // check if there is a matching NSEC3 that matches qname
+
+               mDNSu8                                  qname_hash[MAX_HASH_OUTPUT_SIZE];
+               mDNSu32                                 qname_hash_length;
+               mDNSu8                                  qname_hash_b32_length;
+               const dnssec_nsec3_t *  nsec3_that_matches_qname = mDNSNULL;
+
+               // get the base32 format of qname hash
+
+               qname_hash_length = get_hash_length_for_nsec3_hash_type(hash_algorithm);
+               mDNSBool calculated = calculate_hash_for_nsec3(qname_hash, sizeof(qname_hash), hash_algorithm, qname, qname_length, salt, salt_length, iterations);
+               require_action(calculated, exit, validation_result = dnssec_validation_invalid_internal_state);
+               qname_hash_b32 = (mDNSu8 *)base_n_encode(DNSSEC_BASE_32_HEX, qname_hash, qname_hash_length);
+               qname_hash_b32_length = strlen((char *)qname_hash_b32);
+
+               for (list_node_t * one_nsec3_node = list_get_first(nsec3s);
+                       !list_has_ended(nsec3s, one_nsec3_node);
+                       one_nsec3_node = list_next(one_nsec3_node)) {
+
+                       const one_nsec3_with_rrsigs_t * const   one_nsec3                               = (one_nsec3_with_rrsigs_t *)one_nsec3_node->data;
+                       const dnssec_nsec3_t * const                    dnssec_nsec3                    = &one_nsec3->nsec3_record;
+                       const mDNSu8 * const                                    frist_label_owner_name  = dnssec_nsec3->dnssec_rr.name.c + 1;
+                       const mDNSu8                                                    first_label_length              = *dnssec_nsec3->dnssec_rr.name.c;
+
+                       if (compare_canonical_dns_label(qname_hash_b32, qname_hash_b32_length, frist_label_owner_name, first_label_length) == 0) {
+                               *out_nsec3_1 = dnssec_nsec3;
+                               *out_rrsig_1 = &one_nsec3->rrsig_records;
+                               nsec3_that_matches_qname = dnssec_nsec3;
+                               break;
+                       }
+               }
+
+               if (nsec3_that_matches_qname != mDNSNULL) {
+                       // No Data Responses, QTYPE is not DS
+                       // No Data Responses, QTYPE is DS
+
+                       // An NSEC3 RR that matches QNAME is present.
+                       mDNSBool contains_type;
+                       contains_type = bit_map_contain_dns_type(nsec3_that_matches_qname->type_bit_maps, nsec3_that_matches_qname->type_bit_maps_length, qtype);
+                       require_action(!contains_type, exit, validation_result = dnssec_validation_bogus;
+                               log_default("NSEC3 contains DNS type that should not exist;" PUB_S, DNS_TYPE_STR(qtype)));
+
+                       contains_type = bit_map_contain_dns_type(nsec3_that_matches_qname->type_bit_maps, nsec3_that_matches_qname->type_bit_maps_length, kDNSType_CNAME);
+                       require_action(!contains_type, exit, validation_result = dnssec_validation_bogus;
+                               log_default("NSEC3 contains DNS type that should not exist;" PUB_S, DNS_TYPE_STR(kDNSType_CNAME)));
+
+                       validation_result = dnssec_validation_nsec3_no_data_response;
+                       goto exit;
+               } else {
+                       // Wildcard No Data Responses
+                       // Name Error Responses
+                       // No Data Responses, QTYPE is DS
+                       const mDNSu8 *                  closest_encloser_name;
+                       mDNSu8                                  closest_encloser_name_length;
+                       const dnssec_nsec3_t *  nsec3_closest_encloser_proof;
+                       const list_t *                  rrsig_closest_encloser_proof;
+                       const mDNSu8 *                  next_closer;
+                       const dnssec_nsec3_t *  nsec3_next_closer_proof;
+                       const list_t *                  rrsig_next_closer_proof;
+                       mDNSu8                                  wildcard_closest_encloser[MAX_DOMAIN_NAME];
+                       mDNSu8                                  wildcard_length;
+                       mDNSu8 *                                wildcard_hash_b32 = mDNSNULL;
+                       mDNSu32                                 wildcard_hash_b32_length;
+
+                       // find closest encloser
+                       error = nsec3_proves_closest_encloser(qname, nsec3s, qname, canonical_name, &nsec3_closest_encloser_proof,
+                               &rrsig_closest_encloser_proof, &nsec3_next_closer_proof, &rrsig_next_closer_proof,
+                               &closest_encloser_name, &next_closer);
+                       require_action(error == dnssec_validation_nsec3_provable_closest_encloser, exit,
+                               validation_result = dnssec_validation_bogus;log_default("Cannot find closest encloser;"));
+                       closest_encloser_name_length = DOMAIN_NAME_LENGTH(closest_encloser_name);
+
+                       *out_nsec3_1 = nsec3_closest_encloser_proof;
+                       *out_rrsig_1 = rrsig_closest_encloser_proof;
+                       *out_nsec3_2 = nsec3_next_closer_proof;
+                       *out_rrsig_2 = rrsig_next_closer_proof;
+
+                       // check if it is "No Data Responses, QTYPE is DS" case, the "Opt-out" case
+                       if (qtype == kDNSType_DS) {
+                               if ((nsec3_closest_encloser_proof->flags & NSEC3_FLAG_OPT_OUT_BIT)) {
+                                       validation_result = dnssec_validation_nsec3_no_data_response_opt_out;
+                               } else {
+                                       validation_result = dnssec_validation_bogus;
+                               }
+                               goto exit;
+                       }
+
+                       require_action(closest_encloser_name_length + 2 <= sizeof(wildcard_closest_encloser), exit,
+                               validation_result = dnssec_validation_bogus; log_error("wildcard closest encloser length is invalid"));
+
+                       // check if wildcard exists
+                       // get wildcard name
+                       wildcard_closest_encloser[0] = 1;
+                       wildcard_closest_encloser[1] = '*';
+                       memcpy(wildcard_closest_encloser + 2, closest_encloser_name, closest_encloser_name_length);
+                       wildcard_length = DOMAIN_NAME_LENGTH(wildcard_closest_encloser);
+
+                       wildcard_hash_b32 = calculate_b32_hash_for_nsec3(wildcard_closest_encloser, wildcard_length,
+                               first_dnssec_nsec3->hash_algorithm, first_dnssec_nsec3->salt, first_dnssec_nsec3->salt_length, first_dnssec_nsec3->iterations);
+                       require_action(wildcard_hash_b32 != mDNSNULL, exit,
+                               validation_result = dnssec_validation_no_memory ;log_error("b32_encode failed"));
+
+                       wildcard_hash_b32_length = strlen((char *)wildcard_hash_b32);
+
+                       for (list_node_t *one_nsec3_node = list_get_first(nsec3s);
+                                       !list_has_ended(nsec3s, one_nsec3_node);
+                                       one_nsec3_node = list_next(one_nsec3_node)) {
+
+                               const one_nsec3_with_rrsigs_t * const   one_nsec3               = (one_nsec3_with_rrsigs_t *)one_nsec3_node->data;
+                               const dnssec_nsec3_t * const                    dnssec_nsec3    = &one_nsec3->nsec3_record;
+                               const mDNSu8 *                                                  current                 = (mDNSu8 *)(dnssec_nsec3->dnssec_rr.name.c + 1);
+                               const mDNSu8                                                    current_length  = *(dnssec_nsec3->dnssec_rr.name.c);
+                               const mDNSu8 *                                                  next                    = (mDNSu8 *)(dnssec_nsec3->next_hashed_owner_name_b32);
+                               const mDNSu8                                                    next_length             = dnssec_nsec3->next_hashed_owner_name_b32_length;
+                               mDNSBool                                                                last_nsec               = compare_canonical_dns_label(current, current_length, next, next_length) > 0;
+
+                               if (compare_canonical_dns_label(current, current_length, wildcard_hash_b32, wildcard_hash_b32_length) == 0) {
+                                       // Wildcard No Data Responses
+                                       *out_nsec3_2 = dnssec_nsec3;
+
+                                       // check type map
+                                       mDNSBool contains_type;
+                                       contains_type = bit_map_contain_dns_type(dnssec_nsec3->type_bit_maps, dnssec_nsec3->type_bit_maps_length, qtype);
+                                       require_action(!contains_type, for_loop_exit,
+                                               validation_result = dnssec_validation_bogus;
+                                               log_default("NSEC3 contains DNS type that should not exist;" PUB_S, DNS_TYPE_STR(qtype)));
+
+                                       contains_type = bit_map_contain_dns_type(dnssec_nsec3->type_bit_maps, dnssec_nsec3->type_bit_maps_length, kDNSType_CNAME);
+                                       require_action(!contains_type, for_loop_exit,
+                                               validation_result = dnssec_validation_bogus;
+                                               log_default("NSEC3 contains DNS type that should not exist;" PUB_S, DNS_TYPE_STR(kDNSType_CNAME)));
+
+                                       *out_nsec3_3 = dnssec_nsec3;
+                                       *out_rrsig_3 = &one_nsec3->rrsig_records;
+                                       validation_result = dnssec_validation_nsec3_wildcard_no_data;
+
+                                       goto for_loop_exit;
+                               }
+
+                               if (compare_canonical_dns_label(current, current_length, wildcard_hash_b32, wildcard_hash_b32_length) < 0
+                                       && (last_nsec || compare_canonical_dns_label(wildcard_hash_b32, wildcard_hash_b32_length, next, next_length) < 0)) {
+                                       // Name Error Responses
+                                       *out_nsec3_3 = dnssec_nsec3;
+                                       *out_rrsig_3 = &one_nsec3->rrsig_records;
+                                       validation_result = dnssec_validation_nsec3_name_error;
+
+                                       goto for_loop_exit;
+                               }
+                       }
+                       validation_result = dnssec_validation_bogus;
+               for_loop_exit:
+                       if (wildcard_hash_b32 != mDNSNULL) {
+                               free(wildcard_hash_b32);
+                               wildcard_hash_b32 = mDNSNULL;
+                       }
+               }
+       }
+
+exit:
+       if (qname_hash_b32 != mDNSNULL) {
+               free(qname_hash_b32);
+       }
+       return validation_result;
+}
+
+mDNSexport mDNSu16
+calculate_key_tag(const mDNSu8 key[_Nonnull], const mDNSu16 key_len, const mDNSu8 algorithm)
+{
+       if (algorithm == 1) {
+               // The key tag is defined to be the most significant 16 bits of the least significant 24 bits in the public key modulus.
+               // However, RSA/MD5 whose algorithm number is 1 is not supported by mDNSResponder, so we will not implement it.
+               return 0;
+       }
+
+       mDNSu32 key_tag = 0;
+
+       for (mDNSu32 i = 0; i < key_len; i++)
+       {
+               if (i & 1)      key_tag += key[i];
+               else                    key_tag += (mDNSu32)(key[i] << 8);
+       }
+       key_tag += (key_tag >> 16) & 0xFFFF;
+       key_tag &= 0xFFFF;
+
+       return key_tag;
+}
+
+//======================================================================================================================
+//     Local functions
+//======================================================================================================================
+
+//======================================================================================================================
+//     bit_map_contain_dns_type
+//======================================================================================================================
+
+mDNSlocal mDNSBool
+bit_map_contain_dns_type(const mDNSu8 * const _Nonnull bit_maps, const mDNSu16 bit_maps_length, const mDNSu16 type) {
+       const mDNSu8 window_index       = type / 256;
+       const mDNSu8 offset                     = type % 256;
+       const mDNSu8 * ptr                      = bit_maps;
+       const mDNSu8 * ptr_limit        = ptr + bit_maps_length;
+
+       for (;ptr < ptr_limit; ptr += 2 + *(ptr + 1)) {
+               const mDNSu8    current_window_index    = *ptr;
+               const mDNSu8    block_bit_map_length    = *(ptr + 1);
+               const mDNSu32   bit_count                               = block_bit_map_length * 8;
+               const mDNSu8    mask                                    = 1 << (7 - (offset % 8));
+               const mDNSu8 *  current_block                   = ptr + 2;
+
+               if (current_window_index != window_index) {
+                       continue;
+               }
+
+               if (offset >= bit_count) {
+                       continue;
+               }
+
+               if ((current_block[offset / 8] & mask) != 0) {
+                       return mDNStrue;
+               }
+       }
+
+       return mDNSfalse;
+}
+
+//======================================================================================================================
+//     NSEC result validation
+//======================================================================================================================
+
+//======================================================================================================================
+//     nsec_proves_no_data
+//======================================================================================================================
+
+mDNSlocal mDNSBool
+nsec_proves_no_data(
+       const mDNSu32                                                                   qname_hash,
+       const mDNSu8 * const                            _Nonnull        qname,
+       const mDNSu16                                                                   qclass,
+       const mDNSu16                                                                   qtype,
+       const list_t * const                            _Nonnull        nsecs,
+       const dnssec_nsec_t * _Nullable *       _Nonnull        out_nsec_no_data,
+       const list_t * _Nullable *                      _Nonnull        out_rrsigs) {
+
+       for (list_node_t *nsec_node = list_get_first(nsecs); !list_has_ended(nsecs, nsec_node); nsec_node = list_next(nsec_node)) {
+               const one_nsec_with_rrsigs_t * const one_nsec = (one_nsec_with_rrsigs_t *)nsec_node->data;
+               // have NSEC record
+               const dnssec_nsec_t * const dnssec_nsec = &one_nsec->nsec_record;
+
+               // with same SNAME
+               if (qname_hash != dnssec_nsec->dnssec_rr.name_hash || !DOMAIN_NAME_EQUALS(qname, dnssec_nsec->exist_domain_name)) {
+                       continue;
+               }
+
+               // with same SCLASS
+               if (qclass != dnssec_nsec->dnssec_rr.rr_class) {
+                       continue;
+               }
+
+               // does not contain the STYPE
+               if (bit_map_contain_dns_type(dnssec_nsec->type_bit_maps, dnssec_nsec->type_bit_maps_length, qtype)) {
+                       continue;
+               }
+
+               // proves No Data;
+               *out_nsec_no_data       = dnssec_nsec;
+               *out_rrsigs                     = &one_nsec->rrsig_records;
+               return mDNStrue;
+       }
+
+       return mDNSfalse;
+}
+
+//======================================================================================================================
+//     nsec_proves_name_error
+//======================================================================================================================
+
+mDNSlocal mDNSBool
+nsec_proves_name_error(
+       const mDNSu8 * const                            _Nonnull        qname,
+       const list_t * const                            _Nonnull        nsecs,
+       const dnssec_nsec_t * _Nullable *       _Nonnull        out_nsec_no_exact_match,
+       const list_t * _Nullable                *       _Nonnull        out_rrsigs_no_exact_match,
+       const dnssec_nsec_t * _Nullable *       _Nonnull        out_nsec_no_wildcard,
+       const list_t * _Nullable                *       _Nonnull        out_rrsigs_no_wildcard) {
+
+       mDNSBool no_exact_match         = mDNSfalse;
+       mDNSBool no_wildcard_match      = mDNSfalse;
+
+       for (list_node_t *nsec_node = list_get_first(nsecs); !list_has_ended(nsecs, nsec_node); nsec_node = list_next(nsec_node)) {
+               const one_nsec_with_rrsigs_t * const one_nsec = (one_nsec_with_rrsigs_t *)nsec_node->data;
+               // have NSEC record
+               const dnssec_nsec_t * const dnssec_nsec = &one_nsec->nsec_record;
+               const mDNSu8 * const            prev = dnssec_nsec->exist_domain_name;
+               const mDNSu8 * const            next = dnssec_nsec->next_domain_name;
+               mDNSs8                                          name_compare_result;
+               mDNSBool                                        last_nsec = compare_canonical_dns_name(next, prev) < 0;;
+
+               // check if an NSEC RR proving that there is no exact match for <SNAME, SCLASS> exist
+               while (!no_exact_match) {
+                       // prev < q_name
+                       name_compare_result = compare_canonical_dns_name(qname, prev);
+                       if (name_compare_result <= 0) {
+                               break;
+                       }
+
+                       // q_name < next
+                       name_compare_result = compare_canonical_dns_name(next, qname);
+                       if (!last_nsec && name_compare_result <= 0) {
+                               break;
+                       }
+
+                       *out_nsec_no_exact_match        = dnssec_nsec;
+                       *out_rrsigs_no_exact_match      = &one_nsec->rrsig_records;
+                       no_exact_match                          = mDNStrue;
+               }
+
+               // check if an NSEC RR proving that the zone contains no RRsets that would match <SNAME, SCLASS>
+               // via wildcard name expansion exists
+               while (!no_wildcard_match) {
+                       mDNSu8 buffer[MAX_DOMAIN_NAME];
+                       const mDNSu8 * const wildcard_name = get_wildcard_name(qname, buffer, sizeof(buffer));
+                       if (wildcard_name == mDNSNULL) {
+                               break;
+                       }
+
+                       // prev < wildcard_name
+                       name_compare_result = compare_canonical_dns_name(wildcard_name, prev);
+                       if (name_compare_result <= 0) {
+                               break;
+                       }
+
+                       // wildcard_name < next
+                       name_compare_result = compare_canonical_dns_name(next, wildcard_name);
+                       if (!last_nsec && name_compare_result <= 0) {
+                               break;
+                       }
+
+                       *out_nsec_no_wildcard   = dnssec_nsec;
+                       *out_rrsigs_no_wildcard = &one_nsec->rrsig_records;
+                       no_wildcard_match               = mDNStrue;
+               }
+
+               if (no_exact_match && no_wildcard_match) {
+                       break;
+               }
+       }
+
+       return no_exact_match && no_wildcard_match;
+}
+
+//======================================================================================================================
+//     nsec_proves_wildcard_answer
+//======================================================================================================================
+
+mDNSlocal mDNSBool
+nsec_proves_wildcard_answer(
+       const mDNSu8 * const                            _Nonnull        qname,
+       const mDNSu16                                                                   qtype,
+       const list_t * const                            _Nonnull        nsecs,
+       const list_t * const                            _Nonnull        wildcard_answer,
+       const list_t * const                            _Nonnull        wildcard_rrsig,
+       const dnssec_nsec_t * _Nullable *       _Nonnull        out_nsec_no_exact_match,
+       const list_t * _Nullable                *       _Nonnull        out_rrsigs_no_exact_match) {
+
+       mDNSBool no_exact_match                                 = mDNSfalse;
+       mDNSBool contains_wildcard_answer               = mDNSfalse;
+       mDNSBool contains_wildcard_rrsig                = mDNSfalse;
+
+       for (list_node_t *nsec_node = list_get_first(nsecs); !list_has_ended(nsecs, nsec_node); nsec_node = list_next(nsec_node)) {
+               const one_nsec_with_rrsigs_t * const one_nsec = (one_nsec_with_rrsigs_t *)nsec_node->data;
+               // have NSEC record
+               const dnssec_nsec_t * const dnssec_nsec = &one_nsec->nsec_record;
+               const mDNSu8 * const            prev = dnssec_nsec->exist_domain_name;
+               const mDNSu8 * const            next = dnssec_nsec->next_domain_name;
+               mDNSs8                                          name_compare_result;
+               mDNSBool                                        last_nsec = compare_canonical_dns_name(prev, next) > 0;
+
+               // prev < q_name
+               name_compare_result = compare_canonical_dns_name(qname, prev);
+               if (name_compare_result <= 0) {
+                       continue;
+               }
+
+               // q_name < next
+               name_compare_result = compare_canonical_dns_name(next, qname);
+               if (!last_nsec && name_compare_result <= 0) {
+                       continue;
+               }
+
+               *out_nsec_no_exact_match        = dnssec_nsec;
+               *out_rrsigs_no_exact_match      = &one_nsec->rrsig_records;
+               no_exact_match = mDNStrue;
+       }
+
+       // contains wildcard answer
+       contains_wildcard_answer = !list_empty(wildcard_answer);
+
+       // contains wildcard RRSIG
+       for (list_node_t *rrsig_node = list_get_first(wildcard_rrsig); !list_has_ended(wildcard_rrsig, rrsig_node); rrsig_node = list_next(rrsig_node)) {
+               const dnssec_rrsig_t * const dnssec_rrsig = (dnssec_rrsig_t *)rrsig_node->data;
+               // cover the wildcard
+               if (dnssec_rrsig->type_covered != qtype) {
+                       continue;
+               }
+
+               // label is 1 less than the question name
+               if (dnssec_rrsig->labels + 1 != get_number_of_labels(dnssec_rrsig->dnssec_rr.name.c)) {
+                       continue;
+               }
+
+               contains_wildcard_rrsig = mDNStrue;
+       }
+
+       return no_exact_match && contains_wildcard_answer && contains_wildcard_rrsig;
+}
+
+//======================================================================================================================
+//     nsec_proves_wildcard_no_data
+//======================================================================================================================
+
+mDNSlocal mDNSBool
+nsec_proves_wildcard_no_data(
+       const mDNSu8 * const                            _Nonnull        qname,
+       const mDNSu16                                                                   qclass,
+       const mDNSu16                                                                   qtype,
+       const list_t * const                            _Nonnull        nsecs,
+       const dnssec_nsec_t * _Nullable *       _Nonnull        out_nsec_no_exact_match,
+       const list_t * _Nullable                *       _Nonnull        out_rrsigs_no_exact_match,
+       const dnssec_nsec_t * _Nullable *       _Nonnull        out_nsec_no_matching_stype,
+       const list_t * _Nullable                *       _Nonnull        out_rrsigs_no_matching_stype){
+
+       mDNSBool no_exact_match                                 = mDNSfalse;
+       mDNSBool no_matching_stype                              = mDNSfalse;
+
+       for (list_node_t *nsec_node = list_get_first(nsecs); !list_has_ended(nsecs, nsec_node); nsec_node = list_next(nsec_node)) {
+               const one_nsec_with_rrsigs_t * const one_nsec = (one_nsec_with_rrsigs_t *)nsec_node->data;
+               // have NSEC record
+               const dnssec_nsec_t * const dnssec_nsec = &one_nsec->nsec_record;
+               const mDNSu8 * const            prev = dnssec_nsec->exist_domain_name;
+               const mDNSu8 * const            next = dnssec_nsec->next_domain_name;
+               mDNSs8                                          name_compare_result;
+               mDNSBool                                        last_nsec = compare_canonical_dns_name(prev, next) > 0;
+
+               // check if an NSEC RR proving that there is no exact match for <SNAME, SCLASS> exist
+               while (!no_exact_match) {
+                       // prev < q_name
+                       name_compare_result = compare_canonical_dns_name(qname, prev);
+                       if (name_compare_result <= 0) {
+                               break;
+                       }
+
+                       // q_name < next
+                       name_compare_result = compare_canonical_dns_name(next, qname);
+                       if (!last_nsec && name_compare_result <= 0) {
+                               break;
+                       }
+
+                       *out_nsec_no_exact_match        = dnssec_nsec;
+                       *out_rrsigs_no_exact_match      = &one_nsec->rrsig_records;
+                       no_exact_match                          = mDNStrue;
+               }
+
+               // check if an NSEC RR proving that there are no RRsets matching STYPE at the wildcard owner name that matched
+               // <SNAME, SCLASS> via wildcard expansion, exists
+               while (!no_matching_stype) {
+                       // with same SNAME
+                       mDNSu8 wildcard_name[256];
+
+                       if (!DOMAIN_NAME_EQUALS(get_wildcard_name(qname, wildcard_name, sizeof(wildcard_name)), dnssec_nsec->exist_domain_name)) {
+                               break;
+                       }
+
+                       // with same SCLASS
+                       if (qclass != dnssec_nsec->dnssec_rr.rr_class) {
+                               break;
+                       }
+
+                       // does not contain the STYPE
+                       if (bit_map_contain_dns_type(dnssec_nsec->type_bit_maps, dnssec_nsec->type_bit_maps_length, qtype)) {
+                               break;
+                       }
+
+                       *out_nsec_no_matching_stype             = dnssec_nsec;
+                       *out_rrsigs_no_matching_stype   = &one_nsec->rrsig_records;
+                       no_matching_stype                               = mDNStrue;
+               }
+
+               if (no_exact_match && no_matching_stype) {
+                       break;
+               }
+       }
+
+       return no_exact_match && no_matching_stype;
+}
+
+//======================================================================================================================
+//     NSEC3 result validation
+//======================================================================================================================
+
+mDNSlocal dnssec_validation_result_t
+nsec3_proves_closest_encloser(
+       const mDNSu8 * const                    _Nonnull                                name,
+       const list_t * const                    _Nonnull                                nsec3s,
+       const mDNSu8 * const                    _Nonnull                                zone_name,
+       mDNSu8                                                                                                  canonical_name[MAX_DOMAIN_NAME],
+       const dnssec_nsec3_t *  _Nullable * const _Nonnull              out_nsec3_closest_encloser_proof,
+       const list_t *                  _Nullable * const _Nonnull              out_rrsig_closest_encloser_proof,
+       const dnssec_nsec3_t *  _Nullable * const _Nonnull              out_nsec3_next_closer_proof,
+       const list_t *                  _Nullable * const _Nonnull              out_rrsig_next_closer_proof,
+       const mDNSu8 *                  _Nullable * const _Nonnull              out_closest_encloser_name,
+       const mDNSu8 *                  _Nullable * const _Nonnull              out_next_closer_name) {
+
+       dnssec_validation_result_t                              result                          = dnssec_validation_validating;
+       const one_nsec3_with_rrsigs_t * const   first_one_nsec3         = (one_nsec3_with_rrsigs_t *)list_get_first(nsec3s)->data;
+       const dnssec_nsec3_t * const                    first_dnssec_nsec3      = &first_one_nsec3->nsec3_record;
+       const mDNSu8                                                    hash_algorithm          = first_dnssec_nsec3->hash_algorithm;
+       const mDNSu16                                                   iterations                      = first_dnssec_nsec3->iterations;
+       const mDNSu8 * const                                    salt                            = first_dnssec_nsec3->salt;
+       const mDNSu8                                                    salt_length                     = first_dnssec_nsec3->salt_length;
+       mDNSu8                                                                  checking_flag;
+       mDNSu16                                                                 canonical_name_length;
+       const mDNSu8 *                                                  sname;
+       mDNSu16                                                                 sname_length;
+       mDNSBool                                                                break_loop;
+
+       copy_canonical_name(canonical_name, name);
+       canonical_name_length = DOMAIN_NAME_LENGTH(canonical_name);
+
+       sname                   = canonical_name;
+       sname_length    = canonical_name_length;
+       checking_flag   = mDNSfalse;
+
+       while (*sname != 0 && result == dnssec_validation_validating) {
+               mDNSu8 * const hash_b32 = calculate_b32_hash_for_nsec3(sname, sname_length, hash_algorithm, salt, salt_length, iterations);
+               require_action(hash_b32 != mDNSNULL, exit, result = dnssec_validation_no_memory);
+               mDNSu8 hash_b32_length = strlen((char *)hash_b32);
+               break_loop = mDNSfalse;
+
+               for (list_node_t *one_nsec3_node = list_get_first(nsec3s);
+                               !list_has_ended(nsec3s, one_nsec3_node) && !break_loop;
+                               one_nsec3_node = list_next(one_nsec3_node)) {
+
+                       const one_nsec3_with_rrsigs_t * const   one_nsec3               = (one_nsec3_with_rrsigs_t *)one_nsec3_node->data;
+                       const dnssec_nsec3_t * const                    dnssec_nsec3    = &one_nsec3->nsec3_record;
+                       const mDNSu8 *                                                  current                 = (mDNSu8 *)(dnssec_nsec3->dnssec_rr.name.c + 1);
+                       const mDNSu8                                                    current_length  = *(dnssec_nsec3->dnssec_rr.name.c);
+                       const mDNSu8 *                                                  next                    = (mDNSu8 *)(dnssec_nsec3->next_hashed_owner_name_b32);
+                       const mDNSu8                                                    next_length             = dnssec_nsec3->next_hashed_owner_name_b32_length;
+                       const mDNSBool                                                  last_nsec3              = compare_canonical_dns_label(current, current_length, next, next_length) > 0;
+
+                       // ignore invalid NSEC3 record
+                       if (ignore_this_nsec3_record(dnssec_nsec3)) {
+                               continue;
+                       }
+
+                       // If there is an NSEC3 RR in the response that covers SNAME
+                       if (compare_canonical_dns_label(current, current_length, hash_b32, hash_b32_length) < 0
+                               && (last_nsec3 || compare_canonical_dns_label(hash_b32, hash_b32_length, next, next_length) < 0)) { // SNAME is covered by this NSEC3 record.
+                               //, set the flag.
+                               checking_flag                                   = mDNStrue;
+                               *out_nsec3_next_closer_proof    = dnssec_nsec3;
+                               *out_rrsig_next_closer_proof    = &one_nsec3->rrsig_records;
+                               *out_next_closer_name                   = sname;
+                               break_loop                                              = mDNStrue;
+                       } else if (compare_canonical_dns_label(current, current_length, hash_b32, hash_b32_length) == 0) {
+                               // If there is a matching NSEC3 RR in the response,
+                               // and the flag was set,
+                               require_action_quiet(checking_flag, exit, result = dnssec_validation_bogus);
+
+                               // and the nsec3 record comes from a proper zone,
+                               mDNSu8 subdomain = is_a_subdomain_of_b(sname, zone_name);
+                               require_action_quiet(subdomain, exit, result = dnssec_validation_nsec3_nsec3_not_from_the_zone);
+
+                               // then this NEC3 proves closest encloser
+                               *out_nsec3_closest_encloser_proof       = dnssec_nsec3;
+                               *out_rrsig_closest_encloser_proof       = &one_nsec3->rrsig_records;
+                               *out_closest_encloser_name                      = sname;
+                               break_loop                                                      = mDNStrue;
+                               result                                                          = dnssec_validation_nsec3_provable_closest_encloser;
+                       }
+               }
+
+               free(hash_b32);
+               sname_length    -= 1 + *sname;
+               sname                   += 1 + *sname;
+               if (!break_loop) {
+                       checking_flag   = mDNSfalse;
+               }
+       }
+
+exit:
+       return result;
+}
+
+
+//======================================================================================================================
+//     nsec3_contains_different_hash_iteration_salt
+//======================================================================================================================
+
+mDNSlocal mDNSBool
+nsec3_contains_different_hash_iteration_salt(const list_t * const nsec3s) {
+       if (list_empty(nsec3s)) {
+               return mDNStrue;
+       }
+
+       const one_nsec3_with_rrsigs_t * const   first_one_nsec3         = (one_nsec3_with_rrsigs_t *)(list_get_first(nsec3s)->data);
+       const dnssec_nsec3_t * const                    first_dnssec_nsec3      = &first_one_nsec3->nsec3_record;
+
+       for (list_node_t *one_nsec3_node = list_get_first(nsec3s);
+                       !list_has_ended(nsec3s, one_nsec3_node);
+                       one_nsec3_node = list_next(one_nsec3_node)) {
+
+               const one_nsec3_with_rrsigs_t * const   one_nsec3               = (one_nsec3_with_rrsigs_t *)one_nsec3_node->data;
+               const dnssec_nsec3_t * const                    dnssec_nsec3    = &one_nsec3->nsec3_record;
+
+               if (dnssec_nsec3->hash_algorithm != first_dnssec_nsec3->hash_algorithm
+                       || dnssec_nsec3->iterations != first_dnssec_nsec3->iterations
+                       || dnssec_nsec3->salt_length != first_dnssec_nsec3->salt_length
+                       || memcmp(dnssec_nsec3->salt, first_dnssec_nsec3->salt, dnssec_nsec3->salt_length) != 0) {
+                       return mDNStrue;
+               }
+       }
+
+       return mDNSfalse;
+}
+
+//======================================================================================================================
+//     ignore_this_nsec3_record
+//======================================================================================================================
+
+mDNSlocal mDNSBool
+ignore_this_nsec3_record(const dnssec_nsec3_t * const _Nonnull dnssec_nsec3) {
+       // ignore the NSEC3 with unknown hash type, right now we only support SHA1(1)
+       if (dnssec_nsec3->hash_algorithm != 1) {
+               return mDNStrue;
+       }
+
+       mDNSu8 flags    = dnssec_nsec3->flags;
+       flags                   = (flags & (~1));
+       // ignore the NSEC3 with any flag bits set except for the least significant bit, which is used as Opt-out option
+       if (flags != 0) {
+               return mDNStrue;
+       }
+
+       return mDNSfalse;
+}
+
+//======================================================================================================================
+//     build_trust_from_ksk_to_zsk
+//======================================================================================================================
+
+mDNSlocal dnssec_validation_result_t
+build_trust_from_ksk_to_zsk(
+       const mDNSu32                                                           request_id,
+       const dnssec_zone_t *   const   _Nonnull        zone,
+       const list_t *                  const   _Nonnull        dnskeys,
+       const list_t *                  const   _Nonnull        dses,
+       const list_t * const                    _Nonnull        rrsigs_covering_dnskey,
+       dnssec_validator_node_t *               _Nonnull        children,
+       const mDNSu8                                                            child_size,
+       dnssec_validator_node_t *               _Nonnull        parents,
+       mDNSu8 * const                                  _Nonnull        out_parent_size) {
+
+       dnssec_validation_result_t              result  = dnssec_validation_invalid;
+       dnssec_validation_result_t              error;
+       const mDNSu8 * const                    zone_name                               = zone->domain_name.c;
+       const list_t * const                    dnskey_trust_anchors    = zone->trust_anchor ? &zone->trust_anchor->dnskey_trust_anchors : mDNSNULL;
+       const list_t * const                    ds_trust_anchors                = zone->trust_anchor ? &zone->trust_anchor->ds_trust_anchors : mDNSNULL;
+       mDNSu8 parent_size = 0;
+
+       for (mDNSu8 i = 0; i < child_size; i++) {
+               dnssec_validator_node_t *               child                   = &children[i];
+               const list_t * const                    rrsigs                  = child->rrssigs_covering_it;
+
+               for (const list_node_t * rrsig_node = list_get_first(rrsigs); !list_has_ended(rrsigs, rrsig_node); rrsig_node = list_next(rrsig_node)) {
+                       const dnssec_rrsig_t * const    dnssec_rrsig            = (dnssec_rrsig_t *)rrsig_node->data;
+                       const mDNSu16                                   key_tag_from_rrsig      = dnssec_rrsig->key_tag;
+
+                       verify_action(dnssec_rrsig->type_covered == kDNSType_DNSKEY, continue);
+
+                       const dnssec_dnskey_t *         dnssec_dnskey   = mDNSNULL;
+                       const dnssec_ds_t *                     dnssec_ds               = mDNSNULL;
+                       dnssec_validator_node_t *       parent                  = mDNSNULL;
+                       mDNSBool                                        find_trust_anchor = mDNSfalse;
+
+                       if (dnskey_trust_anchors != mDNSNULL && !list_empty(dnskey_trust_anchors)) {
+                               for (const list_node_t * dnskey_node = list_get_first(dnskey_trust_anchors);
+                                               !list_has_ended(dnskey_trust_anchors, dnskey_node);
+                                               dnskey_node = list_next(dnskey_node)) {
+
+                                       dnssec_dnskey                                           = (dnssec_dnskey_t *)dnskey_node->data;
+                                       const mDNSu16   key_tag_from_dnskey = dnssec_dnskey->key_tag;
+                                       mDNSBool                entry_point                     = (DNSKEY_FLAG_SECURITY_ENTRY_POINT & dnssec_dnskey->flags);
+
+                                       if (key_tag_from_rrsig != key_tag_from_dnskey || !entry_point) {
+                                               dnssec_dnskey = mDNSNULL;
+                                               continue;
+                                       }
+
+                                       find_trust_anchor = mDNStrue;
+                                       goto initialize_parent;
+                               }
+                       }
+
+                       if (!find_trust_anchor && ds_trust_anchors != mDNSNULL && !list_empty(ds_trust_anchors)) {
+                               for (const list_node_t * dnskey_node = list_get_first(dnskeys);
+                                               !list_has_ended(dnskeys, dnskey_node);
+                                               dnskey_node = list_next(dnskey_node)) {
+
+                                       dnssec_dnskey                                           = (dnssec_dnskey_t *)dnskey_node->data;
+                                       const mDNSu16 key_tag_from_dnskey       = dnssec_dnskey->key_tag;
+                                       mDNSBool                entry_point                     = (DNSKEY_FLAG_SECURITY_ENTRY_POINT & dnssec_dnskey->flags);
+
+                                       if (key_tag_from_rrsig != key_tag_from_dnskey || !entry_point) {
+                                               dnssec_dnskey = mDNSNULL;
+                                               continue;
+                                       }
+
+                                       for (const list_node_t * ds_node = list_get_first(ds_trust_anchors);
+                                                       !list_has_ended(ds_trust_anchors, ds_node);
+                                                       ds_node = list_next(ds_node)) {
+
+                                               dnssec_ds                                               = (dnssec_ds_t *)ds_node->data;
+                                               const mDNSu16 key_tag_from_ds   = dnssec_ds->key_tag;
+
+                                               if (key_tag_from_rrsig != key_tag_from_ds) {
+                                                       dnssec_ds = mDNSNULL;
+                                                       continue;
+                                               }
+
+                                               find_trust_anchor = mDNStrue;
+                                               goto initialize_parent;
+                                       }
+                               }
+                       }
+
+                       if (!find_trust_anchor) {
+                               for (const list_node_t * dnskey_node = list_get_first(dnskeys); !list_has_ended(dnskeys, dnskey_node); dnskey_node = list_next(dnskey_node)) {
+                                       dnssec_dnskey                                           = (dnssec_dnskey_t *)dnskey_node->data;
+                                       const mDNSu16 key_tag_from_dnskey       = dnssec_dnskey->key_tag;
+                                       mDNSBool                entry_point                     = (DNSKEY_FLAG_SECURITY_ENTRY_POINT & dnssec_dnskey->flags);
+
+                                       if (key_tag_from_rrsig != key_tag_from_dnskey || !entry_point) {
+                                               dnssec_dnskey = mDNSNULL;
+                                               continue;
+                                       }
+
+                                       for (const list_node_t * ds_node = list_get_first(dses);
+                                                       !list_has_ended(dses, ds_node);
+                                                       ds_node = list_next(ds_node)) {
+
+                                               dnssec_ds                                               = (dnssec_ds_t *)ds_node->data;
+                                               const mDNSu16 key_tag_from_ds   = dnssec_ds->key_tag;
+
+                                               if (key_tag_from_rrsig != key_tag_from_ds) {
+                                                       dnssec_ds = mDNSNULL;
+                                                       continue;
+                                               }
+
+                                               goto initialize_parent;
+                                       }
+                               }
+                       }
+
+                       if (dnssec_dnskey == mDNSNULL && dnssec_ds == mDNSNULL) {
+                               continue;
+                       }
+
+               initialize_parent:
+                       parent = &parents[parent_size];
+                       parent_size++;
+                       uninitialize_validator_node(parent);
+                       initialize_validator_node_with_ksk(parent, dnssec_dnskey, dnssec_rrsig, dnssec_ds, zone_name,
+                               dses, rrsigs_covering_dnskey, find_trust_anchor);
+
+                       break;
+               }
+       }
+
+       require_action_quiet(parent_size == child_size, exit, result = dnssec_validation_no_matching_key_tag);
+
+       error = validate_validator_node(parents, parent_size);
+       require_action_quiet(error == dnssec_validation_valid, exit, result = error);
+
+       error = validate_validator_path_between_parents_and_children(request_id, children, parents, &parent_size);
+       require_action_quiet(error == dnssec_validation_valid, exit, result = error);
+
+       dedup_validator_with_the_same_siblings(parents, &parent_size);
+
+       *out_parent_size = parent_size;
+
+       result = dnssec_validation_valid;
+
+exit:
+       return result;
+}
+
+//======================================================================================================================
+//     build_trust_from_zsk
+//======================================================================================================================
+
+mDNSlocal dnssec_validation_result_t
+build_trust_from_zsk(
+       const mDNSu32                                                           request_id,
+       const dnssec_zone_t *   const   _Nonnull        zone,
+       const list_t *                  const   _Nonnull        dnskeys,
+       const list_t *                  const   _Nonnull        rrsigs_covering_dnskeys,
+       dnssec_validator_node_t *               _Nonnull        children,
+       const mDNSu8                                                            child_size,
+       dnssec_validator_node_t *               _Nonnull        parents,
+       mDNSu8 * const                                  _Nonnull        out_parent_size) {
+
+       dnssec_validation_result_t              result                                  = dnssec_validation_invalid;
+       dnssec_validation_result_t              error;
+       const mDNSu8 * const                    zone_name                               = zone->domain_name.c;
+       const trust_anchors_t * const   trust_anchor                    = zone->trust_anchor;
+       const list_t * const                    dnskey_trust_anchors    = &trust_anchor->dnskey_trust_anchors;
+       mDNSBool                                                contain_trust_anchor    = trust_anchor_contains_dnskey(zone->trust_anchor);
+       mDNSu8 parent_size = 0;
+
+       for (mDNSu8 i = 0; i < child_size; i++) {
+               dnssec_validator_node_t *               child                   = &children[i];
+               const list_t * const                    rrsigs                  = child->rrssigs_covering_it;
+
+               for (const list_node_t * rrsig_node = list_get_first(rrsigs); !list_has_ended(rrsigs, rrsig_node); rrsig_node = list_next(rrsig_node)) {
+                       const dnssec_rrsig_t * const    dnssec_rrsig            = (dnssec_rrsig_t *)rrsig_node->data;
+                       const mDNSu16                                   key_tag_from_rrsig      = dnssec_rrsig->key_tag;
+
+                       if (contain_trust_anchor) {
+                               // has saved dnskey as trust anchor
+                               for (const list_node_t * dnskey_node = list_get_first(dnskey_trust_anchors);
+                                               !list_has_ended(dnskey_trust_anchors, dnskey_node);
+                                               dnskey_node = list_next(dnskey_node)) {
+
+                                       const dnssec_dnskey_t * const   dnssec_dnskey           = (dnssec_dnskey_t *)dnskey_node->data;
+                                       const mDNSu16                                   key_tag_from_dnskey = dnssec_dnskey->key_tag;
+
+                                       if (key_tag_from_rrsig != key_tag_from_dnskey) {
+                                               continue;
+                                       }
+
+                                       dnssec_validator_node_t * const parent = &parents[parent_size];
+                                       parent_size++;
+                                       uninitialize_validator_node(parent);
+                                       initialize_validator_node_with_zsk(parent, dnssec_dnskey, dnssec_rrsig, zone_name, dnskeys, rrsigs_covering_dnskeys, mDNStrue);
+
+                                       goto find_matching_parent;
+                               }
+                       } else {
+                               for (const list_node_t * dnskey_node = list_get_first(dnskeys);
+                                               !list_has_ended(dnskeys, dnskey_node);
+                                               dnskey_node = list_next(dnskey_node)) {
+
+                                       const dnssec_dnskey_t * const   dnssec_dnskey           = (dnssec_dnskey_t *)dnskey_node->data;
+                                       const mDNSu16                                   key_tag_from_dnskey = dnssec_dnskey->key_tag;
+
+                                       if (key_tag_from_rrsig != key_tag_from_dnskey) {
+                                               continue;
+                                       }
+
+                                       dnssec_validator_node_t * const parent = &parents[parent_size];
+                                       parent_size++;
+                                       uninitialize_validator_node(parent);
+                                       initialize_validator_node_with_zsk(parent, dnssec_dnskey, dnssec_rrsig, zone_name, dnskeys, rrsigs_covering_dnskeys, mDNSfalse);
+
+                                       goto find_matching_parent;
+                               }
+                       }
+               }
+       find_matching_parent:
+               continue;
+       }
+       if (contain_trust_anchor && parent_size != child_size) {
+               // trust anchor cannot be used to verify this response, now going back to records retrieval
+               result = dnssec_validation_trust_anchor_does_not_macth;
+               goto exit;
+       }
+       require_action_quiet(parent_size == child_size, exit, result = dnssec_validation_no_matching_key_tag);
+
+       error = validate_validator_node(parents, parent_size);
+       require_action_quiet(error == dnssec_validation_valid, exit, result = error);
+
+       error = validate_validator_path_between_parents_and_children(request_id, children, parents, &parent_size);
+       require_action_quiet(error == dnssec_validation_valid, exit, result = error);
+
+       dedup_validator_with_the_same_siblings(parents, &parent_size);
+
+       *out_parent_size = parent_size;
+
+       result = dnssec_validation_valid;
+
+exit:
+       return result;
+}
+
+//======================================================================================================================
+//     initialize_validator_node_with_rr
+//======================================================================================================================
+
+mDNSlocal void
+initialize_validator_node_with_rr(
+       dnssec_validator_node_t * const _Nonnull        node,
+       const mDNSu8 * const                    _Nonnull        name,
+       const list_t * const                    _Nonnull        siblings,
+       const list_t * const                    _Nonnull        rrsigs_covering_it, // list_t<dnssec_rrsig_t>
+       response_type_t                                                         response_type) {        // list_t<dnssec_original_t>
+
+       node->type                                      = rr_validator;
+       node->u.rr.rr_response_type = response_type;
+       node->name                                      = name;
+       node->siblings                          = siblings;
+       node->rrssigs_covering_it       = rrsigs_covering_it;
+       node->trusted                           = mDNSfalse;
+}
+
+//======================================================================================================================
+//     initialize_validator_node_with_nsec
+//======================================================================================================================
+
+mDNSlocal void
+initialize_validator_node_with_nsec(
+       dnssec_validator_node_t * const _Nonnull        node,
+       const dnssec_nsec_t     *                       _Nullable       nsec,
+       const mDNSu8 * const                    _Nonnull        name,
+       const list_t * const                    _Nonnull        rrsig_covering_it ) {   // list_t<dnssec_rrsig_t>
+
+       node->type                                                      = nsec_validator;
+       node->u.nsec.nsec                                       = nsec;
+       node->name                                                      = name;
+       node->siblings                                          = mDNSNULL;
+       node->rrssigs_covering_it                       = rrsig_covering_it;
+       node->trusted                                           = mDNSfalse;
+}
+
+//======================================================================================================================
+//     initialize_validator_node_with_nsec3
+//======================================================================================================================
+
+mDNSlocal void
+initialize_validator_node_with_nsec3(
+       dnssec_validator_node_t * const _Nonnull        node,
+       const dnssec_nsec3_t    *                       _Nullable       nsec3,
+       const mDNSu8 * const                    _Nonnull        name,
+       const list_t * const                    _Nonnull        rrsig_covering_it) {    // list_t<dnssec_rrsig_t>
+
+       node->type                                              = nsec3_validator;
+       node->u.nsec3.nsec3                             = nsec3;
+       node->name                                              = name;
+       node->siblings                                  = mDNSNULL;
+       node->rrssigs_covering_it               = rrsig_covering_it;
+       node->trusted                                   = mDNSfalse;
+}
+
+//======================================================================================================================
+//     initialize_validator_node_with_zsk
+//======================================================================================================================
+
+mDNSlocal void
+initialize_validator_node_with_zsk(
+       dnssec_validator_node_t * const _Nonnull        node,
+       const dnssec_dnskey_t * const   _Nonnull        key,
+       const dnssec_rrsig_t * const    _Nonnull        sig,
+       const mDNSu8 * const                    _Nonnull        name,
+       const list_t * const                    _Nonnull        siblings,                       // list<dnssec_dnskey_t>
+       const list_t * const                    _Nonnull        rrsig_covering_it,      // list_t<dnssec_rrsig_t>
+       mDNSBool                                                                        trusted) {
+
+       node->type                                      = zsk_validator;
+       node->u.zsk.key                         = key;
+       node->u.zsk.sig                         = sig;
+       node->name                                      = name;
+       node->siblings                          = siblings;
+       node->rrssigs_covering_it       = rrsig_covering_it;
+       node->trusted                           = trusted;
+}
+
+//======================================================================================================================
+//     initialize_validator_node_with_ksk
+//======================================================================================================================
+
+mDNSlocal void
+initialize_validator_node_with_ksk(
+       dnssec_validator_node_t * const _Nonnull        node,
+       const dnssec_dnskey_t * const   _Nonnull        key,
+       const dnssec_rrsig_t * const    _Nonnull        sig,
+       const dnssec_ds_t * const               _Nullable       ds,
+       const mDNSu8 * const                    _Nonnull        name,
+       const list_t * const                    _Nonnull        siblings,                               // list<dnssec_ds_t>
+       const list_t * const                    _Nonnull        rrsig_covering_it,              // list_t<dnssec_rrsig_t>
+       mDNSBool                                                                        trusted) {
+
+       node->type                                      = ksk_validator;
+       node->u.ksk.key                         = key;
+       node->u.ksk.sig                         = sig;
+       node->u.ksk.ds                          = ds;
+       node->name                                      = name;
+       node->siblings                          = siblings;
+       node->rrssigs_covering_it       = rrsig_covering_it;
+       node->trusted                           = trusted;
+}
+
+//======================================================================================================================
+//     uninitialize_validator_node
+//======================================================================================================================
+
+mDNSlocal void
+uninitialize_validator_node(dnssec_validator_node_t * const _Nonnull node) {
+       bzero(node, sizeof(dnssec_validator_node_t));
+}
+
+//======================================================================================================================
+//     is_validator_node_valid
+//======================================================================================================================
+
+mDNSlocal dnssec_validation_result_t
+validate_validator_node(const dnssec_validator_node_t * const _Nonnull nodes, const mDNSu8 nodes_count) {
+
+       dnssec_validation_result_t      result  = dnssec_validation_valid;
+
+       for (mDNSu8 i = 0; i < nodes_count; i++) {
+               const dnssec_validator_node_t * const node = &nodes[i];
+
+               switch (node->type) {
+                       case rr_validator:
+                               break;
+                       case nsec_validator:
+                               result = validate_nsec(node->u.nsec.nsec);
+                               require_quiet(result == dnssec_validation_valid, exit);
+                               break;
+                       case nsec3_validator:
+                               result = validate_nsec3(node->u.nsec3.nsec3);
+                               require_quiet(result == dnssec_validation_valid, exit);
+                               break;
+                       case zsk_validator:
+                               result = validate_dnskey(node->u.zsk.key, mDNSfalse);
+                               require_quiet(result == dnssec_validation_valid, exit);
+
+                               result = validate_rrsig(node->u.zsk.sig);
+                               require_quiet(result == dnssec_validation_valid, exit);
+
+                               require_action(node->u.zsk.key->algorithm == node->u.zsk.sig->algorithm, exit,
+                                                               result = dnssec_validation_algorithm_number_not_equal);
+                               break;
+                       case ksk_validator:
+                               result = validate_dnskey(node->u.ksk.key, mDNStrue);
+                               require_quiet(result == dnssec_validation_valid, exit);
+
+                               result = validate_rrsig(node->u.ksk.sig);
+                               require_quiet(result == dnssec_validation_valid, exit);
+
+                               if (node->u.ksk.ds != mDNSNULL) {
+                                       result = validate_ds(node->u.ksk.ds);
+                                       require_quiet(result == dnssec_validation_valid, exit);
+                               } else{
+                                       require_action_quiet(node->trusted, exit, result = dnssec_validation_invalid_internal_state);
+                               }
+
+                               if (node->u.ksk.ds != mDNSNULL) {
+                                       require_action(node->u.ksk.key->algorithm == node->u.ksk.ds->algorithm, exit,
+                                               result = dnssec_validation_algorithm_number_not_equal);
+                               }
+
+                               require_action(node->u.ksk.key->algorithm == node->u.ksk.sig->algorithm, exit,
+                                                               result = dnssec_validation_algorithm_number_not_equal);
+
+                               if (node->u.ksk.ds != mDNSNULL) {
+                                       result = check_if_ds_ksk_matches(node->u.ksk.ds, node->u.ksk.key);
+                                       require_quiet(result == dnssec_validation_valid, exit);
+                               }
+                               break;
+                       default:
+                               result = dnssec_validation_invalid_internal_state;
+                               break;
+               }
+       }
+
+exit:
+       return result;
+}
+
+//======================================================================================================================
+//     validate_validator_path_between_parents_and_children
+//======================================================================================================================
+
+mDNSlocal dnssec_validation_result_t
+validate_validator_path_between_parents_and_children(
+       const mDNSu32                                                   request_id,
+       dnssec_validator_node_t *       _Nonnull        children,
+       dnssec_validator_node_t *       _Nonnull        parents,
+       mDNSu8 * const                          _Nonnull        out_parent_size) {
+
+       dnssec_validation_result_t      result;
+       dnssec_validation_result_t      error;
+       mDNSu8                                          parent_size = *out_parent_size;
+
+       require_action_quiet(parent_size != 0, exit, result = dnssec_validation_invalid_internal_state);
+
+       for (mDNSu8 i = 0; i < parent_size; i++) {
+               const dnssec_validator_node_t * const child             = &children[i];
+               const dnssec_validator_node_t * const parent    = &parents[i];
+
+               error = validate_validator_path(request_id, child, parent);
+               require_action_quiet(error == dnssec_validation_valid, exit, result = error);
+
+               if (parent->type == ksk_validator) {
+                       const dnssec_dnskey_t * const   ksk = parent->u.ksk.key;
+                       const dnssec_ds_t * const               ds      = parent->u.ksk.ds;
+
+                       error = check_trust_validator_node(parent);
+                       if (error == dnssec_validation_trusted) {
+                               if (ds != mDNSNULL) {
+                                       // ds trust anchor
+                                       log_default("[R%u] " PRI_DM_NAME ": DS (digest_type=%u, tag=%u, trust_anchor) -----> " PRI_DM_NAME ": DNSKEY (KSK, alg=%u, tag=%u, length=%u)",
+                                               request_id, DM_NAME_PARAM(&ds->dnssec_rr.name), ds->digest_type, ds->key_tag,
+                                               DM_NAME_PARAM(&ksk->dnssec_rr.name), ksk->algorithm, ksk->key_tag, ksk->public_key_length);
+                               } else {
+                                       // dnskey trust anchor
+                                       log_default("[R%u] " PRI_DM_NAME ": DNSKEY (KSK, alg=%u, tag=%u, length=%u, trust_anchor)",
+                                               request_id,
+                                               DM_NAME_PARAM(&ksk->dnssec_rr.name), ksk->algorithm, ksk->key_tag, ksk->public_key_length);
+                               }
+
+                               // parent node is trusted by the policy, no need to verify it
+                               if (i < parent_size - 1) {
+                                       children[i] = children[i + 1];
+                                       parents[i]      = parents[i + 1];
+                                       i--;
+                               }
+                               parent_size--;
+                       } else {
+                               log_default("[R%u] " PRI_DM_NAME ": DS (digest_type=%u, tag=%u) -----> " PRI_DM_NAME ": DNSKEY (KSK, alg=%u, tag=%u, length=%u)",
+                                       request_id, DM_NAME_PARAM(&ds->dnssec_rr.name), ds->digest_type, ds->key_tag,
+                                       DM_NAME_PARAM(&ksk->dnssec_rr.name), ksk->algorithm, ksk->key_tag, ksk->public_key_length);
+                       }
+               }
+       }
+
+       *out_parent_size = parent_size;
+       result = dnssec_validation_valid;
+
+exit:
+       return result;
+}
+
+//======================================================================================================================
+//     validate_validator_path
+//======================================================================================================================
+
+mDNSlocal dnssec_validation_result_t
+validate_validator_path(
+       const mDNSu32                                                                   request_id,
+       const dnssec_validator_node_t * const _Nonnull  child,
+       const dnssec_validator_node_t * const _Nonnull  parent) {
+
+       dnssec_validation_result_t result = dnssec_validation_valid;
+
+       if (child->type == zsk_validator && parent->type == ksk_validator) {
+               result = validate_path_from_ksk_to_zsk(request_id, parent, child->siblings);
+       } else if (child->type == ksk_validator && parent->type == zsk_validator) {
+               result = validate_path_from_zsk_to_ds(request_id, parent, child->siblings);
+       } else if (child->type == rr_validator && parent->type == zsk_validator) {
+               result = validate_path_from_zsk_to_rr(request_id, parent, child->siblings, child->u.rr.rr_response_type);
+       } else if (child->type == nsec_validator && parent->type == zsk_validator) {
+               result = validate_path_from_zsk_to_nsec(request_id, parent, child);
+       } else if (child->type == nsec3_validator && parent->type == zsk_validator) {
+               result = validate_path_from_zsk_to_nsec3(request_id, parent, child);
+       } else {
+               result = dnssec_validation_path_invalid_node_type;
+       }
+
+       return result;
+}
+
+
+//======================================================================================================================
+//     check_trust_validator_node
+//======================================================================================================================
+
+mDNSlocal dnssec_validation_result_t
+check_trust_validator_node(const dnssec_validator_node_t * const _Nonnull node) {
+       return node->trusted ? dnssec_validation_trusted : dnssec_validation_not_trusted;
+}
+
+//======================================================================================================================
+//     dedup_validator_with_the_same_siblings
+//======================================================================================================================
+
+mDNSlocal void
+dedup_validator_with_the_same_siblings(
+       dnssec_validator_node_t *       _Nonnull        parents,
+       mDNSu8 * const                          _Nonnull        out_parent_size) {
+
+       const dnssec_validator_node_t * temp_parents[4]         = {mDNSNULL};
+       mDNSu8                                                  temp_parent_size        = 0;
+       mDNSu8                                                  parent_size                     = *out_parent_size;
+
+       for (mDNSu8 i = 0; i < parent_size; i++) {
+               const dnssec_validator_node_t * const   parent  = &parents[i];
+               mDNSBool                                                                duplicate = mDNSfalse;
+
+               for (mDNSu8 j = 0; j < temp_parent_size; j++) {
+                       const dnssec_validator_node_t * const parent_nodup = temp_parents[j];
+                       // if two nodes to be verified have the same siblings to verify, they are the duplicates, since RRSIG signs the entire siblings
+                       if (parent_nodup->siblings == parent->siblings) {
+                               duplicate = mDNStrue;
+                       }
+               }
+
+               if (!duplicate) {
+                       temp_parents[temp_parent_size] = parent;
+                       temp_parent_size++;
+               }
+       }
+
+       for (mDNSu8 i = 0; i < temp_parent_size; i++) {
+               parents[i] = *temp_parents[i];
+       }
+       parent_size                     = temp_parent_size;
+       *out_parent_size        = parent_size;
+}
+
+//======================================================================================================================
+//     dedup_validator_with_the_same_siblings
+//======================================================================================================================
+
+mDNSlocal void __unused
+print_ds_validation_progress(const dnssec_validator_node_t * const _Nonnull nodes, const mDNSu8 nodes_count) {
+       for (mDNSu8 i = 0; i < nodes_count; i++) {
+               const dnssec_validator_node_t * const node = &nodes[i];
+               verify_action(node->type == ksk_validator,
+                       log_error("validator type is not Key Signing Key; type=%u", node->type); continue);
+
+               const dnssec_dnskey_t * const   ksk = node->u.ksk.key;
+               const dnssec_ds_t * const               ds      = node->u.ksk.ds;
+
+               log_default(PRI_DM_NAME ": DS (digest_type=%u, tag=%u) ----->" PRI_DM_NAME ": DNSKEY (KSK, alg=%u, tag=%u, length=%u)",
+                       DM_NAME_PARAM(&ds->dnssec_rr.name), ds->digest_type, ds->key_tag,
+                       DM_NAME_PARAM(&ksk->dnssec_rr.name), ksk->algorithm, ksk->key_tag, ksk->public_key_length);
+
+       }
+}
+
+//======================================================================================================================
+//     validate_zone_records_type
+//======================================================================================================================
+
+mDNSlocal dnssec_validation_result_t
+validate_zone_records_type(const dnssec_zone_t * const _Nonnull zone) {
+       dnssec_validation_result_t result = dnssec_validation_valid;
+
+       if (zone->dnskey_request_started && zone->ds_request_started) {
+               // the most common case where zone does not have any trust anchor.
+               require_action_quiet(zone->dses_with_rrsig.type == original_response, exit, result = dnssec_validation_non_dnskey_ds_record_chain);
+       } else if (!zone->dnskey_request_started && zone->ds_request_started) {
+               // This is impossible because if dnskey request is not started that means we have trust anchor for DNSKEY, and there
+               // is no need to send DS query, thus ds_request_started could not be true
+               result = dnssec_validation_invalid_internal_state;
+               goto exit;
+       } else if (zone->dnskey_request_started && !zone->ds_request_started) {
+               // It means the system has DS trust anchor installed, thus there is no need to query for DS record, only DNSKEY
+               // record is required, and we must have DS trust anchor
+               require_action_quiet(zone->trust_anchor != mDNSNULL && !list_empty(&zone->trust_anchor->ds_trust_anchors), exit, result = dnssec_validation_invalid_internal_state);
+       } else { // !zone->dnskey_request_started && !zone->ds_request_started
+               // The system has DNSKEY trust anchor, and there is no need to query for DNSKEY or DS record at all.
+               // we must have DNSKEY trust anchor
+               require_action_quiet(zone->trust_anchor != mDNSNULL && !list_empty(&zone->trust_anchor->dnskey_trust_anchors), exit, result = dnssec_validation_invalid_internal_state);
+       }
+
+exit:
+       return result;
+}
+
+//======================================================================================================================
+//     validate_ds
+//======================================================================================================================
+
+mDNSlocal dnssec_validation_result_t
+validate_ds(const dnssec_ds_t * const _Nonnull ds) {
+       dnssec_validation_result_t              result = dnssec_validation_valid;
+       mDNSs16                                                 digest_priority;
+       mDNSs16                                                 algorithm_priority;
+       // TODO: print dnssec_ds_t when failing to pass validation
+
+       // check digest type
+       digest_priority = get_priority_of_ds_digest(ds->digest_type);
+       require_action(digest_priority != -1, exit, result = dnssec_validation_ds_digest_not_supported;
+               log_default("Unsupported or invalid DS digest type; digest_type=%u", ds->digest_type));
+
+       // check algorithm type
+       algorithm_priority = get_priority_of_dnskey_algorithm(ds->algorithm);
+       require_action(algorithm_priority != -1, exit, result = dnssec_validation_dnskey_algorithm_not_supported;
+                                       log_default("Unsupported or invalid DNSKEY algorithm type; algorithm=%u", ds->algorithm));
+exit:
+       return result;
+}
+
+//======================================================================================================================
+//     validate_dnskey
+//======================================================================================================================
+
+mDNSlocal dnssec_validation_result_t
+validate_dnskey(const dnssec_dnskey_t * const _Nonnull dnskey, mDNSBool security_entry_point) {
+       dnssec_validation_result_t              result = dnssec_validation_valid;
+       mDNSs16                                                 algorithm_priority;
+
+       // check zone key flag
+       require_action((dnskey->flags & DNSKEY_FLAG_ZONE_KEY) != 0, exit, result = dnssec_validation_dnskey_invalid_flags;
+               log_default("Not a DNSSEC DNSKEY in DNSKEY; flags=%x", dnskey->flags));
+
+       // check the security entry point flag
+       require_action(!security_entry_point || (dnskey->flags & DNSKEY_FLAG_SECURITY_ENTRY_POINT) != 0, exit,
+               result = dnssec_validation_dnskey_invalid_flags);
+
+       // check protocol
+       require_action(dnskey->protocol == 3, exit, result = dnssec_validation_dnskey_wrong_protocol;
+               log_default("Not a DNSSEC Protocol in DNSKEY; protocol=%u", dnskey->protocol));
+
+       // check DNSKEY algorithm
+       algorithm_priority = get_priority_of_dnskey_algorithm(dnskey->algorithm);
+       require_action(algorithm_priority != -1, exit, result = dnssec_validation_dnskey_algorithm_not_supported;
+               log_default("Unsupported or invalid DNSKEY algorithm type in DNSKEY; algorithm=%u", dnskey->algorithm));
+
+exit:
+       return result;
+}
+
+//======================================================================================================================
+//     validate_rrsig
+//======================================================================================================================
+
+mDNSlocal dnssec_validation_result_t
+validate_rrsig(const dnssec_rrsig_t * const _Nonnull rrsig) {
+       dnssec_validation_result_t              result = dnssec_validation_valid;
+       mDNSs16                                                 algorithm_priority;
+       int64_t                                                 now;
+       mDNSu32                                                 now_u32;
+       // TODO: print dnssec_rrsig_t when failing to pass validation
+
+       // check DNSKEY algorithm
+       algorithm_priority = get_priority_of_dnskey_algorithm(rrsig->algorithm);
+       require_action(algorithm_priority != -1, exit, result = dnssec_validation_dnskey_algorithm_not_supported;
+               log_default("Unsupported or invalid DNSKEY algorithm type in RRSIG; algorithm=%u", rrsig->algorithm));
+
+       // TODO: check the label field for RRSIG
+
+       // check inception and expiration time
+       now = time(mDNSNULL);
+       require_action(now <= UINT32_MAX, exit, result = dnssec_validation_invalid_internal_state;
+               log_fault("the value of time(NULL) is now greater than UINT32_MAX"));
+
+       now_u32 = (mDNSu32)now;
+       require_action(now_u32 >= rrsig->signature_inception, exit, result = dnssec_validation_rrsig_use_before_inception;
+               log_default("RRSIG incpetion time is greater than the current time; inception_time=%u, now=%d", rrsig->signature_inception, now_u32));
+
+       require_action(now_u32 <= rrsig->signature_expiration, exit, result = dnssec_validation_rrsig_use_after_expiration;
+               log_default("RRSIG expiration time is less than the current time; expiration_time=%u, now=%d", rrsig->signature_expiration, now_u32));
+
+exit:
+       return result;
+}
+
+//======================================================================================================================
+//     validate_nsec
+//======================================================================================================================
+
+mDNSlocal dnssec_validation_result_t
+validate_nsec(const dnssec_nsec_t * const _Nonnull nsec) {
+       (void) nsec;
+
+       return dnssec_validation_valid;
+}
+
+//======================================================================================================================
+//     validate_nsec3
+//======================================================================================================================
+
+mDNSlocal dnssec_validation_result_t
+validate_nsec3(const dnssec_nsec3_t * const _Nonnull nsec3) {
+       dnssec_validation_result_t result = dnssec_validation_valid;
+       // check if hash algorithm is supported
+       switch (nsec3->hash_algorithm) {
+               case 1: // only SHA-1 is supported
+                       break;
+               default:
+                       result = dnssec_validation_nsec3_unsupported_hash_algorithm;
+                       goto exit;
+       }
+
+       // check flags, only Opt-Out flag is defined, all undefined flags should be zero
+       require_action((nsec3->flags & (~NSEC3_FLAG_SET)) == 0, exit, result = dnssec_validation_nsec3_unsupported_flag);
+
+exit:
+       return result;
+}
+
+//======================================================================================================================
+//     check_if_ds_ksk_matches
+//======================================================================================================================
+
+mDNSlocal dnssec_validation_result_t
+check_if_ds_ksk_matches(const dnssec_ds_t * const _Nonnull ds, const dnssec_dnskey_t * const _Nonnull ksk) {
+       // reconstruct the data that will be hashed
+       dnssec_validation_result_t result = dnssec_validation_valid;
+       mDNSBool                                matches;
+       const mDNSu8 * const    owner_name                                      = ksk->dnssec_rr.name.c;
+       const mDNSu16                   owner_name_length                       = DOMAIN_NAME_LENGTH(owner_name);
+       const mDNSu8 * const    dnskey_rdata                            = ksk->dnssec_rr.rdata;
+       const mDNSu16                   rdata_length                            = ksk->dnssec_rr.rdata_length;
+       mDNSu8                                  digest_buffer[MAX_HASH_OUTPUT_SIZE];
+       mDNSu32                                 digest_size;
+       digest_type_t           digest_type;
+       const mDNSu32                   data_to_be_hashed_length        = owner_name_length + rdata_length;
+       mDNSu8 * const                  data_to_be_hashed                       = malloc(data_to_be_hashed_length);
+       require_action(data_to_be_hashed != mDNSNULL, exit, result = dnssec_validation_no_memory);
+
+       memcpy(data_to_be_hashed, owner_name, owner_name_length);
+       memcpy(data_to_be_hashed + owner_name_length, dnskey_rdata, rdata_length);
+
+       switch (ds->digest_type) {
+               case DS_DIGEST_SHA_1:
+                       digest_type = DIGEST_SHA_1;
+                       break;
+               case DS_DIGEST_SHA_256:
+                       digest_type = DIGEST_SHA_256;
+                       break;
+               case DS_DIGEST_SHA_384:
+                       digest_type = DIGEST_SHA_384;
+                       break;
+               default:
+                       result          = dnssec_validation_ds_digest_not_supported;
+                       goto exit;
+       }
+       digest_size = get_digest_length_for_ds_digest_type(ds->digest_type);
+       mDNSBool calculated = calculate_digest_for_data(data_to_be_hashed, data_to_be_hashed_length, digest_type, digest_buffer, sizeof(digest_buffer));
+       require_action_quiet(calculated, exit, result = dnssec_validation_invalid_internal_state);
+       require_action(digest_size == ds->digest_length, exit, result = dnssec_validation_bogus);
+
+       matches = (memcmp(digest_buffer, ds->digest, digest_size) == 0);
+
+       require_action(matches, exit, result = dnssec_validation_bogus);
+
+exit:
+       if (data_to_be_hashed != mDNSNULL) {
+               free(data_to_be_hashed);
+       }
+       return result;
+}
+
+mDNSlocal dnssec_validation_result_t
+validate_path_from_zsk_to_rr(
+       const mDNSu32                                                                           request_id,
+       const dnssec_validator_node_t * const   _Nonnull        parent,
+       const list_t * const                                    _Nonnull        originals /* list_t<dnssec_original_t> or list_t<dnssec_cname_t> */,
+       response_type_t                                                                         response_type) {
+
+       dnssec_validation_result_t      result                          = dnssec_validation_valid;
+       mDNSu8 *                                        signed_data                     = mDNSNULL;
+       mDNSu32                                         signed_data_length;
+       mDNSBool                                        is_signed_data_valid;
+
+       result = check_rrsig_validity_with_rrs(parent->u.zsk.sig, originals, response_type, kDNSQType_ANY);
+       require_action(result == dnssec_validation_valid, exit, log_default("RRSIG is not valid for validation"));
+
+       signed_data = reconstruct_signed_data_with_rrs(originals, parent->u.zsk.sig, response_type, kDNSQType_ANY, &signed_data_length);
+       require_action(signed_data != mDNSNULL, exit, result = dnssec_validation_no_memory;
+               log_default("No enough memory to allocate for signed data;"));
+
+       is_signed_data_valid = validate_signed_data_with_rrsig_and_dnskey(request_id, signed_data, signed_data_length, parent->u.zsk.sig, parent->u.zsk.key);
+
+       result = is_signed_data_valid ? dnssec_validation_valid : dnssec_validation_invalid;
+
+exit:
+       if (signed_data != mDNSNULL) {
+               free(signed_data);
+               signed_data = mDNSNULL;
+       }
+       return result;
+}
+
+mDNSlocal dnssec_validation_result_t
+validate_path_from_ksk_to_zsk(
+       const mDNSu32                                                                           request_id,
+       const dnssec_validator_node_t * const   _Nonnull        parent,
+       const list_t * const                                    _Nonnull        zsks /* list_t<dnssec_dnskey_t> */) {
+
+       dnssec_validation_result_t      result                          = dnssec_validation_valid;
+       mDNSu8 *                                        signed_data                     = mDNSNULL;
+       mDNSu32                                         signed_data_length;
+       mDNSBool                                        is_signed_data_valid;
+
+       // TODO: original_response could be nsec nsec3
+       result = check_rrsig_validity_with_rrs(parent->u.ksk.sig, zsks, original_response, kDNSType_DNSKEY);
+       require_quiet(result == dnssec_validation_valid, exit);
+
+       signed_data = reconstruct_signed_data_with_rrs(zsks, parent->u.ksk.sig, original_response, kDNSType_DNSKEY, &signed_data_length);
+       require_action(signed_data != mDNSNULL, exit, result = dnssec_validation_no_memory;
+               log_default("No enough memory to allocate for signed data;"));
+
+       is_signed_data_valid = validate_signed_data_with_rrsig_and_dnskey(request_id, signed_data, signed_data_length, parent->u.ksk.sig, parent->u.ksk.key);
+       result = is_signed_data_valid ? dnssec_validation_valid : dnssec_validation_invalid;
+
+exit:
+       if (signed_data != mDNSNULL) {
+               free(signed_data);
+               signed_data = mDNSNULL;
+       }
+       return result;
+}
+
+mDNSlocal dnssec_validation_result_t
+validate_path_from_zsk_to_ds(
+       const mDNSu32                                                                           request_id,
+       const dnssec_validator_node_t * const   _Nonnull        parent,
+       const list_t * const                                    _Nonnull        dses /* list_t<dnssec_ds_t> */) {
+
+       dnssec_validation_result_t      result                          = dnssec_validation_valid;
+       mDNSu8 *                                                signed_data                     = mDNSNULL;
+       mDNSu32                                                 signed_data_length;
+       mDNSBool                                                is_signed_data_valid;
+
+       // TODO: original_response could be nsec nsec3
+       result = check_rrsig_validity_with_rrs(parent->u.zsk.sig, dses, original_response, kDNSType_DS);
+       require_quiet(result == dnssec_validation_valid, exit);
+
+       signed_data = reconstruct_signed_data_with_rrs(dses, parent->u.zsk.sig, original_response, kDNSType_DS, &signed_data_length);
+       require_action(signed_data != mDNSNULL, exit, result = dnssec_validation_no_memory;
+                                       log_default("No enough memory to allocate for signed data;"));
+
+       is_signed_data_valid = validate_signed_data_with_rrsig_and_dnskey(request_id, signed_data, signed_data_length, parent->u.zsk.sig, parent->u.zsk.key);
+       result = is_signed_data_valid ? dnssec_validation_valid : dnssec_validation_invalid;
+
+exit:
+       if (signed_data != mDNSNULL) {
+               free(signed_data);
+               signed_data = mDNSNULL;
+       }
+       return result;
+
+}
+
+mDNSlocal dnssec_validation_result_t
+validate_path_from_zsk_to_nsec(
+       const mDNSu32                                                                           request_id,
+       const dnssec_validator_node_t * const   _Nonnull        parent,
+       const dnssec_validator_node_t * const   _Nonnull        child) {
+
+       dnssec_validation_result_t      result;
+       dnssec_validation_result_t      error;
+       mDNSu8 *                                        signed_data                     = mDNSNULL;
+       mDNSu32                                         signed_data_length;
+       mDNSBool                                        is_signed_data_valid;
+
+       error = check_rrsig_validity_with_dnssec_rr(parent->u.zsk.sig, &child->u.nsec.nsec->dnssec_rr);
+       require_action(error == dnssec_validation_valid, exit, result = error; log_default("RRSIG is invalid"));
+
+       signed_data = reconstruct_signed_data_with_one_dnssec_rr(&child->u.nsec.nsec->dnssec_rr, parent->u.zsk.sig, &signed_data_length);
+       require_action(signed_data != mDNSNULL, exit, result = dnssec_validation_no_memory;
+                                       log_error("No enough memory to allocate for signed data;"));
+
+       is_signed_data_valid = validate_signed_data_with_rrsig_and_dnskey(request_id, signed_data, signed_data_length, parent->u.zsk.sig, parent->u.zsk.key);
+       result = is_signed_data_valid ? dnssec_validation_valid: dnssec_validation_invalid;
+
+exit:
+       if (signed_data != mDNSNULL) {
+               free(signed_data);
+               signed_data = mDNSNULL;
+       }
+       return result;
+}
+
+mDNSlocal dnssec_validation_result_t
+validate_path_from_zsk_to_nsec3(
+       const mDNSu32                                                                           request_id,
+       const dnssec_validator_node_t * const   _Nonnull        parent,
+       const dnssec_validator_node_t * const   _Nonnull        child) {
+
+       dnssec_validation_result_t      result;
+       dnssec_validation_result_t      error;
+       mDNSu8 *                                        signed_data                     = mDNSNULL;
+       mDNSu32                                         signed_data_length;
+       mDNSBool                                        is_signed_data_valid;
+
+       error = check_rrsig_validity_with_dnssec_rr(parent->u.zsk.sig, &child->u.nsec3.nsec3->dnssec_rr);
+       require_action(error == dnssec_validation_valid, exit, result = error; log_default("RRSIG is invalid"));
+
+       signed_data = reconstruct_signed_data_with_one_dnssec_rr(&child->u.nsec3.nsec3->dnssec_rr, parent->u.zsk.sig, &signed_data_length);
+       require_action(signed_data != mDNSNULL, exit, result = dnssec_validation_no_memory;
+               log_error("No enough memory to allocate for signed data;"));
+
+       is_signed_data_valid = validate_signed_data_with_rrsig_and_dnskey(request_id, signed_data, signed_data_length, parent->u.zsk.sig, parent->u.zsk.key);
+       result = is_signed_data_valid ? dnssec_validation_valid: dnssec_validation_invalid;
+
+exit:
+       if (signed_data != mDNSNULL) {
+               free(signed_data);
+               signed_data = mDNSNULL;
+       }
+       return result;
+}
+
+//======================================================================================================================
+//     check_rrsig_validity_with_rr
+//======================================================================================================================
+
+mDNSlocal dnssec_validation_result_t
+check_rrsig_validity_with_dnssec_rr(
+       const dnssec_rrsig_t * const    _Nonnull        rrsig,
+       const dnssec_rr_t * const               _Nonnull        rr) {
+
+       dnssec_validation_result_t      result                          = dnssec_validation_valid;
+       const mDNSu8 * const            owner_name                      = rrsig->dnssec_rr.name.c;
+       const mDNSu32                           owner_name_hash         = rrsig->dnssec_rr.name_hash;
+       const mDNSu16                           class                           = rrsig->dnssec_rr.rr_class;
+       const mDNSu8 * const            signer_name                     = rrsig->signer_name;
+       const mDNSu16                           type_covered            = rrsig->type_covered;
+       const mDNSu8                            labels_rrsig            = rrsig->labels;
+       mDNSu8                                          labels_rr;
+
+       // The RRSIG RR and the RRset MUST have the same owner name,
+       require_action(owner_name_hash == rr->name_hash, exit, result = dnssec_validation_path_unmatched_owner_name;
+                                       log_default("The owner names of RRSIG and records do not match; RRSIG=" PRI_DM_NAME ", RR=" PRI_DM_NAME,
+                                                               DM_NAME_PARAM((const domainname * const)owner_name), DM_NAME_PARAM(&rr->name)));
+       require_action(DOMAIN_NAME_EQUALS(owner_name, rr->name.c), exit, result = dnssec_validation_path_unmatched_owner_name;
+                                       log_default("The owner names of RRSIG and records do not match; RRSIG=" PRI_DM_NAME ", RR=" PRI_DM_NAME,
+                                                               DM_NAME_PARAM((const domainname * const)owner_name), DM_NAME_PARAM(&rr->name)));
+
+       // and the same class.
+       require_action(class == rr->rr_class, exit, result = dnssec_validation_path_unmatched_class;
+                                       log_default("The classes of RRSIG and records do not match; RRSIG=%u, RR=%u", class, rr->rr_class));
+
+       // TODO: The RRSIG RR's Signer's Name field MUST be the name of the zone that contains the RRset
+       (void) signer_name;
+
+       // The RRSIG RR's Type Covered field MUST equal the RRset's type.
+       require_action(type_covered == rr->rr_type, exit, result = dnssec_validation_path_unmatched_type_covered;
+                                       log_default("The RRSIG does not cover the current record; RRSIG=" PUB_S ", RR=" PUB_S,
+                                                               DNS_TYPE_STR(type_covered), DNS_TYPE_STR(rr->rr_type)));
+
+       // The number of labels in the RRset owner name MUST be greater than or equal to the value in the RRSIG RR's Labels field.
+       labels_rr = get_number_of_labels(rr->name.c);
+       require_action(labels_rrsig <= labels_rr, exit, result = dnssec_validation_path_invalid_label_count;
+                                       log_default("the RRSIG's label is not less than or equal to the number of labels in RR's owner's name; RRSIG=%u, RR=%u",
+                                                               labels_rrsig, labels_rr));
+
+exit:
+       return result;
+}
+
+//======================================================================================================================
+//     check_rrsig_validity_with_dnssec_rrs
+//======================================================================================================================
+
+mDNSlocal dnssec_validation_result_t
+check_rrsig_validity_with_rrs(
+       const dnssec_rrsig_t * const    _Nonnull        rrsig,
+       const list_t * const                    _Nonnull        list_to_check,
+       response_type_t                                                         response_type_in_list,
+       const mDNSu16                                                           record_type_in_list) {
+
+       dnssec_validation_result_t      result                          = dnssec_validation_valid;
+       const dnssec_rr_t *                             rr;
+
+       for (list_node_t *node = list_get_first(list_to_check); !list_has_ended(list_to_check, node); node = list_next(node)) {
+               // get correct node from the list
+               switch (response_type_in_list) {
+                       case original_response:
+                       {
+                               if (record_type_in_list == kDNSQType_ANY) {
+                                       const dnssec_original_t * const original = (dnssec_original_t *)node->data;
+                                       rr = &original->dnssec_rr;
+                               } else if (record_type_in_list == kDNSType_DNSKEY) {
+                                       const dnssec_dnskey_t * const dnskey = (dnssec_dnskey_t *)node->data;
+                                       rr = &dnskey->dnssec_rr;
+                               } else if (record_type_in_list == kDNSType_DS) {
+                                       const dnssec_ds_t * const ds = (dnssec_ds_t *)node->data;
+                                       rr = &ds->dnssec_rr;
+                               } else {
+                                       result = dnssec_validation_path_invalid_node_type;
+                                       goto exit;
+                               }
+                               break;
+                       }
+                       case cname_response:
+                       {
+                               const dnssec_cname_t * const cname = (dnssec_cname_t *)node->data;
+                               rr = &cname->dnssec_rr;
+                       }
+                               break;
+                       case nsec_response:
+                       {
+                               if (record_type_in_list == kDNSQType_ANY) {
+                                       rr = (dnssec_rr_t *)node->data;
+                               } else if (record_type_in_list == kDNSType_NSEC) {
+                                       const dnssec_nsec_t * const nsec = (dnssec_nsec_t *)node->data;
+                                       rr = &nsec->dnssec_rr;
+                               } else {
+                                       result = dnssec_validation_path_invalid_node_type;
+                                       goto exit;
+                               }
+                               break;
+                       }
+                       case nsec3_response:
+                       {
+                               const dnssec_nsec3_t * const nsec3 = (dnssec_nsec3_t *)node->data;
+                               rr = &nsec3->dnssec_rr;
+                       }
+                               break;
+                       default:
+                       {
+                               result = dnssec_validation_path_invalid_node_type;
+                               goto exit;
+                       }
+               }
+
+               result = check_rrsig_validity_with_dnssec_rr(rrsig, rr);
+               require_quiet(result == dnssec_validation_valid, exit);
+       }
+
+exit:
+       return result;
+}
+
+//======================================================================================================================
+//     reconstruct_signed_data_with_dnssec_rrs
+//======================================================================================================================
+
+mDNSlocal void *
+reconstruct_signed_data_with_rrs(
+       const list_t * const                    _Nonnull        rr_set,
+       const dnssec_rrsig_t * const    _Nonnull        dnssec_rrsig,
+       const response_type_t                                           response_type,
+       const mDNSu16                                                           record_type,
+       mDNSu32 * const                                 _Nonnull        out_signed_data_length) {
+
+       mDNSu8                                  rr_count                        = 0;
+       const dnssec_rr_t *             rr_array[256]; // The maximum number of RR in one RRSET should be no more than 256
+
+       // sort RRset in canonical order
+       for (list_node_t *node = list_get_first(rr_set); !list_has_ended(rr_set, node); node = list_next(node)) {
+               dnssec_rr_t *rr;
+
+               switch (response_type) {
+                       case original_response:
+                               if (record_type == kDNSQType_ANY) {
+                                       rr = &((dnssec_original_t *)node->data)->dnssec_rr;
+                               } else if (record_type == kDNSType_DNSKEY) {
+                                       rr = &((dnssec_dnskey_t *)node->data)->dnssec_rr;
+                               } else if (record_type == kDNSType_DS) {
+                                       rr = &((dnssec_ds_t *)node->data)->dnssec_rr;
+                               } else {
+                                       // It should never happen.
+                                       log_error("incorrect DNSSEC data type");
+                                       goto exit;
+                               }
+                               break;
+                       case cname_response:
+                               rr = &((dnssec_cname_t *)node->data)->dnssec_rr;
+                               break;
+                       case nsec_response: {
+                               if (record_type == kDNSQType_ANY) {
+                                       rr = (dnssec_rr_t *)node->data;
+                               } else if (record_type == kDNSType_NSEC) {
+                                       rr = &((dnssec_nsec_t *)node->data)->dnssec_rr;
+                               } else {
+                                       goto exit;
+                               }
+                               break;
+                       }
+                       case nsec3_response:
+                               rr = &((dnssec_nsec3_t *)node->data)->dnssec_rr;
+                               break;
+                       default:
+                               // It should never happen.
+                               log_error("signed data has unknown_response type");
+                               goto exit;
+               }
+
+               rr_array[rr_count] = rr;
+               rr_count++;
+       }
+       sort_rr_array_canonically(rr_array, rr_count);
+
+       // deduplicate RRs in RRSet
+       rr_array_dedup(rr_array, rr_count);
+
+       return reconstruct_signed_data_internal(rr_array, rr_count, dnssec_rrsig, out_signed_data_length);
+
+exit:
+       return mDNSNULL;
+}
+
+//======================================================================================================================
+//     reconstruct_signed_data_with_one_dnssec_rr
+//======================================================================================================================
+
+mDNSlocal void *
+reconstruct_signed_data_with_one_dnssec_rr(
+       const dnssec_rr_t * const               _Nonnull        dnssec_rr,
+       const dnssec_rrsig_t * const    _Nonnull        dnssec_rrsig,
+       mDNSu32 * const                                 _Nonnull        out_signed_data_length) {
+
+       mDNSu8                  rr_count = 0;
+       const dnssec_rr_t *rr_array[1];
+
+       rr_array[0] = dnssec_rr;
+       rr_count = 1;
+
+       return reconstruct_signed_data_internal(rr_array, rr_count, dnssec_rrsig, out_signed_data_length);
+}
+
+//======================================================================================================================
+//     reconstruct_signed_data_internal
+//======================================================================================================================
+
+mDNSlocal void *
+reconstruct_signed_data_internal(
+       const dnssec_rr_t * const                               rr_array[],
+       const mDNSu8                                                    rr_count,
+       const dnssec_rrsig_t * const _Nonnull   dnssec_rrsig,
+       mDNSu32 * const _Nonnull                                out_signed_data_length) {
+
+       mStatus error = mStatus_NoError;
+       mDNSu32                 signed_data_length      = 0;
+       mDNSu8 *                signed_data                     = mDNSNULL;
+       mDNSu8 *                data_ptr                        = mDNSNULL;
+       mDNSu8 *                data_limit                      = mDNSNULL;
+       mDNSu32                 number_bytes_copied = 0;
+
+       // calculate correct signed data length taking duplicates, domain name decompression and wildcard expansion into consideration
+       error = calculate_signed_data_length(rr_array, rr_count, dnssec_rrsig, &signed_data_length);
+       require_quiet(error == mStatus_NoError, exit);
+
+       // allocate memory for the signed data
+       signed_data = malloc(signed_data_length);
+       require_action(signed_data != mDNSNULL, exit, error = mStatus_NoMemoryErr; log_fault("malloc failed; error_description='%s'", strerror(errno)));
+
+       data_ptr        = signed_data;
+       data_limit      = data_ptr + signed_data_length;
+
+       // signed_data += RRSIG_RDATA
+       memcpy(data_ptr, dnssec_rrsig->dnssec_rr.rdata, offsetof(dns_type_rrsig_t, signer_name));
+       data_ptr += offsetof(dns_type_rrsig_t, signer_name);
+
+       // signed_data += RRSIG_RDATA(signer's name)
+       data_ptr += copy_canonical_name(data_ptr, dnssec_rrsig->signer_name);
+
+       for (mDNSu8 i = 0; i < rr_count; i++) {
+               const dnssec_rr_t * const rr = rr_array[i];
+               if (rr == mDNSNULL) { // the current record is a duplicate one
+                       continue;
+               }
+
+               // signed_data+= RR(i)
+               data_ptr += copy_rr_for_signed_data(data_ptr, rr, dnssec_rrsig);
+       }
+
+       *out_signed_data_length = signed_data_length;
+       number_bytes_copied = data_ptr - signed_data;
+       require_action(number_bytes_copied == signed_data_length, exit, error = mStatus_UnknownErr;
+                                       log_error("reconstruct_signed_data failed, number of bytes copied is not equal to the size of memory allocated; copied=%u, allocated=%u",
+                                                               number_bytes_copied, signed_data_length));
+       require_quiet(data_ptr == data_limit, exit);
+
+exit:
+       if (error != mStatus_NoError) {
+               if (signed_data != mDNSNULL) {
+                       free(signed_data);
+                       signed_data = mDNSNULL;
+               }
+       }
+       return signed_data;
+}
+
+//======================================================================================================================
+//     calculate_signed_data_length
+//======================================================================================================================
+
+mDNSlocal mStatus
+calculate_signed_data_length(
+       const dnssec_rr_t * const                                       rr_array[_Nonnull],
+       const mDNSu8                                                            rr_count,
+       const dnssec_rrsig_t * const    _Nonnull        dnssec_rrsig,
+       mDNSu32 * const                                 _Nonnull        out_length) {
+
+       mStatus error = mStatus_NoError;
+       mDNSu32 signed_data_length = 0;
+
+       // signed_data += RRSIG_RDATA(before signer's name) + signer's name
+       signed_data_length += offsetof(dns_type_rrsig_t, signer_name) + canonical_form_name_length(dnssec_rrsig->signer_name);
+
+       // signed_data += RR(x)
+       for (mDNSu8 i = 0; i < rr_count; i++) {
+               const dnssec_rr_t * const       rr                      = rr_array[i];
+               mDNSu32                                         rr_length       = 0;
+               mDNSs16                                         name_length = 0;
+               if (rr == mDNSNULL) { // It is a duplicate records, now skips it.
+                       continue;
+               }
+
+               name_length = calculate_name_length_in_signed_data(rr->name.c, dnssec_rrsig->labels);
+               require_action(name_length != -1, exit, error = mStatus_Invalid;
+                                               log_default("rrsig label count is invalid, cannot be used to validate records;"));
+
+               // RR(i) += name
+               rr_length += name_length;
+
+               // RR(i) += type + class + OrigTTL + RDATA length
+               rr_length += sizeof(rr->rr_type) + sizeof(rr->rr_class) + sizeof(dnssec_rrsig->original_TTL) + sizeof(rr->rdata_length);
+
+               // RR(i) += RDATA
+               rr_length += calculate_rdata_length_in_signed_data(rr);
+
+               signed_data_length += rr_length;
+       }
+
+       *out_length = signed_data_length;
+exit:
+       return error;
+}
+
+//======================================================================================================================
+//     calculate_name_length_in_signed_data
+//======================================================================================================================
+
+// length is calculated based on RFC 4035 Section 5.3.2. Reconstructing the Signed Data
+mDNSlocal mDNSs16
+calculate_name_length_in_signed_data(const mDNSu8 * const _Nonnull name, const mDNSu8 rrsig_labels) {
+       // assume that the domainname* is already fully qualified
+       mDNSu8 name_labels = get_number_of_labels(name);
+       if (name_labels == rrsig_labels) {
+               return DOMAIN_NAME_LENGTH(name);
+       } else if (name_labels > rrsig_labels) {
+               // wild card case, ignore this case for now
+               return DOMAIN_NAME_LENGTH(name);
+       } else {
+               // name_labels < rrsig_labels
+               return -1;
+       }
+}
+
+//======================================================================================================================
+//     calculate_rdata_length_in_signed_data
+//======================================================================================================================
+
+// ref: https://tools.ietf.org/html/rfc4034#section-6.2
+mDNSlocal mDNSu16
+calculate_rdata_length_in_signed_data(const dnssec_rr_t * const _Nonnull dnssec_rr) {
+       // assumption:
+       // 1. All the name in the rr is fully expanded and fully qualified.
+       // 2. There is no wildcard name.
+
+       return dnssec_rr->rdata_length;
+}
+
+//======================================================================================================================
+//     copy_canonical_name
+//======================================================================================================================
+mDNSlocal const mDNSu8 *
+get_wildcard_name(const mDNSu8 * const _Nonnull name, mDNSu8 * const _Nonnull buffer, const mDNSu16 buffer_length) {
+       const mDNSu8 *                  ptr                     = name;
+       const mDNSu16                   name_length = DOMAIN_NAME_LENGTH(ptr);
+       const mDNSu8 * const    ptr_limit       = ptr + name_length;
+       mDNSu8 *                                buffer_ptr      = buffer;
+
+       verify_action(name_length <= buffer_length , return mDNSNULL);
+
+       while (ptr < ptr_limit) {
+               const mDNSu8 * const    label = ptr + 1;
+               const mDNSu8                    label_length = *ptr;
+               if (ptr == name) {
+                       buffer_ptr[0] = 1;
+                       buffer_ptr[1] = '*';
+               } else {
+                       buffer_ptr[0] = label_length;
+                       memcpy(buffer_ptr + 1, label, label_length);
+               }
+
+               ptr                     += 1 + ptr[0];
+               buffer_ptr      += 1 + buffer_ptr[0];
+       }
+
+       return buffer;
+}
+
+//======================================================================================================================
+//     copy_rr_for_signed_data
+//======================================================================================================================
+
+mDNSlocal mDNSu32
+copy_rr_for_signed_data(
+       mDNSu8 *                                                _Nonnull        dst,
+       const dnssec_rr_t * const               _Nonnull        rr,
+       const dnssec_rrsig_t * const    _Nonnull        rrsig) {
+
+       mDNSu16                 canonical_rdata_length;
+       mDNSu8 * const  original_dst = dst;
+
+       // RR(i) += name
+       dst += copy_name_in_rr_for_signed_data(dst, rr->name.c, rrsig);
+
+       // RR(i) += type
+       mDNSu16 rr_type_net = htons(rr->rr_type);
+       memcpy(dst, &rr_type_net, sizeof(rr_type_net));
+       dst += sizeof(rr_type_net);
+
+       // RR(i) += class
+       mDNSu16 rr_class_net = htons(rr->rr_class);
+       memcpy(dst, &rr_class_net, sizeof(rr_class_net));
+       dst += sizeof(rr_class_net);
+
+       // RR(i) += Original TTL
+       mDNSu32 original_ttl_net = htonl(rrsig->original_TTL);
+       memcpy(dst, &original_ttl_net, sizeof(original_ttl_net));
+       dst += sizeof(original_ttl_net);
+
+       // RR(i) += RDATA length (canonical form of RDATA)
+       canonical_rdata_length =        calculate_rdata_length_in_signed_data(rr);
+       mDNSu16 canonical_rdata_length_net = ntohs(canonical_rdata_length);
+       memcpy(dst, &canonical_rdata_length_net, sizeof(canonical_rdata_length_net));
+       dst += sizeof(canonical_rdata_length_net);
+
+       // RR(i) += RDATA (canonical form)
+       dst += copy_rdata_in_rr(dst, rr->rdata, rr->rdata_length, rr->rr_type);
+
+       return dst - original_dst;
+}
+
+//======================================================================================================================
+//     copy_name_in_rr_for_signed_data
+//======================================================================================================================
+
+mDNSlocal mDNSu8
+copy_name_in_rr_for_signed_data(
+       mDNSu8 * const                                  _Nonnull        dst,
+       const mDNSu8 * const                    _Nonnull        name,
+       const dnssec_rrsig_t * const    _Nonnull        dnssec_rrsig) {
+
+       mDNSu8 name_labels      = get_number_of_labels(name);
+       mDNSu8 rrsig_labels = dnssec_rrsig->labels;
+       mDNSu8 bytes_write;
+
+       if (name_labels == rrsig_labels) {
+               bytes_write = copy_canonical_name(dst, name);
+       } else {
+               // name_labels > rrsig_labels
+               // It is impossible to have "name_labels < rrsig_labels", since the function calculate_signed_data_length
+               // already checks the validity of the records.
+               // TODO: wildcard domain name.
+               bytes_write = 0;
+       }
+
+       return bytes_write;
+}
+
+//======================================================================================================================
+//     copy_rdata_in_rr
+//======================================================================================================================
+
+// The rdata is in the canonical form. ref: https://tools.ietf.org/html/rfc4034#section-6.2
+mDNSlocal mDNSu16
+copy_rdata_in_rr(mDNSu8 * const _Nonnull dst, const mDNSu8 * const rdata, const mDNSu16 rdata_length, const mDNSu8 rr_type) {
+       // TODO: First assume all the rdata is already in canonical form.
+       (void) rr_type;
+
+       memcpy(dst, rdata, rdata_length);
+
+       return rdata_length;
+}
+
+//======================================================================================================================
+//     sort function
+//======================================================================================================================
+
+//======================================================================================================================
+//     sort_records_with_algorithm
+//======================================================================================================================
+
+mDNSlocal void
+sort_records_with_algorithm(dnssec_context_t * const _Nonnull context) {
+       list_t *                                        rrsigs;
+       originals_with_rrsig_t *        originals_with_rrsig = &context->original.original_result_with_rrsig;
+       response_type_t                         type;
+
+       type = originals_with_rrsig->type;
+       // sort RRSIG in original response
+       switch (type) {
+               case original_response:
+               case cname_response:
+                       if (type == original_response) {
+                               rrsigs = &originals_with_rrsig->u.original.rrsig_records;
+                       } else if (type == cname_response) {
+                               rrsigs = &originals_with_rrsig->u.cname_with_rrsig.rrsig_records;
+                       } else {
+                               goto invalid_type_exit;
+                       }
+                       list_sort(rrsigs, &dnssec_rrsig_t_comparator);
+                       break;
+               case nsec_response:
+               case nsec3_response:
+                       if (type == nsec_response) {
+                               list_t *nsec_list = &originals_with_rrsig->u.nsecs_with_rrsig.nsec_and_rrsigs_same_name;
+                               for (list_node_t *nsec_node = list_get_first(nsec_list); !list_has_ended(nsec_list, nsec_node); nsec_node = list_next(nsec_node)) {
+                                       one_nsec_with_rrsigs_t * const one_nsec = (one_nsec_with_rrsigs_t *)nsec_node->data;
+                                       list_sort(&one_nsec->rrsig_records, &dnssec_rrsig_t_comparator);
+                               }
+                       } else if (type == nsec3_response) {
+                               list_t *nsec3_list = &originals_with_rrsig->u.nsec3s_with_rrsig.nsec3_and_rrsigs_same_name;
+                               for (list_node_t *nsec3_node = list_get_first(nsec3_list); !list_has_ended(nsec3_list, nsec3_node); nsec3_node = list_next(nsec3_node)) {
+                                       one_nsec3_with_rrsigs_t * const one_nsec3 = (one_nsec3_with_rrsigs_t *)nsec3_node->data;
+                                       list_sort(&one_nsec3->rrsig_records, &dnssec_rrsig_t_comparator);
+                               }
+                       } else {
+                               goto invalid_type_exit;
+                       }
+                       break;
+               default:
+                       goto invalid_type_exit;
+       }
+
+
+       // sort RRSIG and DS in zone records
+       for (list_node_t *node = list_get_first(&context->zone_chain); !list_has_ended(&context->zone_chain, node); node = list_next(node)) {
+               dnssec_zone_t *                 zone                            = (dnssec_zone_t *)node->data;
+               dnskeys_with_rrsig_t *  dnskeys_with_rrsig      = &zone->dnskeys_with_rrsig;
+               dses_with_rrsig_t *             dses_with_rrsig         = &zone->dses_with_rrsig;
+
+               // sort DNSKEY RRSIG
+               if (zone->dnskey_request_started) {
+                       list_sort(&dnskeys_with_rrsig->rrsig_records, &dnssec_rrsig_t_comparator);
+               }
+
+               if (zone->ds_request_started)
+               {
+                       // sort DS RRSIG
+                       type = dses_with_rrsig->type;
+                       switch (type) {
+                               case original_response:
+                                       list_sort(&dses_with_rrsig->u.original.rrsig_records, &dnssec_rrsig_t_comparator);
+                                       break;
+                               case nsec_response:
+                               case nsec3_response:
+                                       if (type == nsec_response) {
+                                               list_t *nsec_list = &dses_with_rrsig->u.nsecs_with_rrsig.nsec_and_rrsigs_same_name;
+                                               for (list_node_t *nsec_node = list_get_first(nsec_list); !list_has_ended(nsec_list, nsec_node); nsec_node = list_next(nsec_node)) {
+                                                       one_nsec_with_rrsigs_t * const one_nsec = (one_nsec_with_rrsigs_t *)nsec_node->data;
+                                                       list_sort(&one_nsec->rrsig_records, &dnssec_rrsig_t_comparator);
+                                               }
+                                       } else if (type == nsec3_response) {
+                                               list_t *nsec3_list = &dses_with_rrsig->u.nsec3s_with_rrsig.nsec3_and_rrsigs_same_name;
+                                               for (list_node_t *nsec3_node = list_get_first(nsec3_list); !list_has_ended(nsec3_list, nsec3_node); nsec3_node = list_next(nsec3_node)) {
+                                                       one_nsec3_with_rrsigs_t * const one_nsec3 = (one_nsec3_with_rrsigs_t *)nsec3_node->data;
+                                                       list_sort(&one_nsec3->rrsig_records, &dnssec_rrsig_t_comparator);
+                                               }
+                                       } else {
+                                               goto invalid_type_exit;
+                                       }
+                                       break;
+                               default:
+                                       goto invalid_type_exit;
+                       }
+               }
+       }
+
+       return;
+invalid_type_exit:
+       log_error("Invalid original response type; type=%d", type);
+       return;
+}
+
+//======================================================================================================================
+//     dnssec_ds_t_comparator
+//======================================================================================================================
+mDNSlocal mDNSs8 __unused
+dnssec_ds_t_comparator(const list_node_t * _Nonnull const left, const list_node_t * _Nonnull const right) {
+       mDNSs8                  result;
+       dnssec_ds_t *   left_ds                 = (dnssec_ds_t *)left->data;
+       dnssec_ds_t *   right_ds                = (dnssec_ds_t *)right->data;
+       mDNSs16                 left_priority   = get_priority_of_ds_digest(left_ds->digest_type);
+       mDNSs16                 right_priority  = get_priority_of_ds_digest(right_ds->digest_type);
+
+       if (left_priority < right_priority) {
+               result = -1;
+       } else if (left_priority > right_priority) {
+               result = 1;
+       } else {
+               // left_priority == right_priority
+               result = 0;
+       }
+
+       return result;
+}
+
+//======================================================================================================================
+//     dnssec_rrsig_t_comparator
+//======================================================================================================================
+
+mDNSlocal mDNSs8
+dnssec_rrsig_t_comparator(const list_node_t * _Nonnull const left, const list_node_t * _Nonnull const right) {
+       mDNSs8                  result;
+       dnssec_rrsig_t *left_rrsig              = (dnssec_rrsig_t *)left->data;
+       dnssec_rrsig_t *right_rrsig             = (dnssec_rrsig_t *)right->data;
+       mDNSs16                 left_priority   = get_priority_of_dnskey_algorithm(left_rrsig->algorithm);
+       mDNSs16                 right_priority  = get_priority_of_dnskey_algorithm(right_rrsig->algorithm);
+
+       if (left_priority < right_priority) {
+               result = 1;
+       } else if (left_priority > right_priority) {
+               result = -1;
+       } else {
+               // left_priority == right_priority
+               result = 0;
+       }
+
+       return result;
+}
+
+//======================================================================================================================
+//     sort_rr_array
+//======================================================================================================================
+
+mDNSlocal void
+sort_rr_array_canonically(const dnssec_rr_t * rr_array[_Nonnull], const mDNSu8 rr_count) {
+       // insertion sort is good for partially sorted array
+       for (mDNSs32 j, i = 1; i < rr_count; i++) {
+               const dnssec_rr_t * dnssec_rr_to_insert = rr_array[i];
+               j = i - 1;
+
+               while (j >= 0 && dnssec_rr_t_comparator(dnssec_rr_to_insert, rr_array[j]) == -1) {
+                       rr_array[j + 1] = rr_array[j];
+                       j--;
+               }
+               rr_array[j + 1] = dnssec_rr_to_insert;
+       }
+}
+
+//======================================================================================================================
+//     sort_rr_array
+//======================================================================================================================
+
+mDNSlocal mDNSs8
+dnssec_rr_t_comparator(const dnssec_rr_t * const _Nonnull left, const dnssec_rr_t * const _Nonnull right) {
+       mDNSu16 left_length             = left->rdata_length;
+       mDNSu16 right_length    = right->rdata_length;
+       mDNSu16 min_length              = MIN(left_length, right_length);
+       mDNSs32 memcmp_result   = memcmp(left->rdata, right->rdata, min_length);
+       mDNSs8  result;
+       if (memcmp_result < 0) {
+               result = -1;
+       } else if (memcmp_result > 0) {
+               result = 1;
+       } else {
+               // memcmp_result == 0
+               if (left_length > right_length) {
+                       result = 1;
+               } else if (left_length < right_length) {
+                       result = -1;
+               } else {
+                       // left_length == right_length
+                       log_error("two RR are canonically equal;");
+                       result = 0;
+               }
+       }
+       return result;
+}
+
+//======================================================================================================================
+//     rr_array_dedup
+//======================================================================================================================
+
+mDNSlocal mDNSBool
+rr_array_dedup(const dnssec_rr_t * rr_array[_Nonnull], const mDNSu8 rr_count) {
+       mDNSBool duplicate = mDNSfalse;
+       for (int i = 0, n = (int)rr_count - 1; i < n; i++) {
+               const dnssec_rr_t *prev = rr_array[i];
+               const dnssec_rr_t *next = rr_array[i + 1];
+               if (equal_dnssec_rr_t(prev, next)) {
+                       rr_array[i] = mDNSNULL;
+                       duplicate = mDNStrue;
+               }
+       }
+       return duplicate;
+}
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
diff --git a/mDNSMacOSX/dnssec_v2/dnssec_v2_validation.h b/mDNSMacOSX/dnssec_v2/dnssec_v2_validation.h
new file mode 100644 (file)
index 0000000..682f0ff
--- /dev/null
@@ -0,0 +1,35 @@
+//
+//     dnssec_v2_validation.h
+//     mDNSResponder
+//
+//     Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#ifndef DNSSEC_V2_VALIDATION_H
+#define DNSSEC_V2_VALIDATION_H
+
+#include <stdio.h>
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
+typedef enum dnssec_validator_node_type {
+       rr_validator,
+       nsec_validator,
+       nsec3_validator,
+       zsk_validator,
+       ksk_validator
+} dnssec_validator_node_type_t;
+
+//======================================================================================================================
+//     functions prototype
+//======================================================================================================================
+
+mDNSexport dnssec_validation_result_t
+validate_dnssec(dnssec_context_t * const _Nonnull context);
+
+mDNSexport mDNSu16
+calculate_key_tag(const mDNSu8 key[_Nonnull], const mDNSu16 key_len, const mDNSu8 algorithm);
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
+#endif // DNSSEC_V2_VALIDATION_H
diff --git a/mDNSMacOSX/dnssec_v2/utilities/base_encoding/base_n.c b/mDNSMacOSX/dnssec_v2/utilities/base_encoding/base_n.c
new file mode 100644 (file)
index 0000000..2ffdd3d
--- /dev/null
@@ -0,0 +1,208 @@
+//
+//  base_n.c
+//  mDNSResponder
+//
+//  Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#pragma mark - Includes
+#include "base_n.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include <stdint.h>
+#include <AssertMacros.h>
+#include "dnssec_v2_log.h"
+
+#pragma mark - Constants
+
+
+
+#pragma mark b64_table
+static const char b64_table[] = {
+       'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+       'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+       'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+       'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+       'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+       'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+       'w', 'x', 'y', 'z', '0', '1', '2', '3',
+       '4', '5', '6', '7', '8', '9', '+', '/'
+};
+
+#pragma mark b32_hex_table
+static const char b32_hex_table[] = {
+       '0', '1', '2', '3', '4', '5', '6', '7',
+       '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
+       'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+       'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V'
+};
+
+#pragma mark - Macros
+
+
+
+#define MAX_LENGTH_B64_ENCODING_DATA (SIZE_MAX * 3 / 4)
+#define MAX_LENGTH_B32_HEX_ENCODING_DATA (SIZE_MAX * 5 / 8)
+
+#pragma mark - Functions
+
+
+
+#pragma mark - base_n_encode
+char * _Nullable
+base_n_encode(base_n_t base_n, const unsigned char * const _Nonnull data, size_t data_len) {
+       return base_n_encode_ex(base_n, data, data_len, NULL);
+}
+
+#pragma mark - base_n_encode_ex
+char * _Nullable
+base_n_encode_ex(base_n_t base_n, const unsigned char * const _Nonnull data, size_t data_len, size_t * const _Nullable out_str_len) {
+       char *                                          encoded_str                     = mDNSNULL;
+       const size_t                            encoded_str_len         = get_base_n_encoded_str_length(base_n, data_len);
+       char *                                          encoded_str_ptr;
+       const unsigned char *           data_ptr                        = data;
+       const unsigned char * const data_ptr_limit              = data_ptr + data_len;
+       size_t                                          remain;
+       uint64_t                                        quantum;
+
+       encoded_str = malloc(encoded_str_len + 1);
+       require_quiet(encoded_str != mDNSNULL, exit);
+
+       // ensure that the string always ends with '\0'
+       encoded_str[encoded_str_len]    = '\0';
+       encoded_str_ptr                 = encoded_str;
+
+       if (out_str_len != mDNSNULL) {
+               *out_str_len = encoded_str_len;
+       }
+
+       // encoding starts
+       switch (base_n) {
+               case DNSSEC_BASE_64: {
+                       for(; data_ptr < data_ptr_limit;) {
+                               char            encoded_buf[4];
+                               size_t          encoded_size = 0;
+
+                               remain  = data_ptr_limit - data_ptr;
+                               quantum = 0;
+                               // get 24 bits from 3 bytes
+                               switch (remain) {
+                                       default:
+                                       case 3: quantum |=  (uint64_t)data_ptr[2];                      // Bits 16 - 23
+                                       case 2: quantum |= ((uint64_t)data_ptr[1]) << 8;        // Bits  8 - 15
+                                       case 1: quantum |= ((uint64_t)data_ptr[0]) << 16;       // Bits  0 - 7
+                               }
+                               // advance the data pointer
+                               data_ptr += ((remain > 3) ? 3 : remain);
+
+                               // convert 24 bits to 4 characters
+                               switch (remain) {
+                                       default:
+                                       case 3:
+                                               encoded_buf[3]  = b64_table[ quantum            & 0x3F];
+                                               encoded_size    = 4;
+                                       case 2:
+                                               encoded_buf[2]  = b64_table[(quantum >>  6) & 0x3F];
+                                               if (encoded_size == 0) encoded_size = 3;
+                                       case 1:
+                                               encoded_buf[1]  = b64_table[(quantum >> 12) & 0x3F];
+                                               encoded_buf[0]  = b64_table[(quantum >> 18) & 0x3F];
+                                               if (encoded_size == 0) encoded_size = 2;
+                               }
+
+                               // fill the padding with '='
+                               for (size_t i = encoded_size; i < sizeof(encoded_buf); i++) {
+                                       encoded_buf[i] = '=';
+                               }
+
+                               // move the current encoded string chunk to the returned buffer
+                               memcpy(encoded_str_ptr, encoded_buf, sizeof(encoded_buf));
+                               encoded_str_ptr += sizeof(encoded_buf);
+                       }
+                       break;
+               }
+               case DNSSEC_BASE_32_HEX: {
+                       for(; data_ptr < data_ptr_limit;) {
+                               char            encoded_buf[8];
+                               size_t          encoded_size = 0;
+
+                               remain  = data_ptr_limit - data_ptr;
+                               quantum = 0;
+                               // get 40 bits from 8 bytes
+                               switch (remain) {
+                                       default:
+                                       case 5: quantum |=  (uint64_t)data_ptr[4];          // Bits 32 - 39
+                                       case 4: quantum |= ((uint64_t)data_ptr[3]) << 8;    // Bits 24 - 32
+                                       case 3: quantum |= ((uint64_t)data_ptr[2]) << 16;   // Bits 16 - 23
+                                       case 2: quantum |= ((uint64_t)data_ptr[1]) << 24;   // Bits  8 - 15
+                                       case 1: quantum |= ((uint64_t)data_ptr[0]) << 32;   // Bits  0 -  7
+                               }
+                               // advance the data pointer
+                               data_ptr += ((remain > 5) ? 5 : remain);
+
+                               // convert 40 bits to 8 characters
+                               switch (remain) {
+                                       default:
+                                       case 5:
+                                               encoded_buf[7]  = b32_hex_table[quantum                 & 0x1F];
+                                               encoded_size = 8;
+                                       case 4:
+                                               encoded_buf[6]  = b32_hex_table[(quantum >>  5) & 0x1F];
+                                               encoded_buf[5]  = b32_hex_table[(quantum >> 10) & 0x1F];
+                                               if (encoded_size == 0) encoded_size = 7;
+                                       case 3:
+                                               encoded_buf[4]  = b32_hex_table[(quantum >> 15) & 0x1F];
+                                               if (encoded_size == 0) encoded_size = 5;
+                                       case 2:
+                                               encoded_buf[3]  = b32_hex_table[(quantum >> 20) & 0x1F];
+                                               encoded_buf[2]  = b32_hex_table[(quantum >> 25) & 0x1F];
+                                               if (encoded_size == 0) encoded_size = 4;
+                                       case 1:
+                                               encoded_buf[1]  = b32_hex_table[(quantum >> 30) & 0x1F];
+                                               encoded_buf[0]  = b32_hex_table[(quantum >> 35) & 0x1F];
+                                               if (encoded_size == 0) encoded_size = 2;
+                               }
+
+                               // fill the padding with '='
+                               for (size_t i = encoded_size; i < sizeof(encoded_buf); i++) {
+                                       encoded_buf[i] = '=';
+                               }
+
+                               // move the current encoded string chunk to the returned buffer
+                               memcpy(encoded_str_ptr, encoded_buf, sizeof(encoded_buf));
+                               encoded_str_ptr += sizeof(encoded_buf);
+                       }
+                       break;
+               }
+               default:
+                       // Unsupported Base N, returns empty string.
+                       encoded_str[0] = '\0';
+                       break;
+       }
+
+exit:
+       return encoded_str;
+}
+
+#pragma mark - get_base_n_encoded_str_length
+size_t
+get_base_n_encoded_str_length(base_n_t base_n, size_t data_len) {
+       size_t encoded_str_len = 0;
+
+       switch (base_n) {
+               case DNSSEC_BASE_64:
+                       verify_action(data_len <= MAX_LENGTH_B64_ENCODING_DATA, data_len = MAX_LENGTH_B64_ENCODING_DATA);
+                       encoded_str_len = (data_len + 2) / 3 * 4;
+                       break;
+               case DNSSEC_BASE_32_HEX:
+                       verify_action(data_len <= MAX_LENGTH_B32_HEX_ENCODING_DATA, data_len = MAX_LENGTH_B32_HEX_ENCODING_DATA);
+                       encoded_str_len = (data_len + 4) / 5 * 8;
+                       break;
+               default:
+                       encoded_str_len = 0;
+                       break;
+       }
+
+       return encoded_str_len;
+}
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
diff --git a/mDNSMacOSX/dnssec_v2/utilities/base_encoding/base_n.h b/mDNSMacOSX/dnssec_v2/utilities/base_encoding/base_n.h
new file mode 100644 (file)
index 0000000..bb568ff
--- /dev/null
@@ -0,0 +1,84 @@
+//
+//  base_n.h
+//  mDNSResponder
+//
+//  Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#ifndef base_n_h
+#define base_n_h
+
+#pragma mark - Includes
+#include "mDNSEmbeddedAPI.h"
+#include <stdlib.h>
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
+#pragma mark base_n_t
+typedef enum base_n {
+       DNSSEC_BASE_64,
+       DNSSEC_BASE_32_HEX
+} base_n_t;
+
+#pragma mark base_n_encode
+/*!
+ *     @brief
+ *             Gets the Base N encoding for the binary data
+ *
+ *     @param base_n
+ *             The base we would like to convert, currently it could be Base 64 or Base 32.
+ *
+ *     @param data
+ *             The data that needs to be encoded.
+ *
+ *     @param data_len
+ *             The length of the data that needs to be encoded..
+ *
+ *     @return
+ *             This function returns a malloced string that needs to be freed by the caller.
+ */
+char * _Nullable
+base_n_encode(base_n_t base_n, const unsigned char * _Nonnull data, size_t data_len);
+
+#pragma mark base_n_encode_ex
+/*!
+ *     @brief
+ *             Gets the Base N encoding for the binary data, and return the length of the encoding string.
+ *
+ *     @param base_n
+ *             The base we would like to convert, currently it could be Base 64 or Base 32.
+ *
+ *     @param data
+ *             The data that needs to be encoded.
+ *
+ *     @param data_len
+ *             The length of the data that needs to be encoded.
+ *
+ *     @param out_str_len
+ *             The output pointer to the length of the encoded string.
+ *
+ *     @returns
+ *             This function returns a malloced string that needs to be freed by the caller, and also the length of encoded string.
+ */
+char * _Nullable
+base_n_encode_ex(base_n_t base_n, const unsigned char * _Nonnull data, size_t data_len, size_t * const _Nullable out_str_len);
+
+#pragma mark get_base_n_encoded_str_length
+/*!
+ *     @brief
+ *             Gets the length of the encoded string after Base N encoding
+ *
+ *     @param base_n
+ *             The base we would like to convert, currently it could be DNSSEC_BASE_64 or DNSSEC_BASE_32_HEX.
+ *
+ *     @param data_len
+ *             The length of the data that needs to be encoded.
+ *
+ *     @discussion
+ *             The length does not include the '\0'.
+ */
+size_t
+get_base_n_encoded_str_length(base_n_t base_n, size_t data_len) ;
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
+#endif /* base_n_h */
diff --git a/mDNSMacOSX/dnssec_v2/utilities/list/list.c b/mDNSMacOSX/dnssec_v2/utilities/list/list.c
new file mode 100644 (file)
index 0000000..96dc4e5
--- /dev/null
@@ -0,0 +1,390 @@
+//
+//  list.c
+//  mDNSResponder
+//
+//  Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#pragma mark - Includes
+#include "mDNSEmbeddedAPI.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include "list.h"
+
+#pragma mark - list_init
+mDNSexport void
+list_init(list_t * const _Nonnull list_to_init, const mDNSu32 new_data_size) {
+       // use dummy head and tail to handle the corner case such as adding node to empty list
+       list_to_init->head_ptr                  = &(list_to_init->head);
+       list_to_init->tail_ptr                  = &(list_to_init->tail);
+       list_to_init->head_ptr->next    = list_to_init->tail_ptr;
+       list_to_init->tail_ptr->prev    = list_to_init->head_ptr;
+       // data size is used to prevent from adding different structures into list
+       list_to_init->data_size                 = new_data_size;
+}
+
+#pragma mark - list_append_uinitialized
+mDNSexport mStatus
+list_append_uinitialized(list_t * const _Nonnull list_ptr, const mDNSu32 new_data_size, void * _Nullable * _Nonnull out_data_ptr) {
+       mStatus                 error           = mStatus_NoError;
+       list_node_t *   node_ptr        = mDNSNULL;
+       list_node_t *   real_tail       = mDNSNULL;
+
+       require_action(list_ptr->data_size == new_data_size, exit, error = mStatus_BadParamErr);
+
+       node_ptr = malloc(offsetof(list_node_t, data) + new_data_size);
+       require_action(node_ptr != mDNSNULL, exit, error = mStatus_NoMemoryErr);
+
+       *out_data_ptr = &node_ptr->data;
+
+       real_tail                                       = list_ptr->tail_ptr->prev;
+       real_tail->next                         = node_ptr;
+       node_ptr->prev                          = real_tail;
+       node_ptr->next                          = list_ptr->tail_ptr;
+       list_ptr->tail_ptr->prev        = node_ptr;
+
+exit:
+       if (error != mStatus_NoError && node_ptr != mDNSNULL) free(node_ptr);
+       return error;
+}
+
+#pragma mark - list_prepend_uinitialized
+mDNSexport mStatus
+list_prepend_uinitialized(list_t * const _Nonnull list_ptr, const mDNSu32 new_data_size, void * _Nullable * _Nonnull out_data_ptr) {
+       mStatus                 error           = mStatus_NoError;
+       list_node_t *   node_ptr        = mDNSNULL;
+       list_node_t *   real_head       = mDNSNULL;
+
+       require_action(list_ptr->data_size == new_data_size, exit, error = mStatus_BadParamErr);
+
+       node_ptr = malloc(offsetof(list_node_t, data) + new_data_size);
+       require_action(node_ptr != mDNSNULL, exit, error = mStatus_NoMemoryErr);
+
+       *out_data_ptr = &node_ptr->data;
+
+       real_head                                       = list_ptr->head_ptr->next;
+       real_head->prev                         = node_ptr;
+       node_ptr->next                          = real_head;
+       node_ptr->prev                          = list_ptr->head_ptr;
+       list_ptr->head_ptr->next        = node_ptr;
+
+exit:
+       if (error != mDNSNULL && node_ptr != mDNSNULL) free(node_ptr);
+       return error;
+}
+
+#pragma mark - list_append_node
+mDNSexport void
+list_append_node(list_t * const _Nonnull list_ptr, list_node_t * const _Nonnull node_ptr) {
+       node_ptr->prev                                  = list_ptr->head_ptr;
+       node_ptr->next                                  = list_ptr->head_ptr->next;
+       list_ptr->head_ptr->next->prev  = node_ptr;
+       list_ptr->head_ptr->next                = node_ptr;
+}
+
+#pragma mark - list_prepend_node
+mDNSexport void
+list_prepend_node(list_t *const _Nonnull list_ptr, list_node_t * const _Nonnull node_ptr) {
+       node_ptr->prev                                  = list_ptr->tail_ptr->prev;
+       node_ptr->next                                  = list_ptr->tail_ptr;
+       list_ptr->tail_ptr->prev->next  = node_ptr;
+       list_ptr->tail_ptr->prev                = node_ptr;
+}
+
+#pragma mark - list_append_node_at_node
+mDNSexport void
+list_append_node_at_node(list_node_t * const _Nonnull original_node, list_node_t * const _Nonnull added_node) {
+       list_node_t * const original_next = original_node->next;
+
+       added_node->prev        = original_node;
+       added_node->next        = original_next;
+       original_node->next     = added_node;
+       original_next->prev     = added_node;
+}
+
+#pragma mark - list_prepend_node_at_node
+mDNSexport void
+list_prepend_node_at_node(list_node_t * const _Nonnull original_node, list_node_t * const _Nonnull added_node) {
+       list_node_t * const original_prev = original_node->prev;
+
+       added_node->prev        = original_prev;
+       added_node->next        = original_node;
+       original_prev->next     = added_node;
+       original_node->prev     = added_node;
+}
+
+#pragma mark - list_concatenate
+mDNSexport void
+list_concatenate(list_t * const _Nonnull dst_list, list_t * const _Nonnull src_list) {
+       list_node_t *dst_last_node      = dst_list->tail_ptr->prev;
+       list_node_t *src_first_node     = src_list->head_ptr->next;
+       list_node_t *src_last_node      = src_list->tail_ptr->prev;
+
+       if (list_empty(src_list)) {
+               return;
+       }
+
+       src_list->head_ptr->next        = src_list->tail_ptr;
+       src_list->tail_ptr->prev        = src_list->head_ptr;
+
+       dst_last_node->next                     = src_first_node;
+       src_first_node->prev            = dst_last_node;
+       src_last_node->next                     = dst_list->tail_ptr;
+       dst_list->tail_ptr->prev        = src_last_node;
+}
+
+#pragma mark - node_list_sort
+mDNSlocal void
+node_list_sort(list_node_t * _Nullable * const _Nonnull in_out_list_ptr, list_sort_comparator_t comparator);
+
+#pragma mark - node_list_tail
+mDNSlocal list_node_t *
+node_list_tail(list_node_t * _Nonnull list_ptr);
+
+#pragma mark - list_sort
+mDNSexport void
+list_sort(list_t * const _Nonnull list_ptr, list_sort_comparator_t comparator) {
+       list_node_t *real_head = list_ptr->head_ptr->next;
+
+       if (list_empty(list_ptr)) {
+               return;
+       }
+
+       list_ptr->tail_ptr->prev->next  = mDNSNULL;
+       list_ptr->head_ptr->next                = list_ptr->tail_ptr;
+       list_ptr->tail_ptr->prev                = list_ptr->head_ptr;
+
+       node_list_sort(&real_head, comparator);
+
+       list_ptr->head_ptr->next                = real_head;
+       list_ptr->tail_ptr->prev                = node_list_tail(real_head);
+       real_head->prev                                 = list_ptr->head_ptr;
+       list_ptr->tail_ptr->prev->next  = list_ptr->tail_ptr;
+}
+
+#pragma mark - front_back_split
+mDNSlocal void
+front_back_split(list_node_t * const _Nonnull list_ptr, list_node_t **out_front, list_node_t **out_back);
+
+#pragma mark - sorted_merge
+mDNSlocal list_node_t *
+sorted_merge(list_node_t * _Nullable left, list_node_t * _Nullable right, list_sort_comparator_t comparator);
+
+#pragma mark - node_list_sort
+mDNSlocal void
+node_list_sort(list_node_t * _Nullable * const _Nonnull in_out_list_ptr, list_sort_comparator_t comparator) {
+       list_node_t *list_ptr = *in_out_list_ptr;
+       list_node_t *front_half;
+       list_node_t *back_half;
+
+       if (list_ptr == mDNSNULL || list_ptr->next == mDNSNULL)
+               return;
+
+       front_back_split(list_ptr, &front_half, &back_half);
+
+       node_list_sort(&front_half, comparator);
+       node_list_sort(&back_half, comparator);
+
+       *in_out_list_ptr = sorted_merge(front_half, back_half, comparator);
+}
+
+#pragma mark - front_back_split
+mDNSlocal void
+front_back_split(
+       list_node_t * const _Nonnull        list_ptr,
+       list_node_t * _Nullable * _Nonnull  out_front,
+       list_node_t * _Nullable * _Nonnull  out_back) {
+
+       list_node_t *fast = list_ptr->next;
+       list_node_t *slow = list_ptr;
+
+       while (fast != mDNSNULL) {
+               fast = fast->next;
+               if (fast != mDNSNULL) {
+                       slow = slow->next;
+                       fast = fast->next;
+               }
+       }
+
+       *out_front      = list_ptr;
+       *out_back       = slow->next;
+       slow->next      = mDNSNULL;
+}
+
+#pragma mark - sorted_merge
+mDNSlocal list_node_t *
+sorted_merge(list_node_t * _Nullable left, list_node_t * _Nullable right, list_sort_comparator_t comparator) {
+       list_node_t *sorted_list;
+       list_node_t *tail;
+       mDNSs8 cmp_result;
+
+       if (left == mDNSNULL) {
+               return right;
+       } else if (right == mDNSNULL) {
+               return left;
+       }
+
+       cmp_result = comparator(left, right);
+       if (cmp_result <= 0) {
+               sorted_list = left;
+               tail            = left;
+               left            = left->next;
+       } else {
+               // cmp_result > 0
+               sorted_list     = right;
+               tail            = right;
+               right           = right->next;
+       }
+
+       while (left != mDNSNULL && right != mDNSNULL) {
+               cmp_result = comparator(left, right);
+               if (cmp_result <= 0) {
+                       tail->next      = left;
+                       tail            = left;
+                       left            = left->next;
+               } else {
+                       // cmp_result > 0
+                       tail->next      = right;
+                       tail            = right;
+                       right           = right->next;
+               }
+       }
+
+       if (left != mDNSNULL) {
+               tail->next = left;
+       } else {
+               tail->next = right;
+       }
+
+       return sorted_list;
+}
+
+#pragma mark - node_list_tail
+mDNSlocal list_node_t *
+node_list_tail(list_node_t * _Nonnull list_ptr) {
+       while (list_ptr->next != mDNSNULL) {
+               list_ptr = list_ptr->next;
+       }
+
+       return list_ptr;
+}
+
+#pragma mark - list_node_detach
+mDNSexport void
+list_node_detach(list_node_t * const _Nonnull node) {
+       list_node_t *prev = node->prev;
+       list_node_t *next = node->next;
+       prev->next = next;
+       next->prev = prev;
+}
+
+#pragma mark - list_node_delete
+mDNSexport void
+list_node_delete(list_node_t * const _Nonnull node_ptr) {
+       list_node_t *prev;
+       list_node_t *next;
+
+       prev = node_ptr->prev;
+       next = node_ptr->next;
+
+       prev->next = next;
+       next->prev = prev;
+
+       free(node_ptr);
+}
+
+#pragma mark - list_node_delete_all
+mDNSexport void
+list_node_delete_all(list_t * const _Nonnull list_ptr) {
+       list_node_t *next_node;
+       for (list_node_t * node = list_get_first(list_ptr); !list_has_ended(list_ptr, node); node = next_node) {
+               next_node = list_next(node);
+               list_node_delete(node);
+       }
+}
+
+#pragma mark - list_delete_node_with_data_ptr
+mDNSexport mStatus
+list_delete_node_with_data_ptr(list_t * const _Nonnull list_ptr, void * const _Nonnull data) {
+       mStatus error = mStatus_NoSuchKey;
+
+       for (list_node_t *ptr = list_get_first(list_ptr); !list_has_ended(list_ptr, ptr); ptr = list_next(ptr)) {
+               if (data == &ptr->data) {
+                       list_node_delete(ptr);
+                       error = mStatus_NoError;
+                       break;
+               }
+       }
+
+       return error;
+}
+
+#pragma mark - list_empty
+mDNSexport mDNSBool
+list_empty(const list_t * const _Nonnull list_ptr) {
+       return list_ptr->head_ptr->next == list_ptr->tail_ptr;
+}
+
+#pragma mark - list_count_node
+mDNSexport mDNSu32
+list_count_node(const list_t *const _Nonnull list_ptr) {
+       mDNSu32 count = 0;
+
+       if (list_empty(list_ptr)) {
+               return 0;
+       }
+
+       for (list_node_t *ptr = list_get_first(list_ptr); !list_has_ended(list_ptr, ptr); ptr = list_next(ptr)) {
+               count++;
+       }
+
+       return count;
+}
+
+#pragma mark - list_get_first
+mDNSexport list_node_t * _Nullable
+list_get_first(const list_t * const _Nonnull list_ptr) {
+       if (list_empty(list_ptr)) {
+               return mDNSNULL;
+       }
+
+       return list_ptr->head_ptr->next;
+}
+
+#pragma mark - list_get_last
+mDNSexport list_node_t * _Nullable
+list_get_last(const list_t * const _Nonnull list_ptr) {
+       if (list_empty(list_ptr)) {
+               return mDNSNULL;
+       }
+
+       return list_ptr->tail_ptr->prev;
+}
+
+#pragma mark - list_next
+mDNSexport list_node_t * _Nonnull
+list_next(const list_node_t * const _Nonnull node_ptr) {
+       return node_ptr->next;
+}
+
+#pragma mark - list_has_ended
+mDNSexport mDNSBool
+list_has_ended(const list_t * const _Nonnull list_ptr, const list_node_t * const _Nullable node_ptr) {
+       if (node_ptr == mDNSNULL) {
+               return mDNStrue;
+       }
+       return node_ptr == list_ptr->tail_ptr;
+}
+
+#pragma mark - list_uninit
+mDNSexport void
+list_uninit(list_t * const _Nonnull list_ptr) {
+       list_node_delete_all(list_ptr);
+
+       list_ptr->data_size                     = 0;
+       list_ptr->head_ptr->next        = mDNSNULL;
+       list_ptr->tail_ptr->prev        = mDNSNULL;
+       list_ptr->head_ptr                      = mDNSNULL;
+       list_ptr->tail_ptr                      = mDNSNULL;
+}
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
diff --git a/mDNSMacOSX/dnssec_v2/utilities/list/list.h b/mDNSMacOSX/dnssec_v2/utilities/list/list.h
new file mode 100644 (file)
index 0000000..536a66d
--- /dev/null
@@ -0,0 +1,339 @@
+//
+//  list.h
+//  mDNSResponder
+//
+//  Copyright (c) 2020 Apple Inc. All rights reserved.
+//
+
+#ifndef LIST_H
+#define LIST_H
+
+#pragma mark - Includes
+#include <stdint.h>
+#include <stdio.h>
+#include <AssertMacros.h>       // for require_* macro
+#include "mDNSEmbeddedAPI.h"    // for mStatus
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
+#pragma mark - Structures
+
+
+
+#pragma mark list_node_t
+/*!
+ *     @brief
+ *             A node in the list_t.
+ */
+typedef struct list_node list_node_t;
+struct list_node {
+       list_node_t *   _Nullable       prev;
+       list_node_t *   _Nullable       next;
+       mDNSu8                                          data[0];        // the actual data will be stored starting from data, so list_node_t->data can access it
+};
+
+#pragma mark list_t
+/*!
+ *     @brief
+ *             A structure that represents a generic doublely linked list.
+ */
+typedef struct list list_t;
+struct list {
+       mDNSu32                                         data_size;      // to check if the data put into the list matches the size of actual structure
+       list_node_t *   _Nullable       head_ptr;
+       list_node_t *   _Nullable       tail_ptr;
+       list_node_t                                     head;           // dummy head will be pointed by head_ptr
+       list_node_t                                     tail;           // dummy tail will be pointed by tail_ptr
+};
+
+#pragma mark - Functions
+
+
+
+#pragma mark list_init
+/*!
+ *     @brief
+ *             Initializes list_t, must be called before using the list
+ *
+ *     @param list_to_init
+ *             The pointer to the unintialized list
+ *
+ *     @param new_data_size
+ *             the size of the data that will be put into the list
+ *
+ *     @discussion
+ *             After the list is initialized with "new_data_size", we could only put the same data structure that has the same "data_size" into the list
+ */
+mDNSexport void
+list_init(list_t * const _Nonnull list_to_init, const mDNSu32 new_data_size);
+
+#pragma mark list_append_uinitialized
+/*!
+ *     @brief
+ *             Appends a node at the end of the list, and return a memory pointer that can be used to store the data structure.
+ *
+ *     @param list_ptr
+ *             The pointer to the list.
+ *
+ *     @param new_data_size
+ *             The size of the data structure that will be appended into the list.
+ *
+ *     @param out_data_ptr
+ *             The output pointer to the memory location that can be used to save the data structure.
+ *
+ *     @return
+ *             mStatus_NoError if everything works fine, or other error codes defined in mStatus.
+
+ *     @discussion
+ *             The returned memory pointer can be used to initialize a data structure which will stored in the list.
+ */
+mDNSexport mStatus
+list_append_uinitialized(list_t * const _Nonnull list_ptr, const mDNSu32 new_data_size, void * _Nullable * _Nonnull out_data_ptr);
+
+#pragma mark list_prepend_uinitialized
+/*!
+ *     @brief
+ *             Prepends a node at the start of the list, and return a memory pointer that can be used to store the data structure.
+ *
+ *     @param list_ptr
+ *             The pointer to the list.
+ *
+ *     @param new_data_size
+ *             The size of the data structure that will be appended into the list.
+ *
+ *     @param out_data_ptr
+ *             The output pointer to the memory location that can be used to save the data structure.
+ *
+ *     @return
+ *             mStatus_NoError if everything works fine, or other error codes defined in mStatus.
+
+ *     @discussion
+ *             The returned memory pointer can be used to initialize a data structure which will be stored in the list.
+ */
+mDNSexport mStatus
+list_prepend_uinitialized(list_t * const _Nonnull list_ptr, const mDNSu32 new_data_size, void * _Nullable * _Nonnull out_data_ptr);
+
+#pragma mark list_append_node
+/*!
+ *     @brief
+ *             Given a list_node_t structure, insert it in the end of the list.
+ *     @param list_ptr
+ *             The pointer to the list.
+ *     @param node_ptr
+ *             The list_node_t structure to be inserted.
+ */
+mDNSexport void
+list_append_node(list_t * const _Nonnull list_ptr, list_node_t * const _Nonnull node_ptr);
+
+/*!
+ *     @brief
+ *             Given a list_node_t structure, insert it in the start of the list.
+ *     @param list_ptr
+ *             The pointer to the list.
+ *     @param node_ptr
+ *             The list_node_t structure to be inserted.
+ */
+#pragma mark list_prepend_node
+mDNSexport void
+list_prepend_node(list_t * const _Nonnull list_ptr, list_node_t * const _Nonnull node_ptr);
+
+#pragma mark list_append_node_at_node
+/*!
+ *     @brief
+ *             Append the list_node_t after the given node in the list
+ *     @param original_node
+ *             The node to be appended after.
+ *     @param added_node
+ *             The node to append.
+ */
+mDNSexport void
+list_append_node_at_node(list_node_t * const _Nonnull original_node, list_node_t * const _Nonnull added_node);
+
+#pragma mark list_prepend_node_at_node
+/*!
+ *     @brief
+ *             Prepend the list_node_t before the given node in the list
+ *     @param original_node
+ *             The node to be appended after.
+ *     @param added_node
+ *             The node to prepend.
+ */
+mDNSexport void
+list_prepend_node_at_node(list_node_t * const _Nonnull original_node, list_node_t * const _Nonnull added_node);
+
+#pragma mark list_concatenate
+/*!
+ *     @brief
+ *             Concatenate two lists together.
+ *     @param dst_list
+ *             The list to concatenate with.
+ *     @param src_list
+ *             The list to concatenate with.
+ *     @discussion
+ *             The concatenation result is the dst_list.
+ */
+mDNSexport void
+list_concatenate(list_t * const _Nonnull dst_list, list_t * const _Nonnull src_list);
+
+#pragma mark list_sort_comparator_t
+/*!
+ *     @brief
+ *             The sort comparator for the list.
+ *     @param left
+ *             The list_node_t to be compared.
+ *     @param right
+ *             The list_node_t to be compared.
+ *     @discussion
+ *             If the comparator returns -1, 0 and 1, -1 means left is less than right, 0 means left is equal to right, 1 means left is greater than right
+ */
+typedef mDNSs8 (* _Nonnull list_sort_comparator_t)(const list_node_t * _Nonnull const left, const list_node_t * _Nonnull const right);
+
+#pragma mark list_sort
+/*!
+ *     @brief
+ *             Sort the node in the list according to the comparator.
+ *     @param list_ptr
+ *             The list to be sorted.
+ *     @param comparator
+ *             The list_sort_comparator_t that defines the sort order.
+ *     @discussion
+ *             If the comparator returns -1, 0 and 1, -1 means left is less than right, 0 means left is equal to right, 1 means left is greater than right
+ */
+mDNSexport void
+list_sort(list_t * const _Nonnull list_ptr, list_sort_comparator_t comparator);
+
+#pragma mark list_node_detach
+/*!
+ *     @brief
+ *             Detach the list_node_t from the list
+ *     @param node
+ *             The node to be detached
+ *     @discussion
+ *             It will only seperate the node from the list,  the node itself still exists.
+ */
+mDNSexport void
+list_node_detach(list_node_t * const _Nonnull node);
+
+#pragma mark list_node_delete
+/*!
+ *     @brief
+ *             Delete the current node that is in the list.
+ *     @param node_ptr
+ *             The pointer of the node to be deleted.
+ *     @discussion
+ *             This function must be used for the node that is in the list.
+ */
+mDNSexport void
+list_node_delete(list_node_t * const _Nonnull node_ptr);
+
+#pragma mark list_node_delete_all
+/*!
+ *     @brief
+ *             Delete all nodes in the list
+ *     @param list_ptr
+ *             The pointer to the list.
+ */
+mDNSexport void
+list_node_delete_all(list_t * const _Nonnull list_ptr);
+
+#pragma mark list_delete_node_with_data_ptr
+/*!
+ *     @brief
+ *             Delete the node in the list that contains the data.
+ *     @param list_ptr
+ *             The pointer to the list that will delete the node.
+ *     @param data
+ *             The pointer to the data that one node in list contains.
+ *     @return
+ *             return mStatus_NoError if the node is found and deleted from the list, return mStatus_NoSuchKey if the node does not exist.
+ *     @discussion
+ *             The function will scan through the list, compare the data field of node with the "data" parameter.
+ */
+mDNSexport mStatus
+list_delete_node_with_data_ptr(list_t * const _Nonnull list_ptr, void * const _Nonnull data);
+
+#pragma mark list_empty
+/*!
+ *     @brief
+ *             Test if the list is empty or not.
+ *     @param list_ptr
+ *             The list to test.
+ *     @return
+ *             Returns true if the lis is empty, returns false if the list has nodes inside.
+ */
+mDNSexport mDNSBool
+list_empty(const list_t * const _Nonnull list_ptr);
+
+#pragma mark list_count_node
+/*!
+ *     @brief
+ *             Get the number of nodes in the list.
+ *     @param list_ptr
+ *             The list to count node.
+ *     @return
+ *             The number of nodes.
+ */
+mDNSexport mDNSu32
+list_count_node(const list_t *const _Nonnull list_ptr);
+
+#pragma mark list_get_first
+/*!
+ *     @brief
+ *             Get the first node in the list.
+ *     @param list_ptr
+ *             The pointer to the list.
+ *     @return
+ *             The first list_node_t in the list, if the list is empty, NULL is returned.
+ */
+mDNSexport list_node_t * _Nullable
+list_get_first(const list_t * const _Nonnull list_ptr);
+
+#pragma mark list_get_last
+/*!
+ *     @brief
+ *             Get the last node in the list.
+ *     @param list_ptr
+ *             The pointer to the list.
+ *     @return
+ *             The last list_node_t in the list, if the list is empty, NULL is returned.
+ */
+mDNSexport list_node_t * _Nullable
+list_get_last(const list_t * const _Nonnull list_ptr);
+
+#pragma mark list_next
+/*!
+ *     @brief
+ *             Get the next node of the current node in the list.
+ *     @param node_ptr
+ *             The pointer to the current node.
+ *     @return
+ *             The next node.
+ */
+mDNSexport list_node_t * _Nonnull
+list_next(const list_node_t * const _Nonnull node_ptr);
+
+#pragma mark list_has_ended
+/*!
+ *     @brief
+ *             Test if the current node has reached the end of the list.
+ *     @param list_ptr
+ *             The pointer to the list.
+ *     @param node_ptr
+ *             The pointer to the node.
+ *     @return
+ *             Returns true if the node_ptr points to the end of the list, returns false if it has more nodes coming next.
+ */
+mDNSexport mDNSBool
+list_has_ended(const list_t * const _Nonnull list_ptr, const list_node_t * const _Nullable node_ptr);
+
+#pragma mark list_uninit
+/*!
+ *     @brief
+ *             Delete all the existing nodes in the list, and untialize the list structure.
+ *     @param list_ptr
+ *             The pointer to the list that will be uninitialized.
+ */
+mDNSexport void
+list_uninit(list_t * const _Nonnull list_ptr);
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#endif // LIST_H
index 25bb05211ddc9cebfbd0396156b31d0e5a78c534..57d87df3c4bad83e8c515e991dd5ebaa961200a0 100644 (file)
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define _FORTIFY_SOURCE 2
-
 #include <CoreFoundation/CoreFoundation.h>
 #include <sys/cdefs.h>
 #include <sys/socket.h>
index 4783ee5ecd7597668b6bde6718dff81d1ecaf6c1..fecd36bec49517faff90f41474882cedf516e12c 100644 (file)
@@ -295,7 +295,7 @@ int mDNSKeychainGetSecrets(CFArrayRef *result)
     
     CFPropertyListRef plist = NULL;
     CFDataRef bytes = NULL;
-    unsigned int numsecrets = 0;
+    uint64_t numsecrets = 0;
     size_t secretsCnt = 0;
     int error_code = kHelperErr_NotConnected;
     xpc_object_t reply_dict = NULL;
@@ -313,11 +313,11 @@ int mDNSKeychainGetSecrets(CFArrayRef *result)
     {
         numsecrets = xpc_dictionary_get_uint64(reply_dict, "keychain_num_secrets");
         sec = xpc_dictionary_get_data(reply_dict, "keychain_secrets", &secretsCnt);
-        error_code = xpc_dictionary_get_int64(reply_dict,   kHelperErrCode);
+        error_code = (int)xpc_dictionary_get_int64(reply_dict,   kHelperErrCode);
     }
  
     mDNSHELPER_DEBUG("mDNSKeychainGetSecrets: Using XPC IPC calling out to Helper: numsecrets is %u, secretsCnt is %u error_code is %d",
-                     numsecrets, (unsigned int)secretsCnt, error_code);
+                     (unsigned int)numsecrets, (unsigned int)secretsCnt, error_code);
      
     if (NULL == (bytes = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (void*)sec, secretsCnt, kCFAllocatorNull)))
     {
@@ -491,11 +491,11 @@ int mDNSRetrieveTCPInfo(int family, v6addr_t laddr, uint16_t lport, v6addr_t rad
     
     if (reply_dict != NULL)
     {
-        *seq = xpc_dictionary_get_uint64(reply_dict, "retreive_tcpinfo_seq");
-        *ack = xpc_dictionary_get_uint64(reply_dict, "retreive_tcpinfo_ack");
-        *win = xpc_dictionary_get_uint64(reply_dict, "retreive_tcpinfo_win");
+        *seq = (uint32_t)xpc_dictionary_get_uint64(reply_dict, "retreive_tcpinfo_seq");
+        *ack = (uint32_t)xpc_dictionary_get_uint64(reply_dict, "retreive_tcpinfo_ack");
+        *win = (uint16_t)xpc_dictionary_get_uint64(reply_dict, "retreive_tcpinfo_win");
         *intfid = (int32_t)xpc_dictionary_get_uint64(reply_dict, "retreive_tcpinfo_ifid");
-        error_code = xpc_dictionary_get_int64(reply_dict, kHelperErrCode);
+        error_code = (int)xpc_dictionary_get_int64(reply_dict, kHelperErrCode);
     }
     
     mDNSHELPER_DEBUG("mDNSRetrieveTCPInfo: Using XPC IPC calling out to Helper: seq is %d, ack is %d, win is %d, intfid is %d, error is %d",
index dae2cc4d788ffee057340fa94eedfe22fd07df94..5c21ffd27739d83b75c47109555bcaf5da4e216e 100644 (file)
@@ -52,6 +52,8 @@
 #include "helper.h"
 #include "helper-server.h"
 #include "P2PPacketFilter.h"
+#include "setup_assistant_helper.h"
+#include <stdatomic.h>
 
 #include <netinet/ip.h>
 #include <netinet/tcp.h>
@@ -68,6 +70,7 @@
 #endif
 
 // Embed the client stub code here, so we can access private functions like ConnectToServer, create_hdr, deliver_request
+#define XPC_AUTH_CONNECTION   0
 #include "../mDNSShared/dnssd_ipc.c"
 #include "../mDNSShared/dnssd_clientstub.c"
 
@@ -493,6 +496,23 @@ static void update_notification(void)
 {
 #ifndef NO_CFUSERNOTIFICATION
     os_log_debug(log_handle,"entry ucn=%s, uhn=%s, lcn=%s, lhn=%s", usercompname, userhostname, lastcompname, lasthostname);
+    buddy_state_t buddy_state = assistant_helper_get_buddy_state();
+    if (buddy_state != buddy_state_done)
+    {
+        static _Atomic uint32_t notify_count = 0;
+        os_log_info(log_handle, "update_notification: buddy is %{public}s so skipping notification (%u)", buddy_state_to_string(buddy_state), atomic_load(&notify_count));
+        if (buddy_state == buddy_state_in_process &&
+            atomic_load(&notify_count) == 0)
+        {
+            assistant_helper_notify_when_buddy_done(^{
+                os_log_info(log_handle, "update_notification: buddy done notification (%u)", atomic_load(&notify_count));
+                update_notification();
+                atomic_store(&notify_count, 0);
+            });
+        }
+        atomic_fetch_add(&notify_count, 1);
+        return;
+    }
     if (!CFS_OQ)
     {
         // Note: The "\xEF\xBB\xBF" byte sequence (U+FEFF) in the CFS_Format string is the UTF-8 encoding of the zero-width non-breaking space character.
index 3236966988d0a54bb0f6db426aea01a8bcfe76a9..b94371cba696be67bc3d34dfa0bdbe46bdc77007 100644 (file)
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4; c-file-style: "bsd"; c-basic-offset: 4; fill-column: 108; indent-tabs-mode: nil; -*-
  *
- * Copyright (c) 2002-2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "dns_sd_internal.h"
 #include "PlatformCommon.h"
 #include "uds_daemon.h"
-#include "CryptoSupport.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, ANALYTICS)
+#include "dnssd_analytics.h"
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+#include "mdns_trust.h"
+#include <os/feature_private.h>
+#endif
 
 #include <stdio.h>
 #include <stdarg.h>                 // For va_list support
 
 #include <SystemConfiguration/SCPrivate.h>
 
-#if TARGET_OS_IPHONE
+#if (TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST)
 #include <MobileWiFi/WiFiManagerClient.h> // For WiFiManagerClientRef etc, declarations.
 #include <dlfcn.h>
 #endif // TARGET_OS_IPHONE
 
-#if MDNSRESPONDER_SUPPORTS(APPLE, IGNORE_HOSTS_FILE)
-#include "system_utilities.h"               // For os_variant_has_internal_diagnostics().
-#endif
+#include "system_utilities.h"
 
 // Include definition of opaque_presence_indication for KEV_DL_NODE_PRESENCE handling logic.
 #include <Kernel/IOKit/apple80211/apple80211_var.h>
 #include <IOKit/platform/IOPlatformSupportPrivate.h>
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+#include "QuerierSupport.h"
+#endif
+
 #ifdef UNIT_TEST
 #include "unittest.h"
 #endif
@@ -382,7 +390,7 @@ mDNSexport void mDNSDynamicStoreSetConfig(int key, const char *subkey, CFPropert
     }
     if (subkey)
     {
-        int len    = strlen(subkey);
+        const mDNSu32 len = (mDNSu32)strlen(subkey);
         subkeyCopy = mDNSPlatformMemAllocate(len + 1);
         if (!subkeyCopy)
         {
@@ -473,6 +481,7 @@ mDNSexport NetworkInterfaceInfoOSX *IfindexToInterfaceInfoOSX(mDNSInterfaceID if
     return mDNSNULL;
 }
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 mDNSexport mdns_interface_monitor_t GetInterfaceMonitorForIndex(uint32_t ifIndex)
 {
     mDNS *const m = &mDNSStorage;
@@ -568,6 +577,7 @@ mDNSexport mdns_interface_monitor_t GetInterfaceMonitorForIndex(uint32_t ifIndex
 
     return monitor;
 }
+#endif
 
 mDNSexport mDNSInterfaceID mDNSPlatformInterfaceIDfromInterfaceIndex(mDNS *const m, mDNSu32 ifindex)
 {
@@ -618,6 +628,44 @@ mDNSexport mDNSu32 mDNSPlatformInterfaceIndexfromInterfaceID(mDNS *const m, mDNS
     return(0);
 }
 
+mDNSlocal mDNSBool GetInterfaceSupportsWakeOnLANPacket(mDNSInterfaceID id)
+{
+    NetworkInterfaceInfoOSX *info = IfindexToInterfaceInfoOSX(id);
+    if (info == NULL)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR, "GetInterfaceSupportsWakeOnLANPacket: Invalid interface id %p", id);
+        return mDNSfalse;
+    }
+    else
+    {
+        return (info->ift_family == IFRTYPE_FAMILY_ETHERNET) ? mDNStrue : mDNSfalse;
+    }
+}
+
+mDNSlocal uint32_t GetIFTFamily(const char * _Nonnull if_name)
+{
+    uint32_t ift_family = IFRTYPE_FAMILY_ANY;
+    int s = socket(AF_INET, SOCK_DGRAM, 0);
+    if (s == -1)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR, "GetIFTFamily: socket() failed: " PUB_S, strerror(errno));
+        return ift_family;
+    }
+    struct ifreq ifr;
+    memset(&ifr, 0, sizeof(ifr));
+    strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name));
+    if (ioctl(s, SIOCGIFTYPE, (caddr_t)&ifr) == -1)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "GetIFTFamily: SIOCGIFTYPE failed: " PUB_S, strerror(errno));
+    }
+    else
+    {
+        ift_family = ifr.ifr_type.ift_family;
+    }
+    close(s);
+    return ift_family;
+}
+
 #if COMPILER_LIKES_PRAGMA_MARK
 #pragma mark -
 #pragma mark - UDP & TCP send & receive
@@ -733,8 +781,9 @@ mDNSexport mStatus mDNSPlatformSendUDP(const mDNS *const m, const void *const ms
 {
     NetworkInterfaceInfoOSX *info = mDNSNULL;
     struct sockaddr_storage to;
-    int s = -1, err;
+    int s = -1;
     mStatus result = mStatus_NoError;
+    ssize_t sentlen;
     int sendto_errno;
     const DNSMessage *const dns_msg = msg;
 
@@ -781,6 +830,7 @@ mDNSexport mStatus mDNSPlatformSendUDP(const mDNS *const m, const void *const ms
         }
         else if (info)
         {
+            int err;
         #ifdef IP_MULTICAST_IFINDEX
             err = setsockopt(s, IPPROTO_IP, IP_MULTICAST_IFINDEX, &info->scope_id, sizeof(info->scope_id));
             // We get an error when we compile on a machine that supports this option and run the binary on
@@ -824,7 +874,7 @@ mDNSexport mStatus mDNSPlatformSendUDP(const mDNS *const m, const void *const ms
         s = (src ? src->ss : m->p->permanentsockets).sktv6;
         if (info && mDNSAddrIsDNSMulticast(dst))    // Specify outgoing interface
         {
-            err = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &info->scope_id, sizeof(info->scope_id));
+            const int err = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &info->scope_id, sizeof(info->scope_id));
             if (err < 0)
             {
                 const int setsockopt_errno = errno;
@@ -888,19 +938,19 @@ mDNSexport mStatus mDNSPlatformSendUDP(const mDNS *const m, const void *const ms
     if (useBackgroundTrafficClass)
         setTrafficClass(s, useBackgroundTrafficClass);
 
-    err = sendto(s, msg, (UInt8*)end - (UInt8*)msg, 0, (struct sockaddr *)&to, to.ss_len);
-    sendto_errno = (err < 0) ? errno : 0;
+    sentlen = sendto(s, msg, (UInt8*)end - (UInt8*)msg, 0, (struct sockaddr *)&to, to.ss_len);
+    sendto_errno = (sentlen < 0) ? errno : 0;
 
     // set traffic class back to default value
     if (useBackgroundTrafficClass)
         setTrafficClass(s, mDNSfalse);
 
-    if (err < 0)
+    if (sentlen < 0)
     {
         static int MessageCount = 0;
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
-                  "[Q%u] mDNSPlatformSendUDP -> sendto(%d) failed to send packet on InterfaceID %p " PUB_S "/%d to " PRI_IP_ADDR ":%d skt %d error %d errno %d (" PUB_S ") %u",
-                  mDNSVal16(dns_msg->h.id), s, InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s, err, sendto_errno, strerror(sendto_errno), (mDNSu32)(m->timenow));
+            "[Q%u] mDNSPlatformSendUDP -> sendto(%d) failed to send packet on InterfaceID %p " PUB_S "/%d to " PRI_IP_ADDR ":%d skt %d error %ld errno %d (" PUB_S ") %u",
+            mDNSVal16(dns_msg->h.id), s, InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s, (long)sentlen, sendto_errno, strerror(sendto_errno), (mDNSu32)(m->timenow));
         if (!mDNSAddressIsAllDNSLinkGroup(dst))
         {
             if ((sendto_errno == EHOSTUNREACH) || (sendto_errno == ENETUNREACH)) return(mStatus_HostUnreachErr);
@@ -915,8 +965,8 @@ mDNSexport mStatus mDNSPlatformSendUDP(const mDNS *const m, const void *const ms
         if (sendto_errno == EHOSTUNREACH || sendto_errno == EADDRNOTAVAIL || sendto_errno == ENETDOWN)
         {
             LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
-                      "[Q%u] mDNSPlatformSendUDP sendto(%d) failed to send packet on InterfaceID %p " PUB_S "/%d to " PRI_IP_ADDR ":%d skt %d error %d errno %d (" PUB_S ") %u",
-                      mDNSVal16(dns_msg->h.id), s, InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s, err, sendto_errno, strerror(sendto_errno), (mDNSu32)(m->timenow));
+                "[Q%u] mDNSPlatformSendUDP sendto(%d) failed to send packet on InterfaceID %p " PUB_S "/%d to " PRI_IP_ADDR ":%d skt %d error %ld errno %d (" PUB_S ") %u",
+                mDNSVal16(dns_msg->h.id), s, InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s, (long)sentlen, sendto_errno, strerror(sendto_errno), (mDNSu32)(m->timenow));
         }
         else
         {
@@ -924,14 +974,14 @@ mDNSexport mStatus mDNSPlatformSendUDP(const mDNS *const m, const void *const ms
             if (MessageCount < 50) // Cap and ensure NO spamming of LogMsgs
             {
                 LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
-                          "[Q%u] mDNSPlatformSendUDP: sendto(%d) failed to send packet on InterfaceID %p " PUB_S "/%d to " PRI_IP_ADDR ":%d skt %d error %d errno %d (" PUB_S ") %u MessageCount is %d",
-                          mDNSVal16(dns_msg->h.id), s, InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s, err, sendto_errno, strerror(sendto_errno), (mDNSu32)(m->timenow), MessageCount);
+                    "[Q%u] mDNSPlatformSendUDP: sendto(%d) failed to send packet on InterfaceID %p " PUB_S "/%d to " PRI_IP_ADDR ":%d skt %d error %ld errno %d (" PUB_S ") %u MessageCount is %d",
+                    mDNSVal16(dns_msg->h.id), s, InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s, (long)sentlen, sendto_errno, strerror(sendto_errno), (mDNSu32)(m->timenow), MessageCount);
             }
             else // If logging is enabled, remove the cap and log aggressively
             {
                 LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-                          "[Q%u] mDNSPlatformSendUDP: sendto(%d) failed to send packet on InterfaceID %p " PUB_S "/%d to " PRI_IP_ADDR ":%d skt %d error %d errno %d (" PUB_S ") %u MessageCount is %d",
-                          mDNSVal16(dns_msg->h.id), s, InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s, err, sendto_errno, strerror(sendto_errno), (mDNSu32)(m->timenow), MessageCount);
+                    "[Q%u] mDNSPlatformSendUDP: sendto(%d) failed to send packet on InterfaceID %p " PUB_S "/%d to " PRI_IP_ADDR ":%d skt %d error %ld errno %d (" PUB_S ") %u MessageCount is %d",
+                    mDNSVal16(dns_msg->h.id), s, InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s, (long)sentlen, sendto_errno, strerror(sendto_errno), (mDNSu32)(m->timenow), MessageCount);
             }
         }
 
@@ -941,8 +991,8 @@ mDNSexport mStatus mDNSPlatformSendUDP(const mDNS *const m, const void *const ms
     return(result);
 }
 
-mDNSexport ssize_t myrecvfrom(const int s, void *const buffer, const size_t max,
-                             struct sockaddr *const from, size_t *const fromlen, mDNSAddr *dstaddr, char ifname[IF_NAMESIZE], mDNSu8 *ttl)
+mDNSlocal ssize_t myrecvfrom(const int s, void *const buffer, const size_t max,
+                             struct sockaddr *const from, socklen_t *const fromlen, mDNSAddr *dstaddr, char ifname[IF_NAMESIZE], mDNSu8 *ttl)
 {
     static unsigned int numLogMessages = 0;
     struct iovec databuffers = { (char *)buffer, max };
@@ -975,6 +1025,8 @@ mDNSexport ssize_t myrecvfrom(const int s, void *const buffer, const size_t max,
                                            s, n, msg.msg_controllen, sizeof(struct cmsghdr), errno);
         return(-1);
     }
+    // Note: MSG_TRUNC means the datagram was truncated, while MSG_CTRUNC means that the control data was truncated.
+    // The mDNS core is capable of handling truncated DNS messages, so MSG_TRUNC isn't checked.
     if (msg.msg_flags & MSG_CTRUNC)
     {
         if (numLogMessages++ < 100) LogMsg("mDNSMacOSX.c: recvmsg(%d) msg.msg_flags & MSG_CTRUNC", s);
@@ -1061,7 +1113,8 @@ mDNSexport void myKQSocketCallBack(int s1, short filter, void *context, mDNSBool
 {
     KQSocketSet *const ss = (KQSocketSet *)context;
     mDNS *const m = ss->m;
-    int err = 0, count = 0, closed = 0, recvfrom_errno = 0;
+    ssize_t recvlen;
+    int count = 0, closed = 0, recvfrom_errno = 0;
 
     if (filter != EVFILT_READ)
         LogMsg("myKQSocketCallBack: Why is filter %d not EVFILT_READ (%d)?", filter, EVFILT_READ);
@@ -1093,11 +1146,11 @@ mDNSexport void myKQSocketCallBack(int s1, short filter, void *context, mDNSBool
         mDNSAddr senderAddr, destAddr = zeroAddr;
         mDNSIPPort senderPort;
         struct sockaddr_storage from;
-        size_t fromlen = sizeof(from);
+        socklen_t fromlen = sizeof(from);
         char packetifname[IF_NAMESIZE] = "";
         mDNSu8 ttl;
-        err = myrecvfrom(s1, &m->imsg, sizeof(m->imsg), (struct sockaddr *)&from, &fromlen, &destAddr, packetifname, &ttl);
-        if (err < 0)
+        recvlen = myrecvfrom(s1, &m->imsg, sizeof(m->imsg), (struct sockaddr *)&from, &fromlen, &destAddr, packetifname, &ttl);
+        if (recvlen < 0)
         {
             recvfrom_errno = errno;
             break;
@@ -1165,12 +1218,12 @@ mDNSexport void myKQSocketCallBack(int s1, short filter, void *context, mDNSBool
 
         if (ss->proxy)
         {
-            m->p->UDPProxyCallback(&m->p->UDPProxy, &m->imsg.m, (unsigned char*)&m->imsg + err, &senderAddr,
+            m->p->UDPProxyCallback(&m->p->UDPProxy, &m->imsg.m, (unsigned char*)&m->imsg + recvlen, &senderAddr,
                 senderPort, &destAddr, ss->port, InterfaceID, NULL);
         }
         else
         {
-            mDNSCoreReceive(m, &m->imsg.m, (unsigned char*)&m->imsg + err, &senderAddr, senderPort, &destAddr, ss->port, InterfaceID);
+            mDNSCoreReceive(m, &m->imsg.m, (unsigned char*)&m->imsg + recvlen, &senderAddr, senderPort, &destAddr, ss->port, InterfaceID);
         }
 
         // if we didn't close, we can safely dereference the socketset, and should to
@@ -1181,14 +1234,14 @@ mDNSexport void myKQSocketCallBack(int s1, short filter, void *context, mDNSBool
     // If a client application's sockets are marked as defunct
     // sockets we have delegated to it with SO_DELEGATED will also go defunct.
     // We get an ENOTCONN error for defunct sockets and should just close the socket in that case.
-    if (err < 0 && recvfrom_errno == ENOTCONN)
+    if (recvlen < 0 && recvfrom_errno == ENOTCONN)
     {
         LogInfo("myKQSocketCallBack: ENOTCONN, closing socket");
         close(s1);
         return;
     }
 
-    if (err < 0 && (recvfrom_errno != EWOULDBLOCK || count == 0))
+    if (recvlen < 0 && (recvfrom_errno != EWOULDBLOCK || count == 0))
     {
         // Something is busted here.
         // kqueue says there is a packet, but myrecvfrom says there is not.
@@ -1218,7 +1271,7 @@ mDNSexport void myKQSocketCallBack(int s1, short filter, void *context, mDNSBool
             LogMsg("myKQSocketCallBack ioctl(FIONREAD) error %d", errno);
         if (numLogMessages++ < 100)
             LogMsg("myKQSocketCallBack recvfrom skt %d error %d errno %d (%s) select %d (%spackets waiting) so_error %d so_nread %d fionread %d count %d",
-                   s1, err, recvfrom_errno, strerror(recvfrom_errno), selectresult, FD_ISSET(s1, &readfds) ? "" : "*NO* ", so_error, so_nread, fionread, count);
+                   s1, (int)recvlen, recvfrom_errno, strerror(recvfrom_errno), selectresult, FD_ISSET(s1, &readfds) ? "" : "*NO* ", so_error, so_nread, fionread, count);
         if (numLogMessages > 5)
             NotifyOfElusiveBug("Flaw in Kernel (select/recvfrom mismatch)",
                                "Congratulations, you've reproduced an elusive bug.\r"
@@ -1244,9 +1297,9 @@ mDNSlocal void doTcpSocketCallback(TCPSocket *sock)
 
 mDNSlocal OSStatus tlsWriteSock(SSLConnectionRef connection, const void *data, size_t *dataLength)
 {
-    int ret = send(((TCPSocket *)connection)->fd, data, *dataLength, 0);
-    if (ret >= 0 && (size_t)ret < *dataLength) { *dataLength = ret; return(errSSLWouldBlock); }
-    if (ret >= 0)                              { *dataLength = ret; return(noErr); }
+    const ssize_t ret = send(((TCPSocket *)connection)->fd, data, *dataLength, 0);
+    if (ret >= 0 && (size_t)ret < *dataLength) { *dataLength = (size_t)ret; return(errSSLWouldBlock); }
+    if (ret >= 0)                              { *dataLength = (size_t)ret; return(noErr); }
     *dataLength = 0;
     if (errno == EAGAIN                      ) return(errSSLWouldBlock);
     if (errno == ENOENT                      ) return(errSSLClosedGraceful);
@@ -1257,9 +1310,9 @@ mDNSlocal OSStatus tlsWriteSock(SSLConnectionRef connection, const void *data, s
 
 mDNSlocal OSStatus tlsReadSock(SSLConnectionRef connection, void *data, size_t *dataLength)
 {
-    int ret = recv(((TCPSocket *)connection)->fd, data, *dataLength, 0);
-    if (ret > 0 && (size_t)ret < *dataLength) { *dataLength = ret; return(errSSLWouldBlock); }
-    if (ret > 0)                              { *dataLength = ret; return(noErr); }
+    const ssize_t ret = recv(((TCPSocket *)connection)->fd, data, *dataLength, 0);
+    if (ret > 0 && (size_t)ret < *dataLength) { *dataLength = (size_t)ret; return(errSSLWouldBlock); }
+    if (ret > 0)                              { *dataLength = (size_t)ret; return(noErr); }
     *dataLength = 0;
     if (ret == 0 || errno == ENOENT    ) return(errSSLClosedGraceful);
     if (            errno == EAGAIN    ) return(errSSLWouldBlock);
@@ -1272,6 +1325,8 @@ mDNSlocal OSStatus tlsSetupSock(TCPSocket *sock, SSLProtocolSide pside, SSLConne
 {
     char domname_cstr[MAX_ESCAPED_DOMAIN_NAME];
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     sock->tlsContext = SSLCreateContext(kCFAllocatorDefault, pside, ctype);
     if (!sock->tlsContext) 
     { 
@@ -1317,7 +1372,7 @@ mDNSlocal OSStatus tlsSetupSock(TCPSocket *sock, SSLProtocolSide pside, SSLConne
         LogMsg("ERROR: tlsSetupSock: SSLSetPeerDomainname: %s failed with error code: %d", domname_cstr, err); 
         goto fail; 
     }
-
+#pragma clang diagnostic pop
     return(err);
 
 fail:
@@ -1385,8 +1440,10 @@ mDNSlocal void *doSSLHandshake(TCPSocket *sock)
 {
     // Warning: Touching sock without the kqueue lock!
     // We're protected because sock->handshake == handshake_in_progress
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
     mStatus err = SSLHandshake(sock->tlsContext);
-
+#pragma clang diagnostic pop
     KQueueLock();
     debugf("doSSLHandshake %p: got lock", sock); // Log *after* we get the lock
 
@@ -1509,13 +1566,16 @@ mDNSlocal void tcpKQSocketCallback(__unused int fd, short filter, void *context,
         if (sock->err == mStatus_NoError) {
             if (!sock->setup) 
             {
-                sock->setup = mDNStrue; 
+                sock->setup = mDNStrue;
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
                 sock->err = tlsSetupSock(sock, kSSLClientSide, kSSLStreamType);
                 if (sock->err)
                 {
                     LogMsg("ERROR: tcpKQSocketCallback: tlsSetupSock failed with error code: %d", sock->err);
                     return;
                 }
+#pragma clang diagnostic pop
             }
             if (sock->handshake == handshake_required) 
             { 
@@ -1896,6 +1956,9 @@ mDNSexport TCPSocket *mDNSPlatformTCPAccept(TCPSocketFlags flags, int fd)
     if (flags & kTCPSocketFlags_UseTLS)
     {
 #ifndef NO_SECURITYFRAMEWORK
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
         if (!ServerCerts) { LogMsg("ERROR: mDNSPlatformTCPAccept: unable to find TLS certificates"); err = mStatus_UnknownErr; goto exit; }
 
         err = tlsSetupSock(sock, kSSLServerSide, kSSLStreamType);
@@ -1903,6 +1966,7 @@ mDNSexport TCPSocket *mDNSPlatformTCPAccept(TCPSocketFlags flags, int fd)
 
         err = SSLSetCertificate(sock->tlsContext, ServerCerts);
         if (err) { LogMsg("ERROR: mDNSPlatformTCPAccept: SSLSetCertificate failed with error code: %d", err); goto exit; }
+#pragma clang diagnostic pop
 #else
         err = mStatus_UnsupportedErr;
 #endif /* NO_SECURITYFRAMEWORK */
@@ -2020,11 +2084,13 @@ mDNSexport void mDNSPlatformTCPCloseConnection(TCPSocket *sock)
                 sock->handshake = handshake_to_be_closed;
                 return;
             }
-
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
             SSLClose(sock->tlsContext);
             CFRelease(sock->tlsContext);
             sock->tlsContext = NULL;
         }
+#pragma clang diagnostic pop
 #endif /* NO_SECURITYFRAMEWORK */
         if (sock->fd != -1) {
             shutdown(sock->fd, 2);
@@ -2055,7 +2121,10 @@ mDNSexport long mDNSPlatformReadTCP(TCPSocket *sock, void *buf, unsigned long bu
         else if (sock->handshake != handshake_completed) LogMsg("mDNSPlatformReadTCP called with unexpected SSLHandshake status: %d", sock->handshake);
 
         //LogMsg("Starting SSLRead %d %X", sock->fd, fcntl(sock->fd, F_GETFL, 0));
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
         mStatus err = SSLRead(sock->tlsContext, buf, buflen, (size_t *)&nread);
+#pragma clang diagnostic pop
         //LogMsg("SSLRead returned %d (%d) nread %d buflen %d", err, errSSLWouldBlock, nread, buflen);
         if (err == errSSLClosedGraceful) { nread = 0; *closed = mDNStrue; }
         else if (err && err != errSSLWouldBlock)
@@ -2075,7 +2144,7 @@ mDNSexport long mDNSPlatformReadTCP(TCPSocket *sock, void *buf, unsigned long bu
 
 mDNSexport long mDNSPlatformWriteTCP(TCPSocket *sock, const char *msg, unsigned long len)
 {
-    int nsent;
+    long nsent;
 
     if (!sock->connected) {
         return mStatus_DefunctConnection;
@@ -2089,9 +2158,12 @@ mDNSexport long mDNSPlatformWriteTCP(TCPSocket *sock, const char *msg, unsigned
         if (sock->handshake == handshake_in_progress) return 0;
         else if (sock->handshake != handshake_completed) LogMsg("mDNSPlatformWriteTCP called with unexpected SSLHandshake status: %d", sock->handshake);
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
         mStatus err = SSLWrite(sock->tlsContext, msg, len, &processed);
+#pragma clang diagnostic pop
 
-        if (!err) nsent = (int) processed;
+        if (!err) nsent = (long)processed;
         else if (err == errSSLWouldBlock) nsent = 0;
         else { LogMsg("ERROR: mDNSPlatformWriteTCP - SSLWrite returned %d", err); nsent = -1; }
 #else
@@ -2544,7 +2616,8 @@ mDNSlocal int getMACAddress(int family, v6addr_t raddr, v6addr_t gaddr, int *gfa
     
     struct rt_msghdr *rtm = &(m_rtmsg.m_rtm);
     char  *cp  = m_rtmsg.m_space;
-    int    seq = 6367, sock, rlen, i;
+    int    seq = 6367, sock, i;
+    ssize_t rlen;
     struct sockaddr_in      *sin  = NULL;
     struct sockaddr_in6     *sin6 = NULL;
     struct sockaddr_dl      *sdl  = NULL;
@@ -2595,7 +2668,8 @@ cp += len;                  \
         NEXTADDR(RTA_DST, sin6, sin6->sin6_len);
     }
     NEXTADDR(RTA_GATEWAY, &sdl_m, sdl_m.sdl_len);
-    rtm->rtm_msglen = rlen = cp - (char *)&m_rtmsg;
+    rtm->rtm_msglen = (u_short)(cp - (char *)&m_rtmsg);
+    rlen = rtm->rtm_msglen;
     
     if (write(sock, (char *)&m_rtmsg, rlen) < 0)
     {
@@ -3590,10 +3664,10 @@ mDNSlocal mDNSBool isExcludedInterface(int sockFD, char * ifa_name)
 {
     struct ifreq ifr;
 
-    // llw0 interface is excluded from Bonjour discover.
-    // There currently is no interface attributed based way to identify this interface
+    // llw0 and nan0 interfaces are excluded from Bonjour discover.
+    // There currently is no interface attributed based way to identify these interfaces
     // until rdar://problem/47933782 is addressed.
-    if (strncmp(ifa_name, "llw", 3) == 0)
+    if ((strncmp(ifa_name, "llw", 3) == 0) || (strncmp(ifa_name, "nan", 3) == 0))
     {
         LogMsg("isExcludedInterface: excluding %s", ifa_name);
         return mDNStrue;
@@ -3624,7 +3698,7 @@ mDNSlocal mDNSBool isExcludedInterface(int sockFD, char * ifa_name)
         return mDNSfalse;
 }
 
-#if TARGET_OS_IPHONE
+#if (TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST)
 
 // Function pointers for the routines we use in the MobileWiFi framework.
 static WiFiManagerClientRef (*WiFiManagerClientCreate_p)(CFAllocatorRef allocator, WiFiClientType type) = mDNSNULL;
@@ -3790,7 +3864,7 @@ mDNSlocal mDNSBool IsCarPlaySSID(char *ifa_name)
 // may return NULL if out of memory (unlikely) or parameters are invalid for some reason
 // (e.g. sa_family not AF_INET or AF_INET6)
 
-mDNSlocal NetworkInterfaceInfoOSX *AddInterfaceToList(struct ifaddrs *ifa, mDNSs32 utc)
+mDNSlocal NetworkInterfaceInfoOSX *AddInterfaceToList(const struct ifaddrs *ifa, const mDNSs32 utc)
 {
     mDNS *const m = &mDNSStorage;
     mDNSu32 scope_id  = if_nametoindex(ifa->ifa_name);
@@ -3803,16 +3877,24 @@ mDNSlocal NetworkInterfaceInfoOSX *AddInterfaceToList(struct ifaddrs *ifa, mDNSs
 
     NetworkInterfaceInfoOSX **p;
     for (p = &m->p->InterfaceList; *p; p = &(*p)->next)
+    {
         if (scope_id == (*p)->scope_id &&
             mDNSSameAddress(&ip, &(*p)->ifinfo.ip) &&
             mDNSSameEthAddress(&bssid, &(*p)->BSSID))
         {
             debugf("AddInterfaceToList: Found existing interface %lu %.6a with address %#a at %p, ifname before %s, after %s", scope_id, &bssid, &ip, *p, (*p)->ifinfo.ifname, ifa->ifa_name);
+            if ((*p)->Exists)
+            {
+                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+                    "Ignoring attempt to re-add interface (" PUB_S ", " PRI_IP_ADDR ") already marked as existing",
+                    ifa->ifa_name, &ip);
+                return(*p);
+            }
             // The name should be updated to the new name so that we don't report a wrong name in our SIGINFO output.
             // When interfaces are created with same MAC address, kernel resurrects the old interface.
             // Even though the interface index is the same (which should be sufficient), when we receive a UDP packet
             // we get the corresponding name for the interface index on which the packet was received and check against
-            // the InterfaceList for a matching name. So, keep the name in sync
+            // the InterfaceList for a matching name. So, keep the name in sync.
             strlcpy((*p)->ifinfo.ifname, ifa->ifa_name, sizeof((*p)->ifinfo.ifname));
 
             // Determine if multicast state has changed.
@@ -3851,7 +3933,7 @@ mDNSlocal NetworkInterfaceInfoOSX *AddInterfaceToList(struct ifaddrs *ifa, mDNSs
 
             return(*p);
         }
-
+    }
     NetworkInterfaceInfoOSX *i = (NetworkInterfaceInfoOSX *) callocL("NetworkInterfaceInfoOSX", sizeof(*i));
     debugf("AddInterfaceToList: Making   new   interface %lu %.6a with address %#a at %p", scope_id, &bssid, &ip, i);
     if (!i) return(mDNSNULL);
@@ -3895,8 +3977,6 @@ mDNSlocal NetworkInterfaceInfoOSX *AddInterfaceToList(struct ifaddrs *ifa, mDNSs
         // limited AWDL resources so we don't set the kDNSQClass_UnicastResponse bit in
         // Bonjour requests over the AWDL interface.
         i->ifinfo.SupportsUnicastMDNSResponse = mDNSfalse;
-        AWDLInterfaceID = i->ifinfo.InterfaceID;
-        LogInfo("AddInterfaceToList: AWDLInterfaceID = %d", (int) AWDLInterfaceID);
     }
     else
     {
@@ -3920,6 +4000,7 @@ mDNSlocal NetworkInterfaceInfoOSX *AddInterfaceToList(struct ifaddrs *ifa, mDNSs
     GetMAC(&i->ifinfo.MAC, scope_id);
     if (i->ifinfo.NetWake && !i->ifinfo.MAC.l[0])
         LogMsg("AddInterfaceToList: Bad MAC address %.6a for %d %s %#a", &i->ifinfo.MAC, scope_id, i->ifinfo.ifname, &ip);
+    i->ift_family = GetIFTFamily(i->ifinfo.ifname);
 
     *p = i;
     return(i);
@@ -4004,7 +4085,7 @@ mDNSlocal mStatus UpdateInterfaceList(mDNSs32 utc)
 
     if (m->SleepState == SleepState_Sleeping) ifa = NULL;
 
-    while (ifa)
+    for (; ifa; ifa = ifa->ifa_next)
     {
 #if LIST_ALL_INTERFACES
         if (ifa->ifa_addr)
@@ -4041,14 +4122,32 @@ mDNSlocal mStatus UpdateInterfaceList(mDNSs32 utc)
                    ifa->ifa_addr ? ifa->ifa_addr->sa_family : 0);
 #endif
 
-        if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_LINK)
+        if (!ifa->ifa_addr || isExcludedInterface(InfoSocket, ifa->ifa_name))
         {
-            struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+            continue;
+        }
+
+        if (ifa->ifa_addr->sa_family == AF_LINK)
+        {
+            const struct sockaddr_dl *const sdl = (const struct sockaddr_dl *)ifa->ifa_addr;
             if (sdl->sdl_type == IFT_ETHER && sdl->sdl_alen == sizeof(m->PrimaryMAC) && mDNSSameEthAddress(&m->PrimaryMAC, &zeroEthAddr))
+            {
                 mDNSPlatformMemCopy(m->PrimaryMAC.b, sdl->sdl_data + sdl->sdl_nlen, 6);
+            }
+            if (!AWDLInterfaceID && (sdl->sdl_index > 0))
+            {
+                const uint64_t eflags = getExtendedFlags(ifa->ifa_name);
+                if (eflags & IFEF_AWDL)
+                {
+                    AWDLInterfaceID = (mDNSInterfaceID)((uintptr_t)sdl->sdl_index);
+                    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+                        "UpdateInterfaceList: AWDLInterfaceID = %lu", (unsigned long) AWDLInterfaceID);
+                }
+            }
         }
 
-        if (ifa->ifa_flags & IFF_UP && ifa->ifa_addr && !isExcludedInterface(InfoSocket, ifa->ifa_name))
+        if (ifa->ifa_flags & IFF_UP)
+        {
             if (ifa->ifa_addr->sa_family == AF_INET || ifa->ifa_addr->sa_family == AF_INET6)
             {
                 if (!ifa->ifa_netmask)
@@ -4097,7 +4196,7 @@ mDNSlocal mStatus UpdateInterfaceList(mDNSs32 utc)
                     }
                 }
             }
-        ifa = ifa->ifa_next;
+        }
     }
 
     if (InfoSocket >= 0) 
@@ -4166,6 +4265,61 @@ mDNSlocal int CountMaskBits(mDNSAddr *mask)
     return(bits);
 }
 
+mDNSlocal void mDNSGroupJoinOrLeave(const int sock, const NetworkInterfaceInfoOSX *const i, const mDNSBool join)
+{
+    int level;
+    struct group_req gr;
+    mDNSPlatformMemZero(&gr, sizeof(gr));
+    gr.gr_interface = i->scope_id;
+    switch (i->sa_family)
+    {
+        case AF_INET: {
+            struct sockaddr_in *const sin = (struct sockaddr_in *)&gr.gr_group;
+            sin->sin_len         = sizeof(*sin);
+            sin->sin_family      = AF_INET;
+            sin->sin_addr.s_addr = AllDNSLinkGroup_v4.ip.v4.NotAnInteger;
+            level = IPPROTO_IP;
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, PUB_S "ing mcast group " PUB_IPv4_ADDR " on " PUB_S " (%u)",
+                join ? "Join" : "Leav", &sin->sin_addr.s_addr, i->ifinfo.ifname, i->scope_id);
+            break;
+        }
+        case AF_INET6: {
+            struct sockaddr_in6 *const sin6 = (struct sockaddr_in6 *)&gr.gr_group;
+            sin6->sin6_len    = sizeof(*sin6);
+            sin6->sin6_family = AF_INET6;
+            memcpy(sin6->sin6_addr.s6_addr, AllDNSLinkGroup_v6.ip.v6.b, sizeof(sin6->sin6_addr.s6_addr));
+            level = IPPROTO_IPV6;
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, PUB_S "ing mcast group " PUB_IPv6_ADDR " on " PUB_S " (%u)",
+                join ? "Join" : "Leav", sin6->sin6_addr.s6_addr, i->ifinfo.ifname, i->scope_id);
+            break;
+        }
+        default:
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+                "Cannot " PUB_S " mcast group on " PUB_S " (%u) for unrecognized address family %d",
+                join ? "join" : "leave", i->ifinfo.ifname, i->scope_id, i->sa_family);
+            goto exit;
+    }
+    const int err = setsockopt(sock, level, join ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP, &gr, sizeof(gr));
+    if (err)
+    {
+        // When joining a group, ignore EADDRINUSE errors, which can ocur when the same group is joined twice.
+        // When leaving a group, ignore EADDRNOTAVAIL errors, which can occur when an interface is no longer present.
+        const int opterrno = errno;
+        if ((join && (opterrno != EADDRINUSE)) || (!join && (opterrno != EADDRNOTAVAIL)))
+        {
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+                "setsockopt - IPPROTO_IP" PUB_S "/MCAST_" PUB_S "_GROUP error %d errno %d (%s) on " PUB_S " (%u)",
+                (level == IPPROTO_IPV6) ? "V6" : "", join ? "JOIN" : "LEAVE", err, opterrno, strerror(opterrno),
+                i->ifinfo.ifname, i->scope_id);
+        }
+    }
+
+exit:
+    return;
+}
+#define mDNSGroupJoin(SOCK, INTERFACE)  mDNSGroupJoinOrLeave(SOCK, INTERFACE, mDNStrue)
+#define mDNSGroupLeave(SOCK, INTERFACE) mDNSGroupJoinOrLeave(SOCK, INTERFACE, mDNSfalse)
+
 // Returns count of non-link local V4 addresses registered (why? -- SC)
 mDNSlocal int SetupActiveInterfaces(mDNSs32 utc)
 {
@@ -4240,89 +4394,36 @@ mDNSlocal int SetupActiveInterfaces(mDNSs32 utc)
 #if TARGET_OS_IPHONE
                     // We join the Bonjour multicast group on Apple embedded platforms ONLY when a client request is active,
                     // so we leave the multicast group here to clear any residual group membership.
-                    if (i->sa_family == AF_INET)
+                    if ((i->sa_family == AF_INET) || (i->sa_family == AF_INET6))
                     {
-                        struct ip_mreq imr;
-                        primary->ifa_v4addr.s_addr = n->ip.ip.v4.NotAnInteger;
-                        imr.imr_multiaddr.s_addr = AllDNSLinkGroup_v4.ip.v4.NotAnInteger;
-                        imr.imr_interface        = primary->ifa_v4addr;
-
-                        if (SearchForInterfaceByName(i->ifinfo.ifname, AF_INET) == i)
+                        const int sock = (i->sa_family == AF_INET) ? m->p->permanentsockets.sktv4 : m->p->permanentsockets.sktv6;
+                        if (SearchForInterfaceByName(i->ifinfo.ifname, i->sa_family) == i)
                         {
-                            LogInfo("SetupActiveInterfaces: %5s(%lu) Doing IP_DROP_MEMBERSHIP for %.4a on %.4a", i->ifinfo.ifname, i->scope_id, &imr.imr_multiaddr, &imr.imr_interface);
-                            mStatus err = setsockopt(m->p->permanentsockets.sktv4, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr, sizeof(imr));
-                            if (err < 0 && (errno != EADDRNOTAVAIL))
-                                LogMsg("setsockopt - IP_DROP_MEMBERSHIP error %d errno %d (%s)", err, errno, strerror(errno));
+                            mDNSGroupLeave(sock, i);
                         }
                     }
-                    if (i->sa_family == AF_INET6)
-                    {
-                        struct ipv6_mreq i6mr;
-                        i6mr.ipv6mr_interface = primary->scope_id;
-                        i6mr.ipv6mr_multiaddr = *(struct in6_addr*)&AllDNSLinkGroup_v6.ip.v6;
-
-                        if (SearchForInterfaceByName(i->ifinfo.ifname, AF_INET6) == i)
-                        {
-                            LogInfo("SetupActiveInterfaces: %5s(%lu) Doing IPV6_LEAVE_GROUP for %.16a on %u", i->ifinfo.ifname, i->scope_id, &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface);
-                            mStatus err = setsockopt(m->p->permanentsockets.sktv6, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &i6mr, sizeof(i6mr));
-                            if (err < 0 && (errno != EADDRNOTAVAIL))
-                                LogMsg("setsockopt - IPV6_LEAVE_GROUP error %d errno %d (%s) group %.16a on %u", err, errno, strerror(errno), &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface);
-                        }
-                    }
-#endif // TARGET_OS_IPHONE
+#endif
                 }
                 else
                 {
-                    if (i->sa_family == AF_INET)
+                    if ((i->sa_family == AF_INET) || (i->sa_family == AF_INET6))
                     {
-                        struct ip_mreq imr;
-                        primary->ifa_v4addr.s_addr = n->ip.ip.v4.NotAnInteger;
-                        imr.imr_multiaddr.s_addr = AllDNSLinkGroup_v4.ip.v4.NotAnInteger;
-                        imr.imr_interface        = primary->ifa_v4addr;
-
-                        // If this is our *first* IPv4 instance for this interface name, we need to do a IP_DROP_MEMBERSHIP first,
+                        // If this is our *first* address family instance for this interface name, we need to do a leave first,
                         // before trying to join the group, to clear out stale kernel state which may be lingering.
                         // In particular, this happens with removable network interfaces like USB Ethernet adapters -- the kernel has stale state
                         // from the last time the USB Ethernet adapter was connected, and part of the kernel thinks we've already joined the group
                         // on that interface (so we get EADDRINUSE when we try to join again) but a different part of the kernel thinks we haven't
-                        // joined the group (so we receive no multicasts). Doing an IP_DROP_MEMBERSHIP before joining seems to flush the stale state.
+                        // joined the group (so we receive no multicasts). Doing a leave before joining seems to flush the stale state.
                         // Also, trying to make the code leave the group when the adapter is removed doesn't work either,
                         // because by the time we get the configuration change notification, the interface is already gone,
                         // so attempts to unsubscribe fail with EADDRNOTAVAIL (errno 49 "Can't assign requested address").
                         // <rdar://problem/5585972> IP_ADD_MEMBERSHIP fails for previously-connected removable interfaces
-                        if (SearchForInterfaceByName(i->ifinfo.ifname, AF_INET) == i)
+                        const int sock = (i->sa_family == AF_INET) ? m->p->permanentsockets.sktv4 : m->p->permanentsockets.sktv6;
+                        if (SearchForInterfaceByName(i->ifinfo.ifname, i->sa_family) == i)
                         {
-                            LogInfo("SetupActiveInterfaces: %5s(%lu) Doing precautionary IP_DROP_MEMBERSHIP for %.4a on %.4a", i->ifinfo.ifname, i->scope_id, &imr.imr_multiaddr, &imr.imr_interface);
-                            mStatus err = setsockopt(m->p->permanentsockets.sktv4, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr, sizeof(imr));
-                            if (err < 0 && (errno != EADDRNOTAVAIL))
-                                LogMsg("setsockopt - IP_DROP_MEMBERSHIP error %d errno %d (%s)", err, errno, strerror(errno));
+                            mDNSGroupLeave(sock, i);
                         }
-
-                        LogInfo("SetupActiveInterfaces: %5s(%lu) joining IPv4 mcast group %.4a on %.4a", i->ifinfo.ifname, i->scope_id, &imr.imr_multiaddr, &imr.imr_interface);
-                        mStatus err = setsockopt(m->p->permanentsockets.sktv4, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(imr));
-                        // Joining same group twice can give "Address already in use" error -- no need to report that
-                        if (err < 0 && (errno != EADDRINUSE))
-                            LogMsg("setsockopt - IP_ADD_MEMBERSHIP error %d errno %d (%s) group %.4a on %.4a", err, errno, strerror(errno), &imr.imr_multiaddr, &imr.imr_interface);
-                    }
-                    if (i->sa_family == AF_INET6)
-                    {
-                        struct ipv6_mreq i6mr;
-                        i6mr.ipv6mr_interface = primary->scope_id;
-                        i6mr.ipv6mr_multiaddr = *(struct in6_addr*)&AllDNSLinkGroup_v6.ip.v6;
-
-                        if (SearchForInterfaceByName(i->ifinfo.ifname, AF_INET6) == i)
-                        {
-                            LogInfo("SetupActiveInterfaces: %5s(%lu) Doing precautionary IPV6_LEAVE_GROUP for %.16a on %u", i->ifinfo.ifname, i->scope_id, &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface);
-                            mStatus err = setsockopt(m->p->permanentsockets.sktv6, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &i6mr, sizeof(i6mr));
-                            if (err < 0 && (errno != EADDRNOTAVAIL))
-                                LogMsg("setsockopt - IPV6_LEAVE_GROUP error %d errno %d (%s) group %.16a on %u", err, errno, strerror(errno), &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface);
-                        }
-
-                        LogInfo("SetupActiveInterfaces: %5s(%lu) joining IPv6 mcast group %.16a on %u", i->ifinfo.ifname, i->scope_id, &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface);
-                        mStatus err = setsockopt(m->p->permanentsockets.sktv6, IPPROTO_IPV6, IPV6_JOIN_GROUP, &i6mr, sizeof(i6mr));
-                        // Joining same group twice can give "Address already in use" error -- no need to report that
-                        if (err < 0 && (errno != EADDRINUSE))
-                            LogMsg("setsockopt - IPV6_JOIN_GROUP error %d errno %d (%s) group %.16a on %u", err, errno, strerror(errno), &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface);
+                        mDNSGroupJoin(sock, i);
                     }
                 }
             }
@@ -4357,6 +4458,7 @@ mDNSlocal int ClearInactiveInterfaces(mDNSs32 utc)
         // If this interface is no longer active, or its InterfaceID is changing, deregister it
         NetworkInterfaceInfoOSX *primary = SearchForInterfaceByName(i->ifinfo.ifname, AF_UNSPEC);
         if (i->Registered)
+        {
             if (i->Exists == 0 || i->Exists == MulticastStateChanged || i->Registered != primary)
             {
                 InterfaceActivationSpeed activationSpeed;
@@ -4401,6 +4503,7 @@ mDNSlocal int ClearInactiveInterfaces(mDNSs32 utc)
                 // Caution: If we ever decide to add code here to leave the multicast group, we need to make sure that this
                 // is the LAST representative of this physical interface, or we'll unsubscribe from the group prematurely.
             }
+        }
     }
 
     // Second pass:
@@ -4590,6 +4693,7 @@ mDNSlocal void ConfigNonUnicastResolver(dns_resolver_t *r)
     }
 }
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 mDNSlocal void ConfigDNSServers(dns_resolver_t *r, mDNSInterfaceID interfaceID, mDNSu32 scope, mDNSu32 resGroupID)
 {
     domainname domain;
@@ -4647,6 +4751,7 @@ mDNSlocal void ConfigDNSServers(dns_resolver_t *r, mDNSInterfaceID interfaceID,
         }
     }
 }
+#endif
 
 // ConfigResolvers is called for different types of resolvers: Unscoped resolver, Interface scope resolver and
 // Service scope resolvers. This is indicated by the scope argument.
@@ -4713,10 +4818,12 @@ mDNSlocal void ConfigResolvers(dns_config_t *config, mDNSu32 scope, mDNSBool set
         {
             ConfigNonUnicastResolver(r);
         }
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
         else
         {
             ConfigDNSServers(r, interface, scope, mDNS_GetNextResolverGroupID());
         }
+#endif
     }
 }
 
@@ -4752,6 +4859,7 @@ mDNSexport void mDNSPlatformUpdateDNSStatus(const DNSQuestion *q)
     if (!QuestionValidForDNSTrigger(q))
         return;
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     // Ignore applications that start and stop queries for no reason before we ever talk
     // to any DNS server.
     if (!q->triedAllServersOnce)
@@ -4759,6 +4867,7 @@ mDNSexport void mDNSPlatformUpdateDNSStatus(const DNSQuestion *q)
         LogInfo("QuestionValidForDNSTrigger: question %##s (%s) stopped too soon", q->qname.c, DNSTypeName(q->qtype));
         return;
     }
+#endif
     if (q->qtype == kDNSType_A)
         m->p->v4answers = 0;
     if (q->qtype == kDNSType_AAAA)
@@ -5069,27 +5178,37 @@ mDNSexport mDNSBool mDNSPlatformSetDNSConfig(mDNSBool setservers, mDNSBool setse
             // on every update; and only updating when the generation number changes.
 
             // If this is a DNS server update and the configuration hasn't changed, then skip update
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            if (setservers && m->p->LastConfigGeneration == config->generation)
+#else
             if (setservers && !m->p->if_interface_changed && m->p->LastConfigGeneration == config->generation)
+#endif
             {
                 LogInfo("mDNSPlatformSetDNSConfig(setservers): generation number %llu same, not processing", config->generation);
                 dns_configuration_free(config);
                 SetupDDNSDomains(fqdn, RegDomains, BrowseDomains);
                 return mDNSfalse;
             }
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
             if (setservers) {
                 // Must check if setservers is true, because mDNSPlatformSetDNSConfig can be called for multiple times
                 // with setservers equals to false. If setservers is false, we will end up with clearing if_interface_changed
                 // without really updating the server.
                 m->p->if_interface_changed = mDNSfalse;
             }
+#endif
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_DOTLOCAL)
             SetupActiveDirectoryDomain(config);
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            if (setservers) Querier_ApplyDNSConfig(config);
 #endif
             ConfigResolvers(config, kScopeNone, setsearch, setservers, &sdc);
             ConfigResolvers(config, kScopeInterfaceID, setsearch, setservers, &sdc);
             ConfigResolvers(config, kScopeServiceID, setsearch, setservers, &sdc);
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
             const CFIndex n = m->p->InterfaceMonitors ? CFArrayGetCount(m->p->InterfaceMonitors) : 0;
             for (CFIndex i = n - 1; i >= 0; i--)
             {
@@ -5112,7 +5231,7 @@ mDNSexport mDNSBool mDNSPlatformSetDNSConfig(mDNSBool setservers, mDNSBool setse
                     mdns_release(monitor);
                 }
             }
-
+#endif
             // Acking provides a hint to other processes that the current DNS configuration has completed
             // its update.  When configd receives the ack, it publishes a notification.
             // Applications monitoring the notification then know when to re-issue their DNS queries
@@ -5317,7 +5436,8 @@ mDNSlocal void SetDomainSecrets_internal(mDNS *m)
         // Iterate through the secrets
         for (i = 0; i < ArrayCount; ++i)
         {
-            int j, offset;
+            int j;
+            size_t offset;
             CFArrayRef entry = CFArrayGetValueAtIndex(secrets, i);
             if (CFArrayGetTypeID() != CFGetTypeID(entry) || itemsPerEntry != CFArrayGetCount(entry))
             { LogMsg("SetDomainSecrets: malformed entry %d, itemsPerEntry %d", i, itemsPerEntry); continue; }
@@ -5882,7 +6002,7 @@ mDNSlocal void GetProxyRecords(DNSMessage *const msg, uint32_t *const numbytes,
             }
         }
     }
-    *numbytes = p - msg->data;
+    *numbytes = (mDNSu32)(p - msg->data);
     if (outRecordCount) *outRecordCount = count;
 }
 
@@ -6040,6 +6160,9 @@ mDNSlocal mDNSBool SystemSleepOnlyIfWakeOnLAN(void)
 
 mDNSlocal mDNSBool IsAppleNetwork(mDNS *const m)
 {
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    (void)m;
+#else
     DNSServer *s;
     // Determine if we're on AppleNW based on DNSServer having 17.x.y.z IPv4 addr
     for (s = m->DNSServers; s; s = s->next)
@@ -6050,6 +6173,7 @@ mDNSlocal mDNSBool IsAppleNetwork(mDNS *const m)
             return mDNStrue;
         }     
     }
+#endif
     return mDNSfalse;
 }
 
@@ -6206,11 +6330,11 @@ mDNSlocal int ChangedKeysHaveIPv4LL(CFArrayRef inkeys)
     CFMutableArrayRef a;
     const void **keys = NULL, **vals = NULL;
     CFStringRef pattern = NULL;
-    int i, ic, j, jc;
+    CFIndex i, ic, j, jc;
     int found = 0;
 
     jc = CFArrayGetCount(inkeys);
-    if (!jc) goto done;
+    if (jc <= 0) goto done;
 
     a = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
     if (a == NULL) goto done;
@@ -6232,13 +6356,19 @@ mDNSlocal int ChangedKeysHaveIPv4LL(CFArrayRef inkeys)
 
     if (!dict)
     {
-        LogMsg("ChangedKeysHaveIPv4LL: Empty dictionary");
+        LogMsg("ChangedKeysHaveIPv4LL: No dictionary");
         goto done;
     }
 
     ic = CFDictionaryGetCount(dict);
-    vals = (const void **) mDNSPlatformMemAllocate(sizeof(void *) * ic);
-    keys = (const void **) mDNSPlatformMemAllocate(sizeof(void *) * ic);
+    if (ic <= 0)
+    {
+        LogMsg("ChangedKeysHaveIPv4LL: Empty dictionary");
+        goto done;
+    }
+
+    vals = (const void **) mDNSPlatformMemAllocate(sizeof(void *) * (mDNSu32)ic);
+    keys = (const void **) mDNSPlatformMemAllocate(sizeof(void *) * (mDNSu32)ic);
     CFDictionaryGetKeysAndValues(dict, keys, vals);
 
     // For each key we were given...
@@ -6323,7 +6453,7 @@ mDNSlocal void NetworkChanged(SCDynamicStoreRef store, CFArrayRef changedKeys, v
     //mDNSs32 delay = mDNSPlatformOneSecond * 2;                // Start off assuming a two-second delay
     const mDNSs32 delay = (mDNSPlatformOneSecond + 39) / 40;    // 25 ms delay
 
-    const int c = CFArrayGetCount(changedKeys);                   // Count changes
+    const CFIndex c = CFArrayGetCount(changedKeys);             // Count changes
     CFRange range = { 0, c };
     const int c_host = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_Hostnames   ) != 0);
     const int c_comp = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_Computername) != 0);
@@ -6338,7 +6468,7 @@ mDNSlocal void NetworkChanged(SCDynamicStoreRef store, CFArrayRef changedKeys, v
     {
         CFArrayRef  labels;
         CFIndex     n;
-        for (int i = 0; i < c; i++)
+        for (CFIndex i = 0; i < c; i++)
         {
             CFStringRef key = CFArrayGetValueAtIndex(changedKeys, i);
 
@@ -6381,7 +6511,7 @@ mDNSlocal void NetworkChanged(SCDynamicStoreRef store, CFArrayRef changedKeys, v
 
     if (mDNS_LoggingEnabled)
     {
-        int i;
+        CFIndex i;
         for (i=0; i<c; i++)
         {
             char buf[256];
@@ -6389,8 +6519,8 @@ mDNSlocal void NetworkChanged(SCDynamicStoreRef store, CFArrayRef changedKeys, v
             LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "*** Network Configuration Change *** SC key: " PUB_S, buf);
         }
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-            "*** Network Configuration Change *** %d change" PUB_S " " PUB_S PUB_S PUB_S PUB_S PUB_S PUB_S "delay %d" PUB_S,
-            c, c>1 ? "s" : "",
+            "*** Network Configuration Change *** %ld change" PUB_S " " PUB_S PUB_S PUB_S PUB_S PUB_S PUB_S "delay %d" PUB_S,
+            (long)c, c>1 ? "s" : "",
             c_host ? "(Local Hostname) " : "",
             c_comp ? "(Computer Name) "  : "",
             c_udns ? "(DNS) "            : "",
@@ -6721,13 +6851,13 @@ mDNSlocal void SysEventCallBack(int s1, short __unused filter, void *context, __
     mDNS_Lock(m);
 
     struct { struct kern_event_msg k; char extra[256]; } msg;
-    int bytes = recv(s1, &msg, sizeof(msg), 0);
+    ssize_t bytes = recv(s1, &msg, sizeof(msg), 0);
     if (bytes < 0)
-        LogMsg("SysEventCallBack: recv error %d errno %d (%s)", bytes, errno, strerror(errno));
+        LogMsg("SysEventCallBack: recv error %ld errno %d (%s)", (long)bytes, errno, strerror(errno));
     else
     {
-        LogInfo("SysEventCallBack got %d bytes size %d %X %s %X %s %X %s id %d code %d %s",
-                bytes, msg.k.total_size,
+        LogInfo("SysEventCallBack got %ld bytes size %u %X %s %X %s %X %s id %d code %d %s",
+                (long)bytes, msg.k.total_size,
                 msg.k.vendor_code, msg.k.vendor_code  == KEV_VENDOR_APPLE  ? "KEV_VENDOR_APPLE"  : "?",
                 msg.k.kev_class, msg.k.kev_class    == KEV_NETWORK_CLASS ? "KEV_NETWORK_CLASS" : "?",
                 msg.k.kev_subclass, msg.k.kev_subclass == KEV_DL_SUBCLASS   ? "KEV_DL_SUBCLASS"   : "?",
@@ -7023,7 +7153,7 @@ mDNSlocal void SnowLeopardPowerChanged(void *refcon, IOPMConnection connection,
         if (m->SleepLimit)
         {
             LogSPS("SnowLeopardPowerChanged: Waking up, Acking old Sleep, SleepLimit %d SleepState %d", m->SleepLimit, m->SleepState);
-            IOPMConnectionAcknowledgeEvent(connection, m->p->SleepCookie);
+            IOPMConnectionAcknowledgeEvent(connection, (IOPMConnectionMessageToken)m->p->SleepCookie);
             m->SleepLimit = 0;
         }
         LogSPS("SnowLeopardPowerChanged: Waking up, Acking Wakeup, SleepLimit %d SleepState %d", m->SleepLimit, m->SleepState);
@@ -7239,7 +7369,7 @@ mDNSlocal int EtcHostsParseOneName(int start, int length, char *buffer, char **n
     return -1;
 }
 
-mDNSlocal void mDNSMacOSXParseEtcHostsLine(char *buffer, ssize_t length, AuthHash *auth)
+mDNSlocal void mDNSMacOSXParseEtcHostsLine(char *buffer, int length, AuthHash *auth)
 {
     int i;
     int ifStart = 0;
@@ -7391,7 +7521,7 @@ mDNSlocal void mDNSMacOSXParseEtcHosts(int fd, AuthHash *auth)
 {
     mDNSBool good;
     char buf[ETCHOSTS_BUFSIZE];
-    ssize_t len;
+    size_t len;
     FILE *fp;
 
     if (fd == -1) { LogInfo("mDNSMacOSXParseEtcHosts: fd is -1"); return; }
@@ -7435,7 +7565,7 @@ mDNSlocal void mDNSMacOSXParseEtcHosts(int fd, AuthHash *auth)
             LogMsg("mDNSMacOSXParseEtcHosts: Length is zero!");
             continue;
         }
-        mDNSMacOSXParseEtcHostsLine(buf, len, auth);
+        mDNSMacOSXParseEtcHostsLine(buf, (int)len, auth);
     }
     fclose(fp);
 }
@@ -7463,7 +7593,7 @@ mDNSlocal int mDNSMacOSXGetEtcHostsFD(void)
         return -1;
     }
 
-    if (hostssrc) return dispatch_source_get_handle(hostssrc);
+    if (hostssrc) return (int)dispatch_source_get_handle(hostssrc);
 #endif
 
     int fd = open("/etc/hosts", O_RDONLY);
@@ -7486,7 +7616,7 @@ mDNSlocal int mDNSMacOSXGetEtcHostsFD(void)
         }
         dispatch_source_set_event_handler(etcsrc,
                                           ^{
-                                              u_int32_t flags = dispatch_source_get_data(etcsrc);
+                                              const unsigned long flags = dispatch_source_get_data(etcsrc);
                                               LogMsg("mDNSMacOSXGetEtcHostsFD: /etc changed 0x%x", flags);
                                               if ((flags & (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME)) != 0)
                                               {
@@ -7520,7 +7650,7 @@ mDNSlocal int mDNSMacOSXGetEtcHostsFD(void)
     }
     dispatch_source_set_event_handler(hostssrc,
                                       ^{
-                                          u_int32_t flags = dispatch_source_get_data(hostssrc);
+                                          const unsigned long flags = dispatch_source_get_data(hostssrc);
                                           LogInfo("mDNSMacOSXGetEtcHostsFD: /etc/hosts changed 0x%x", flags);
                                           if ((flags & (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME)) != 0)
                                           {
@@ -7851,8 +7981,12 @@ mDNSexport void mDNSMacOSXSystemBuildNumber(char *HINFO_SWstring)
         mDNS_snprintf(HINFO_SWstring, 256, "%s %s (%s), %s", prodname, prodvers, buildver, STRINGIFY(mDNSResponderVersion));
     //LogMsg("%s %s (%s), %d %c %d", prodname, prodvers, buildver, major, letter, minor);
 
-    // If product name is "Mac OS X" (or similar) we set OSXVers, else we set iOSVers;
-    if ((prodname[0] & 0xDF) == 'M') 
+    // If product name starts with "M" (case insensitive), thus it the current ProductName attribute "macOS"
+    // for macOS; or it matches the old ProductName attribute "Mac OS X" for macOS, we set OSXVers, else we set iOSVers.
+    // Note that "& 0xDF" operation converts prodname[0] to uppercase alphabetic character, do not use it make the ASCII
+    // character uppercase, since "& 0xDF" will incorrectly change the ASCII characters that are not in the A~Z, a~z
+    // range. For the detail, go to https://blog.cloudflare.com/the-oldest-trick-in-the-ascii-book/
+    if ((prodname[0] & 0xDF) == 'M')
         OSXVers = major;
     else 
         iOSVers = major;
@@ -8040,7 +8174,7 @@ mDNSlocal mStatus mDNSPlatformInit_setup(mDNS *const m)
 
     // For names of the form "iPhone2,1" we use "iPhone" as the prefix for automatic name generation.
     // For names of the form "N88AP" containg no comma, we use the entire string.
-    HINFO_HWstring_prefixlen = strchr(HINFO_HWstring_buffer, ',') ? strcspn(HINFO_HWstring, "0123456789") : strlen(HINFO_HWstring);
+    HINFO_HWstring_prefixlen = (int)(strchr(HINFO_HWstring_buffer, ',') ? strcspn(HINFO_HWstring, "0123456789") : strlen(HINFO_HWstring));
 
     if (mDNSPlatformInit_CanReceiveUnicast()) 
         m->CanReceiveUnicastOn5353 = mDNStrue;
@@ -8087,7 +8221,9 @@ mDNSlocal mStatus mDNSPlatformInit_setup(mDNS *const m)
     }
 
     m->p->InterfaceList         = mDNSNULL;
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     m->p->InterfaceMonitors     = NULL;
+#endif
     m->p->userhostlabel.c[0]    = 0;
     m->p->usernicelabel.c[0]    = 0;
     m->p->prevoldnicelabel.c[0] = 0;
@@ -8104,7 +8240,9 @@ mDNSlocal mStatus mDNSPlatformInit_setup(mDNS *const m)
     m->p->v6answers          = 1;
     m->p->DNSTrigger         = 0;
     m->p->LastConfigGeneration = 0;
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     m->p->if_interface_changed = mDNSfalse;
+#endif
 
     NetworkChangedKey_IPv4         = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4);
     NetworkChangedKey_IPv6         = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetIPv6);
@@ -8251,6 +8389,17 @@ mDNSlocal mStatus mDNSPlatformInit_setup(mDNS *const m)
     dso_transport_init();
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, ANALYTICS)
+    dnssd_analytics_init();
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+    if (os_feature_enabled(mDNSResponder, bonjour_privacy))
+    {
+        mdns_trust_init();
+   }
+#endif
+
     return(mStatus_NoError);
 }
 
@@ -8274,9 +8423,10 @@ mDNSexport mStatus mDNSPlatformInit(mDNS *const m)
     if (result == mStatus_NoError)
     {
         mDNSCoreInitComplete(m, mStatus_NoError);
+#if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
         initializeD2DPlugins(m);
+#endif
     }
-    result = DNSSECCryptoInit(m);
     return(result);
 }
 
@@ -8321,13 +8471,16 @@ mDNSexport void mDNSPlatformClose(mDNS *const m)
     }
 
     if (m->p->SysEventNotifier >= 0) { close(m->p->SysEventNotifier); m->p->SysEventNotifier = -1; }
+#if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
     terminateD2DPlugins();
+#endif
 
     mDNSs32 utc = mDNSPlatformUTC();
     MarkAllInterfacesInactive(utc);
     ClearInactiveInterfaces(utc);
     CloseSocketSet(&m->p->permanentsockets);
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     if (m->p->InterfaceMonitors)
     {
         CFArrayRef monitors = m->p->InterfaceMonitors;
@@ -8339,6 +8492,7 @@ mDNSexport void mDNSPlatformClose(mDNS *const m)
         }
         CFRelease(monitors);
     }
+#endif
 }
 
 #if COMPILER_LIKES_PRAGMA_MARK
@@ -8376,7 +8530,7 @@ mDNSexport mStatus mDNSPlatformTimeInit(void)
     // When we ship Macs with clock frequencies above 1000GHz, we may have to update this code.
     struct mach_timebase_info tbi;
     kern_return_t result = mach_timebase_info(&tbi);
-    if (result == KERN_SUCCESS) mDNSPlatformClockDivisor = ((uint64_t)tbi.denom * 1000000) / tbi.numer;
+    if (result == KERN_SUCCESS) mDNSPlatformClockDivisor = (mDNSu32)(((uint64_t)tbi.denom * 1000000) / tbi.numer);
     return(result);
 }
 
@@ -8407,14 +8561,14 @@ mDNSexport mDNSs32 mDNSPlatformRawTime(void)
 
 mDNSexport mDNSs32 mDNSPlatformUTC(void)
 {
-    return time(NULL);
+    return (mDNSs32)time(NULL);
 }
 
 // Locking is a no-op here, because we're single-threaded with a CFRunLoop, so we can never interrupt ourselves
 mDNSexport void     mDNSPlatformLock   (const mDNS *const m) { (void)m; }
 mDNSexport void     mDNSPlatformUnlock (const mDNS *const m) { (void)m; }
-mDNSexport mDNSu32  mDNSPlatformStrLCopy(     void *dst, const void *src, mDNSu32 dstlen) { return (strlcpy((char *)dst, (const char *)src, dstlen)); }
-mDNSexport mDNSu32  mDNSPlatformStrLen (                 const void *src)              { return(strlen((const char*)src)); }
+mDNSexport mDNSu32  mDNSPlatformStrLCopy(     void *dst, const void *src, mDNSu32 dstlen) { return((mDNSu32)strlcpy((char *)dst, (const char *)src, dstlen)); }
+mDNSexport mDNSu32  mDNSPlatformStrLen (                 const void *src)              { return((mDNSu32)strlen((const char*)src)); }
 mDNSexport void     mDNSPlatformMemCopy(      void *dst, const void *src, mDNSu32 len) { memcpy(dst, src, len); }
 mDNSexport mDNSBool mDNSPlatformMemSame(const void *dst, const void *src, mDNSu32 len) { return(memcmp(dst, src, len) == 0); }
 mDNSexport int      mDNSPlatformMemCmp(const void *dst, const void *src, mDNSu32 len) { return(memcmp(dst, src, len)); }
@@ -8494,16 +8648,19 @@ mDNSexport void mDNSPlatformPreventSleep(mDNSu32 timeout, const char *reason)
 
 mDNSexport void mDNSPlatformSendWakeupPacket(mDNSInterfaceID InterfaceID, char *EthAddr, char *IPAddr, int iteration)
 {
-    mDNSu32 ifindex;
-
-    // Sanity check
-    ifindex = mDNSPlatformInterfaceIndexfromInterfaceID(&mDNSStorage, InterfaceID, mDNStrue);
-    if (ifindex <= 0)
+    if (GetInterfaceSupportsWakeOnLANPacket(InterfaceID))
     {
-        LogMsg("mDNSPlatformSendWakeupPacket: ERROR!! Invalid InterfaceID %u", ifindex);
-        return;
+        mDNSu32 ifindex;
+
+        // Sanity check
+        ifindex = mDNSPlatformInterfaceIndexfromInterfaceID(&mDNSStorage, InterfaceID, mDNStrue);
+        if (ifindex <= 0)
+        {
+            LogMsg("mDNSPlatformSendWakeupPacket: ERROR!! Invalid InterfaceID %u", ifindex);
+            return;
+        }
+        mDNSSendWakeupPacket(ifindex, EthAddr, IPAddr, iteration);
     }
-    mDNSSendWakeupPacket(ifindex, EthAddr, IPAddr, iteration);
 }
 
 mDNSexport mDNSBool mDNSPlatformInterfaceIsD2D(mDNSInterfaceID InterfaceID)
@@ -8664,35 +8821,14 @@ mDNSexport void mDNSPlatformDispatchAsync(mDNS *const m, void *context, AsyncDis
 #define OSX_VER_LEN     sizeof_string(OSX_VER) 
 #define VER_NUM_LEN     2  // 2 digits of version number added to base string
 
-#define MODEL_COLOR           "ecolor="
-#define MODEL_COLOR_LEN       sizeof_string(MODEL_COLOR)
-#define MODEL_RGB_VALUE_LEN   sizeof_string("255,255,255") // 'r,g,b'
+#define MODEL_RGB_COLOR       "ecolor="
+#define MODEL_INDEX_COLOR     "icolor="
+#define MODEL_COLOR_LEN       sizeof_string(MODEL_RGB_COLOR) // Same len as MODEL_INDEX_COLOR
+#define MODEL_COLOR_VALUE_LEN sizeof_string("255,255,255")   // 'r,g,b', 'i' MAXUINT32('4294967295')
 
 // Bytes available in TXT record for model name after subtracting space for other
 // fixed size strings and their length bytes.
-#define MAX_MODEL_NAME_LEN   (256 - (DEVINFO_MODEL_LEN + 1) - (OSX_VER_LEN + VER_NUM_LEN + 1) - (MODEL_COLOR_LEN + MODEL_RGB_VALUE_LEN + 1))
-
-mDNSlocal mDNSu8 getModelIconColors(char *color)
-{
-    mDNSPlatformMemZero(color, MODEL_RGB_VALUE_LEN + 1);
-
-#if TARGET_OS_OSX && defined(kIOPlatformDeviceEnclosureColorKey)
-    mDNSu8   red      = 0;
-    mDNSu8   green    = 0;
-    mDNSu8   blue     = 0;
-
-    IOReturn rGetDeviceColor = IOPlatformGetDeviceColor(kIOPlatformDeviceEnclosureColorKey,
-                                                        &red, &green, &blue);
-    if (kIOReturnSuccess == rGetDeviceColor)
-    {
-        // IOKit was able to get enclosure color for the current device.
-        return snprintf(color, MODEL_RGB_VALUE_LEN + 1, "%d,%d,%d", red, green, blue);
-    }
-#endif
-
-    return 0;
-}
-
+#define MAX_MODEL_NAME_LEN   (256 - (DEVINFO_MODEL_LEN + 1) - (OSX_VER_LEN + VER_NUM_LEN + 1) - (MODEL_COLOR_LEN + MODEL_COLOR_VALUE_LEN + 1))
 
 // Initialize device-info TXT record contents and return total length of record data.
 mDNSexport mDNSu32 initializeDeviceInfoTXT(mDNS *m, mDNSu8 *ptr)
@@ -8721,22 +8857,27 @@ mDNSexport mDNSu32 initializeDeviceInfoTXT(mDNS *m, mDNSu8 *ptr)
         mDNSPlatformMemCopy(ptr, ver_num, VER_NUM_LEN);
         ptr += VER_NUM_LEN;
 
-        char rgb[MODEL_RGB_VALUE_LEN + 1]; // RGB value + null written by snprintf
-        len = getModelIconColors(rgb);
-        if (len)
+        const uint8_t max_color_len = MODEL_COLOR_VALUE_LEN + 1;
+        char color[max_color_len]; // Color string value + null written by snprintf
+        util_enclosure_color_t color_type = util_get_enclosure_color_str(color, max_color_len, &len);
+        if (color_type != util_enclosure_color_none && len < max_color_len)
         {
             *ptr = MODEL_COLOR_LEN + len; // length byte
             ptr++;
 
-            mDNSPlatformMemCopy(ptr, MODEL_COLOR, MODEL_COLOR_LEN);
+            if (color_type == util_enclosure_color_rgb) {
+                mDNSPlatformMemCopy(ptr, MODEL_RGB_COLOR, MODEL_COLOR_LEN);
+            } else {
+                mDNSPlatformMemCopy(ptr, MODEL_INDEX_COLOR, MODEL_COLOR_LEN);
+            }
             ptr += MODEL_COLOR_LEN;
 
-            mDNSPlatformMemCopy(ptr, rgb, len);
+            mDNSPlatformMemCopy(ptr, color, len);
             ptr += len;
         }
     }
 
-    return (ptr - bufferStart);
+    return (mDNSu32)(ptr - bufferStart);
 }
 
 #if APPLE_OSX_mDNSResponder // Don't compile for dnsextd target
@@ -8747,9 +8888,10 @@ mDNSlocal mDNSBool vectorSameDomainLabel(const mDNSu8 *a, const mDNSu8 *b);
 mDNSlocal mDNSBool (*SameDomainLabelPointer)(const mDNSu8 *a, const mDNSu8 *b) = scalarSameDomainLabel;
 
 #include <System/machine/cpu_capabilities.h>
-#define _cpu_capabilities   ((uint32_t*) _COMM_PAGE_CPU_CAPABILITIES)[0]
+// `address_space(1)` attribute opts access out of ASan instrumentation see rdar://problem/68953642 .
+#define _cpu_capabilities   ((__attribute__((address_space(1))) uint32_t*) _COMM_PAGE_CPU_CAPABILITIES)[0]
 
-#if TARGET_OS_IPHONE
+#if __arm64__ || __arm__
 #include <arm_neon.h>
 
 // Cache line aligned table that returns 32 for the upper case letters.
@@ -8935,9 +9077,9 @@ mDNSlocal void setSameDomainLabelPointer(void)
     else
         LogMsg("setSameDomainLabelPointer: using scalar code");
 }
-#endif // TARGET_OS_IPHONE
+#endif // __arm64__ || __arm__
 
-#if TARGET_OS_OSX
+#if __x86_64__
 #include <smmintrin.h>
 
 // Cache line aligned table that returns 32 for the upper case letters.
@@ -9058,7 +9200,7 @@ mDNSlocal void setSameDomainLabelPointer(void)
     else
         LogMsg("setSameDomainLabelPointer: using scalar code");
 }
-#endif // TARGET_OS_OSX
+#endif // __x86_64__
 
 // Original SameDomainLabel() implementation.
 mDNSlocal mDNSBool scalarSameDomainLabel(const mDNSu8 *a, const mDNSu8 *b)
@@ -9108,6 +9250,18 @@ mDNSexport void GetRandomUUIDLocalHostname(domainname *hostname)
 }
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
+mDNSexport void uDNSMetricsClear(uDNSMetrics *const metrics)
+{
+    if (metrics->originalQName)
+    {
+        mDNSPlatformMemFree(metrics->originalQName);
+        metrics->originalQName = mDNSNULL;
+    }
+    mDNSPlatformMemZero(metrics, (mDNSu32)sizeof(*metrics));
+}
+#endif
+
 #ifdef UNIT_TEST
 #include "../unittests/mdns_macosx_ut.c"
 #endif
index b0a3b12016808ca2bc304ccecf6c2e52f1275a50..8c55ad58be161f657e01d0e3ac8f9746f94d87e1 100644 (file)
@@ -155,6 +155,7 @@ struct NetworkInterfaceInfoOSX_struct
                                                 // If an interface goes away temporarily and then comes back then
                                                 // AppearanceTime is updated to the time of the most recent appearance.
     mDNSs32 LastSeen;                           // If Exists==0, last time this interface appeared in getifaddrs list
+    uint32_t ift_family;                        // IFRTYPE_FAMILY_XXX
     unsigned int ifa_flags;
     struct in_addr ifa_v4addr;
     mDNSu32 scope_id;                           // interface index / IPv6 scope ID
@@ -176,7 +177,9 @@ struct NetworkInterfaceInfoOSX_struct
 struct mDNS_PlatformSupport_struct
 {
     NetworkInterfaceInfoOSX *InterfaceList;
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     CFMutableArrayRef InterfaceMonitors;
+#endif
     KQSocketSet permanentsockets;
     int num_mcasts;                             // Number of multicasts received during this CPU scheduling period (used for CPU limiting)
     domainlabel userhostlabel;                  // The hostlabel as it was set in System Preferences the last time we looked
@@ -219,9 +222,11 @@ struct mDNS_PlatformSupport_struct
     mDNSu8 v6answers;                  // for A/AAAA from external DNS servers
     mDNSs32 DNSTrigger;                // Time the DNSTrigger was given
     uint64_t LastConfigGeneration;     // DNS configuration generation number
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     mDNSBool if_interface_changed;     // There are some changes that we do not know from LastConfigGeneration, such as
                                        // if the interface is expensive/constrained or not. Therefore, we need an additional
                                        // field to determine if the interface has changed.
+#endif
     UDPSocket UDPProxy;
     TCPSocket TCPProxyV4;
     TCPSocket TCPProxyV6;
@@ -258,8 +263,6 @@ extern int KQueueSet(int fd, u_short flags, short filter, const KQueueEntry *con
 extern void KQueueLock(void);
 extern void KQueueUnlock(const char* task);
 extern void mDNSPlatformCloseFD(KQueueEntry *kq, int fd);
-extern ssize_t myrecvfrom(const int s, void *const buffer, const size_t max,
-                             struct sockaddr *const from, size_t *const fromlen, mDNSAddr *dstaddr, char *ifname, mDNSu8 *ttl);
 
 extern mDNSBool DictionaryIsEnabled(CFDictionaryRef dict);
 
@@ -286,7 +289,7 @@ struct CompileTimeAssertionChecks_mDNSMacOSX
     // Check our structures are reasonable sizes. Including overly-large buffers, or embedding
     // other overly-large structures instead of having a pointer to them, can inadvertently
     // cause structure sizes (and therefore memory usage) to balloon unreasonably.
-    char sizecheck_NetworkInterfaceInfoOSX[(sizeof(NetworkInterfaceInfoOSX) <=  8488) ? 1 : -1];
+    char sizecheck_NetworkInterfaceInfoOSX[(sizeof(NetworkInterfaceInfoOSX) <=  8704) ? 1 : -1];
     char sizecheck_mDNS_PlatformSupport   [(sizeof(mDNS_PlatformSupport)    <=  1378) ? 1 : -1];
 };
 
index 049f4fc4091594f82c26b46755d180a97b396181..609f9d2a7bbd38d892b6ef1c6e4e0e2be31c72b5 100644 (file)
        <true/>
        <key>com.apple.wifip2pd</key>
        <true/>
+       <key>com.apple.bluetooth.system</key>
+       <true/>
+       <key>com.apple.private.networkextension.configuration</key>
+       <true/>
+       <key>com.apple.symptom_analytics.delegate_symptom</key>
+       <true/>
 </dict>
 </plist>
index 493e3a23a757c0b60a36110b3ea3f98bbdd34281..25921fd853ef813a7bdf5621ecc5eb02e9efa59b 100644 (file)
@@ -1,5 +1,5 @@
 ;
-; Copyright (c) 2012-2019 Apple Inc. All rights reserved.
+; Copyright (c) 2012-2020 Apple Inc. All rights reserved.
 ;
 ; Redistribution and use in source and binary forms, with or without 
 ; modification, are permitted provided that the following conditions are met:
@@ -52,7 +52,9 @@
        (global-name "com.apple.distributed_notifications.2")
        (global-name "com.apple.distributed_notifications@1v3")
        (global-name "com.apple.lsd.mapdb")
+       (global-name "com.apple.nesessionmanager.content-filter")
        (global-name "com.apple.ocspd")
+       (global-name "com.apple.powerlog.plxpclogger.xpc")
        (global-name "com.apple.PowerManagement.control")
        (global-name "com.apple.mDNSResponderHelper")
        (global-name "com.apple.mDNSResponder_Helper")
        (global-name "com.apple.webcontentfilter.dns")
        (global-name "com.apple.server.bluetooth")
        (global-name "com.apple.server.bluetooth.le.att.xpc")
+       (global-name "com.apple.server.bluetooth.general.xpc")
        (global-name "com.apple.awacs")
-       (global-name "com.apple.networkd")
        (global-name "com.apple.securityd")
        (global-name "com.apple.wifi.manager")
        (global-name "com.apple.wifip2pd")
-       ; "com.apple.blued" is the name used in pre Lobo builds,
-       ; leave it in place while still running roots on pre Lobo targets
-       (global-name "com.apple.blued")
        (global-name "com.apple.bluetoothd")
        (global-name "com.apple.mobilegestalt.xpc")
-       (global-name "com.apple.ReportCrash.SimulateCrash")
-       (global-name "com.apple.snhelper")
-       (global-name "com.apple.networkd_privileged"))
+       (global-name "com.apple.networkd_privileged")
+       (global-name "com.apple.dnssd.service")
+       (global-name "com.apple.nehelper"))
 
 (allow mach-register
        (global-name "com.apple.d2d.ipc"))
        (literal "/Library/Security/Trust Settings/Admin.plist")
        (regex #"^/Library/Preferences/com\.apple\.security\.")
        (literal "/Library/Preferences/SystemConfiguration/com.apple.PowerManagement.plist")
+       (literal "/private/var/preferences/com.apple.networkd.plist")
        (literal "/private/var/preferences/SystemConfiguration/preferences.plist")
        (subpath "/System/Library/Preferences/Logging")
        (subpath "/AppleInternal/Library/Preferences/Logging")
 ; "/private/var/log/mDNSResponder" is owned by user "_mdnsresponder" who is in "wheel" group, no one else would have the
 ; access to this directory, so there is not much security concern.
 (allow file-read* file-write* (subpath "/private/var/log/mDNSResponder"))
+
+; <rdar://problem/65700296> mDNSResponder sandbox profile needs to allow loading dylibs from /usr/appleinternal/lib/sanitizers
+(with-filter (system-attribute apple-internal)
+    (allow file-read* file-map-executable
+               (subpath "/usr/appleinternal/lib/sanitizers/")))
index 1aafbd120a08bb4e91a606b1ab6cd5418e84ce7b..d5b59fec8b9b0964972645e2a8a5c44f2c47c723 100644 (file)
@@ -17,6 +17,7 @@
                                03067D6E0C83A39C0022BE1F /* PBXTargetDependency */,
                                B7237FE62194D99200B113B1 /* PBXTargetDependency */,
                                D4528FFE21F91263004D61BF /* PBXTargetDependency */,
+                               BD7BFBFC236FDCB2000456BC /* PBXTargetDependency */,
                        );
                        name = "Build Core";
                        productName = "Build Some";
                        name = SystemLibrariesStatic;
                        productName = SystemLibrariesStatic;
                };
+               8941BE4C23E352C2004AF1CA /* Build Extras-tvOS */ = {
+                       isa = PBXAggregateTarget;
+                       buildConfigurationList = 8941BE4D23E352C2004AF1CA /* Build configuration list for PBXAggregateTarget "Build Extras-tvOS" */;
+                       buildPhases = (
+                       );
+                       dependencies = (
+                               89DC990423F72754009598E1 /* PBXTargetDependency */,
+                               8941BE5523E367C2004AF1CA /* PBXTargetDependency */,
+                               8941BE5123E352DD004AF1CA /* PBXTargetDependency */,
+                               8941BE5323E352DD004AF1CA /* PBXTargetDependency */,
+                       );
+                       name = "Build Extras-tvOS";
+                       productName = "Build Extras-tvOS";
+               };
                B70F38A5217AA6CE00612D3A /* Build Extras */ = {
                        isa = PBXAggregateTarget;
                        buildConfigurationList = B70F38B0217AA6CE00612D3A /* Build configuration list for PBXAggregateTarget "Build Extras" */;
                        buildPhases = (
                        );
                        dependencies = (
+                               89458D9523A1A66B00C3978D /* PBXTargetDependency */,
                                B70F38A6217AA6CE00612D3A /* PBXTargetDependency */,
                                B70F38AE217AA6CE00612D3A /* PBXTargetDependency */,
                                B70F38A8217AA6CE00612D3A /* PBXTargetDependency */,
                        buildPhases = (
                        );
                        dependencies = (
+                               89DC990623F72765009598E1 /* PBXTargetDependency */,
                                B7237FDC2194D16900B113B1 /* PBXTargetDependency */,
                                B7DB58C2215F04490054CD46 /* PBXTargetDependency */,
                                B7DB58A0215EB61C0054CD46 /* PBXTargetDependency */,
                        name = "Build Extras-macOS";
                        productName = "Build UI";
                };
+               B7E6DAC9244E28F800C898EF /* Build Services-macOS */ = {
+                       isa = PBXAggregateTarget;
+                       buildConfigurationList = B7E6DACE244E28F800C898EF /* Build configuration list for PBXAggregateTarget "Build Services-macOS" */;
+                       buildPhases = (
+                       );
+                       dependencies = (
+                               B7E6DACA244E28F800C898EF /* PBXTargetDependency */,
+                       );
+                       name = "Build Services-macOS";
+                       productName = "Build Some";
+               };
                FFA572650AF190F10055A0F1 /* SystemLibrariesDynamic */ = {
                        isa = PBXAggregateTarget;
                        buildConfigurationList = FFA5726E0AF191200055A0F1 /* Build configuration list for PBXAggregateTarget "SystemLibrariesDynamic" */;
                        );
                        dependencies = (
                                2141DCFD123FFB7D0086D23E /* PBXTargetDependency */,
+                               B7C90F602346D1A200AA89F1 /* PBXTargetDependency */,
+                               B7A41B3C245E33B500FA6163 /* PBXTargetDependency */,
+                               B7C90F682346D1B000AA89F1 /* PBXTargetDependency */,
                        );
                        name = "Build All";
                        productName = "Build All";
                0C1596B51D7740B500E09998 /* mDNSPosix.c in Sources */ = {isa = PBXBuildFile; fileRef = 0C1596B31D7740B500E09998 /* mDNSPosix.c */; };
                0C1596B61D7740B500E09998 /* NetMonitor.c in Sources */ = {isa = PBXBuildFile; fileRef = 0C1596B41D7740B500E09998 /* NetMonitor.c */; };
                0C1596B81D7740C100E09998 /* mDNSUNP.c in Sources */ = {isa = PBXBuildFile; fileRef = 0C1596B71D7740C100E09998 /* mDNSUNP.c */; };
-               0C1596BA1D7740D200E09998 /* CryptoAlg.c in Sources */ = {isa = PBXBuildFile; fileRef = 21A57F4A145B2AE100939099 /* CryptoAlg.c */; };
                0C1596BB1D7740D700E09998 /* DNSDigest.c in Sources */ = {isa = PBXBuildFile; fileRef = 7F461DB5062DBF2900672BF3 /* DNSDigest.c */; };
                0C1596BC1D7740DC00E09998 /* DNSCommon.c in Sources */ = {isa = PBXBuildFile; fileRef = 7F18A9F60587CEF6001880B3 /* DNSCommon.c */; };
                0C1596BD1D7740E300E09998 /* uDNS.c in Sources */ = {isa = PBXBuildFile; fileRef = 7F18A9F70587CEF6001880B3 /* uDNS.c */; };
                0C635A931E9418D90026C796 /* LLRBTree.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C635A871E9418D90026C796 /* LLRBTree.cpp */; };
                0C635A941E9418D90026C796 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C635A881E9418D90026C796 /* main.cpp */; };
                0C6FB90F1D77767300DF6F51 /* mDNSNetMonitor.8 in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0C6FB90E1D775FE900DF6F51 /* mDNSNetMonitor.8 */; };
-               21070E5F16486B9000A69507 /* DNSSECSupport.c in Sources */ = {isa = PBXBuildFile; fileRef = 21070E5D16486B9000A69507 /* DNSSECSupport.c */; };
-               21070E6116486B9000A69507 /* DNSSECSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 21070E5E16486B9000A69507 /* DNSSECSupport.h */; };
-               2124FA2C1471E98C0021D7BB /* nsec.h in Headers */ = {isa = PBXBuildFile; fileRef = 2124FA2B1471E98C0021D7BB /* nsec.h */; };
-               2124FA301471E9B50021D7BB /* dnssec.h in Headers */ = {isa = PBXBuildFile; fileRef = 2124FA2F1471E9B50021D7BB /* dnssec.h */; };
-               2124FA331471E9DE0021D7BB /* nsec.c in Sources */ = {isa = PBXBuildFile; fileRef = 2124FA321471E9DE0021D7BB /* nsec.c */; };
-               2127A47715C3C7B900A857FC /* nsec3.c in Sources */ = {isa = PBXBuildFile; fileRef = 2127A47515C3C7B900A857FC /* nsec3.c */; };
-               2127A47915C3C7B900A857FC /* nsec3.h in Headers */ = {isa = PBXBuildFile; fileRef = 2127A47615C3C7B900A857FC /* nsec3.h */; };
-               213BDC6D147319F400000896 /* dnssec.c in Sources */ = {isa = PBXBuildFile; fileRef = 213BDC6C147319F400000896 /* dnssec.c */; };
                213FB23C12028C4A002B3A08 /* BonjourEvents.c in Sources */ = {isa = PBXBuildFile; fileRef = 213FB22C12028B53002B3A08 /* BonjourEvents.c */; };
                213FB23D12028C5A002B3A08 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09AB6884FE841BABC02AAC07 /* CoreFoundation.framework */; };
                215FFAEE124000F900470DE1 /* dnssd_ipc.c in Sources */ = {isa = PBXBuildFile; fileRef = F5E11B5A04A28126019798ED /* dnssd_ipc.c */; };
                215FFAFA1240013400470DE1 /* dnssd_ipc.c in Sources */ = {isa = PBXBuildFile; fileRef = F5E11B5A04A28126019798ED /* dnssd_ipc.c */; };
                215FFAFB1240013400470DE1 /* dnssd_clientlib.c in Sources */ = {isa = PBXBuildFile; fileRef = FFFA38620AEEDB090065B80A /* dnssd_clientlib.c */; };
                215FFAFC1240013400470DE1 /* dnssd_clientstub.c in Sources */ = {isa = PBXBuildFile; fileRef = FFFA38640AEEDB130065B80A /* dnssd_clientstub.c */; };
-               216D9ACE1720C9F5008066E1 /* uDNSPathEvalulation.c in Sources */ = {isa = PBXBuildFile; fileRef = 216D9ACD1720C9F5008066E1 /* uDNSPathEvalulation.c */; };
+               216D9ACE1720C9F5008066E1 /* uDNSPathEvaluation.c in Sources */ = {isa = PBXBuildFile; fileRef = 216D9ACD1720C9F5008066E1 /* uDNSPathEvaluation.c */; };
                218E8E51156D8C0300720DA0 /* dnsproxy.c in Sources */ = {isa = PBXBuildFile; fileRef = 218E8E4F156D8C0300720DA0 /* dnsproxy.c */; };
                218E8E53156D8C0300720DA0 /* dnsproxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 218E8E50156D8C0300720DA0 /* dnsproxy.h */; };
                219D5542149ED645004464AE /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 219D5541149ED645004464AE /* libxml2.dylib */; };
-               21A57F4C145B2AE100939099 /* CryptoAlg.c in Sources */ = {isa = PBXBuildFile; fileRef = 21A57F4A145B2AE100939099 /* CryptoAlg.c */; };
-               21A57F4E145B2AE100939099 /* CryptoAlg.h in Headers */ = {isa = PBXBuildFile; fileRef = 21A57F4B145B2AE100939099 /* CryptoAlg.h */; };
-               21A57F53145B2B1400939099 /* CryptoSupport.c in Sources */ = {isa = PBXBuildFile; fileRef = 21A57F51145B2B1400939099 /* CryptoSupport.c */; };
-               21A57F55145B2B1400939099 /* CryptoSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 21A57F52145B2B1400939099 /* CryptoSupport.h */; };
                21B830A71D8A641F00AE2001 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 37538E131D7A43B600226BE4 /* libicucore.dylib */; };
                21B830A81D8A642200AE2001 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 37538E131D7A43B600226BE4 /* libicucore.dylib */; };
-               21DCD05C1461B23700702FC8 /* CryptoAlg.c in Sources */ = {isa = PBXBuildFile; fileRef = 21A57F4A145B2AE100939099 /* CryptoAlg.c */; };
                21DED43515702C0F0060B6B9 /* DNSProxySupport.c in Sources */ = {isa = PBXBuildFile; fileRef = 21DED43415702C0F0060B6B9 /* DNSProxySupport.c */; };
                21F51DC11B3541940070B05C /* com.apple.mDNSResponder.plist in CopyFiles */ = {isa = PBXBuildFile; fileRef = 21F51DBF1B35412D0070B05C /* com.apple.mDNSResponder.plist */; };
                21F51DC31B3541F50070B05C /* com.apple.mDNSResponderHelper.plist in CopyFiles */ = {isa = PBXBuildFile; fileRef = 21F51DBE1B3541030070B05C /* com.apple.mDNSResponderHelper.plist */; };
                4AAE0C9A0C68EA81003882A5 /* mDNSResponderHelper.8 in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4AAE0C7A0C68E97F003882A5 /* mDNSResponderHelper.8 */; };
                4BD2B63A134FE09F002B96D5 /* P2PPacketFilter.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BD2B638134FE09F002B96D5 /* P2PPacketFilter.c */; };
                4BD2B63B134FE09F002B96D5 /* P2PPacketFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BD2B639134FE09F002B96D5 /* P2PPacketFilter.h */; };
+               5A9E8E5D23F47817003B4CAD /* DNSHeuristicsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5A9E8E5C23F47817003B4CAD /* DNSHeuristicsTest.m */; };
+               5A9E8E6223F4BC8A003B4CAD /* DNSHeuristics.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AF23B2823F37309004AB237 /* DNSHeuristics.m */; };
+               5A9E8E6423F4BE29003B4CAD /* mDNSResponderTests-Entitlements.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5A9E8E6323F4BE29003B4CAD /* mDNSResponderTests-Entitlements.plist */; };
+               5AF23B2923F37309004AB237 /* DNSHeuristics.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AF23B2823F37309004AB237 /* DNSHeuristics.m */; };
+               5AF23B2B23F3731F004AB237 /* DNSHeuristics.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AF23B2823F37309004AB237 /* DNSHeuristics.m */; };
+               5AF23B2C23F3732F004AB237 /* DNSHeuristics.h in Headers */ = {isa = PBXBuildFile; fileRef = 5AF23B2A23F37316004AB237 /* DNSHeuristics.h */; };
+               5AF23B2D23F37331004AB237 /* DNSHeuristics.h in Headers */ = {isa = PBXBuildFile; fileRef = 5AF23B2A23F37316004AB237 /* DNSHeuristics.h */; };
+               787E8B74239485D200DC9D01 /* HTTPUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 787E8B72239485D200DC9D01 /* HTTPUtilities.h */; };
+               787E8B75239485D200DC9D01 /* HTTPUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 787E8B72239485D200DC9D01 /* HTTPUtilities.h */; };
+               787E8B76239485D200DC9D01 /* HTTPUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 787E8B73239485D200DC9D01 /* HTTPUtilities.m */; };
+               787E8B77239485D200DC9D01 /* HTTPUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 787E8B73239485D200DC9D01 /* HTTPUtilities.m */; };
                789036921F7AC1FA0077A962 /* libnetwork.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 789036911F7AC1F90077A962 /* libnetwork.tbd */; };
+               78A4903C23343B8500FA2CCA /* dnssd_descriptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 78A4903B23343B8400FA2CCA /* dnssd_descriptions.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; };
+               78BD2BF923FC521C004A3D8B /* dnssd_svcb.c in Sources */ = {isa = PBXBuildFile; fileRef = 78BD2BF723FC521B004A3D8B /* dnssd_svcb.c */; };
+               78BD2BFA23FC521C004A3D8B /* dnssd_svcb.c in Sources */ = {isa = PBXBuildFile; fileRef = 78BD2BF723FC521B004A3D8B /* dnssd_svcb.c */; };
+               78BD2BFC23FC521C004A3D8B /* dnssd_svcb.h in Headers */ = {isa = PBXBuildFile; fileRef = 78BD2BF823FC521B004A3D8B /* dnssd_svcb.h */; };
+               78F4751323342B9700FE942B /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD893CE6206C0EAF0055F9E7 /* CoreFoundation.framework */; };
                848DA5D616547F7200D2E8B4 /* xpc_clients.h in Headers */ = {isa = PBXBuildFile; fileRef = 848DA5D516547F7200D2E8B4 /* xpc_clients.h */; };
                84C5B33C166553F100C324A8 /* dns_services.c in Sources */ = {isa = PBXBuildFile; fileRef = 84C5B339166553AF00C324A8 /* dns_services.c */; };
                84F4C090188F050200D1E1DE /* dns_services.h in Headers */ = {isa = PBXBuildFile; fileRef = 84F4C08F188F04CF00D1E1DE /* dns_services.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               891F2F8A2397EA79001EC153 /* srp-mdns-proxy.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F862397EA79001EC153 /* srp-mdns-proxy.c */; };
+               891F2F8B2397EA79001EC153 /* srp-parse.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F872397EA79001EC153 /* srp-parse.c */; };
+               891F2FA72397EE93001EC153 /* mDNSEmbeddedAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 654BE64F02B63B93000001D1 /* mDNSEmbeddedAPI.h */; };
+               892936D4246AC49D00548526 /* Network.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B7A861A5212B40BC00E81CC3 /* Network.framework */; };
+               892EF6D4246B5B8700DB9EA1 /* CoreUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD9BA7571EAF929C00658CCF /* CoreUtils.framework */; };
+               892EF6D6246DBF7F00DB9EA1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 892EF6D5246DBF7F00DB9EA1 /* UIKit.framework */; };
+               892EF6D8246DBF9F00DB9EA1 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 892EF6D7246DBF9F00DB9EA1 /* CoreGraphics.framework */; };
+               892EF6DA246DBFB800DB9EA1 /* Preferences.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 892EF6D9246DBFB800DB9EA1 /* Preferences.framework */; };
+               892EF6DC246DBFF800DB9EA1 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 892EF6DB246DBFF800DB9EA1 /* SystemConfiguration.framework */; };
+               892EF6E2246DC02A00DB9EA1 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 892EF6E1246DC02A00DB9EA1 /* CFNetwork.framework */; };
+               892EF6E6246DC10600DB9EA1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 892EF6E5246DC10600DB9EA1 /* QuartzCore.framework */; };
+               892EF6E9246DCD7600DB9EA1 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B799C9672385A5AA00675816 /* CoreServices.framework */; };
+               892EF6EF246DCE2600DB9EA1 /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 892EF6EE246DCE2600DB9EA1 /* SafariServices.framework */; };
+               892EF6F1246DD00000DB9EA1 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 892EF6F0246DD00000DB9EA1 /* AppKit.framework */; };
+               8934D09123FE395F00403EA6 /* libipconfig.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = F910D36823FE06D7007AA74C /* libipconfig.tbd */; };
+               8934D09223FE397800403EA6 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD893CE4206C0D980055F9E7 /* SystemConfiguration.framework */; };
+               8940E58823A1ABCF0060F8C8 /* srp.h in Headers */ = {isa = PBXBuildFile; fileRef = 891F2FA42397EC47001EC153 /* srp.h */; };
+               8940E58923A1ABCF0060F8C8 /* dns-msg.h in Headers */ = {isa = PBXBuildFile; fileRef = 891F2F952397EB30001EC153 /* dns-msg.h */; };
+               8940E58A23A1ABCF0060F8C8 /* ioloop.h in Headers */ = {isa = PBXBuildFile; fileRef = 891F2F902397EB30001EC153 /* ioloop.h */; };
+               89458DA123A1AA6100C3978D /* srp-client.c in Sources */ = {isa = PBXBuildFile; fileRef = 89458D9F23A1AA5000C3978D /* srp-client.c */; };
+               89458DA223A1AA6100C3978D /* srp-ioloop.c in Sources */ = {isa = PBXBuildFile; fileRef = 89458DA023A1AA5000C3978D /* srp-ioloop.c */; };
                894A55D722C438AF008CDEA1 /* bats_test_proxy.sh in Resources */ = {isa = PBXBuildFile; fileRef = 894A55D622C438AF008CDEA1 /* bats_test_proxy.sh */; };
+               89511E4B23C5FE3B00D603D4 /* advertising_proxy_services.h in Headers */ = {isa = PBXBuildFile; fileRef = 89511E4923C5FE3B00D603D4 /* advertising_proxy_services.h */; };
+               89511E4C23C5FE3B00D603D4 /* advertising_proxy_services.h in Headers */ = {isa = PBXBuildFile; fileRef = 89511E4923C5FE3B00D603D4 /* advertising_proxy_services.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               89511E4D23C5FE3B00D603D4 /* advertising_proxy_services.c in Sources */ = {isa = PBXBuildFile; fileRef = 89511E4A23C5FE3B00D603D4 /* advertising_proxy_services.c */; };
+               89511E4E23C5FE3B00D603D4 /* advertising_proxy_services.c in Sources */ = {isa = PBXBuildFile; fileRef = 89511E4A23C5FE3B00D603D4 /* advertising_proxy_services.c */; };
+               8954B0362406F4A80032D78F /* cti-services.h in Headers */ = {isa = PBXBuildFile; fileRef = 8954B0342406F4A80032D78F /* cti-services.h */; };
+               8954B0372406F4A80032D78F /* cti-services.c in Sources */ = {isa = PBXBuildFile; fileRef = 8954B0352406F4A80032D78F /* cti-services.c */; };
+               895A278224CC6F6900697AB1 /* cti-services.c in Sources */ = {isa = PBXBuildFile; fileRef = 8954B0352406F4A80032D78F /* cti-services.c */; };
+               895A278324D464C300697AB1 /* DNSMessage.c in Sources */ = {isa = PBXBuildFile; fileRef = BD97754D221D64BF00F68FFC /* DNSMessage.c */; };
+               895A278524D4660400697AB1 /* DebugServices.c in Sources */ = {isa = PBXBuildFile; fileRef = 895A278424D465FC00697AB1 /* DebugServices.c */; };
+               895A390424F47F6D0064C387 /* advertising_proxy_services.c in Sources */ = {isa = PBXBuildFile; fileRef = 89511E4A23C5FE3B00D603D4 /* advertising_proxy_services.c */; };
                897729B22202A5370018FAEB /* dso.c in Sources */ = {isa = PBXBuildFile; fileRef = 897729AE2202A5370018FAEB /* dso.c */; };
                897729B32202A5370018FAEB /* dso-transport.c in Sources */ = {isa = PBXBuildFile; fileRef = 897729AF2202A5370018FAEB /* dso-transport.c */; };
                897729B42202A5370018FAEB /* dso-transport.h in Headers */ = {isa = PBXBuildFile; fileRef = 897729B02202A5370018FAEB /* dso-transport.h */; };
                898E98392203633800812DC6 /* dnssd_clientshim.c in Sources */ = {isa = PBXBuildFile; fileRef = 897729B62202A5480018FAEB /* dnssd_clientshim.c */; };
                898E983A2203633800812DC6 /* dso-transport.c in Sources */ = {isa = PBXBuildFile; fileRef = 897729AF2202A5370018FAEB /* dso-transport.c */; };
                898E983B2203633800812DC6 /* dso.c in Sources */ = {isa = PBXBuildFile; fileRef = 897729AE2202A5370018FAEB /* dso.c */; };
+               8990BCD223E341E500ECA799 /* xpc_client_advertising_proxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 89B0BF3723C55A4400245A42 /* xpc_client_advertising_proxy.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               8996E15023E217E0004DB7FA /* posix.c in Sources */ = {isa = PBXBuildFile; fileRef = 89D4BBFA239EB1CE00FCFB7E /* posix.c */; };
+               8996E15123E217E0004DB7FA /* fromwire.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F932397EB30001EC153 /* fromwire.c */; };
+               8996E15223E217E0004DB7FA /* macos-ioloop.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F8D2397EB30001EC153 /* macos-ioloop.c */; };
+               8996E15323E217E0004DB7FA /* verify-macos.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F912397EB30001EC153 /* verify-macos.c */; };
+               8996E15423E217E0004DB7FA /* wireutils.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F942397EB30001EC153 /* wireutils.c */; };
+               8996E15523E21808004DB7FA /* towire.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F8F2397EB30001EC153 /* towire.c */; };
+               8996E15623E21813004DB7FA /* sign-macos.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F8E2397EB30001EC153 /* sign-macos.c */; };
+               8996E15723E2182F004DB7FA /* posix.c in Sources */ = {isa = PBXBuildFile; fileRef = 89D4BBFA239EB1CE00FCFB7E /* posix.c */; };
+               8996E15823E2185A004DB7FA /* macos-ioloop.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F8D2397EB30001EC153 /* macos-ioloop.c */; };
+               8996E15923E2185A004DB7FA /* sign-macos.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F8E2397EB30001EC153 /* sign-macos.c */; };
+               8996E15A23E2185A004DB7FA /* towire.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F8F2397EB30001EC153 /* towire.c */; };
+               8996E15B23E2185A004DB7FA /* wireutils.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F942397EB30001EC153 /* wireutils.c */; };
+               8998717723C6987A00C3AF39 /* srputil.c in Sources */ = {isa = PBXBuildFile; fileRef = 8998717523C6977E00C3AF39 /* srputil.c */; };
+               89AFC1E623F5DE2B00538084 /* ra-tester.c in Sources */ = {isa = PBXBuildFile; fileRef = 89AFC1E523F5DE2B00538084 /* ra-tester.c */; };
+               89AFC1EA23F5E00500538084 /* route.c in Sources */ = {isa = PBXBuildFile; fileRef = 89C38ECB23C93B6600800A42 /* route.c */; };
+               89AFC1EB23F5E00C00538084 /* macos-ioloop.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F8D2397EB30001EC153 /* macos-ioloop.c */; };
+               89AFC1EC23F5E01600538084 /* fromwire.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F932397EB30001EC153 /* fromwire.c */; };
+               89AFC1ED23F5E01E00538084 /* towire.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F8F2397EB30001EC153 /* towire.c */; };
+               89AFC1F323F5E0E100538084 /* Network.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B7BAFB5A23A2A27F0045705F /* Network.framework */; };
+               89AFC1F423F5E2A000538084 /* posix.c in Sources */ = {isa = PBXBuildFile; fileRef = 89D4BBFA239EB1CE00FCFB7E /* posix.c */; };
+               89AFC1F523F5E2A900538084 /* sign-macos.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F8E2397EB30001EC153 /* sign-macos.c */; };
+               89AFC1F623F5E2BD00538084 /* wireutils.c in Sources */ = {isa = PBXBuildFile; fileRef = 891F2F942397EB30001EC153 /* wireutils.c */; };
+               89B0BF3823C55A4C00245A42 /* xpc_client_advertising_proxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 89B0BF3723C55A4400245A42 /* xpc_client_advertising_proxy.h */; };
+               89B2979523C6A50700FEEA56 /* com.apple.srp-mdns-proxy.plist in CopyFiles */ = {isa = PBXBuildFile; fileRef = 89D9064B23C68BA300A45D6F /* com.apple.srp-mdns-proxy.plist */; };
+               89C277F2246B01AC001F3828 /* Network.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B7A861A5212B40BC00E81CC3 /* Network.framework */; };
+               89C277F3246B025A001F3828 /* CoreUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD9BA7571EAF929C00658CCF /* CoreUtils.framework */; };
+               89C277F4246B0260001F3828 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDF1CEE522F1787E009AB795 /* Security.framework */; };
+               89C38ECC23C93B9400800A42 /* route.c in Sources */ = {isa = PBXBuildFile; fileRef = 89C38ECB23C93B6600800A42 /* route.c */; };
+               89D2E11524297ABD002E4C8B /* advertising_proxy_services.c in Sources */ = {isa = PBXBuildFile; fileRef = 89511E4A23C5FE3B00D603D4 /* advertising_proxy_services.c */; };
+               89D3B57E2463363900FA67EB /* com.apple.srp-mdns-proxy.plist in Copy AppleInternal Logging Profile */ = {isa = PBXBuildFile; fileRef = 89D3B57D2463363900FA67EB /* com.apple.srp-mdns-proxy.plist */; };
+               89D3B5812463369F00FA67EB /* com.apple.srp-mdns-proxy.plist in Copy Base Logging Profile */ = {isa = PBXBuildFile; fileRef = 89D3B57F2463365B00FA67EB /* com.apple.srp-mdns-proxy.plist */; };
+               89D4BBEF239EA83400FCFB7E /* DNSCommon.h in Headers */ = {isa = PBXBuildFile; fileRef = B79FA162211CF0AF00B7861E /* DNSCommon.h */; };
+               89D4BBF0239EA83400FCFB7E /* mDNSEmbeddedAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = B79FA164211CF0AF00B7861E /* mDNSEmbeddedAPI.h */; };
+               AA48E83E240DEADD007918D7 /* mdns_tlv.c in Sources */ = {isa = PBXBuildFile; fileRef = BDFE3FFB2408C58B00C77011 /* mdns_tlv.c */; };
+               AA48E83F240DEADD007918D7 /* mdns_xpc.c in Sources */ = {isa = PBXBuildFile; fileRef = BDFE400D2408F62E00C77011 /* mdns_xpc.c */; };
                B7016F521D5D0D2900107E7C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B7016F511D5D0D2900107E7C /* Localizable.strings */; };
                B701E7041D9DD811008F3022 /* BonjourSCStore.m in Sources */ = {isa = PBXBuildFile; fileRef = B701E7031D9DD811008F3022 /* BonjourSCStore.m */; };
                B7024D171E82FA9500312DEF /* com.apple.preference.bonjour.tool.xpc in CopyFiles */ = {isa = PBXBuildFile; fileRef = B76783AC1E82D65900DA271E /* com.apple.preference.bonjour.tool.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
                B71C8B091E79F2CD00E99939 /* BonjourSCStore.m in Sources */ = {isa = PBXBuildFile; fileRef = B701E7031D9DD811008F3022 /* BonjourSCStore.m */; };
+               B72BC06323ABFD8C0021E0C9 /* bundle_utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = B72BC06123ABFD8C0021E0C9 /* bundle_utilities.m */; };
+               B72BC06423ABFD8C0021E0C9 /* bundle_utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = B72BC06223ABFD8C0021E0C9 /* bundle_utilities.h */; };
                B72D38B41ECB96ED00B10E39 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B72D38B31ECB96ED00B10E39 /* Localizable.strings */; };
                B7325FE81DA4737400663834 /* CNBrowseDomainsController.m in Sources */ = {isa = PBXBuildFile; fileRef = B7325FE71DA4737400663834 /* CNBrowseDomainsController.m */; };
                B7325FF71DA47F9100663834 /* CNDomainBrowserViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = B7A214081D1B29D6005F7DD9 /* CNDomainBrowserViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
                B7325FF91DA47FB000663834 /* _CNDomainBrowser.m in Sources */ = {isa = PBXBuildFile; fileRef = B7D6CA5E1D107573005E24CF /* _CNDomainBrowser.m */; };
                B7325FFD1DA4809400663834 /* DomainBrowser.h in Headers */ = {isa = PBXBuildFile; fileRef = B74EC1271D494C5800A1D155 /* DomainBrowser.h */; settings = {ATTRIBUTES = (Public, ); }; };
                B7325FFF1DA480A500663834 /* DomainBrowser.strings in Resources */ = {isa = PBXBuildFile; fileRef = B7016F4F1D5D0D1900107E7C /* DomainBrowser.strings */; };
+               B737059322CD65E500477EB9 /* dnssd_analytics.c in Sources */ = {isa = PBXBuildFile; fileRef = B737059122CD65E400477EB9 /* dnssd_analytics.c */; };
+               B737059422CD65E500477EB9 /* dnssd_analytics.h in Headers */ = {isa = PBXBuildFile; fileRef = B737059222CD65E400477EB9 /* dnssd_analytics.h */; };
+               B73C5CCF24B78EE80002050A /* setup_assistant_helper.h in Headers */ = {isa = PBXBuildFile; fileRef = B73C5CCD24B78EE70002050A /* setup_assistant_helper.h */; };
+               B73C5CD024B78EE80002050A /* setup_assistant_helper.m in Sources */ = {isa = PBXBuildFile; fileRef = B73C5CCE24B78EE70002050A /* setup_assistant_helper.m */; };
                B7473E671EC3954400D31B9D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B7473E661EC3954400D31B9D /* AppDelegate.m */; };
                B7473E691EC3954400D31B9D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B7473E681EC3954400D31B9D /* main.m */; };
                B7473E6C1EC3954400D31B9D /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B7473E6B1EC3954400D31B9D /* ViewController.m */; };
                B7473E971EC3C77D00D31B9D /* CNDomainBrowserPathUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = B799209E1DA6BA8E00C6E02B /* CNDomainBrowserPathUtils.m */; };
                B7473E981EC3C78300D31B9D /* _CNDomainBrowser.m in Sources */ = {isa = PBXBuildFile; fileRef = B7D6CA5E1D107573005E24CF /* _CNDomainBrowser.m */; };
                B7473E991EC3C86600D31B9D /* ClientCommon.c in Sources */ = {isa = PBXBuildFile; fileRef = FF5852100DD27BD300862BDF /* ClientCommon.c */; };
-               B74A96261DD4EDE60084A8C5 /* Preferences.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B74A96251DD4EDE60084A8C5 /* Preferences.framework */; };
-               B74BF92E2322E97400E35354 /* SuspiciousReplyTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B74BF92D2322E97400E35354 /* SuspiciousReplyTest.m */; };
                B74BF931232701F600E35354 /* CacheOrderTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B74BF930232701F600E35354 /* CacheOrderTest.m */; };
+               B74ECA1824134BDC001DDFE8 /* CoreUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD9BA7571EAF929C00658CCF /* CoreUtils.framework */; };
+               B74ECA1924134C5F001DDFE8 /* CoreUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD9BA7571EAF929C00658CCF /* CoreUtils.framework */; };
                B74F16F4211BA55400BEBE84 /* DNSMessageTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B74F16F3211BA55400BEBE84 /* DNSMessageTest.m */; };
                B74F2B461E82FEAE0084960E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDA3F0891C48DB910054FB4B /* Foundation.framework */; };
                B74F2B471E82FEFE0084960E /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F869685066EE02400D2A2DC /* Security.framework */; };
+               B756FA9122D502BC0052F12C /* dnssd_analytics.c in Sources */ = {isa = PBXBuildFile; fileRef = B737059122CD65E400477EB9 /* dnssd_analytics.c */; };
                B75700241E8347A6005CD56C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = B75700231E8347A6005CD56C /* InfoPlist.strings */; };
+               B75B9DA223AD566D00349070 /* bundle_utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = B72BC06123ABFD8C0021E0C9 /* bundle_utilities.m */; };
+               B75B9DA323AD567D00349070 /* bundle_utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = B72BC06123ABFD8C0021E0C9 /* bundle_utilities.m */; };
                B76373761ECA25DE00B9404A /* CNServiceBrowserView.m in Sources */ = {isa = PBXBuildFile; fileRef = B76373751ECA25DE00B9404A /* CNServiceBrowserView.m */; };
                B764319F1DB0423800DB376D /* ClientCommon.c in Sources */ = {isa = PBXBuildFile; fileRef = FF5852100DD27BD300862BDF /* ClientCommon.c */; };
                B76431A01DB0423900DB376D /* ClientCommon.c in Sources */ = {isa = PBXBuildFile; fileRef = FF5852100DD27BD300862BDF /* ClientCommon.c */; };
                B79920A21DA6BA8E00C6E02B /* CNDomainBrowserPathUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = B799209E1DA6BA8E00C6E02B /* CNDomainBrowserPathUtils.m */; };
                B79920A41DA6C49700C6E02B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B79920A31DA6C49700C6E02B /* Assets.xcassets */; };
                B79FA14B211CE8CA00B7861E /* DNSCommon.c in Sources */ = {isa = PBXBuildFile; fileRef = 7F18A9F60587CEF6001880B3 /* DNSCommon.c */; };
+               B7A5C3412399D74E00615DBD /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B799C9672385A5AA00675816 /* CoreServices.framework */; };
+               B7A5C343239B040900615DBD /* dnssd_xpc.c in Sources */ = {isa = PBXBuildFile; fileRef = BD11266E21DB1AFE006115E6 /* dnssd_xpc.c */; };
                B7A8618921274BFC00E81CC3 /* ResourceRecordTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B7A8618821274BFC00E81CC3 /* ResourceRecordTest.m */; };
                B7A8618B21274FA200E81CC3 /* mDNSCoreReceiveTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B7A8618A21274FA200E81CC3 /* mDNSCoreReceiveTest.m */; };
                B7A861952127806600E81CC3 /* unittest_common.c in Sources */ = {isa = PBXBuildFile; fileRef = 0C7C00491DD553490078BA89 /* unittest_common.c */; };
                B7A861A7212B410D00E81CC3 /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B7A861A4212B40BC00E81CC3 /* libxml2.tbd */; };
                B7A861A8212B411200E81CC3 /* libicucore.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B7A861A6212B40BC00E81CC3 /* libicucore.tbd */; };
                B7A861A9212B411600E81CC3 /* Network.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B7A861A5212B40BC00E81CC3 /* Network.framework */; };
+               B7BAFB4823A0322E0045705F /* mDNSResponder.plist in Copy Feature Flags */ = {isa = PBXBuildFile; fileRef = B7A5C33A2399A24C00615DBD /* mDNSResponder.plist */; };
+               B7BAFB4A23A033D80045705F /* mdns_trust_checks.m in Sources */ = {isa = PBXBuildFile; fileRef = B7BAFB4923A033D80045705F /* mdns_trust_checks.m */; };
+               B7BAFB4C23A035150045705F /* system_utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = B7BAFB4B23A035140045705F /* system_utilities.m */; };
+               B7BAFB4E23A0453A0045705F /* system_utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = B7BAFB4B23A035140045705F /* system_utilities.m */; };
+               B7BAFB5423A2A2080045705F /* libarchive.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D470807222123E44006BAB32 /* libarchive.tbd */; };
+               B7BAFB5523A2A2390045705F /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD893CE6206C0EAF0055F9E7 /* CoreFoundation.framework */; };
+               B7BAFB5623A2A2460045705F /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDF1CEE522F1787E009AB795 /* Security.framework */; };
+               B7BAFB5723A2A2600045705F /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD893CE6206C0EAF0055F9E7 /* CoreFoundation.framework */; };
+               B7BAFB5823A2A26C0045705F /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDF1CEE522F1787E009AB795 /* Security.framework */; settings = {ATTRIBUTES = (Required, ); }; };
                B7CEF6131F6354AA008B08D3 /* ToolbarItemIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = B7CEF6121F635404008B08D3 /* ToolbarItemIcon.png */; };
                B7D566C91E81DA0000E43008 /* com.apple.preference.bonjour.remoteservice.xpc in CopyFiles */ = {isa = PBXBuildFile; fileRef = B7D566BA1E81D8FD00E43008 /* com.apple.preference.bonjour.remoteservice.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
                B7D566CD1E81DDB600E43008 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDA3F0891C48DB910054FB4B /* Foundation.framework */; };
                B7E06B0D1DBA9DFE00E4580C /* ClientCommon.c in Sources */ = {isa = PBXBuildFile; fileRef = FF5852100DD27BD300862BDF /* ClientCommon.c */; };
                B7E06B0E1DBA9E9700E4580C /* DomainBrowser.strings in Resources */ = {isa = PBXBuildFile; fileRef = B7016F4F1D5D0D1900107E7C /* DomainBrowser.strings */; };
                B7E06CE12329B0480021401F /* LocalOnlyWithInterfacesTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B7E06CDF2329AA7B0021401F /* LocalOnlyWithInterfacesTest.m */; };
+               B7E8092D23F70EFE002CB309 /* mdns_trust_checks.h in Headers */ = {isa = PBXBuildFile; fileRef = B7E8092C23F70EF6002CB309 /* mdns_trust_checks.h */; };
                B7EEF7C1212601460093828F /* mDNSMacOSX.c in Sources */ = {isa = PBXBuildFile; fileRef = 6575FBEB022EAF7200000109 /* mDNSMacOSX.c */; };
                B7EEF7C2212602EC0093828F /* mDNS.c in Sources */ = {isa = PBXBuildFile; fileRef = 6575FBE9022EAF5A00000109 /* mDNS.c */; };
                B7EEF7C4212603B20093828F /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD893CE4206C0D980055F9E7 /* SystemConfiguration.framework */; };
-               B7EEF7C5212603D50093828F /* CryptoAlg.c in Sources */ = {isa = PBXBuildFile; fileRef = 21A57F4A145B2AE100939099 /* CryptoAlg.c */; };
                B7EEF7CA2126046A0093828F /* uDNS.c in Sources */ = {isa = PBXBuildFile; fileRef = 7F18A9F70587CEF6001880B3 /* uDNS.c */; };
                B7EEF7CB2126048F0093828F /* SymptomReporter.c in Sources */ = {isa = PBXBuildFile; fileRef = BD03E88C1AD31278005E8A81 /* SymptomReporter.c */; };
                B7EEF7CC212604A80093828F /* LegacyNATTraversal.c in Sources */ = {isa = PBXBuildFile; fileRef = 7FC8F9D406D14E66007E879D /* LegacyNATTraversal.c */; };
                B7EEF7CD212604CB0093828F /* DNSDigest.c in Sources */ = {isa = PBXBuildFile; fileRef = 7F461DB5062DBF2900672BF3 /* DNSDigest.c */; };
-               B7EEF7D0212605170093828F /* D2D.c in Sources */ = {isa = PBXBuildFile; fileRef = 22C7633D1D777B8C0077AFCA /* D2D.c */; };
-               B7EEF7D6212606F50093828F /* uDNSPathEvalulation.c in Sources */ = {isa = PBXBuildFile; fileRef = 216D9ACD1720C9F5008066E1 /* uDNSPathEvalulation.c */; };
+               B7EEF7D6212606F50093828F /* uDNSPathEvaluation.c in Sources */ = {isa = PBXBuildFile; fileRef = 216D9ACD1720C9F5008066E1 /* uDNSPathEvaluation.c */; };
                B7EEF7D7212607520093828F /* mDNSDebug.c in Sources */ = {isa = PBXBuildFile; fileRef = DBAAFE29057E8F4D0085CAD0 /* mDNSDebug.c */; };
-               B7EEF7D82126076F0093828F /* dnssec.c in Sources */ = {isa = PBXBuildFile; fileRef = 213BDC6C147319F400000896 /* dnssec.c */; };
-               B7EEF7D9212607C40093828F /* nsec.c in Sources */ = {isa = PBXBuildFile; fileRef = 2124FA321471E9DE0021D7BB /* nsec.c */; };
-               B7EEF7DA212608F50093828F /* nsec3.c in Sources */ = {isa = PBXBuildFile; fileRef = 2127A47515C3C7B900A857FC /* nsec3.c */; };
-               B7EEF7DB2126090D0093828F /* DNSSECSupport.c in Sources */ = {isa = PBXBuildFile; fileRef = 21070E5D16486B9000A69507 /* DNSSECSupport.c */; };
                B7EEF7E021260A1F0093828F /* helper-stubs.c in Sources */ = {isa = PBXBuildFile; fileRef = 2E96A52D0C39C1A50087C4D2 /* helper-stubs.c */; };
                B7EEF7E221260A610093828F /* PlatformCommon.c in Sources */ = {isa = PBXBuildFile; fileRef = FFCB6D73075D539900B8AF62 /* PlatformCommon.c */; };
                B7EEF7E421260DC90093828F /* dnssd_ipc.c in Sources */ = {isa = PBXBuildFile; fileRef = F5E11B5A04A28126019798ED /* dnssd_ipc.c */; };
                B7EEF7E521260DE80093828F /* daemon.c in Sources */ = {isa = PBXBuildFile; fileRef = 6575FBEC022EAF7200000109 /* daemon.c */; };
-               B7EEF7E921260E4C0093828F /* CryptoSupport.c in Sources */ = {isa = PBXBuildFile; fileRef = 21A57F51145B2B1400939099 /* CryptoSupport.c */; };
                B7EEF7EA212613260093828F /* uds_daemon.c in Sources */ = {isa = PBXBuildFile; fileRef = F525E72804AA167501F1CF4D /* uds_daemon.c */; };
                B7EEF7EC212613D10093828F /* dnsproxy.c in Sources */ = {isa = PBXBuildFile; fileRef = 218E8E4F156D8C0300720DA0 /* dnsproxy.c */; };
                B7EEF7ED212613D50093828F /* DNSProxySupport.c in Sources */ = {isa = PBXBuildFile; fileRef = 21DED43415702C0F0060B6B9 /* DNSProxySupport.c */; };
                B7F1FEDE234F89C50081159C /* TestUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = B7F1FEDC234F89C50081159C /* TestUtils.h */; };
                B7F1FEDF234F89C50081159C /* TestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = B7F1FEDD234F89C50081159C /* TestUtils.m */; };
+               B7F707A823971FD700A31B5A /* dnssd.c in Sources */ = {isa = PBXBuildFile; fileRef = BD11267721DB2A9A006115E6 /* dnssd.c */; };
+               B7F707AA23972EBF00A31B5A /* libbsm.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B7F707A923972EBE00A31B5A /* libbsm.tbd */; };
+               B7F9AB7B237F4F4300C2BEA2 /* mdns_trust.c in Sources */ = {isa = PBXBuildFile; fileRef = B7F9AB79237F4F4300C2BEA2 /* mdns_trust.c */; };
+               B7F9AB7D237F4F5000C2BEA2 /* mdns_trust.h in Headers */ = {isa = PBXBuildFile; fileRef = B7F9AB7A237F4F4300C2BEA2 /* mdns_trust.h */; };
                BD03E88D1AD31278005E8A81 /* SymptomReporter.c in Sources */ = {isa = PBXBuildFile; fileRef = BD03E88C1AD31278005E8A81 /* SymptomReporter.c */; };
+               BD0FFC702502E78A00B6DB73 /* mdns_managed_defaults.h in Headers */ = {isa = PBXBuildFile; fileRef = BD0FFC6E2502E78900B6DB73 /* mdns_managed_defaults.h */; };
+               BD0FFC712502E78A00B6DB73 /* mdns_managed_defaults.h in Headers */ = {isa = PBXBuildFile; fileRef = BD0FFC6E2502E78900B6DB73 /* mdns_managed_defaults.h */; };
+               BD0FFC722502E78A00B6DB73 /* mdns_managed_defaults.c in Sources */ = {isa = PBXBuildFile; fileRef = BD0FFC6F2502E78A00B6DB73 /* mdns_managed_defaults.c */; };
+               BD0FFC732502E78A00B6DB73 /* mdns_managed_defaults.c in Sources */ = {isa = PBXBuildFile; fileRef = BD0FFC6F2502E78A00B6DB73 /* mdns_managed_defaults.c */; };
                BD11267121DB1B25006115E6 /* dnssd_server.h in Headers */ = {isa = PBXBuildFile; fileRef = BD11267021DB1AFE006115E6 /* dnssd_server.h */; };
                BD11267221DB1B29006115E6 /* dnssd_xpc.h in Headers */ = {isa = PBXBuildFile; fileRef = BD11266D21DB1AFE006115E6 /* dnssd_xpc.h */; };
                BD11267321DB1B34006115E6 /* dnssd_server.c in Sources */ = {isa = PBXBuildFile; fileRef = BD11266F21DB1AFE006115E6 /* dnssd_server.c */; };
                BD11267421DB1B4D006115E6 /* dnssd_xpc.c in Sources */ = {isa = PBXBuildFile; fileRef = BD11266E21DB1AFE006115E6 /* dnssd_xpc.c */; };
                BD11267821DB2A9B006115E6 /* dnssd_private.h in Headers */ = {isa = PBXBuildFile; fileRef = BD11267521DB2A9A006115E6 /* dnssd_private.h */; settings = {ATTRIBUTES = (Private, ); }; };
                BD11267921DB2A9B006115E6 /* dnssd_object.m in Sources */ = {isa = PBXBuildFile; fileRef = BD11267621DB2A9A006115E6 /* dnssd_object.m */; };
-               BD11267A21DB2A9B006115E6 /* dnssd.c in Sources */ = {isa = PBXBuildFile; fileRef = BD11267721DB2A9A006115E6 /* dnssd.c */; };
                BD11267C21DB2C7C006115E6 /* dnssd_object.h in Headers */ = {isa = PBXBuildFile; fileRef = BD11267B21DB2C7C006115E6 /* dnssd_object.h */; };
                BD11267E21DB3019006115E6 /* dnssd_xpc.h in Headers */ = {isa = PBXBuildFile; fileRef = BD11266D21DB1AFE006115E6 /* dnssd_xpc.h */; };
-               BD11267F21DB303F006115E6 /* dnssd_xpc.c in Sources */ = {isa = PBXBuildFile; fileRef = BD11266E21DB1AFE006115E6 /* dnssd_xpc.c */; };
+               BD12709324469F1C00BC84D8 /* DNSServerDNSSEC.c in Sources */ = {isa = PBXBuildFile; fileRef = BD12709124469F1C00BC84D8 /* DNSServerDNSSEC.c */; };
+               BD12709424469F1C00BC84D8 /* DNSServerDNSSEC.h in Headers */ = {isa = PBXBuildFile; fileRef = BD12709224469F1C00BC84D8 /* DNSServerDNSSEC.h */; };
                BD1628CF2168B02700020528 /* ClientRequests.h in Headers */ = {isa = PBXBuildFile; fileRef = BD1628CD2168B02600020528 /* ClientRequests.h */; };
                BD1628D02168B02700020528 /* ClientRequests.c in Sources */ = {isa = PBXBuildFile; fileRef = BD1628CE2168B02700020528 /* ClientRequests.c */; };
+               BD1904EE235E759500F146D6 /* mdns_dns_service.h in Headers */ = {isa = PBXBuildFile; fileRef = BD1904EC235E759500F146D6 /* mdns_dns_service.h */; };
+               BD1904EF235E759500F146D6 /* mdns_dns_service.c in Sources */ = {isa = PBXBuildFile; fileRef = BD1904ED235E759500F146D6 /* mdns_dns_service.c */; };
+               BD1904F0235E83D500F146D6 /* mdns_address.c in Sources */ = {isa = PBXBuildFile; fileRef = BDA37F1623471F6100B9266D /* mdns_address.c */; };
+               BD1904F1235E83F700F146D6 /* mdns_address.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA37F1D23471F6100B9266D /* mdns_address.h */; };
+               BD1904F2235E958900F146D6 /* mdns_resolver.c in Sources */ = {isa = PBXBuildFile; fileRef = BDA37F1523471F6100B9266D /* mdns_resolver.c */; };
+               BD1904F3235E958D00F146D6 /* mdns_resolver.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA37F1B23471F6100B9266D /* mdns_resolver.h */; };
+               BD1970B523F90E94001BA06F /* mdns_powerlog.h in Headers */ = {isa = PBXBuildFile; fileRef = BD1970B323F90E94001BA06F /* mdns_powerlog.h */; };
+               BD1970B623F90E94001BA06F /* mdns_powerlog.c in Sources */ = {isa = PBXBuildFile; fileRef = BD1970B423F90E94001BA06F /* mdns_powerlog.c */; };
                BD28AE8F207B892D00F0B257 /* bonjour-mcast-diagnose in Copy diagnose scripts */ = {isa = PBXBuildFile; fileRef = BD28AE8E207B88F600F0B257 /* bonjour-mcast-diagnose */; };
-               BD2A15B9225ED30C00BEA50A /* mdns_private.h in Headers */ = {isa = PBXBuildFile; fileRef = BD2A15B8225ED2E500BEA50A /* mdns_private.h */; };
-               BD2A15BA225ED31C00BEA50A /* mdns.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2A15B7225ED2E500BEA50A /* mdns.c */; };
-               BD2A15BB225ED33C00BEA50A /* mdns_object.m in Sources */ = {isa = PBXBuildFile; fileRef = BD2A15B5225ED2E500BEA50A /* mdns_object.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
                BD2A15BC225ED38600BEA50A /* Network.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B7A861A5212B40BC00E81CC3 /* Network.framework */; };
                BD41B27D203EBE6100A53629 /* dns_sd.h in Headers */ = {isa = PBXBuildFile; fileRef = FFA572630AF190C20055A0F1 /* dns_sd.h */; settings = {ATTRIBUTES = (Public, ); }; };
                BD41F9C4209B60AC0077F8B6 /* libpcap.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = BD41F9C3209B60AC0077F8B6 /* libpcap.tbd */; };
                BD42EE2021DB41970053A651 /* libobjc.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = BD42EE1F21DB41970053A651 /* libobjc.tbd */; };
+               BD4FC0D724A81974000B0B21 /* mdns_message.c in Sources */ = {isa = PBXBuildFile; fileRef = BD4FC0D624A81643000B0B21 /* mdns_message.c */; };
+               BD4FC0D824A81977000B0B21 /* mdns_message.h in Headers */ = {isa = PBXBuildFile; fileRef = BD4FC0D524A81643000B0B21 /* mdns_message.h */; };
+               BD4FC0D924A8D37F000B0B21 /* mdns_message.c in Sources */ = {isa = PBXBuildFile; fileRef = BD4FC0D624A81643000B0B21 /* mdns_message.c */; };
+               BD4FC0DA24A8D382000B0B21 /* mdns_message.h in Headers */ = {isa = PBXBuildFile; fileRef = BD4FC0D524A81643000B0B21 /* mdns_message.h */; };
+               BD52398323F3BBCD004AC878 /* mdns_set.c in Sources */ = {isa = PBXBuildFile; fileRef = BD52398123F3BBCD004AC878 /* mdns_set.c */; };
+               BD52398423F3BBCD004AC878 /* mdns_set.h in Headers */ = {isa = PBXBuildFile; fileRef = BD52398223F3BBCD004AC878 /* mdns_set.h */; };
+               BD52398523F3D17B004AC878 /* mdns_set.c in Sources */ = {isa = PBXBuildFile; fileRef = BD52398123F3BBCD004AC878 /* mdns_set.c */; };
+               BD52398623F3D17E004AC878 /* mdns_set.h in Headers */ = {isa = PBXBuildFile; fileRef = BD52398223F3BBCD004AC878 /* mdns_set.h */; };
+               BD5278D823F69D4B00235117 /* PowerLog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD5278D723F69D4B00235117 /* PowerLog.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
+               BD61858524C6B9B50016A102 /* com.apple.mdns.plist in Copy AppleInternal Logging Profiles */ = {isa = PBXBuildFile; fileRef = BD61858224C6B9240016A102 /* com.apple.mdns.plist */; };
                BD691B2A1ED2F47100E6F317 /* DNS64.c in Sources */ = {isa = PBXBuildFile; fileRef = BD691B281ED2F43200E6F317 /* DNS64.c */; };
                BD691B2B1ED2F4AB00E6F317 /* DNS64.h in Headers */ = {isa = PBXBuildFile; fileRef = BD691B291ED2F43200E6F317 /* DNS64.h */; };
-               BD75E940206ADEF400656ED3 /* com.apple.mDNSResponder.plist in Copy AppleInternal Logging Profile */ = {isa = PBXBuildFile; fileRef = BDB61846206ADDDF00AFF600 /* com.apple.mDNSResponder.plist */; };
+               BD75E940206ADEF400656ED3 /* com.apple.mDNSResponder.plist in Copy AppleInternal Logging Profiles */ = {isa = PBXBuildFile; fileRef = BDB61846206ADDDF00AFF600 /* com.apple.mDNSResponder.plist */; };
+               BD7BFBDB236D5342000456BC /* mdns_symptoms.h in Headers */ = {isa = PBXBuildFile; fileRef = BD7BFBD9236D5342000456BC /* mdns_symptoms.h */; };
+               BD7BFBDC236D5342000456BC /* mdns_symptoms.c in Sources */ = {isa = PBXBuildFile; fileRef = BD7BFBDA236D5342000456BC /* mdns_symptoms.c */; settings = {COMPILER_FLAGS = "-Wno-import-preprocessor-directive-pedantic"; }; };
+               BD7BFBF8236FD019000456BC /* log_mdns.m in Sources */ = {isa = PBXBuildFile; fileRef = BD7BFBF7236FD019000456BC /* log_mdns.m */; };
+               BD7BFBF9236FD5F2000456BC /* DNSMessage.c in Sources */ = {isa = PBXBuildFile; fileRef = BD97754D221D64BF00F68FFC /* DNSMessage.c */; };
+               BD7BFBFA236FD5F7000456BC /* DNSMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = BD97754E221D64BF00F68FFC /* DNSMessage.h */; };
                BD893CE5206C0D980055F9E7 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD893CE4206C0D980055F9E7 /* SystemConfiguration.framework */; };
                BD893CE7206C0EAF0055F9E7 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD893CE6206C0EAF0055F9E7 /* CoreFoundation.framework */; };
-               BD93516E21E369B90078582E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDA3F0891C48DB910054FB4B /* Foundation.framework */; };
                BD97754C221D643600F68FFC /* dnssdutil.c in Sources */ = {isa = PBXBuildFile; fileRef = BD97754B221D643500F68FFC /* dnssdutil.c */; };
                BD97754F221D64BF00F68FFC /* DNSMessage.c in Sources */ = {isa = PBXBuildFile; fileRef = BD97754D221D64BF00F68FFC /* DNSMessage.c */; };
                BD977550221D64BF00F68FFC /* DNSMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = BD97754E221D64BF00F68FFC /* DNSMessage.h */; };
                BD98A798213A417C0002EC47 /* mDNSResponder.plist in Copy BATS test plist */ = {isa = PBXBuildFile; fileRef = BD98A796213A3EAE0002EC47 /* mDNSResponder.plist */; };
                BD9BA7581EAF929C00658CCF /* CoreUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD9BA7571EAF929C00658CCF /* CoreUtils.framework */; };
+               BDA37F2123471FE600B9266D /* mdns_private.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA37F1E23471F6100B9266D /* mdns_private.h */; };
+               BDA37F222347200900B9266D /* mdns_address.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA37F1D23471F6100B9266D /* mdns_address.h */; };
+               BDA37F232347200F00B9266D /* mdns_interface_monitor.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA37F1A23471F6100B9266D /* mdns_interface_monitor.h */; };
+               BDA37F242347201700B9266D /* mdns_object.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA37F1723471F6100B9266D /* mdns_object.h */; };
+               BDA37F252347201E00B9266D /* mdns_resolver.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA37F1B23471F6100B9266D /* mdns_resolver.h */; };
+               BDA37F262347204F00B9266D /* mdns_interface_monitor.c in Sources */ = {isa = PBXBuildFile; fileRef = BDA37F1223471F6100B9266D /* mdns_interface_monitor.c */; };
+               BDA37F272347206300B9266D /* mdns_object.c in Sources */ = {isa = PBXBuildFile; fileRef = BDA37F1C23471F6100B9266D /* mdns_object.c */; };
+               BDA37F282347207200B9266D /* mdns_helpers.c in Sources */ = {isa = PBXBuildFile; fileRef = BDA37F1F23471F6100B9266D /* mdns_helpers.c */; };
+               BDA37F29234720AC00B9266D /* mdns_objects.m in Sources */ = {isa = PBXBuildFile; fileRef = BDA37F1823471F6100B9266D /* mdns_objects.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+               BDA37F2A2347258C00B9266D /* mdns_address.c in Sources */ = {isa = PBXBuildFile; fileRef = BDA37F1623471F6100B9266D /* mdns_address.c */; };
+               BDA37F2B23472B1500B9266D /* mdns_interface_monitor.c in Sources */ = {isa = PBXBuildFile; fileRef = BDA37F1223471F6100B9266D /* mdns_interface_monitor.c */; };
+               BDA37F2C23472B3B00B9266D /* mdns_object.c in Sources */ = {isa = PBXBuildFile; fileRef = BDA37F1C23471F6100B9266D /* mdns_object.c */; };
+               BDA37F2D23472B4700B9266D /* mdns_helpers.c in Sources */ = {isa = PBXBuildFile; fileRef = BDA37F1F23471F6100B9266D /* mdns_helpers.c */; };
+               BDA37F2E23472B5700B9266D /* mdns_objects.m in Sources */ = {isa = PBXBuildFile; fileRef = BDA37F1823471F6100B9266D /* mdns_objects.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+               BDA37F2F2347387300B9266D /* mdns_interface_monitor.c in Sources */ = {isa = PBXBuildFile; fileRef = BDA37F1223471F6100B9266D /* mdns_interface_monitor.c */; };
+               BDA37F302347389D00B9266D /* mdns_object.c in Sources */ = {isa = PBXBuildFile; fileRef = BDA37F1C23471F6100B9266D /* mdns_object.c */; };
+               BDA37F31234738D500B9266D /* mdns_helpers.c in Sources */ = {isa = PBXBuildFile; fileRef = BDA37F1F23471F6100B9266D /* mdns_helpers.c */; };
+               BDA37F32234738F300B9266D /* mdns_objects.m in Sources */ = {isa = PBXBuildFile; fileRef = BDA37F1823471F6100B9266D /* mdns_objects.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
                BDA3F08A1C48DB920054FB4B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDA3F0891C48DB910054FB4B /* Foundation.framework */; };
                BDA3F08E1C48DCA50054FB4B /* Metrics.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA3F0871C48DB6D0054FB4B /* Metrics.h */; };
                BDA3F08F1C48DCA50054FB4B /* Metrics.m in Sources */ = {isa = PBXBuildFile; fileRef = BDA3F0881C48DB6D0054FB4B /* Metrics.m */; };
                BDA82B8A22BF8A7000A31CBE /* libdns_services.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = BDA82B8922BF8A7000A31CBE /* libdns_services.tbd */; };
                BDA9A7881B3A924C00523835 /* dns_sd_private.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA9A7871B3A923600523835 /* dns_sd_private.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               BDA9C35B2370304E00DC86BD /* QuerierSupport.c in Sources */ = {isa = PBXBuildFile; fileRef = BDA9C35A2370304E00DC86BD /* QuerierSupport.c */; };
+               BDA9C35D2370307600DC86BD /* QuerierSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA9C35C2370307600DC86BD /* QuerierSupport.h */; };
+               BDA9C35E237031BF00DC86BD /* mdns_resolver.c in Sources */ = {isa = PBXBuildFile; fileRef = BDA37F1523471F6100B9266D /* mdns_resolver.c */; };
+               BDA9C35F237031E000DC86BD /* DNSMessage.c in Sources */ = {isa = PBXBuildFile; fileRef = BD97754D221D64BF00F68FFC /* DNSMessage.c */; };
+               BDA9C360237031E400DC86BD /* DNSMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = BD97754E221D64BF00F68FFC /* DNSMessage.h */; };
+               BDA9C361237031FD00DC86BD /* mdns_symptoms.c in Sources */ = {isa = PBXBuildFile; fileRef = BD7BFBDA236D5342000456BC /* mdns_symptoms.c */; };
+               BDA9C3622370320000DC86BD /* mdns_symptoms.h in Headers */ = {isa = PBXBuildFile; fileRef = BD7BFBD9236D5342000456BC /* mdns_symptoms.h */; };
+               BDA9C3632370320D00DC86BD /* mdns_dns_service.c in Sources */ = {isa = PBXBuildFile; fileRef = BD1904ED235E759500F146D6 /* mdns_dns_service.c */; };
+               BDA9C3642370321200DC86BD /* mdns_dns_service.h in Headers */ = {isa = PBXBuildFile; fileRef = BD1904EC235E759500F146D6 /* mdns_dns_service.h */; };
                BDAF4BC020B52D3D0062219E /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDAF4BBF20B52D3D0062219E /* CFNetwork.framework */; };
                BDB61845206ADB9D00AFF600 /* com.apple.mDNSResponder.plist in Copy Base Logging Profile */ = {isa = PBXBuildFile; fileRef = BDB61843206ADB7700AFF600 /* com.apple.mDNSResponder.plist */; };
                BDBF9B941ED74B9C001498A8 /* DNS64State.h in Headers */ = {isa = PBXBuildFile; fileRef = BDBF9B931ED74B8C001498A8 /* DNS64State.h */; };
+               BDEF597A24FDD157001F8CB5 /* dnssd_clientstub_apple.c in Sources */ = {isa = PBXBuildFile; fileRef = BDFE3FFF2408C8D800C77011 /* dnssd_clientstub_apple.c */; };
+               BDEF597B24FDD158001F8CB5 /* dnssd_clientstub_apple.c in Sources */ = {isa = PBXBuildFile; fileRef = BDFE3FFF2408C8D800C77011 /* dnssd_clientstub_apple.c */; };
+               BDEF597C24FDD158001F8CB5 /* dnssd_clientstub_apple.c in Sources */ = {isa = PBXBuildFile; fileRef = BDFE3FFF2408C8D800C77011 /* dnssd_clientstub_apple.c */; };
+               BDEF597D24FDD15B001F8CB5 /* dnssd_clientstub_apple.h in Headers */ = {isa = PBXBuildFile; fileRef = BDFE40002408C8D900C77011 /* dnssd_clientstub_apple.h */; };
+               BDEF597E24FDD15C001F8CB5 /* dnssd_clientstub_apple.h in Headers */ = {isa = PBXBuildFile; fileRef = BDFE40002408C8D900C77011 /* dnssd_clientstub_apple.h */; };
+               BDEF597F24FDD15C001F8CB5 /* dnssd_clientstub_apple.h in Headers */ = {isa = PBXBuildFile; fileRef = BDFE40002408C8D900C77011 /* dnssd_clientstub_apple.h */; };
+               BDF1CEE622F1787E009AB795 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDF1CEE522F1787E009AB795 /* Security.framework */; };
                BDF2D02A2169961900D0DBF5 /* ClientRequests.c in Sources */ = {isa = PBXBuildFile; fileRef = BD1628CE2168B02700020528 /* ClientRequests.c */; };
                BDF2D02C2169B77C00D0DBF5 /* SymptomReporter.h in Headers */ = {isa = PBXBuildFile; fileRef = BDF2D02B2169B77B00D0DBF5 /* SymptomReporter.h */; };
                BDF2D02E216A25E800D0DBF5 /* ApplePlatformFeatures.h in Headers */ = {isa = PBXBuildFile; fileRef = BDF2D02D216A25E800D0DBF5 /* ApplePlatformFeatures.h */; };
+               BDFE3FFD2408C58B00C77011 /* mdns_tlv.c in Sources */ = {isa = PBXBuildFile; fileRef = BDFE3FFB2408C58B00C77011 /* mdns_tlv.c */; };
+               BDFE3FFE2408C58B00C77011 /* mdns_tlv.h in Headers */ = {isa = PBXBuildFile; fileRef = BDFE3FFC2408C58B00C77011 /* mdns_tlv.h */; };
+               BDFE40012408C8D900C77011 /* dnssd_clientstub_apple.c in Sources */ = {isa = PBXBuildFile; fileRef = BDFE3FFF2408C8D800C77011 /* dnssd_clientstub_apple.c */; };
+               BDFE40022408C8D900C77011 /* dnssd_clientstub_apple.h in Headers */ = {isa = PBXBuildFile; fileRef = BDFE40002408C8D900C77011 /* dnssd_clientstub_apple.h */; };
+               BDFE40032408D4B800C77011 /* dnssd_clientstub_apple.c in Sources */ = {isa = PBXBuildFile; fileRef = BDFE3FFF2408C8D800C77011 /* dnssd_clientstub_apple.c */; };
+               BDFE40042408D4B900C77011 /* dnssd_clientstub_apple.c in Sources */ = {isa = PBXBuildFile; fileRef = BDFE3FFF2408C8D800C77011 /* dnssd_clientstub_apple.c */; };
+               BDFE40052408D4BD00C77011 /* dnssd_clientstub_apple.h in Headers */ = {isa = PBXBuildFile; fileRef = BDFE40002408C8D900C77011 /* dnssd_clientstub_apple.h */; };
+               BDFE40062408D4BE00C77011 /* dnssd_clientstub_apple.h in Headers */ = {isa = PBXBuildFile; fileRef = BDFE40002408C8D900C77011 /* dnssd_clientstub_apple.h */; };
+               BDFE40072408D4D400C77011 /* mdns_tlv.c in Sources */ = {isa = PBXBuildFile; fileRef = BDFE3FFB2408C58B00C77011 /* mdns_tlv.c */; };
+               BDFE40082408D4D500C77011 /* mdns_tlv.c in Sources */ = {isa = PBXBuildFile; fileRef = BDFE3FFB2408C58B00C77011 /* mdns_tlv.c */; };
+               BDFE40092408D4D800C77011 /* mdns_tlv.h in Headers */ = {isa = PBXBuildFile; fileRef = BDFE3FFC2408C58B00C77011 /* mdns_tlv.h */; };
+               BDFE400A2408D4D900C77011 /* mdns_tlv.h in Headers */ = {isa = PBXBuildFile; fileRef = BDFE3FFC2408C58B00C77011 /* mdns_tlv.h */; };
+               BDFE400B2408E9DF00C77011 /* mdns_tlv.c in Sources */ = {isa = PBXBuildFile; fileRef = BDFE3FFB2408C58B00C77011 /* mdns_tlv.c */; };
+               BDFE400C2408E9E300C77011 /* mdns_tlv.h in Headers */ = {isa = PBXBuildFile; fileRef = BDFE3FFC2408C58B00C77011 /* mdns_tlv.h */; };
+               BDFE400F2408F62E00C77011 /* mdns_xpc.c in Sources */ = {isa = PBXBuildFile; fileRef = BDFE400D2408F62E00C77011 /* mdns_xpc.c */; };
+               BDFE40102408F62E00C77011 /* mdns_xpc.h in Headers */ = {isa = PBXBuildFile; fileRef = BDFE400E2408F62E00C77011 /* mdns_xpc.h */; };
+               BF0E381624882B650028D528 /* DNSMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = BD97754E221D64BF00F68FFC /* DNSMessage.h */; };
+               BF0E381824882B650028D528 /* DNSMessage.c in Sources */ = {isa = PBXBuildFile; fileRef = BD97754D221D64BF00F68FFC /* DNSMessage.c */; };
+               BF0E381B24882B650028D528 /* CoreUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD9BA7571EAF929C00658CCF /* CoreUtils.framework */; };
+               BF0E382124882BB60028D528 /* log_srp.m in Sources */ = {isa = PBXBuildFile; fileRef = BF0E382024882BB60028D528 /* log_srp.m */; };
                D284BE540ADD80740027CCDF /* dnssd_ipc.h in Headers */ = {isa = PBXBuildFile; fileRef = F5E11B5B04A28126019798ED /* dnssd_ipc.h */; };
                D284BE580ADD80740027CCDF /* mDNS.c in Sources */ = {isa = PBXBuildFile; fileRef = 6575FBE9022EAF5A00000109 /* mDNS.c */; };
                D284BE590ADD80740027CCDF /* uDNS.c in Sources */ = {isa = PBXBuildFile; fileRef = 7F18A9F70587CEF6001880B3 /* uDNS.c */; };
                D284BE5A0ADD80740027CCDF /* DNSCommon.c in Sources */ = {isa = PBXBuildFile; fileRef = 7F18A9F60587CEF6001880B3 /* DNSCommon.c */; };
-               D284BE5B0ADD80740027CCDF /* DNSDigest.c in Sources */ = {isa = PBXBuildFile; fileRef = 7F461DB5062DBF2900672BF3 /* DNSDigest.c */; };
+               D284BE5B0ADD80740027CCDF /* DNSDigest.c in Sources */ = {isa = PBXBuildFile; fileRef = 7F461DB5062DBF2900672BF3 /* DNSDigest.c */; settings = {COMPILER_FLAGS = "-Wno-conversion"; }; };
                D284BE5D0ADD80740027CCDF /* mDNSDebug.c in Sources */ = {isa = PBXBuildFile; fileRef = DBAAFE29057E8F4D0085CAD0 /* mDNSDebug.c */; };
                D284BE5E0ADD80740027CCDF /* uds_daemon.c in Sources */ = {isa = PBXBuildFile; fileRef = F525E72804AA167501F1CF4D /* uds_daemon.c */; };
                D284BE5F0ADD80740027CCDF /* dnssd_ipc.c in Sources */ = {isa = PBXBuildFile; fileRef = F5E11B5A04A28126019798ED /* dnssd_ipc.c */; };
                D284BF040ADD80B00027CCDF /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FF2609FA07B4433800CE10E5 /* Cocoa.framework */; };
                D284BF050ADD80B00027CCDF /* PreferencePanes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FF260A1F07B4436900CE10E5 /* PreferencePanes.framework */; };
                D284BF060ADD80B00027CCDF /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09AB6884FE841BABC02AAC07 /* CoreFoundation.framework */; };
-               D401238B227284FE006C9BBE /* mdns.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2A15B7225ED2E500BEA50A /* mdns.c */; };
-               D401238D22728506006C9BBE /* mdns_private.h in Headers */ = {isa = PBXBuildFile; fileRef = BD2A15B8225ED2E500BEA50A /* mdns_private.h */; };
-               D401238F227286BE006C9BBE /* mdns_object.m in Sources */ = {isa = PBXBuildFile; fileRef = BD2A15B5225ED2E500BEA50A /* mdns_object.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
-               D40123912272B6E3006C9BBE /* mdns_object.m in Sources */ = {isa = PBXBuildFile; fileRef = BD2A15B5225ED2E500BEA50A /* mdns_object.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
-               D40123922272B7A7006C9BBE /* mdns.c in Sources */ = {isa = PBXBuildFile; fileRef = BD2A15B7225ED2E500BEA50A /* mdns.c */; };
+               D41DED3623DFC3C3009F9854 /* base_n.h in Headers */ = {isa = PBXBuildFile; fileRef = D41DED3423DFC3C3009F9854 /* base_n.h */; };
+               D41DED3723DFC3C3009F9854 /* base_n.c in Sources */ = {isa = PBXBuildFile; fileRef = D41DED3523DFC3C3009F9854 /* base_n.c */; };
+               D41DED3A23E0CC40009F9854 /* BaseNEncodingDecodingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = D41DED3923E0CC40009F9854 /* BaseNEncodingDecodingTest.m */; };
+               D41DED3C23E0CD3D009F9854 /* base_n.c in Sources */ = {isa = PBXBuildFile; fileRef = D41DED3523DFC3C3009F9854 /* base_n.c */; };
+               D41DED3F23E0F71D009F9854 /* DigestCalculationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = D41DED3E23E0F71D009F9854 /* DigestCalculationTest.m */; };
+               D41DED4323E21D65009F9854 /* NSEC3HashTest.m in Sources */ = {isa = PBXBuildFile; fileRef = D41DED4223E21D65009F9854 /* NSEC3HashTest.m */; };
                D421B3DF22178BE700D35C20 /* system_utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = D421B3DD22178BE700D35C20 /* system_utilities.h */; };
-               D421B3E022178BE700D35C20 /* system_utilities.c in Sources */ = {isa = PBXBuildFile; fileRef = D421B3DE22178BE700D35C20 /* system_utilities.c */; };
+               D43B121023A442CE00B8D941 /* dnssec_v2_crypto.h in Headers */ = {isa = PBXBuildFile; fileRef = D43B120E23A442CE00B8D941 /* dnssec_v2_crypto.h */; };
+               D43B121223A442CE00B8D941 /* dnssec_v2_crypto.c in Sources */ = {isa = PBXBuildFile; fileRef = D43B120F23A442CE00B8D941 /* dnssec_v2_crypto.c */; };
+               D43B121423ABEFD300B8D941 /* dnssec_v2_crypto.c in Sources */ = {isa = PBXBuildFile; fileRef = D43B120F23A442CE00B8D941 /* dnssec_v2_crypto.c */; };
+               D43B121623BEB1A200B8D941 /* CanonicalMethodsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = D43B121523BEB1A200B8D941 /* CanonicalMethodsTest.m */; };
                D448F7A8222DAB8E0069E1D2 /* bats_test_state_dump.sh in Copy script-based unit tests */ = {isa = PBXBuildFile; fileRef = D448F7A7222DAA1F0069E1D2 /* bats_test_state_dump.sh */; };
-               D448F7A8222DAB8E0069E1D3 /* bats_test_proxy.sh in Copy script-based unit tests */ = {isa = PBXBuildFile; fileRef = 894A55D622C438AF008CDEA1 /* bats_test_proxy.sh */; };
-               D448F7AC222DCB5C0069E1D2 /* libarchive.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D448F7AB222DCB5B0069E1D2 /* libarchive.tbd */; };
-               D448F7AF222DCC8F0069E1D2 /* system_utilities.c in Sources */ = {isa = PBXBuildFile; fileRef = D421B3DE22178BE700D35C20 /* system_utilities.c */; };
                D459F0D4222862BA0056AC5B /* HelperFunctionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = D459F0D3222862BA0056AC5B /* HelperFunctionTest.m */; };
                D461F94A2203A6B400A88910 /* xpc_services.h in Headers */ = {isa = PBXBuildFile; fileRef = D461F9482203A6B400A88910 /* xpc_services.h */; };
                D461F94B2203A6B400A88910 /* xpc_services.c in Sources */ = {isa = PBXBuildFile; fileRef = D461F9492203A6B400A88910 /* xpc_services.c */; };
                D461F9502203A8AA00A88910 /* xpc_service_dns_proxy.c in Sources */ = {isa = PBXBuildFile; fileRef = D461F94E2203A8AA00A88910 /* xpc_service_dns_proxy.c */; };
                D461F9512203A8AA00A88910 /* xpc_service_dns_proxy.c in Sources */ = {isa = PBXBuildFile; fileRef = D461F94E2203A8AA00A88910 /* xpc_service_dns_proxy.c */; };
                D470807322123E44006BAB32 /* libarchive.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D470807222123E44006BAB32 /* libarchive.tbd */; };
+               D473A5112396F1F800D0F827 /* dnssec_v2_trust_anchor.h in Headers */ = {isa = PBXBuildFile; fileRef = D473A50F2396F1F800D0F827 /* dnssec_v2_trust_anchor.h */; };
+               D473A5122396F1F800D0F827 /* dnssec_v2_trust_anchor.c in Sources */ = {isa = PBXBuildFile; fileRef = D473A5102396F1F800D0F827 /* dnssec_v2_trust_anchor.c */; };
+               D473A5132396F1F800D0F827 /* dnssec_v2_trust_anchor.c in Sources */ = {isa = PBXBuildFile; fileRef = D473A5102396F1F800D0F827 /* dnssec_v2_trust_anchor.c */; };
+               D473A51B2397176A00D0F827 /* dnssec_v2_client.h in Headers */ = {isa = PBXBuildFile; fileRef = D473A5192397176A00D0F827 /* dnssec_v2_client.h */; };
+               D473A51C2397176A00D0F827 /* dnssec_v2_client.c in Sources */ = {isa = PBXBuildFile; fileRef = D473A51A2397176A00D0F827 /* dnssec_v2_client.c */; };
+               D473A51D2397176A00D0F827 /* dnssec_v2_client.c in Sources */ = {isa = PBXBuildFile; fileRef = D473A51A2397176A00D0F827 /* dnssec_v2_client.c */; };
+               D473A5202398895E00D0F827 /* dnssec_v2_log.h in Headers */ = {isa = PBXBuildFile; fileRef = D473A51F2398895E00D0F827 /* dnssec_v2_log.h */; };
                D4A1591622E27D43002F6278 /* DNSMessage.c in Sources */ = {isa = PBXBuildFile; fileRef = BD97754D221D64BF00F68FFC /* DNSMessage.c */; };
                D4A1591822E27F61002F6278 /* DNSMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = BD97754E221D64BF00F68FFC /* DNSMessage.h */; };
                D4A9F60521FA797F0079D0C6 /* dns_sd_private.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA9A7871B3A923600523835 /* dns_sd_private.h */; };
                D4CFA7D121E7BA0E00F5AD0E /* liblog_mdnsresponder.m in Sources */ = {isa = PBXBuildFile; fileRef = D4CFA7D021E7BA0E00F5AD0E /* liblog_mdnsresponder.m */; };
                D4CFA7D421E7BA9700F5AD0E /* mDNSFeatures.h in Headers */ = {isa = PBXBuildFile; fileRef = D4CFA7D221E7BA8F00F5AD0E /* mDNSFeatures.h */; };
                D4E219202268E09F00F06AA5 /* bats_test_state_dump.sh in Resources */ = {isa = PBXBuildFile; fileRef = D448F7A7222DAA1F0069E1D2 /* bats_test_state_dump.sh */; };
+               D4ED20B92360D16900632A59 /* list.h in Headers */ = {isa = PBXBuildFile; fileRef = D4ED20B72360D16900632A59 /* list.h */; };
+               D4ED20BA2360D16900632A59 /* list.c in Sources */ = {isa = PBXBuildFile; fileRef = D4ED20B82360D16900632A59 /* list.c */; };
+               D4ED20BB2360D16900632A59 /* list.c in Sources */ = {isa = PBXBuildFile; fileRef = D4ED20B82360D16900632A59 /* list.c */; };
+               D4ED20BE236117F700632A59 /* dnssec_v2.h in Headers */ = {isa = PBXBuildFile; fileRef = D4ED20BC236117F700632A59 /* dnssec_v2.h */; };
+               D4ED20C0236117F700632A59 /* dnssec_v2.c in Sources */ = {isa = PBXBuildFile; fileRef = D4ED20BD236117F700632A59 /* dnssec_v2.c */; };
+               D4ED20C1236117F700632A59 /* dnssec_v2.c in Sources */ = {isa = PBXBuildFile; fileRef = D4ED20BD236117F700632A59 /* dnssec_v2.c */; };
+               D4ED20D023749DA900632A59 /* dnssec_v2_retrieval.h in Headers */ = {isa = PBXBuildFile; fileRef = D4ED20CE23749DA900632A59 /* dnssec_v2_retrieval.h */; };
+               D4ED20D123749DA900632A59 /* dnssec_v2_retrieval.c in Sources */ = {isa = PBXBuildFile; fileRef = D4ED20CF23749DA900632A59 /* dnssec_v2_retrieval.c */; };
+               D4ED20D223749DA900632A59 /* dnssec_v2_retrieval.c in Sources */ = {isa = PBXBuildFile; fileRef = D4ED20CF23749DA900632A59 /* dnssec_v2_retrieval.c */; };
+               D4ED20D42374A03A00632A59 /* dnssec_v2_embedded.h in Headers */ = {isa = PBXBuildFile; fileRef = D4ED20D32374A03A00632A59 /* dnssec_v2_embedded.h */; };
+               D4ED20D72375F99700632A59 /* dnssec_v2_structs.h in Headers */ = {isa = PBXBuildFile; fileRef = D4ED20D52375F99700632A59 /* dnssec_v2_structs.h */; };
+               D4ED20D82375F99700632A59 /* dnssec_v2_structs.c in Sources */ = {isa = PBXBuildFile; fileRef = D4ED20D62375F99700632A59 /* dnssec_v2_structs.c */; };
+               D4ED20D92375F99700632A59 /* dnssec_v2_structs.c in Sources */ = {isa = PBXBuildFile; fileRef = D4ED20D62375F99700632A59 /* dnssec_v2_structs.c */; };
+               D4ED20DC237CC90200632A59 /* dnssec_v2_validation.h in Headers */ = {isa = PBXBuildFile; fileRef = D4ED20DA237CC90200632A59 /* dnssec_v2_validation.h */; };
+               D4ED20DD237CC90200632A59 /* dnssec_v2_validation.c in Sources */ = {isa = PBXBuildFile; fileRef = D4ED20DB237CC90200632A59 /* dnssec_v2_validation.c */; };
+               D4ED20DE237CC90200632A59 /* dnssec_v2_validation.c in Sources */ = {isa = PBXBuildFile; fileRef = D4ED20DB237CC90200632A59 /* dnssec_v2_validation.c */; };
+               D4ED20E1237DCD7000632A59 /* dnssec_v2_helper.h in Headers */ = {isa = PBXBuildFile; fileRef = D4ED20DF237DCD7000632A59 /* dnssec_v2_helper.h */; };
+               D4ED20E2237DCD7000632A59 /* dnssec_v2_helper.c in Sources */ = {isa = PBXBuildFile; fileRef = D4ED20E0237DCD7000632A59 /* dnssec_v2_helper.c */; };
+               D4ED20E3237DCD7000632A59 /* dnssec_v2_helper.c in Sources */ = {isa = PBXBuildFile; fileRef = D4ED20E0237DCD7000632A59 /* dnssec_v2_helper.c */; };
+               D4F2AA44239ADFB10073E461 /* ListTMethodsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = D4F2AA43239ADFB10073E461 /* ListTMethodsTest.m */; };
                D4F2BB2822CD21CB00234A38 /* posix_utilities.c in Sources */ = {isa = PBXBuildFile; fileRef = D4BFF8D922B1C52100A0BA86 /* posix_utilities.c */; };
+               F910D36923FE06D7007AA74C /* libipconfig.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = F910D36823FE06D7007AA74C /* libipconfig.tbd */; };
+               F910D36A23FE06E1007AA74C /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD893CE6206C0EAF0055F9E7 /* CoreFoundation.framework */; };
+               F910D36B23FE076D007AA74C /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD893CE4206C0D980055F9E7 /* SystemConfiguration.framework */; };
                FFA572330AF18F1C0055A0F1 /* dnssd_ipc.c in Sources */ = {isa = PBXBuildFile; fileRef = F5E11B5A04A28126019798ED /* dnssd_ipc.c */; };
                FFA572340AF18F1C0055A0F1 /* dnssd_clientlib.c in Sources */ = {isa = PBXBuildFile; fileRef = FFFA38620AEEDB090065B80A /* dnssd_clientlib.c */; };
                FFA572350AF18F1C0055A0F1 /* dnssd_clientstub.c in Sources */ = {isa = PBXBuildFile; fileRef = FFFA38640AEEDB130065B80A /* dnssd_clientstub.c */; };
                FFA572400AF18F450055A0F1 /* dnssd_clientlib.c in Sources */ = {isa = PBXBuildFile; fileRef = FFFA38620AEEDB090065B80A /* dnssd_clientlib.c */; };
                FFA572410AF18F450055A0F1 /* dnssd_clientstub.c in Sources */ = {isa = PBXBuildFile; fileRef = FFFA38640AEEDB130065B80A /* dnssd_clientstub.c */; };
                FFB437150EB165BD00E17C68 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00CA213D02786FC30CCA2C71 /* IOKit.framework */; };
-               FFD52A9E1AF858DD00CAD3EC /* CryptoAlg.h in Headers */ = {isa = PBXBuildFile; fileRef = 21A57F4B145B2AE100939099 /* CryptoAlg.h */; };
                FFF589B70E37F66800EF515C /* ClientCommon.c in Sources */ = {isa = PBXBuildFile; fileRef = FF5852100DD27BD300862BDF /* ClientCommon.c */; };
                FFF589C10E37F67E00EF515C /* ClientCommon.c in Sources */ = {isa = PBXBuildFile; fileRef = FF5852100DD27BD300862BDF /* ClientCommon.c */; };
                FFFA38630AEEDB090065B80A /* dnssd_clientlib.c in Sources */ = {isa = PBXBuildFile; fileRef = FFFA38620AEEDB090065B80A /* dnssd_clientlib.c */; };
                        remoteGlobalIDString = 4AE471670EAFF81900A6C5AD;
                        remoteInfo = dns_sd.jar;
                };
+               8941BE5023E352DD004AF1CA /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = 891F2F7E2397EA1C001EC153;
+                       remoteInfo = "srp-mdns-proxy";
+               };
+               8941BE5223E352DD004AF1CA /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = 895E9DA423C68D5000DA0FE0;
+                       remoteInfo = srputil;
+               };
+               8941BE5423E367C2004AF1CA /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = B70F38A5217AA6CE00612D3A;
+                       remoteInfo = "Build Extras";
+               };
+               89458D9423A1A66B00C3978D /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = 89D4BBE6239EA80E00FCFB7E;
+                       remoteInfo = "srp-client";
+               };
+               89DC990323F72754009598E1 /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = 89AFC1E223F5DE2B00538084;
+                       remoteInfo = "ra-tester";
+               };
+               89DC990523F72765009598E1 /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = 89AFC1E223F5DE2B00538084;
+                       remoteInfo = "ra-tester";
+               };
                B70F38A7217AA6CE00612D3A /* PBXContainerItemProxy */ = {
                        isa = PBXContainerItemProxy;
                        containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
                        remoteGlobalIDString = B76783AB1E82D65900DA271E;
                        remoteInfo = ddnsWriteTool;
                };
+               B7A41B3B245E33B500FA6163 /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = B7E6DAC9244E28F800C898EF;
+                       remoteInfo = "Build Services-macOS";
+               };
+               B7C90F5F2346D1A200AA89F1 /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = 03067D640C83A3700022BE1F;
+                       remoteInfo = "Build Core";
+               };
+               B7C90F672346D1B000AA89F1 /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = B70F38A5217AA6CE00612D3A;
+                       remoteInfo = "Build Extras";
+               };
                B7D566C71E81D9E700E43008 /* PBXContainerItemProxy */ = {
                        isa = PBXContainerItemProxy;
                        containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
                        remoteGlobalIDString = 0C635A751E9418A60026C796;
                        remoteInfo = BonjourTop;
                };
+               B7E6DACB244E28F800C898EF /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = 84C5B3341665529800C324A8;
+                       remoteInfo = dns_services;
+               };
+               BD7BFBFB236FDCB2000456BC /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = BD7BFBF2236FCF0E000456BC;
+                       remoteInfo = log_mdns;
+               };
                D4528FFD21F91263004D61BF /* PBXContainerItemProxy */ = {
                        isa = PBXContainerItemProxy;
                        containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */;
                        name = "Copy Base Logging Profile";
                        runOnlyForDeploymentPostprocessing = 1;
                };
+               891F2F7D2397EA1C001EC153 /* CopyFiles */ = {
+                       isa = PBXCopyFilesBuildPhase;
+                       buildActionMask = 8;
+                       dstPath = /System/Library/LaunchDaemons;
+                       dstSubfolderSpec = 0;
+                       files = (
+                               89B2979523C6A50700FEEA56 /* com.apple.srp-mdns-proxy.plist in CopyFiles */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 1;
+               };
+               895E9DA323C68D5000DA0FE0 /* CopyFiles */ = {
+                       isa = PBXCopyFilesBuildPhase;
+                       buildActionMask = 2147483647;
+                       dstPath = /usr/share/man/man1/;
+                       dstSubfolderSpec = 0;
+                       files = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 1;
+               };
+               89AFC1E123F5DE2B00538084 /* CopyFiles */ = {
+                       isa = PBXCopyFilesBuildPhase;
+                       buildActionMask = 2147483647;
+                       dstPath = /usr/share/man/man1/;
+                       dstSubfolderSpec = 0;
+                       files = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 1;
+               };
+               89D3B57C2463357D00FA67EB /* Copy AppleInternal Logging Profile */ = {
+                       isa = PBXCopyFilesBuildPhase;
+                       buildActionMask = 8;
+                       dstPath = /AppleInternal/Library/Preferences/Logging/Subsystems;
+                       dstSubfolderSpec = 0;
+                       files = (
+                               89D3B57E2463363900FA67EB /* com.apple.srp-mdns-proxy.plist in Copy AppleInternal Logging Profile */,
+                       );
+                       name = "Copy AppleInternal Logging Profile";
+                       runOnlyForDeploymentPostprocessing = 1;
+               };
+               89D3B5802463366700FA67EB /* Copy Base Logging Profile */ = {
+                       isa = PBXCopyFilesBuildPhase;
+                       buildActionMask = 8;
+                       dstPath = /System/Library/Preferences/Logging/Subsystems;
+                       dstSubfolderSpec = 0;
+                       files = (
+                               89D3B5812463369F00FA67EB /* com.apple.srp-mdns-proxy.plist in Copy Base Logging Profile */,
+                       );
+                       name = "Copy Base Logging Profile";
+                       runOnlyForDeploymentPostprocessing = 1;
+               };
+               89D4BBE5239EA80E00FCFB7E /* CopyFiles */ = {
+                       isa = PBXCopyFilesBuildPhase;
+                       buildActionMask = 2147483647;
+                       dstPath = /usr/share/man/man1/;
+                       dstSubfolderSpec = 0;
+                       files = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 1;
+               };
                B7473E951EC395C300D31B9D /* Embed App Extensions */ = {
                        isa = PBXCopyFilesBuildPhase;
                        buildActionMask = 2147483647;
                        name = "Embed App Extensions";
                        runOnlyForDeploymentPostprocessing = 0;
                };
+               B7A5C33B2399A34800615DBD /* Copy Feature Flags */ = {
+                       isa = PBXCopyFilesBuildPhase;
+                       buildActionMask = 8;
+                       dstPath = /System/Library/FeatureFlags/Domain;
+                       dstSubfolderSpec = 0;
+                       files = (
+                               B7BAFB4823A0322E0045705F /* mDNSResponder.plist in Copy Feature Flags */,
+                       );
+                       name = "Copy Feature Flags";
+                       runOnlyForDeploymentPostprocessing = 1;
+               };
                B7D566C61E81D9B600E43008 /* CopyFiles */ = {
                        isa = PBXCopyFilesBuildPhase;
                        buildActionMask = 2147483647;
                        name = "Copy diagnose scripts";
                        runOnlyForDeploymentPostprocessing = 1;
                };
-               BD75E93F206ADEAD00656ED3 /* Copy AppleInternal Logging Profile */ = {
+               BD75E93F206ADEAD00656ED3 /* Copy AppleInternal Logging Profiles */ = {
                        isa = PBXCopyFilesBuildPhase;
                        buildActionMask = 8;
                        dstPath = /AppleInternal/Library/Preferences/Logging/Subsystems;
                        dstSubfolderSpec = 0;
                        files = (
-                               BD75E940206ADEF400656ED3 /* com.apple.mDNSResponder.plist in Copy AppleInternal Logging Profile */,
+                               BD61858524C6B9B50016A102 /* com.apple.mdns.plist in Copy AppleInternal Logging Profiles */,
+                               BD75E940206ADEF400656ED3 /* com.apple.mDNSResponder.plist in Copy AppleInternal Logging Profiles */,
                        );
-                       name = "Copy AppleInternal Logging Profile";
+                       name = "Copy AppleInternal Logging Profiles";
                        runOnlyForDeploymentPostprocessing = 1;
                };
                BD98A797213A41240002EC47 /* Copy BATS test plist */ = {
                        dstSubfolderSpec = 0;
                        files = (
                                D448F7A8222DAB8E0069E1D2 /* bats_test_state_dump.sh in Copy script-based unit tests */,
-                               D448F7A8222DAB8E0069E1D3 /* bats_test_proxy.sh in Copy script-based unit tests */,
                        );
                        name = "Copy script-based unit tests";
                        runOnlyForDeploymentPostprocessing = 1;
                0C6FB90E1D775FE900DF6F51 /* mDNSNetMonitor.8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = mDNSNetMonitor.8; sourceTree = "<group>"; };
                0C7C00491DD553490078BA89 /* unittest_common.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = unittest_common.c; path = ../unittests/unittest_common.c; sourceTree = "<group>"; };
                0C7C004A1DD553490078BA89 /* unittest_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = unittest_common.h; path = ../unittests/unittest_common.h; sourceTree = "<group>"; };
-               21070E5D16486B9000A69507 /* DNSSECSupport.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DNSSECSupport.c; sourceTree = "<group>"; };
-               21070E5E16486B9000A69507 /* DNSSECSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNSSECSupport.h; sourceTree = "<group>"; };
-               2124FA2B1471E98C0021D7BB /* nsec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nsec.h; path = ../mDNSCore/nsec.h; sourceTree = "<group>"; };
-               2124FA2F1471E9B50021D7BB /* dnssec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dnssec.h; path = ../mDNSCore/dnssec.h; sourceTree = "<group>"; };
-               2124FA321471E9DE0021D7BB /* nsec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = nsec.c; path = ../mDNSCore/nsec.c; sourceTree = "<group>"; };
-               2127A47515C3C7B900A857FC /* nsec3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = nsec3.c; path = ../mDNSCore/nsec3.c; sourceTree = "<group>"; };
-               2127A47615C3C7B900A857FC /* nsec3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nsec3.h; path = ../mDNSCore/nsec3.h; sourceTree = "<group>"; };
-               213BDC6C147319F400000896 /* dnssec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dnssec.c; path = ../mDNSCore/dnssec.c; sourceTree = "<group>"; };
                213FB21812028A7A002B3A08 /* BonjourEvents.plugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BonjourEvents.plugin; sourceTree = BUILT_PRODUCTS_DIR; };
                213FB22C12028B53002B3A08 /* BonjourEvents.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = BonjourEvents.c; sourceTree = "<group>"; };
                213FB22D12028B53002B3A08 /* BonjourEvents-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "BonjourEvents-Info.plist"; sourceTree = "<group>"; };
                2141DD1D123FFCDB0086D23E /* libdns_sd.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libdns_sd.a; sourceTree = BUILT_PRODUCTS_DIR; };
                2141DD24123FFD0F0086D23E /* libdns_sd_debug.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libdns_sd_debug.a; sourceTree = BUILT_PRODUCTS_DIR; };
                2141DD2A123FFD2C0086D23E /* libdns_sd_profile.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libdns_sd_profile.a; sourceTree = BUILT_PRODUCTS_DIR; };
-               216D9ACD1720C9F5008066E1 /* uDNSPathEvalulation.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = uDNSPathEvalulation.c; sourceTree = "<group>"; };
+               216D9ACD1720C9F5008066E1 /* uDNSPathEvaluation.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = uDNSPathEvaluation.c; sourceTree = "<group>"; };
                218E8E4F156D8C0300720DA0 /* dnsproxy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dnsproxy.c; path = ../mDNSCore/dnsproxy.c; sourceTree = "<group>"; };
                218E8E50156D8C0300720DA0 /* dnsproxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dnsproxy.h; path = ../mDNSCore/dnsproxy.h; sourceTree = "<group>"; };
                219D5541149ED645004464AE /* libxml2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = /usr/lib/libxml2.dylib; sourceTree = "<absolute>"; };
-               21A57F4A145B2AE100939099 /* CryptoAlg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = CryptoAlg.c; path = ../mDNSCore/CryptoAlg.c; sourceTree = "<group>"; };
-               21A57F4B145B2AE100939099 /* CryptoAlg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CryptoAlg.h; path = ../mDNSCore/CryptoAlg.h; sourceTree = "<group>"; };
-               21A57F51145B2B1400939099 /* CryptoSupport.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CryptoSupport.c; sourceTree = "<group>"; };
-               21A57F52145B2B1400939099 /* CryptoSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoSupport.h; sourceTree = "<group>"; };
                21DED43415702C0F0060B6B9 /* DNSProxySupport.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DNSProxySupport.c; sourceTree = "<group>"; };
                21F51DBD1B3540DB0070B05C /* com.apple.dnsextd.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.apple.dnsextd.plist; sourceTree = "<group>"; };
                21F51DBE1B3541030070B05C /* com.apple.mDNSResponderHelper.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.apple.mDNSResponderHelper.plist; sourceTree = "<group>"; };
                4ADB5F230F6AB9F400B95BF3 /* helper-entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "helper-entitlements.plist"; sourceTree = "<group>"; };
                4BD2B638134FE09F002B96D5 /* P2PPacketFilter.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = P2PPacketFilter.c; sourceTree = "<group>"; };
                4BD2B639134FE09F002B96D5 /* P2PPacketFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = P2PPacketFilter.h; sourceTree = "<group>"; };
+               5A9E8E5723F46913003B4CAD /* CoreWLAN.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreWLAN.framework; path = System/Library/Frameworks/CoreWLAN.framework; sourceTree = SDKROOT; };
+               5A9E8E5923F46921003B4CAD /* CoreWiFi.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreWiFi.framework; path = System/Library/PrivateFrameworks/CoreWiFi.framework; sourceTree = SDKROOT; };
+               5A9E8E5C23F47817003B4CAD /* DNSHeuristicsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DNSHeuristicsTest.m; sourceTree = "<group>"; };
+               5A9E8E5E23F4B99A003B4CAD /* DNSHeuristicsInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DNSHeuristicsInternal.h; sourceTree = "<group>"; };
+               5A9E8E6323F4BE29003B4CAD /* mDNSResponderTests-Entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "mDNSResponderTests-Entitlements.plist"; sourceTree = "<group>"; };
+               5AF23B2823F37309004AB237 /* DNSHeuristics.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DNSHeuristics.m; sourceTree = "<group>"; };
+               5AF23B2A23F37316004AB237 /* DNSHeuristics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DNSHeuristics.h; sourceTree = "<group>"; };
                654BE64F02B63B93000001D1 /* mDNSEmbeddedAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mDNSEmbeddedAPI.h; path = ../mDNSCore/mDNSEmbeddedAPI.h; sourceTree = "<group>"; };
-               654BE65002B63B93000001D1 /* mDNSDebug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mDNSDebug.h; path = ../mDNSCore/mDNSDebug.h; sourceTree = "<group>"; };
+               654BE65002B63B93000001D1 /* mDNSDebug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mDNSDebug.h; path = ../mDNSCore/mDNSDebug.h; sourceTree = "<group>"; usesTabs = 0; };
                65713D46025A293200000109 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = /System/Library/Frameworks/SystemConfiguration.framework; sourceTree = "<absolute>"; };
                6575FBE9022EAF5A00000109 /* mDNS.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.c; name = mDNS.c; path = ../mDNSCore/mDNS.c; sourceTree = "<group>"; tabWidth = 4; usesTabs = 0; };
                6575FBEB022EAF7200000109 /* mDNSMacOSX.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = mDNSMacOSX.c; sourceTree = "<group>"; tabWidth = 4; usesTabs = 0; };
                6575FBEC022EAF7200000109 /* daemon.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = daemon.c; sourceTree = "<group>"; tabWidth = 4; usesTabs = 0; };
+               787E8B72239485D200DC9D01 /* HTTPUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTTPUtilities.h; sourceTree = "<group>"; };
+               787E8B73239485D200DC9D01 /* HTTPUtilities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HTTPUtilities.m; sourceTree = "<group>"; };
                789036911F7AC1F90077A962 /* libnetwork.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libnetwork.tbd; path = usr/lib/libnetwork.tbd; sourceTree = SDKROOT; };
+               78A4903B23343B8400FA2CCA /* dnssd_descriptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = dnssd_descriptions.m; sourceTree = "<group>"; };
+               78BD2BF723FC521B004A3D8B /* dnssd_svcb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dnssd_svcb.c; sourceTree = "<group>"; };
+               78BD2BF823FC521B004A3D8B /* dnssd_svcb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dnssd_svcb.h; sourceTree = "<group>"; };
                7F18A9F60587CEF6001880B3 /* DNSCommon.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = DNSCommon.c; path = ../mDNSCore/DNSCommon.c; sourceTree = SOURCE_ROOT; };
                7F18A9F70587CEF6001880B3 /* uDNS.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = uDNS.c; path = ../mDNSCore/uDNS.c; sourceTree = SOURCE_ROOT; };
                7F461DB5062DBF2900672BF3 /* DNSDigest.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = DNSDigest.c; path = ../mDNSCore/DNSDigest.c; sourceTree = SOURCE_ROOT; };
                84C5B3351665529800C324A8 /* libdns_services.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libdns_services.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
                84C5B339166553AF00C324A8 /* dns_services.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = dns_services.c; path = Private/dns_services.c; sourceTree = "<group>"; };
                84F4C08F188F04CF00D1E1DE /* dns_services.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dns_services.h; path = Private/dns_services.h; sourceTree = "<group>"; };
+               891F2F7F2397EA1C001EC153 /* srp-mdns-proxy */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "srp-mdns-proxy"; sourceTree = BUILT_PRODUCTS_DIR; };
+               891F2F862397EA79001EC153 /* srp-mdns-proxy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "srp-mdns-proxy.c"; sourceTree = "<group>"; };
+               891F2F872397EA79001EC153 /* srp-parse.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "srp-parse.c"; sourceTree = "<group>"; };
+               891F2F882397EA79001EC153 /* srp-proxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "srp-proxy.h"; sourceTree = "<group>"; };
+               891F2F892397EA79001EC153 /* srp-mdns-proxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "srp-mdns-proxy.h"; sourceTree = "<group>"; };
+               891F2F8D2397EB30001EC153 /* macos-ioloop.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "macos-ioloop.c"; sourceTree = "<group>"; };
+               891F2F8E2397EB30001EC153 /* sign-macos.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "sign-macos.c"; sourceTree = "<group>"; };
+               891F2F8F2397EB30001EC153 /* towire.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = towire.c; sourceTree = "<group>"; };
+               891F2F902397EB30001EC153 /* ioloop.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ioloop.h; sourceTree = "<group>"; };
+               891F2F912397EB30001EC153 /* verify-macos.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "verify-macos.c"; sourceTree = "<group>"; };
+               891F2F922397EB30001EC153 /* hmac-macos.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "hmac-macos.c"; sourceTree = "<group>"; };
+               891F2F932397EB30001EC153 /* fromwire.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fromwire.c; sourceTree = "<group>"; };
+               891F2F942397EB30001EC153 /* wireutils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = wireutils.c; sourceTree = "<group>"; };
+               891F2F952397EB30001EC153 /* dns-msg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "dns-msg.h"; sourceTree = "<group>"; };
+               891F2FA42397EC47001EC153 /* srp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = srp.h; sourceTree = "<group>"; };
+               892EF6D5246DBF7F00DB9EA1 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.Internal.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
+               892EF6D7246DBF9F00DB9EA1 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.Internal.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; };
+               892EF6D9246DBFB800DB9EA1 /* Preferences.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Preferences.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.Internal.sdk/System/Library/PrivateFrameworks/Preferences.framework; sourceTree = DEVELOPER_DIR; };
+               892EF6DB246DBFF800DB9EA1 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.Internal.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; };
+               892EF6DD246DC00500DB9EA1 /* CoreUtils.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreUtils.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.Internal.sdk/System/Library/PrivateFrameworks/CoreUtils.framework; sourceTree = DEVELOPER_DIR; };
+               892EF6DF246DC01500DB9EA1 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.Internal.sdk/System/Library/Frameworks/CoreFoundation.framework; sourceTree = DEVELOPER_DIR; };
+               892EF6E1246DC02A00DB9EA1 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.Internal.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; };
+               892EF6E3246DC09100DB9EA1 /* CoreAnalytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAnalytics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.Internal.sdk/System/Library/PrivateFrameworks/CoreAnalytics.framework; sourceTree = DEVELOPER_DIR; };
+               892EF6E5246DC10600DB9EA1 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.Internal.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; };
+               892EF6E7246DCD4E00DB9EA1 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.Internal.sdk/System/Library/Frameworks/CoreServices.framework; sourceTree = DEVELOPER_DIR; };
+               892EF6EA246DCD8A00DB9EA1 /* SafariServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SafariServices.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.16.sdk/System/Library/Frameworks/SafariServices.framework; sourceTree = DEVELOPER_DIR; };
+               892EF6EC246DCD9F00DB9EA1 /* libswiftSafariServices.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftSafariServices.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.16.sdk/usr/lib/swift/libswiftSafariServices.tbd; sourceTree = DEVELOPER_DIR; };
+               892EF6EE246DCE2600DB9EA1 /* SafariServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SafariServices.framework; path = System/Library/Frameworks/SafariServices.framework; sourceTree = SDKROOT; };
+               892EF6F0246DD00000DB9EA1 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.16.sdk/System/Library/Frameworks/AppKit.framework; sourceTree = DEVELOPER_DIR; };
+               8941BE4A23E34AA7004AF1CA /* libdns_services.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libdns_services.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS14.0.Internal.sdk/usr/lib/libdns_services.tbd; sourceTree = DEVELOPER_DIR; };
+               89458D9F23A1AA5000C3978D /* srp-client.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "srp-client.c"; sourceTree = "<group>"; };
+               89458DA023A1AA5000C3978D /* srp-ioloop.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "srp-ioloop.c"; sourceTree = "<group>"; };
                894A55D622C438AF008CDEA1 /* bats_test_proxy.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = bats_test_proxy.sh; sourceTree = "<group>"; };
+               89511E4923C5FE3B00D603D4 /* advertising_proxy_services.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = advertising_proxy_services.h; path = ../mDNSMacOSX/Private/advertising_proxy_services.h; sourceTree = "<group>"; };
+               89511E4A23C5FE3B00D603D4 /* advertising_proxy_services.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = advertising_proxy_services.c; path = ../mDNSMacOSX/Private/advertising_proxy_services.c; sourceTree = "<group>"; };
+               8954B0342406F4A80032D78F /* cti-services.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "cti-services.h"; path = "Private/cti-services.h"; sourceTree = SOURCE_ROOT; };
+               8954B0352406F4A80032D78F /* cti-services.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "cti-services.c"; path = "Private/cti-services.c"; sourceTree = SOURCE_ROOT; };
+               895A278124CB743C00697AB1 /* srp-client-entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "srp-client-entitlements.plist"; sourceTree = "<group>"; };
+               895A278424D465FC00697AB1 /* DebugServices.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = DebugServices.c; path = ../mDNSShared/DebugServices.c; sourceTree = "<group>"; };
+               895E9DA523C68D5000DA0FE0 /* srputil */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = srputil; sourceTree = BUILT_PRODUCTS_DIR; };
                897729AE2202A5370018FAEB /* dso.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dso.c; path = ../DSO/dso.c; sourceTree = "<group>"; };
                897729AF2202A5370018FAEB /* dso-transport.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "dso-transport.c"; path = "../DSO/dso-transport.c"; sourceTree = "<group>"; };
                897729B02202A5370018FAEB /* dso-transport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "dso-transport.h"; path = "../DSO/dso-transport.h"; sourceTree = "<group>"; };
                897729B12202A5370018FAEB /* dso.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dso.h; path = ../DSO/dso.h; sourceTree = "<group>"; };
                897729B62202A5480018FAEB /* dnssd_clientshim.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dnssd_clientshim.c; path = ../mDNSShared/dnssd_clientshim.c; sourceTree = "<group>"; };
+               8998717523C6977E00C3AF39 /* srputil.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srputil.c; path = ../Clients/srputil/srputil.c; sourceTree = "<group>"; };
+               8998717623C697BE00C3AF39 /* srputil-entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "srputil-entitlements.plist"; path = "../Clients/srputil/srputil-entitlements.plist"; sourceTree = "<group>"; };
+               89AFC1E323F5DE2B00538084 /* ra-tester */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "ra-tester"; sourceTree = BUILT_PRODUCTS_DIR; };
+               89AFC1E523F5DE2B00538084 /* ra-tester.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "ra-tester.c"; sourceTree = "<group>"; };
+               89AFC1EE23F5E09500538084 /* config-parse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "config-parse.h"; sourceTree = "<group>"; };
+               89AFC1EF23F5E09500538084 /* srp-crypto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "srp-crypto.h"; sourceTree = "<group>"; };
+               89AFC1F023F5E09500538084 /* dnssd-proxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "dnssd-proxy.h"; sourceTree = "<group>"; };
+               89AFC1F123F5E09500538084 /* srp-tls.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "srp-tls.h"; sourceTree = "<group>"; };
+               89AFC1F223F5E09500538084 /* srp-gw.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "srp-gw.h"; sourceTree = "<group>"; };
+               89AFC1F723F610E900538084 /* cti-wrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "cti-wrapper.h"; path = "Private/cti-wrapper.h"; sourceTree = SOURCE_ROOT; };
+               89AFC1F823F6115000538084 /* cti-wrapper.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "cti-wrapper.mm"; path = "Private/cti-wrapper.mm"; sourceTree = SOURCE_ROOT; };
+               89B0BF3723C55A4400245A42 /* xpc_client_advertising_proxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xpc_client_advertising_proxy.h; sourceTree = "<group>"; };
+               89C38ECA23C93B6600800A42 /* route.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = route.h; sourceTree = "<group>"; };
+               89C38ECB23C93B6600800A42 /* route.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = route.c; sourceTree = "<group>"; };
+               89D3B57D2463363900FA67EB /* com.apple.srp-mdns-proxy.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "com.apple.srp-mdns-proxy.plist"; sourceTree = "<group>"; };
+               89D3B57F2463365B00FA67EB /* com.apple.srp-mdns-proxy.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "com.apple.srp-mdns-proxy.plist"; sourceTree = "<group>"; };
+               89D4BBE7239EA80E00FCFB7E /* srp-client */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "srp-client"; sourceTree = BUILT_PRODUCTS_DIR; };
+               89D4BBFA239EB1CE00FCFB7E /* posix.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = posix.c; sourceTree = "<group>"; };
+               89D9064A23C68BA300A45D6F /* srp-mdns-proxy-entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "srp-mdns-proxy-entitlements.plist"; sourceTree = SOURCE_ROOT; };
+               89D9064B23C68BA300A45D6F /* com.apple.srp-mdns-proxy.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "com.apple.srp-mdns-proxy.plist"; sourceTree = SOURCE_ROOT; };
                B7016F4F1D5D0D1900107E7C /* DomainBrowser.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = DomainBrowser.strings; sourceTree = "<group>"; };
                B7016F511D5D0D2900107E7C /* Localizable.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = Localizable.strings; path = ../SettingsBundle/Localizable.strings; sourceTree = "<group>"; };
                B701E7031D9DD811008F3022 /* BonjourSCStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BonjourSCStore.m; path = ../SettingsBundle/BonjourSCStore.m; sourceTree = "<group>"; };
                B716801A1E8330B400459A35 /* entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = entitlements.plist; sourceTree = "<group>"; };
+               B72BC06123ABFD8C0021E0C9 /* bundle_utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = bundle_utilities.m; sourceTree = "<group>"; };
+               B72BC06223ABFD8C0021E0C9 /* bundle_utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bundle_utilities.h; sourceTree = "<group>"; };
                B72C96091D6236A500AD682A /* BonjourSCStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BonjourSCStore.h; path = ../SettingsBundle/BonjourSCStore.h; sourceTree = "<group>"; };
                B72D38B31ECB96ED00B10E39 /* Localizable.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
                B7325FE61DA4737400663834 /* CNBrowseDomainsController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CNBrowseDomainsController.h; path = ../SettingsBundle/CNBrowseDomainsController.h; sourceTree = "<group>"; };
                B7325FE71DA4737400663834 /* CNBrowseDomainsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CNBrowseDomainsController.m; path = ../SettingsBundle/CNBrowseDomainsController.m; sourceTree = "<group>"; };
                B7325FEE1DA47EBA00663834 /* DomainBrowser.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DomainBrowser.framework; sourceTree = BUILT_PRODUCTS_DIR; };
                B735A2EE21B8293900025BB0 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+               B737059122CD65E400477EB9 /* dnssd_analytics.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dnssd_analytics.c; sourceTree = "<group>"; };
+               B737059222CD65E400477EB9 /* dnssd_analytics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dnssd_analytics.h; sourceTree = "<group>"; };
+               B73C5CCD24B78EE70002050A /* setup_assistant_helper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = setup_assistant_helper.h; sourceTree = "<group>"; };
+               B73C5CCE24B78EE70002050A /* setup_assistant_helper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = setup_assistant_helper.m; sourceTree = "<group>"; };
+               B73C5CD124B78FC80002050A /* SetupAssistantFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SetupAssistantFramework.framework; path = System/Library/PrivateFrameworks/SetupAssistantFramework.framework; sourceTree = SDKROOT; };
                B7473E611EC3954400D31B9D /* Bonjour Safari Menu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Bonjour Safari Menu.app"; sourceTree = BUILT_PRODUCTS_DIR; };
                B7473E641EC3954400D31B9D /* BonjourSafariMenu.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BonjourSafariMenu.entitlements; sourceTree = "<group>"; };
                B7473E651EC3954400D31B9D /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
                B799209D1DA6BA8E00C6E02B /* CNDomainBrowserPathUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CNDomainBrowserPathUtils.h; sourceTree = "<group>"; };
                B799209E1DA6BA8E00C6E02B /* CNDomainBrowserPathUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CNDomainBrowserPathUtils.m; sourceTree = "<group>"; };
                B79920A31DA6C49700C6E02B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = ../SettingsBundle/Assets.xcassets; sourceTree = "<group>"; };
+               B799C9672385A5AA00675816 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; };
+               B799C9692385A5B900675816 /* libsystem_coreservices.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsystem_coreservices.tbd; path = usr/lib/system/libsystem_coreservices.tbd; sourceTree = SDKROOT; };
+               B799C96B2385A5E500675816 /* System.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = System.framework; path = System/Library/Frameworks/System.framework; sourceTree = SDKROOT; };
                B79FA153211CF0AF00B7861E /* dnsproxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnsproxy.h; sourceTree = "<group>"; };
                B79FA154211CF0AF00B7861E /* uDNS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = uDNS.h; sourceTree = "<group>"; };
-               B79FA155211CF0AF00B7861E /* nsec.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nsec.h; sourceTree = "<group>"; };
                B79FA156211CF0AF00B7861E /* DNSCommon.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = DNSCommon.c; sourceTree = "<group>"; };
                B79FA157211CF0AF00B7861E /* mDNSDebug.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mDNSDebug.h; sourceTree = "<group>"; };
-               B79FA158211CF0AF00B7861E /* dnssec.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec.c; sourceTree = "<group>"; };
-               B79FA159211CF0AF00B7861E /* CryptoAlg.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CryptoAlg.h; sourceTree = "<group>"; };
-               B79FA15A211CF0AF00B7861E /* nsec3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nsec3.h; sourceTree = "<group>"; };
                B79FA15B211CF0AF00B7861E /* Implementer Notes.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Implementer Notes.txt"; sourceTree = "<group>"; };
-               B79FA15C211CF0AF00B7861E /* nsec.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = nsec.c; sourceTree = "<group>"; };
                B79FA15D211CF0AF00B7861E /* uDNS.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = uDNS.c; sourceTree = "<group>"; };
                B79FA15E211CF0AF00B7861E /* dnsproxy.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnsproxy.c; sourceTree = "<group>"; };
-               B79FA15F211CF0AF00B7861E /* dnssec.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec.h; sourceTree = "<group>"; };
-               B79FA160211CF0AF00B7861E /* mDNS.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mDNS.c; sourceTree = "<group>"; };
+               B79FA160211CF0AF00B7861E /* mDNS.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mDNS.c; sourceTree = "<group>"; usesTabs = 0; };
                B79FA162211CF0AF00B7861E /* DNSCommon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DNSCommon.h; sourceTree = "<group>"; };
-               B79FA163211CF0AF00B7861E /* CryptoAlg.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = CryptoAlg.c; sourceTree = "<group>"; };
                B79FA164211CF0AF00B7861E /* mDNSEmbeddedAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mDNSEmbeddedAPI.h; sourceTree = "<group>"; };
-               B79FA165211CF0AF00B7861E /* nsec3.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = nsec3.c; sourceTree = "<group>"; };
                B79FA166211CF0AF00B7861E /* DNSDigest.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = DNSDigest.c; sourceTree = "<group>"; };
                B7A214081D1B29D6005F7DD9 /* CNDomainBrowserViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CNDomainBrowserViewController.h; sourceTree = "<group>"; };
                B7A214091D1B29D6005F7DD9 /* CNDomainBrowserViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CNDomainBrowserViewController.m; sourceTree = "<group>"; };
+               B7A5C33623986BBF00615DBD /* mDNSResponder-entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "mDNSResponder-entitlements.plist"; sourceTree = "<group>"; };
+               B7A5C33A2399A24C00615DBD /* mDNSResponder.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = mDNSResponder.plist; sourceTree = "<group>"; };
                B7A8618821274BFC00E81CC3 /* ResourceRecordTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ResourceRecordTest.m; sourceTree = "<group>"; };
                B7A8618A21274FA200E81CC3 /* mDNSCoreReceiveTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = mDNSCoreReceiveTest.m; sourceTree = "<group>"; };
                B7A861962127845700E81CC3 /* CNameRecordTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CNameRecordTest.m; sourceTree = "<group>"; };
                B7A861A4212B40BC00E81CC3 /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = usr/lib/libxml2.tbd; sourceTree = SDKROOT; };
                B7A861A5212B40BC00E81CC3 /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = System/Library/Frameworks/Network.framework; sourceTree = SDKROOT; };
                B7A861A6212B40BC00E81CC3 /* libicucore.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libicucore.tbd; path = usr/lib/libicucore.tbd; sourceTree = SDKROOT; };
+               B7BAFB4923A033D80045705F /* mdns_trust_checks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = mdns_trust_checks.m; sourceTree = "<group>"; };
+               B7BAFB4B23A035140045705F /* system_utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = system_utilities.m; sourceTree = "<group>"; };
+               B7BAFB5A23A2A27F0045705F /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.16.sdk/System/Library/Frameworks/Network.framework; sourceTree = DEVELOPER_DIR; };
                B7CEF6121F635404008B08D3 /* ToolbarItemIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ToolbarItemIcon.png; sourceTree = "<group>"; };
                B7D566A51E81D6A900E43008 /* BonjourPrefRemoteViewService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "BonjourPrefRemoteViewService-Info.plist"; sourceTree = "<group>"; };
                B7D566A71E81D6A900E43008 /* BonjourPrefRemoteViewService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BonjourPrefRemoteViewService.h; sourceTree = "<group>"; };
                B7D6CA691D1076C6005E24CF /* CNDomainBrowserView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CNDomainBrowserView.m; sourceTree = "<group>"; };
                B7D6CA701D1076F3005E24CF /* DomainBrowser.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DomainBrowser.framework; sourceTree = BUILT_PRODUCTS_DIR; };
                B7E06CDF2329AA7B0021401F /* LocalOnlyWithInterfacesTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocalOnlyWithInterfacesTest.m; sourceTree = "<group>"; };
+               B7E8092C23F70EF6002CB309 /* mdns_trust_checks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_trust_checks.h; sourceTree = "<group>"; };
                B7F1FEDC234F89C50081159C /* TestUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestUtils.h; sourceTree = "<group>"; };
                B7F1FEDD234F89C50081159C /* TestUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestUtils.m; sourceTree = "<group>"; };
                B7F1FEEA234FAF0F0081159C /* dnssdutil-entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "dnssdutil-entitlements.plist"; sourceTree = "<group>"; };
+               B7F7078C2395F19C00A31B5A /* TCC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TCC.framework; path = System/Library/PrivateFrameworks/TCC.framework; sourceTree = SDKROOT; };
+               B7F707A923972EBE00A31B5A /* libbsm.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libbsm.tbd; path = usr/lib/libbsm.tbd; sourceTree = SDKROOT; };
+               B7F9AB79237F4F4300C2BEA2 /* mdns_trust.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdns_trust.c; sourceTree = "<group>"; };
+               B7F9AB7A237F4F4300C2BEA2 /* mdns_trust.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdns_trust.h; sourceTree = "<group>"; };
                BD03E88C1AD31278005E8A81 /* SymptomReporter.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SymptomReporter.c; sourceTree = "<group>"; };
+               BD0FFC6E2502E78900B6DB73 /* mdns_managed_defaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdns_managed_defaults.h; sourceTree = "<group>"; };
+               BD0FFC6F2502E78A00B6DB73 /* mdns_managed_defaults.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdns_managed_defaults.c; sourceTree = "<group>"; };
                BD11266D21DB1AFE006115E6 /* dnssd_xpc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dnssd_xpc.h; sourceTree = "<group>"; };
                BD11266E21DB1AFE006115E6 /* dnssd_xpc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dnssd_xpc.c; sourceTree = "<group>"; };
                BD11266F21DB1AFE006115E6 /* dnssd_server.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dnssd_server.c; sourceTree = "<group>"; };
                BD11267621DB2A9A006115E6 /* dnssd_object.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = dnssd_object.m; sourceTree = "<group>"; };
                BD11267721DB2A9A006115E6 /* dnssd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dnssd.c; sourceTree = "<group>"; };
                BD11267B21DB2C7C006115E6 /* dnssd_object.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dnssd_object.h; sourceTree = "<group>"; };
+               BD12709124469F1C00BC84D8 /* DNSServerDNSSEC.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DNSServerDNSSEC.c; sourceTree = "<group>"; usesTabs = 1; };
+               BD12709224469F1C00BC84D8 /* DNSServerDNSSEC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNSServerDNSSEC.h; sourceTree = "<group>"; usesTabs = 1; };
                BD1628CD2168B02600020528 /* ClientRequests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ClientRequests.h; path = ../mDNSShared/ClientRequests.h; sourceTree = "<group>"; };
-               BD1628CE2168B02700020528 /* ClientRequests.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ClientRequests.c; path = ../mDNSShared/ClientRequests.c; sourceTree = "<group>"; };
+               BD1628CE2168B02700020528 /* ClientRequests.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ClientRequests.c; path = ../mDNSShared/ClientRequests.c; sourceTree = "<group>"; usesTabs = 0; };
+               BD1904EC235E759500F146D6 /* mdns_dns_service.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdns_dns_service.h; sourceTree = "<group>"; };
+               BD1904ED235E759500F146D6 /* mdns_dns_service.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdns_dns_service.c; sourceTree = "<group>"; };
+               BD1970B323F90E94001BA06F /* mdns_powerlog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdns_powerlog.h; sourceTree = "<group>"; };
+               BD1970B423F90E94001BA06F /* mdns_powerlog.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdns_powerlog.c; sourceTree = "<group>"; };
                BD28AE8E207B88F600F0B257 /* bonjour-mcast-diagnose */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "bonjour-mcast-diagnose"; sourceTree = "<group>"; };
-               BD2A15B5225ED2E500BEA50A /* mdns_object.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = mdns_object.m; sourceTree = "<group>"; };
-               BD2A15B6225ED2E500BEA50A /* mdns_object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_object.h; sourceTree = "<group>"; };
-               BD2A15B7225ED2E500BEA50A /* mdns.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mdns.c; sourceTree = "<group>"; };
-               BD2A15B8225ED2E500BEA50A /* mdns_private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_private.h; sourceTree = "<group>"; };
                BD41F9C3209B60AC0077F8B6 /* libpcap.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libpcap.tbd; path = usr/lib/libpcap.tbd; sourceTree = SDKROOT; };
                BD42EE1F21DB41970053A651 /* libobjc.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libobjc.tbd; path = usr/lib/libobjc.tbd; sourceTree = SDKROOT; };
+               BD4FC0D524A81643000B0B21 /* mdns_message.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_message.h; sourceTree = "<group>"; };
+               BD4FC0D624A81643000B0B21 /* mdns_message.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mdns_message.c; sourceTree = "<group>"; };
+               BD52398123F3BBCD004AC878 /* mdns_set.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdns_set.c; sourceTree = "<group>"; };
+               BD52398223F3BBCD004AC878 /* mdns_set.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdns_set.h; sourceTree = "<group>"; };
+               BD5278D723F69D4B00235117 /* PowerLog.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PowerLog.framework; path = System/Library/PrivateFrameworks/PowerLog.framework; sourceTree = SDKROOT; };
+               BD61858224C6B9240016A102 /* com.apple.mdns.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.apple.mdns.plist; sourceTree = "<group>"; };
                BD691B281ED2F43200E6F317 /* DNS64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DNS64.c; sourceTree = "<group>"; };
                BD691B291ED2F43200E6F317 /* DNS64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNS64.h; sourceTree = "<group>"; };
+               BD7BFBD9236D5342000456BC /* mdns_symptoms.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdns_symptoms.h; sourceTree = "<group>"; };
+               BD7BFBDA236D5342000456BC /* mdns_symptoms.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdns_symptoms.c; sourceTree = "<group>"; };
+               BD7BFBF3236FCF0E000456BC /* liblog_mdns.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = liblog_mdns.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
+               BD7BFBF7236FD019000456BC /* log_mdns.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = log_mdns.m; sourceTree = "<group>"; };
                BD893CE4206C0D980055F9E7 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
                BD893CE6206C0EAF0055F9E7 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
-               BD97754B221D643500F68FFC /* dnssdutil.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dnssdutil.c; sourceTree = "<group>"; };
+               BD97754B221D643500F68FFC /* dnssdutil.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dnssdutil.c; sourceTree = "<group>"; usesTabs = 1; };
                BD97754D221D64BF00F68FFC /* DNSMessage.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DNSMessage.c; sourceTree = "<group>"; usesTabs = 1; };
                BD97754E221D64BF00F68FFC /* DNSMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNSMessage.h; sourceTree = "<group>"; };
                BD98A796213A3EAE0002EC47 /* mDNSResponder.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = mDNSResponder.plist; sourceTree = "<group>"; };
                BD9BA7531EAF90E400658CCF /* dnssdutil */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = dnssdutil; sourceTree = BUILT_PRODUCTS_DIR; };
                BD9BA7571EAF929C00658CCF /* CoreUtils.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreUtils.framework; path = System/Library/PrivateFrameworks/CoreUtils.framework; sourceTree = SDKROOT; };
+               BDA37F1223471F6100B9266D /* mdns_interface_monitor.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mdns_interface_monitor.c; sourceTree = "<group>"; };
+               BDA37F1323471F6100B9266D /* mdns_base.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_base.h; sourceTree = "<group>"; };
+               BDA37F1423471F6100B9266D /* mdns_internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_internal.h; sourceTree = "<group>"; };
+               BDA37F1523471F6100B9266D /* mdns_resolver.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mdns_resolver.c; sourceTree = "<group>"; };
+               BDA37F1623471F6100B9266D /* mdns_address.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mdns_address.c; sourceTree = "<group>"; };
+               BDA37F1723471F6100B9266D /* mdns_object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_object.h; sourceTree = "<group>"; };
+               BDA37F1823471F6100B9266D /* mdns_objects.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = mdns_objects.m; sourceTree = "<group>"; };
+               BDA37F1923471F6100B9266D /* mdns_helpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_helpers.h; sourceTree = "<group>"; };
+               BDA37F1A23471F6100B9266D /* mdns_interface_monitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_interface_monitor.h; sourceTree = "<group>"; };
+               BDA37F1B23471F6100B9266D /* mdns_resolver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_resolver.h; sourceTree = "<group>"; };
+               BDA37F1C23471F6100B9266D /* mdns_object.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mdns_object.c; sourceTree = "<group>"; };
+               BDA37F1D23471F6100B9266D /* mdns_address.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_address.h; sourceTree = "<group>"; };
+               BDA37F1E23471F6100B9266D /* mdns_private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_private.h; sourceTree = "<group>"; };
+               BDA37F1F23471F6100B9266D /* mdns_helpers.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mdns_helpers.c; sourceTree = "<group>"; };
+               BDA37F2023471F6100B9266D /* mdns_objects.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_objects.h; sourceTree = "<group>"; };
                BDA3F0871C48DB6D0054FB4B /* Metrics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Metrics.h; sourceTree = "<group>"; };
                BDA3F0881C48DB6D0054FB4B /* Metrics.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Metrics.m; sourceTree = "<group>"; };
                BDA3F0891C48DB910054FB4B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
                BDA82B8922BF8A7000A31CBE /* libdns_services.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libdns_services.tbd; path = usr/lib/libdns_services.tbd; sourceTree = SDKROOT; };
                BDA9A7871B3A923600523835 /* dns_sd_private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = dns_sd_private.h; path = ../mDNSShared/dns_sd_private.h; sourceTree = "<group>"; };
+               BDA9C35A2370304E00DC86BD /* QuerierSupport.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = QuerierSupport.c; sourceTree = "<group>"; };
+               BDA9C35C2370307600DC86BD /* QuerierSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QuerierSupport.h; sourceTree = "<group>"; };
                BDAF4BBF20B52D3D0062219E /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
                BDB61843206ADB7700AFF600 /* com.apple.mDNSResponder.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.apple.mDNSResponder.plist; sourceTree = "<group>"; };
                BDB61846206ADDDF00AFF600 /* com.apple.mDNSResponder.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = com.apple.mDNSResponder.plist; sourceTree = "<group>"; };
                BDBF9B931ED74B8C001498A8 /* DNS64State.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNS64State.h; sourceTree = "<group>"; };
                BDE238C11DF69D8300B9F696 /* dns_sd_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dns_sd_internal.h; path = ../mDNSShared/dns_sd_internal.h; sourceTree = "<group>"; };
+               BDF1CEE522F1787E009AB795 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
                BDF2D02B2169B77B00D0DBF5 /* SymptomReporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SymptomReporter.h; sourceTree = "<group>"; };
                BDF2D02D216A25E800D0DBF5 /* ApplePlatformFeatures.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ApplePlatformFeatures.h; sourceTree = "<group>"; };
+               BDFE3FFB2408C58B00C77011 /* mdns_tlv.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdns_tlv.c; sourceTree = "<group>"; };
+               BDFE3FFC2408C58B00C77011 /* mdns_tlv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdns_tlv.h; sourceTree = "<group>"; };
+               BDFE3FFF2408C8D800C77011 /* dnssd_clientstub_apple.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dnssd_clientstub_apple.c; path = ../mDNSShared/dnssd_clientstub_apple.c; sourceTree = "<group>"; };
+               BDFE40002408C8D900C77011 /* dnssd_clientstub_apple.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dnssd_clientstub_apple.h; path = ../mDNSShared/dnssd_clientstub_apple.h; sourceTree = "<group>"; };
+               BDFE400D2408F62E00C77011 /* mdns_xpc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdns_xpc.c; sourceTree = "<group>"; };
+               BDFE400E2408F62E00C77011 /* mdns_xpc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdns_xpc.h; sourceTree = "<group>"; };
+               BF0E381F24882B650028D528 /* liblog_srp.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = liblog_srp.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
+               BF0E382024882BB60028D528 /* log_srp.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = log_srp.m; sourceTree = "<group>"; };
                D284BE730ADD80740027CCDF /* mDNSResponder */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = mDNSResponder; sourceTree = BUILT_PRODUCTS_DIR; };
                D284BEB00ADD80920027CCDF /* dns-sd */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "dns-sd"; sourceTree = BUILT_PRODUCTS_DIR; };
                D284BEBE0ADD809A0027CCDF /* libjdns_sd.jnilib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libjdns_sd.jnilib; sourceTree = BUILT_PRODUCTS_DIR; };
                D284BED90ADD80A20027CCDF /* dnsextd */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = dnsextd; sourceTree = BUILT_PRODUCTS_DIR; };
                D284BF0C0ADD80B00027CCDF /* Bonjour.prefPane */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Bonjour.prefPane; sourceTree = BUILT_PRODUCTS_DIR; };
-               D408BAAF221243DA00139EDA /* libarchive.2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libarchive.2.tbd; path = usr/lib/libarchive.2.tbd; sourceTree = SDKROOT; };
+               D41DED3423DFC3C3009F9854 /* base_n.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = base_n.h; sourceTree = "<group>"; usesTabs = 1; };
+               D41DED3523DFC3C3009F9854 /* base_n.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = base_n.c; sourceTree = "<group>"; usesTabs = 1; };
+               D41DED3923E0CC40009F9854 /* BaseNEncodingDecodingTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BaseNEncodingDecodingTest.m; sourceTree = "<group>"; };
+               D41DED3E23E0F71D009F9854 /* DigestCalculationTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DigestCalculationTest.m; sourceTree = "<group>"; };
+               D41DED4223E21D65009F9854 /* NSEC3HashTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NSEC3HashTest.m; sourceTree = "<group>"; };
                D421B3DD22178BE700D35C20 /* system_utilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = system_utilities.h; sourceTree = "<group>"; };
-               D421B3DE22178BE700D35C20 /* system_utilities.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = system_utilities.c; sourceTree = "<group>"; };
+               D43B120E23A442CE00B8D941 /* dnssec_v2_crypto.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_crypto.h; sourceTree = "<group>"; usesTabs = 1; };
+               D43B120F23A442CE00B8D941 /* dnssec_v2_crypto.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec_v2_crypto.c; sourceTree = "<group>"; usesTabs = 1; };
+               D43B121523BEB1A200B8D941 /* CanonicalMethodsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CanonicalMethodsTest.m; sourceTree = "<group>"; };
                D448F7A7222DAA1F0069E1D2 /* bats_test_state_dump.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = bats_test_state_dump.sh; sourceTree = "<group>"; };
-               D448F7AB222DCB5B0069E1D2 /* libarchive.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libarchive.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.0.Internal.sdk/usr/lib/libarchive.tbd; sourceTree = DEVELOPER_DIR; };
-               D448F7AD222DCC0A0069E1D2 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.0.Internal.sdk/System/Library/Frameworks/CoreFoundation.framework; sourceTree = DEVELOPER_DIR; };
                D448F7B0222E035A0069E1D2 /* uds_daemon_ut.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = uds_daemon_ut.c; path = ../unittests/uds_daemon_ut.c; sourceTree = "<group>"; };
                D459F0D3222862BA0056AC5B /* HelperFunctionTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HelperFunctionTest.m; sourceTree = "<group>"; };
                D461F9482203A6B400A88910 /* xpc_services.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = xpc_services.h; sourceTree = "<group>"; };
                D461F94D2203A8AA00A88910 /* xpc_service_dns_proxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = xpc_service_dns_proxy.h; sourceTree = "<group>"; };
                D461F94E2203A8AA00A88910 /* xpc_service_dns_proxy.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = xpc_service_dns_proxy.c; sourceTree = "<group>"; };
                D470807222123E44006BAB32 /* libarchive.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libarchive.tbd; path = usr/lib/libarchive.tbd; sourceTree = SDKROOT; };
+               D473A50F2396F1F800D0F827 /* dnssec_v2_trust_anchor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_trust_anchor.h; sourceTree = "<group>"; usesTabs = 1; };
+               D473A5102396F1F800D0F827 /* dnssec_v2_trust_anchor.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec_v2_trust_anchor.c; sourceTree = "<group>"; usesTabs = 1; };
+               D473A5192397176A00D0F827 /* dnssec_v2_client.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_client.h; sourceTree = "<group>"; usesTabs = 1; };
+               D473A51A2397176A00D0F827 /* dnssec_v2_client.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec_v2_client.c; sourceTree = "<group>"; usesTabs = 1; };
+               D473A51F2398895E00D0F827 /* dnssec_v2_log.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_log.h; sourceTree = "<group>"; usesTabs = 1; };
                D49ECA17220BBAC400655887 /* dns-sd-entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "dns-sd-entitlements.plist"; sourceTree = "<group>"; };
                D4BFF8D922B1C52100A0BA86 /* posix_utilities.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = posix_utilities.c; path = ../mDNSPosix/posix_utilities.c; sourceTree = "<group>"; };
                D4BFF8DA22B1C52100A0BA86 /* posix_utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = posix_utilities.h; path = ../mDNSPosix/posix_utilities.h; sourceTree = "<group>"; };
                D4CFA7CC21E7B95E00F5AD0E /* liblog_mdnsresponder.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = liblog_mdnsresponder.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
                D4CFA7D021E7BA0E00F5AD0E /* liblog_mdnsresponder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = liblog_mdnsresponder.m; sourceTree = "<group>"; };
                D4CFA7D221E7BA8F00F5AD0E /* mDNSFeatures.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mDNSFeatures.h; path = ../mDNSShared/mDNSFeatures.h; sourceTree = "<group>"; };
+               D4ED20B72360D16900632A59 /* list.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = list.h; sourceTree = "<group>"; usesTabs = 1; };
+               D4ED20B82360D16900632A59 /* list.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = list.c; sourceTree = "<group>"; usesTabs = 1; };
+               D4ED20BC236117F700632A59 /* dnssec_v2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2.h; sourceTree = "<group>"; usesTabs = 1; };
+               D4ED20BD236117F700632A59 /* dnssec_v2.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec_v2.c; sourceTree = "<group>"; usesTabs = 1; };
+               D4ED20CE23749DA900632A59 /* dnssec_v2_retrieval.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_retrieval.h; sourceTree = "<group>"; usesTabs = 1; };
+               D4ED20CF23749DA900632A59 /* dnssec_v2_retrieval.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec_v2_retrieval.c; sourceTree = "<group>"; usesTabs = 1; };
+               D4ED20D32374A03A00632A59 /* dnssec_v2_embedded.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_embedded.h; sourceTree = "<group>"; usesTabs = 1; };
+               D4ED20D52375F99700632A59 /* dnssec_v2_structs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_structs.h; sourceTree = "<group>"; usesTabs = 1; };
+               D4ED20D62375F99700632A59 /* dnssec_v2_structs.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec_v2_structs.c; sourceTree = "<group>"; usesTabs = 1; };
+               D4ED20DA237CC90200632A59 /* dnssec_v2_validation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_validation.h; sourceTree = "<group>"; usesTabs = 1; };
+               D4ED20DB237CC90200632A59 /* dnssec_v2_validation.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec_v2_validation.c; sourceTree = "<group>"; usesTabs = 1; };
+               D4ED20DF237DCD7000632A59 /* dnssec_v2_helper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_helper.h; sourceTree = "<group>"; usesTabs = 1; };
+               D4ED20E0237DCD7000632A59 /* dnssec_v2_helper.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec_v2_helper.c; sourceTree = "<group>"; usesTabs = 1; };
+               D4F2AA43239ADFB10073E461 /* ListTMethodsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ListTMethodsTest.m; sourceTree = "<group>"; };
                DB2CC4430662DD1100335AB3 /* BaseListener.java */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.java; name = BaseListener.java; path = ../mDNSShared/Java/BaseListener.java; sourceTree = SOURCE_ROOT; };
                DB2CC4440662DD1100335AB3 /* BrowseListener.java */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.java; name = BrowseListener.java; path = ../mDNSShared/Java/BrowseListener.java; sourceTree = SOURCE_ROOT; };
                DB2CC4450662DD1100335AB3 /* DNSRecord.java */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.java; name = DNSRecord.java; path = ../mDNSShared/Java/DNSRecord.java; sourceTree = SOURCE_ROOT; };
                F525E72804AA167501F1CF4D /* uds_daemon.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = uds_daemon.c; path = ../mDNSShared/uds_daemon.c; sourceTree = SOURCE_ROOT; usesTabs = 0; };
                F5E11B5A04A28126019798ED /* dnssd_ipc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dnssd_ipc.c; path = ../mDNSShared/dnssd_ipc.c; sourceTree = SOURCE_ROOT; };
                F5E11B5B04A28126019798ED /* dnssd_ipc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dnssd_ipc.h; path = ../mDNSShared/dnssd_ipc.h; sourceTree = SOURCE_ROOT; };
+               F910D36823FE06D7007AA74C /* libipconfig.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libipconfig.tbd; path = usr/lib/libipconfig.tbd; sourceTree = SDKROOT; };
                FF08480607CEB8E800AE6769 /* inprogress.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = inprogress.tiff; path = PreferencePane/Artwork/inprogress.tiff; sourceTree = SOURCE_ROOT; };
                FF13FFEA0A5DA44A00897C81 /* dnsextd_lexer.l */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.lex; name = dnsextd_lexer.l; path = ../mDNSShared/dnsextd_lexer.l; sourceTree = SOURCE_ROOT; };
                FF13FFEC0A5DA45500897C81 /* dnsextd_parser.y */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.yacc; name = dnsextd_parser.y; path = ../mDNSShared/dnsextd_parser.y; sourceTree = SOURCE_ROOT; };
                        isa = PBXFrameworksBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               78F4751323342B9700FE942B /* CoreFoundation.framework in Frameworks */,
                                BD42EE2021DB41970053A651 /* libobjc.tbd in Frameworks */,
-                               BD93516E21E369B90078582E /* Foundation.framework in Frameworks */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               891F2F7C2397EA1C001EC153 /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               892EF6D4246B5B8700DB9EA1 /* CoreUtils.framework in Frameworks */,
+                               892936D4246AC49D00548526 /* Network.framework in Frameworks */,
+                               8934D09223FE397800403EA6 /* SystemConfiguration.framework in Frameworks */,
+                               8934D09123FE395F00403EA6 /* libipconfig.tbd in Frameworks */,
+                               B7BAFB5523A2A2390045705F /* CoreFoundation.framework in Frameworks */,
+                               B7BAFB5623A2A2460045705F /* Security.framework in Frameworks */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               895E9DA223C68D5000DA0FE0 /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               89AFC1E023F5DE2B00538084 /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               89C277F4246B0260001F3828 /* Security.framework in Frameworks */,
+                               89C277F3246B025A001F3828 /* CoreUtils.framework in Frameworks */,
+                               F910D36B23FE076D007AA74C /* SystemConfiguration.framework in Frameworks */,
+                               F910D36A23FE06E1007AA74C /* CoreFoundation.framework in Frameworks */,
+                               F910D36923FE06D7007AA74C /* libipconfig.tbd in Frameworks */,
+                               89AFC1F323F5E0E100538084 /* Network.framework in Frameworks */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               89D4BBE4239EA80E00FCFB7E /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               89C277F2246B01AC001F3828 /* Network.framework in Frameworks */,
+                               B7BAFB5723A2A2600045705F /* CoreFoundation.framework in Frameworks */,
+                               B7BAFB5823A2A26C0045705F /* Security.framework in Frameworks */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        isa = PBXFrameworksBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               892EF6F1246DD00000DB9EA1 /* AppKit.framework in Frameworks */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        isa = PBXFrameworksBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               892EF6EF246DCE2600DB9EA1 /* SafariServices.framework in Frameworks */,
                                B7A861A1212B3FF400E81CC3 /* Cocoa.framework in Frameworks */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                        isa = PBXFrameworksBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
-                               B74A96261DD4EDE60084A8C5 /* Preferences.framework in Frameworks */,
+                               892EF6E6246DC10600DB9EA1 /* QuartzCore.framework in Frameworks */,
+                               892EF6E2246DC02A00DB9EA1 /* CFNetwork.framework in Frameworks */,
+                               892EF6DC246DBFF800DB9EA1 /* SystemConfiguration.framework in Frameworks */,
+                               892EF6DA246DBFB800DB9EA1 /* Preferences.framework in Frameworks */,
+                               892EF6D8246DBF9F00DB9EA1 /* CoreGraphics.framework in Frameworks */,
+                               892EF6D6246DBF7F00DB9EA1 /* UIKit.framework in Frameworks */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        isa = PBXFrameworksBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
-                               D448F7AC222DCB5C0069E1D2 /* libarchive.tbd in Frameworks */,
+                               B7BAFB5423A2A2080045705F /* libarchive.tbd in Frameworks */,
                                B7A861A3212B409200E81CC3 /* IOKit.framework in Frameworks */,
+                               B7A861A9212B411600E81CC3 /* Network.framework in Frameworks */,
                                B7A861A8212B411200E81CC3 /* libicucore.tbd in Frameworks */,
                                B7A861A7212B410D00E81CC3 /* libxml2.tbd in Frameworks */,
                                B7A861A2212B408D00E81CC3 /* Security.framework in Frameworks */,
-                               B7EEF7C4212603B20093828F /* SystemConfiguration.framework in Frameworks */,
-                               B7A861A9212B411600E81CC3 /* Network.framework in Frameworks */,
                                B77CAFD0234EAE72006706B4 /* NetworkExtension.framework in Frameworks */,
+                               B7EEF7C4212603B20093828F /* SystemConfiguration.framework in Frameworks */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        isa = PBXFrameworksBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               892EF6E9246DCD7600DB9EA1 /* CoreServices.framework in Frameworks */,
                                B74F2B461E82FEAE0084960E /* Foundation.framework in Frameworks */,
                                B74F2B471E82FEFE0084960E /* Security.framework in Frameworks */,
                        );
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
+               BD7BFBF1236FCF0E000456BC /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               B74ECA1924134C5F001DDFE8 /* CoreUtils.framework in Frameworks */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
                BD9BA74D1EAF90E400658CCF /* Frameworks */ = {
                        isa = PBXFrameworksBuildPhase;
                        buildActionMask = 2147483647;
                                BD893CE7206C0EAF0055F9E7 /* CoreFoundation.framework in Frameworks */,
                                BD9BA7581EAF929C00658CCF /* CoreUtils.framework in Frameworks */,
                                BD2A15BC225ED38600BEA50A /* Network.framework in Frameworks */,
+                               BDF1CEE622F1787E009AB795 /* Security.framework in Frameworks */,
                                BD893CE5206C0D980055F9E7 /* SystemConfiguration.framework in Frameworks */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
+               BF0E381A24882B650028D528 /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               BF0E381B24882B650028D528 /* CoreUtils.framework in Frameworks */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
                D284BE640ADD80740027CCDF /* Frameworks */ = {
                        isa = PBXFrameworksBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
-                               D470807322123E44006BAB32 /* libarchive.tbd in Frameworks */,
                                D284BE650ADD80740027CCDF /* CoreFoundation.framework in Frameworks */,
+                               B7A5C3412399D74E00615DBD /* CoreServices.framework in Frameworks */,
                                BDA3F08A1C48DB920054FB4B /* Foundation.framework in Frameworks */,
                                D284BE670ADD80740027CCDF /* IOKit.framework in Frameworks */,
+                               BD5278D823F69D4B00235117 /* PowerLog.framework in Frameworks */,
                                D284BE680ADD80740027CCDF /* Security.framework in Frameworks */,
                                D284BE660ADD80740027CCDF /* SystemConfiguration.framework in Frameworks */,
+                               D470807322123E44006BAB32 /* libarchive.tbd in Frameworks */,
+                               B7F707AA23972EBF00A31B5A /* libbsm.tbd in Frameworks */,
                                21B830A81D8A642200AE2001 /* libicucore.dylib in Frameworks */,
-                               219D5542149ED645004464AE /* libxml2.dylib in Frameworks */,
                                789036921F7AC1FA0077A962 /* libnetwork.tbd in Frameworks */,
+                               219D5542149ED645004464AE /* libxml2.dylib in Frameworks */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        isa = PBXFrameworksBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               B74ECA1824134BDC001DDFE8 /* CoreUtils.framework in Frameworks */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                BD11266C21DB1AB3006115E6 /* mDNSMacOSX */,
                                D4BFF8D822B1C29E00A0BA86 /* mDNSPosix */,
                                08FB7795FE84155DC02AAC07 /* mDNS Server Sources */,
+                               891F2F8C2397EAB0001EC153 /* ServiceRegistration */,
+                               891F2F802397EA1C001EC153 /* srp-mdns-proxy */,
+                               B7BAFB4723A025390045705F /* FeatureFlags */,
                                6575FC1F022EB78C00000109 /* Command-Line Clients */,
                                213FB20912028902002B3A08 /* Bonjour Events Plugin */,
                                DB2CC4420662DCE500335AB3 /* Java Support */,
                08FB7795FE84155DC02AAC07 /* mDNS Server Sources */ = {
                        isa = PBXGroup;
                        children = (
+                               895A278424D465FC00697AB1 /* DebugServices.c */,
                                897729B62202A5480018FAEB /* dnssd_clientshim.c */,
                                897729AF2202A5370018FAEB /* dso-transport.c */,
                                897729B02202A5370018FAEB /* dso-transport.h */,
                                21F51DBD1B3540DB0070B05C /* com.apple.dnsextd.plist */,
                                21F51DBF1B35412D0070B05C /* com.apple.mDNSResponder.plist */,
                                21F51DBE1B3541030070B05C /* com.apple.mDNSResponderHelper.plist */,
-                               21A57F4A145B2AE100939099 /* CryptoAlg.c */,
-                               21A57F4B145B2AE100939099 /* CryptoAlg.h */,
-                               21A57F51145B2B1400939099 /* CryptoSupport.c */,
-                               21A57F52145B2B1400939099 /* CryptoSupport.h */,
                                6575FBEC022EAF7200000109 /* daemon.c */,
                                BD691B281ED2F43200E6F317 /* DNS64.c */,
                                BD691B291ED2F43200E6F317 /* DNS64.h */,
                                21DED43415702C0F0060B6B9 /* DNSProxySupport.c */,
                                FFFA38620AEEDB090065B80A /* dnssd_clientlib.c */,
                                FFFA38640AEEDB130065B80A /* dnssd_clientstub.c */,
+                               BDFE3FFF2408C8D800C77011 /* dnssd_clientstub_apple.c */,
+                               BDFE40002408C8D900C77011 /* dnssd_clientstub_apple.h */,
                                F5E11B5A04A28126019798ED /* dnssd_ipc.c */,
                                F5E11B5B04A28126019798ED /* dnssd_ipc.h */,
-                               213BDC6C147319F400000896 /* dnssec.c */,
-                               2124FA2F1471E9B50021D7BB /* dnssec.h */,
-                               21070E5D16486B9000A69507 /* DNSSECSupport.c */,
-                               21070E5E16486B9000A69507 /* DNSSECSupport.h */,
                                DBAAFE2C057E8F660085CAD0 /* GenLinkedList.c */,
                                4ADB5F230F6AB9F400B95BF3 /* helper-entitlements.plist */,
                                2E0406CA0C31E9AD00F13B59 /* helper-main.c */,
                                000753D303367C1C0CCA2C71 /* mDNSMacOSX.h */,
                                FF485D5105632E0000130380 /* mDNSResponder.8 */,
                                FF85880B0BD599F40080D89F /* mDNSResponder.sb */,
+                               B7A5C33623986BBF00615DBD /* mDNSResponder-entitlements.plist */,
                                4AAE0C7A0C68E97F003882A5 /* mDNSResponderHelper.8 */,
                                BDA3F0871C48DB6D0054FB4B /* Metrics.h */,
                                BDA3F0881C48DB6D0054FB4B /* Metrics.m */,
-                               2124FA321471E9DE0021D7BB /* nsec.c */,
-                               2124FA2B1471E98C0021D7BB /* nsec.h */,
-                               2127A47515C3C7B900A857FC /* nsec3.c */,
-                               2127A47615C3C7B900A857FC /* nsec3.h */,
+                               787E8B72239485D200DC9D01 /* HTTPUtilities.h */,
+                               787E8B73239485D200DC9D01 /* HTTPUtilities.m */,
+                               5AF23B2A23F37316004AB237 /* DNSHeuristics.h */,
+                               5A9E8E5E23F4B99A003B4CAD /* DNSHeuristicsInternal.h */,
+                               5AF23B2823F37309004AB237 /* DNSHeuristics.m */,
                                4BD2B638134FE09F002B96D5 /* P2PPacketFilter.c */,
                                4BD2B639134FE09F002B96D5 /* P2PPacketFilter.h */,
                                FFCB6D73075D539900B8AF62 /* PlatformCommon.c */,
                                BD03E88C1AD31278005E8A81 /* SymptomReporter.c */,
                                BDF2D02B2169B77B00D0DBF5 /* SymptomReporter.h */,
                                7F18A9F70587CEF6001880B3 /* uDNS.c */,
-                               216D9ACD1720C9F5008066E1 /* uDNSPathEvalulation.c */,
+                               216D9ACD1720C9F5008066E1 /* uDNSPathEvaluation.c */,
                                F525E72804AA167501F1CF4D /* uds_daemon.c */,
                                4A2E69DD0F5475A3004A87B0 /* uds_daemon.h */,
                        );
                                B7473E7A1EC395C300D31B9D /* Bonjour Safari Extension.appex */,
                                B74F16EB2114E49C00BEBE84 /* Tests.xctest */,
                                D4CFA7CC21E7B95E00F5AD0E /* liblog_mdnsresponder.dylib */,
+                               891F2F7F2397EA1C001EC153 /* srp-mdns-proxy */,
+                               89D4BBE7239EA80E00FCFB7E /* srp-client */,
+                               BD7BFBF3236FCF0E000456BC /* liblog_mdns.dylib */,
+                               895E9DA523C68D5000DA0FE0 /* srputil */,
+                               89AFC1E323F5DE2B00538084 /* ra-tester */,
+                               BF0E381F24882B650028D528 /* liblog_srp.dylib */,
                        );
                        name = Products;
                        sourceTree = "<group>";
                                FF1C919D07021D77001048AB /* dns-sd.1 */,
                                FF1C919F07021E3F001048AB /* dns-sd.c */,
                                FF5852100DD27BD300862BDF /* ClientCommon.c */,
+                               895E9DAC23C68D7300DA0FE0 /* srputil */,
                        );
                        name = "Command-Line Clients";
                        sourceTree = "<group>";
                };
+               891F2F802397EA1C001EC153 /* srp-mdns-proxy */ = {
+                       isa = PBXGroup;
+                       children = (
+                               8954B0352406F4A80032D78F /* cti-services.c */,
+                               8954B0342406F4A80032D78F /* cti-services.h */,
+                               89AFC1F823F6115000538084 /* cti-wrapper.mm */,
+                               89AFC1F723F610E900538084 /* cti-wrapper.h */,
+                               89AFC1E423F5DE2B00538084 /* ra-tester */,
+                               89D9064B23C68BA300A45D6F /* com.apple.srp-mdns-proxy.plist */,
+                               89D9064A23C68BA300A45D6F /* srp-mdns-proxy-entitlements.plist */,
+                               891F2F862397EA79001EC153 /* srp-mdns-proxy.c */,
+                               891F2F892397EA79001EC153 /* srp-mdns-proxy.h */,
+                               891F2F872397EA79001EC153 /* srp-parse.c */,
+                               891F2F882397EA79001EC153 /* srp-proxy.h */,
+                       );
+                       name = "srp-mdns-proxy";
+                       path = ../ServiceRegistration;
+                       sourceTree = "<group>";
+               };
+               891F2F8C2397EAB0001EC153 /* ServiceRegistration */ = {
+                       isa = PBXGroup;
+                       children = (
+                               895A278124CB743C00697AB1 /* srp-client-entitlements.plist */,
+                               89AFC1EE23F5E09500538084 /* config-parse.h */,
+                               89AFC1F023F5E09500538084 /* dnssd-proxy.h */,
+                               89AFC1EF23F5E09500538084 /* srp-crypto.h */,
+                               89AFC1F223F5E09500538084 /* srp-gw.h */,
+                               89AFC1F123F5E09500538084 /* srp-tls.h */,
+                               89C38ECB23C93B6600800A42 /* route.c */,
+                               89C38ECA23C93B6600800A42 /* route.h */,
+                               89D4BBFA239EB1CE00FCFB7E /* posix.c */,
+                               891F2FA42397EC47001EC153 /* srp.h */,
+                               891F2F952397EB30001EC153 /* dns-msg.h */,
+                               891F2F932397EB30001EC153 /* fromwire.c */,
+                               891F2F922397EB30001EC153 /* hmac-macos.c */,
+                               891F2F902397EB30001EC153 /* ioloop.h */,
+                               891F2F8D2397EB30001EC153 /* macos-ioloop.c */,
+                               891F2F8E2397EB30001EC153 /* sign-macos.c */,
+                               891F2F8F2397EB30001EC153 /* towire.c */,
+                               891F2F912397EB30001EC153 /* verify-macos.c */,
+                               891F2F942397EB30001EC153 /* wireutils.c */,
+                               89511E4923C5FE3B00D603D4 /* advertising_proxy_services.h */,
+                               89511E4A23C5FE3B00D603D4 /* advertising_proxy_services.c */,
+                               89458D9F23A1AA5000C3978D /* srp-client.c */,
+                               89458DA023A1AA5000C3978D /* srp-ioloop.c */,
+                               BF0E382024882BB60028D528 /* log_srp.m */,
+                       );
+                       name = ServiceRegistration;
+                       path = ../ServiceRegistration;
+                       sourceTree = "<group>";
+               };
+               895E9DAC23C68D7300DA0FE0 /* srputil */ = {
+                       isa = PBXGroup;
+                       children = (
+                               8998717623C697BE00C3AF39 /* srputil-entitlements.plist */,
+                               8998717523C6977E00C3AF39 /* srputil.c */,
+                       );
+                       name = srputil;
+                       sourceTree = "<group>";
+               };
+               89AFC1E423F5DE2B00538084 /* ra-tester */ = {
+                       isa = PBXGroup;
+                       children = (
+                               89AFC1E523F5DE2B00538084 /* ra-tester.c */,
+                       );
+                       path = "ra-tester";
+                       sourceTree = "<group>";
+               };
                B7473E621EC3954400D31B9D /* Bonjour Safari Menu */ = {
                        isa = PBXGroup;
                        children = (
                B74BF92F2322E99700E35354 /* Unit Tests */ = {
                        isa = PBXGroup;
                        children = (
+                               D4F2AA40239ADD940073E461 /* DNSSEC Unit Tests */,
                                B74F16EF2114E49D00BEBE84 /* Info.plist */,
                                B7A86198212B074500E81CC3 /* LocalOnlyTimeoutTest.m */,
                                B7A861962127845700E81CC3 /* CNameRecordTest.m */,
                                D459F0D3222862BA0056AC5B /* HelperFunctionTest.m */,
                                B74BF92D2322E97400E35354 /* SuspiciousReplyTest.m */,
                                B74BF930232701F600E35354 /* CacheOrderTest.m */,
+                               5A9E8E5C23F47817003B4CAD /* DNSHeuristicsTest.m */,
                                B7E06CDF2329AA7B0021401F /* LocalOnlyWithInterfacesTest.m */,
                                B77CAFCD234EADE5006706B4 /* PathEvaluationTest.m */,
                        );
                        isa = PBXGroup;
                        children = (
                                BD98A796213A3EAE0002EC47 /* mDNSResponder.plist */,
+                               5A9E8E6323F4BE29003B4CAD /* mDNSResponderTests-Entitlements.plist */,
                                D448F7A6222DA98E0069E1D2 /* BATS Scripts */,
                                37DDE9241BA382280092AC61 /* Unit Test Utilities */,
                                B74BF92F2322E99700E35354 /* Unit Tests */,
                        children = (
                                B79FA153211CF0AF00B7861E /* dnsproxy.h */,
                                B79FA154211CF0AF00B7861E /* uDNS.h */,
-                               B79FA155211CF0AF00B7861E /* nsec.h */,
                                B79FA156211CF0AF00B7861E /* DNSCommon.c */,
                                B79FA157211CF0AF00B7861E /* mDNSDebug.h */,
-                               B79FA158211CF0AF00B7861E /* dnssec.c */,
-                               B79FA159211CF0AF00B7861E /* CryptoAlg.h */,
-                               B79FA15A211CF0AF00B7861E /* nsec3.h */,
                                B79FA15B211CF0AF00B7861E /* Implementer Notes.txt */,
-                               B79FA15C211CF0AF00B7861E /* nsec.c */,
                                B79FA15D211CF0AF00B7861E /* uDNS.c */,
                                B79FA15E211CF0AF00B7861E /* dnsproxy.c */,
-                               B79FA15F211CF0AF00B7861E /* dnssec.h */,
                                B79FA160211CF0AF00B7861E /* mDNS.c */,
                                B79FA162211CF0AF00B7861E /* DNSCommon.h */,
-                               B79FA163211CF0AF00B7861E /* CryptoAlg.c */,
                                B79FA164211CF0AF00B7861E /* mDNSEmbeddedAPI.h */,
-                               B79FA165211CF0AF00B7861E /* nsec3.c */,
                                B79FA166211CF0AF00B7861E /* DNSDigest.c */,
                        );
                        name = mDNSCore;
                        path = iOS;
                        sourceTree = "<group>";
                };
+               B7BAFB4723A025390045705F /* FeatureFlags */ = {
+                       isa = PBXGroup;
+                       children = (
+                               B7A5C33A2399A24C00615DBD /* mDNSResponder.plist */,
+                       );
+                       path = FeatureFlags;
+                       sourceTree = "<group>";
+               };
                B7D566A41E81D6A900E43008 /* RemoteViewService */ = {
                        isa = PBXGroup;
                        children = (
                BD11266C21DB1AB3006115E6 /* mDNSMacOSX */ = {
                        isa = PBXGroup;
                        children = (
+                               D4ED20A92360C52D00632A59 /* dnssec_v2 */,
+                               BDA37F1123471F6100B9266D /* mdns_objects */,
                                D421B3DC22178B7800D35C20 /* utilities */,
                                BDF2D02D216A25E800D0DBF5 /* ApplePlatformFeatures.h */,
                                BD11267721DB2A9A006115E6 /* dnssd.c */,
+                               B737059122CD65E400477EB9 /* dnssd_analytics.c */,
+                               B737059222CD65E400477EB9 /* dnssd_analytics.h */,
+                               78A4903B23343B8400FA2CCA /* dnssd_descriptions.m */,
                                BD11267B21DB2C7C006115E6 /* dnssd_object.h */,
                                BD11267621DB2A9A006115E6 /* dnssd_object.m */,
                                BD11267521DB2A9A006115E6 /* dnssd_private.h */,
                                BD11266F21DB1AFE006115E6 /* dnssd_server.c */,
                                BD11267021DB1AFE006115E6 /* dnssd_server.h */,
+                               78BD2BF723FC521B004A3D8B /* dnssd_svcb.c */,
+                               78BD2BF823FC521B004A3D8B /* dnssd_svcb.h */,
                                BD11266E21DB1AFE006115E6 /* dnssd_xpc.c */,
                                BD11266D21DB1AFE006115E6 /* dnssd_xpc.h */,
-                               BD2A15B7225ED2E500BEA50A /* mdns.c */,
-                               BD2A15B6225ED2E500BEA50A /* mdns_object.h */,
-                               BD2A15B5225ED2E500BEA50A /* mdns_object.m */,
-                               BD2A15B8225ED2E500BEA50A /* mdns_private.h */,
+                               BDA9C35A2370304E00DC86BD /* QuerierSupport.c */,
+                               BDA9C35C2370307600DC86BD /* QuerierSupport.h */,
                        );
                        name = mDNSMacOSX;
                        sourceTree = "<group>";
                        isa = PBXGroup;
                        children = (
                                B7F1FEEA234FAF0F0081159C /* dnssdutil-entitlements.plist */,
+                               BD97754B221D643500F68FFC /* dnssdutil.c */,
                                BD97754D221D64BF00F68FFC /* DNSMessage.c */,
                                BD97754E221D64BF00F68FFC /* DNSMessage.h */,
+                               BD12709124469F1C00BC84D8 /* DNSServerDNSSEC.c */,
+                               BD12709224469F1C00BC84D8 /* DNSServerDNSSEC.h */,
                                B7F1FEDC234F89C50081159C /* TestUtils.h */,
                                B7F1FEDD234F89C50081159C /* TestUtils.m */,
-                               BD97754B221D643500F68FFC /* dnssdutil.c */,
                        );
                        name = dnssdutil;
                        path = ../Clients/dnssdutil;
                BD9BA7561EAF929C00658CCF /* Frameworks */ = {
                        isa = PBXGroup;
                        children = (
+                               B73C5CD124B78FC80002050A /* SetupAssistantFramework.framework */,
+                               892EF6F0246DD00000DB9EA1 /* AppKit.framework */,
+                               892EF6EE246DCE2600DB9EA1 /* SafariServices.framework */,
+                               892EF6EC246DCD9F00DB9EA1 /* libswiftSafariServices.tbd */,
+                               892EF6EA246DCD8A00DB9EA1 /* SafariServices.framework */,
+                               892EF6E7246DCD4E00DB9EA1 /* CoreServices.framework */,
+                               892EF6E5246DC10600DB9EA1 /* QuartzCore.framework */,
+                               892EF6E3246DC09100DB9EA1 /* CoreAnalytics.framework */,
+                               892EF6E1246DC02A00DB9EA1 /* CFNetwork.framework */,
+                               892EF6DF246DC01500DB9EA1 /* CoreFoundation.framework */,
+                               892EF6DD246DC00500DB9EA1 /* CoreUtils.framework */,
+                               892EF6DB246DBFF800DB9EA1 /* SystemConfiguration.framework */,
+                               892EF6D9246DBFB800DB9EA1 /* Preferences.framework */,
+                               892EF6D7246DBF9F00DB9EA1 /* CoreGraphics.framework */,
+                               892EF6D5246DBF7F00DB9EA1 /* UIKit.framework */,
+                               F910D36823FE06D7007AA74C /* libipconfig.tbd */,
+                               BD5278D723F69D4B00235117 /* PowerLog.framework */,
+                               5A9E8E5923F46921003B4CAD /* CoreWiFi.framework */,
+                               5A9E8E5723F46913003B4CAD /* CoreWLAN.framework */,
+                               8941BE4A23E34AA7004AF1CA /* libdns_services.tbd */,
+                               B7F707A923972EBE00A31B5A /* libbsm.tbd */,
+                               B7F7078C2395F19C00A31B5A /* TCC.framework */,
+                               B799C96B2385A5E500675816 /* System.framework */,
+                               B799C9692385A5B900675816 /* libsystem_coreservices.tbd */,
+                               B799C9672385A5AA00675816 /* CoreServices.framework */,
+                               BDF1CEE522F1787E009AB795 /* Security.framework */,
                                B77CAFCF234EAE72006706B4 /* NetworkExtension.framework */,
                                BDA82B8922BF8A7000A31CBE /* libdns_services.tbd */,
-                               D448F7AD222DCC0A0069E1D2 /* CoreFoundation.framework */,
-                               D448F7AB222DCB5B0069E1D2 /* libarchive.tbd */,
-                               D408BAAF221243DA00139EDA /* libarchive.2.tbd */,
                                D470807222123E44006BAB32 /* libarchive.tbd */,
                                BD42EE1F21DB41970053A651 /* libobjc.tbd */,
                                BDAF4BBF20B52D3D0062219E /* CFNetwork.framework */,
                                BD893CE6206C0EAF0055F9E7 /* CoreFoundation.framework */,
                                BD893CE4206C0D980055F9E7 /* SystemConfiguration.framework */,
                                789036911F7AC1F90077A962 /* libnetwork.tbd */,
+                               B7BAFB5A23A2A27F0045705F /* Network.framework */,
                                BD9BA7571EAF929C00658CCF /* CoreUtils.framework */,
                                B735A2EE21B8293900025BB0 /* Foundation.framework */,
                        );
                        name = Frameworks;
                        sourceTree = "<group>";
                };
+               BDA37F1123471F6100B9266D /* mdns_objects */ = {
+                       isa = PBXGroup;
+                       children = (
+                               BD7BFBF7236FD019000456BC /* log_mdns.m */,
+                               BDA37F1623471F6100B9266D /* mdns_address.c */,
+                               BDA37F1D23471F6100B9266D /* mdns_address.h */,
+                               BDA37F1323471F6100B9266D /* mdns_base.h */,
+                               BD1904ED235E759500F146D6 /* mdns_dns_service.c */,
+                               BD1904EC235E759500F146D6 /* mdns_dns_service.h */,
+                               BDA37F1F23471F6100B9266D /* mdns_helpers.c */,
+                               BDA37F1923471F6100B9266D /* mdns_helpers.h */,
+                               BDA37F1223471F6100B9266D /* mdns_interface_monitor.c */,
+                               BDA37F1A23471F6100B9266D /* mdns_interface_monitor.h */,
+                               BDA37F1423471F6100B9266D /* mdns_internal.h */,
+                               BD0FFC6F2502E78A00B6DB73 /* mdns_managed_defaults.c */,
+                               BD0FFC6E2502E78900B6DB73 /* mdns_managed_defaults.h */,
+                               BD4FC0D624A81643000B0B21 /* mdns_message.c */,
+                               BD4FC0D524A81643000B0B21 /* mdns_message.h */,
+                               BDA37F1C23471F6100B9266D /* mdns_object.c */,
+                               BDA37F1723471F6100B9266D /* mdns_object.h */,
+                               BDA37F2023471F6100B9266D /* mdns_objects.h */,
+                               BDA37F1823471F6100B9266D /* mdns_objects.m */,
+                               BD1970B423F90E94001BA06F /* mdns_powerlog.c */,
+                               BD1970B323F90E94001BA06F /* mdns_powerlog.h */,
+                               BDA37F1E23471F6100B9266D /* mdns_private.h */,
+                               BDA37F1523471F6100B9266D /* mdns_resolver.c */,
+                               BDA37F1B23471F6100B9266D /* mdns_resolver.h */,
+                               BD52398123F3BBCD004AC878 /* mdns_set.c */,
+                               BD52398223F3BBCD004AC878 /* mdns_set.h */,
+                               BD7BFBDA236D5342000456BC /* mdns_symptoms.c */,
+                               BD7BFBD9236D5342000456BC /* mdns_symptoms.h */,
+                               BDFE3FFB2408C58B00C77011 /* mdns_tlv.c */,
+                               BDFE3FFC2408C58B00C77011 /* mdns_tlv.h */,
+                               B7F9AB79237F4F4300C2BEA2 /* mdns_trust.c */,
+                               B7F9AB7A237F4F4300C2BEA2 /* mdns_trust.h */,
+                               B7E8092C23F70EF6002CB309 /* mdns_trust_checks.h */,
+                               B7BAFB4923A033D80045705F /* mdns_trust_checks.m */,
+                               BDFE400D2408F62E00C77011 /* mdns_xpc.c */,
+                               BDFE400E2408F62E00C77011 /* mdns_xpc.h */,
+                       );
+                       path = mdns_objects;
+                       sourceTree = "<group>";
+               };
                BDB61842206ADB7700AFF600 /* LoggingProfiles */ = {
                        isa = PBXGroup;
                        children = (
-                               D4CFA7D021E7BA0E00F5AD0E /* liblog_mdnsresponder.m */,
-                               BDB61843206ADB7700AFF600 /* com.apple.mDNSResponder.plist */,
                                BDB61844206ADB7700AFF600 /* AppleInternal */,
+                               BDB61843206ADB7700AFF600 /* com.apple.mDNSResponder.plist */,
+                               89D3B57F2463365B00FA67EB /* com.apple.srp-mdns-proxy.plist */,
+                               D4CFA7D021E7BA0E00F5AD0E /* liblog_mdnsresponder.m */,
                        );
                        path = LoggingProfiles;
                        sourceTree = "<group>";
                BDB61844206ADB7700AFF600 /* AppleInternal */ = {
                        isa = PBXGroup;
                        children = (
+                               BD61858224C6B9240016A102 /* com.apple.mdns.plist */,
                                BDB61846206ADDDF00AFF600 /* com.apple.mDNSResponder.plist */,
+                               89D3B57D2463363900FA67EB /* com.apple.srp-mdns-proxy.plist */,
                        );
                        path = AppleInternal;
                        sourceTree = "<group>";
                };
+               D41DED3123DFBF3A009F9854 /* base_encoding */ = {
+                       isa = PBXGroup;
+                       children = (
+                               D41DED3423DFC3C3009F9854 /* base_n.h */,
+                               D41DED3523DFC3C3009F9854 /* base_n.c */,
+                       );
+                       path = base_encoding;
+                       sourceTree = "<group>";
+               };
+               D41DED3D23E0F6C2009F9854 /* Crypto */ = {
+                       isa = PBXGroup;
+                       children = (
+                               D43B121523BEB1A200B8D941 /* CanonicalMethodsTest.m */,
+                               D41DED3E23E0F71D009F9854 /* DigestCalculationTest.m */,
+                               D41DED4223E21D65009F9854 /* NSEC3HashTest.m */,
+                       );
+                       path = Crypto;
+                       sourceTree = "<group>";
+               };
+               D41DED4023E0F72A009F9854 /* Utility */ = {
+                       isa = PBXGroup;
+                       children = (
+                               D4F2AA43239ADFB10073E461 /* ListTMethodsTest.m */,
+                               D41DED3923E0CC40009F9854 /* BaseNEncodingDecodingTest.m */,
+                       );
+                       path = Utility;
+                       sourceTree = "<group>";
+               };
                D421B3DC22178B7800D35C20 /* utilities */ = {
                        isa = PBXGroup;
                        children = (
+                               B72BC06223ABFD8C0021E0C9 /* bundle_utilities.h */,
+                               B72BC06123ABFD8C0021E0C9 /* bundle_utilities.m */,
+                               B73C5CCD24B78EE70002050A /* setup_assistant_helper.h */,
+                               B73C5CCE24B78EE70002050A /* setup_assistant_helper.m */,
                                D421B3DD22178BE700D35C20 /* system_utilities.h */,
-                               D421B3DE22178BE700D35C20 /* system_utilities.c */,
+                               B7BAFB4B23A035140045705F /* system_utilities.m */,
                        );
                        path = utilities;
                        sourceTree = "<group>";
                D461F9472203A59200A88910 /* xpc_services */ = {
                        isa = PBXGroup;
                        children = (
+                               89B0BF3723C55A4400245A42 /* xpc_client_advertising_proxy.h */,
                                848DA5D516547F7200D2E8B4 /* xpc_clients.h */,
                                D461F9482203A6B400A88910 /* xpc_services.h */,
                                D461F9492203A6B400A88910 /* xpc_services.c */,
                        path = xpc_services;
                        sourceTree = "<group>";
                };
+               D473A51E239842C900D0F827 /* list */ = {
+                       isa = PBXGroup;
+                       children = (
+                               D4ED20B72360D16900632A59 /* list.h */,
+                               D4ED20B82360D16900632A59 /* list.c */,
+                       );
+                       path = list;
+                       sourceTree = "<group>";
+               };
                D49ECA15220BBA2D00655887 /* command_line_client_entitlements */ = {
                        isa = PBXGroup;
                        children = (
                        name = mDNSPosix;
                        sourceTree = "<group>";
                };
+               D4ED20A92360C52D00632A59 /* dnssec_v2 */ = {
+                       isa = PBXGroup;
+                       children = (
+                               D4ED20AC2360CCC800632A59 /* utilities */,
+                               D4ED20D32374A03A00632A59 /* dnssec_v2_embedded.h */,
+                               D4ED20DF237DCD7000632A59 /* dnssec_v2_helper.h */,
+                               D4ED20E0237DCD7000632A59 /* dnssec_v2_helper.c */,
+                               D4ED20D52375F99700632A59 /* dnssec_v2_structs.h */,
+                               D4ED20D62375F99700632A59 /* dnssec_v2_structs.c */,
+                               D4ED20CE23749DA900632A59 /* dnssec_v2_retrieval.h */,
+                               D4ED20CF23749DA900632A59 /* dnssec_v2_retrieval.c */,
+                               D4ED20DA237CC90200632A59 /* dnssec_v2_validation.h */,
+                               D4ED20DB237CC90200632A59 /* dnssec_v2_validation.c */,
+                               D43B120E23A442CE00B8D941 /* dnssec_v2_crypto.h */,
+                               D43B120F23A442CE00B8D941 /* dnssec_v2_crypto.c */,
+                               D473A50F2396F1F800D0F827 /* dnssec_v2_trust_anchor.h */,
+                               D473A5102396F1F800D0F827 /* dnssec_v2_trust_anchor.c */,
+                               D473A5192397176A00D0F827 /* dnssec_v2_client.h */,
+                               D473A51A2397176A00D0F827 /* dnssec_v2_client.c */,
+                               D473A51F2398895E00D0F827 /* dnssec_v2_log.h */,
+                               D4ED20BC236117F700632A59 /* dnssec_v2.h */,
+                               D4ED20BD236117F700632A59 /* dnssec_v2.c */,
+                       );
+                       path = dnssec_v2;
+                       sourceTree = "<group>";
+               };
+               D4ED20AC2360CCC800632A59 /* utilities */ = {
+                       isa = PBXGroup;
+                       children = (
+                               D41DED3123DFBF3A009F9854 /* base_encoding */,
+                               D473A51E239842C900D0F827 /* list */,
+                       );
+                       path = utilities;
+                       sourceTree = "<group>";
+               };
+               D4F2AA40239ADD940073E461 /* DNSSEC Unit Tests */ = {
+                       isa = PBXGroup;
+                       children = (
+                               D41DED4023E0F72A009F9854 /* Utility */,
+                               D41DED3D23E0F6C2009F9854 /* Crypto */,
+                       );
+                       path = "DNSSEC Unit Tests";
+                       sourceTree = "<group>";
+               };
                DB2CC4420662DCE500335AB3 /* Java Support */ = {
                        isa = PBXGroup;
                        children = (
                        isa = PBXHeadersBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               BDEF597D24FDD15B001F8CB5 /* dnssd_clientstub_apple.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        isa = PBXHeadersBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               BDEF597E24FDD15C001F8CB5 /* dnssd_clientstub_apple.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        isa = PBXHeadersBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               BDEF597F24FDD15C001F8CB5 /* dnssd_clientstub_apple.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        files = (
                                2EC8F8EC0C39CCAC003C9C48 /* helper.h in Headers */,
                                2EDC5E730C39EA640092701B /* helper-server.h in Headers */,
+                               B73C5CCF24B78EE80002050A /* setup_assistant_helper.h in Headers */,
                                2ECC11A80C4FEC3800CB1885 /* helpermsg-types.h in Headers */,
                                4BD2B63B134FE09F002B96D5 /* P2PPacketFilter.h in Headers */,
                        );
                        files = (
                                D4C2AEDD22052A3400B35685 /* xpc_clients.h in Headers */,
                                84F4C090188F050200D1E1DE /* dns_services.h in Headers */,
+                               89511E4C23C5FE3B00D603D4 /* advertising_proxy_services.h in Headers */,
+                               78BD2BFC23FC521C004A3D8B /* dnssd_svcb.h in Headers */,
+                               8990BCD223E341E500ECA799 /* xpc_client_advertising_proxy.h in Headers */,
                                BD11267821DB2A9B006115E6 /* dnssd_private.h in Headers */,
                                BD11267C21DB2C7C006115E6 /* dnssd_object.h in Headers */,
                                BD11267E21DB3019006115E6 /* dnssd_xpc.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
+               891F2FA52397EE6A001EC153 /* Headers */ = {
+                       isa = PBXHeadersBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               8954B0362406F4A80032D78F /* cti-services.h in Headers */,
+                               89B0BF3823C55A4C00245A42 /* xpc_client_advertising_proxy.h in Headers */,
+                               891F2FA72397EE93001EC153 /* mDNSEmbeddedAPI.h in Headers */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               89D4BBEE239EA82700FCFB7E /* Headers */ = {
+                       isa = PBXHeadersBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               8940E58823A1ABCF0060F8C8 /* srp.h in Headers */,
+                               8940E58923A1ABCF0060F8C8 /* dns-msg.h in Headers */,
+                               8940E58A23A1ABCF0060F8C8 /* ioloop.h in Headers */,
+                               89D4BBEF239EA83400FCFB7E /* DNSCommon.h in Headers */,
+                               89D4BBF0239EA83400FCFB7E /* mDNSEmbeddedAPI.h in Headers */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
                B7325FEB1DA47EBA00663834 /* Headers */ = {
                        isa = PBXHeadersBuildPhase;
                        buildActionMask = 2147483647;
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
+               BD7BFBEF236FCF0E000456BC /* Headers */ = {
+                       isa = PBXHeadersBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               BD7BFBFA236FD5F7000456BC /* DNSMessage.h in Headers */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
                BD9BA7491EAF90E400658CCF /* Headers */ = {
                        isa = PBXHeadersBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
-                               BD977550221D64BF00F68FFC /* DNSMessage.h in Headers */,
                                D4A9F60521FA797F0079D0C6 /* dns_sd_private.h in Headers */,
+                               5AF23B2D23F37331004AB237 /* DNSHeuristics.h in Headers */,
+                               BD977550221D64BF00F68FFC /* DNSMessage.h in Headers */,
+                               BD12709424469F1C00BC84D8 /* DNSServerDNSSEC.h in Headers */,
+                               787E8B75239485D200DC9D01 /* HTTPUtilities.h in Headers */,
+                               BD1904F1235E83F700F146D6 /* mdns_address.h in Headers */,
+                               BD1904EE235E759500F146D6 /* mdns_dns_service.h in Headers */,
+                               BD0FFC712502E78A00B6DB73 /* mdns_managed_defaults.h in Headers */,
+                               BD4FC0D824A81977000B0B21 /* mdns_message.h in Headers */,
+                               BD1904F3235E958D00F146D6 /* mdns_resolver.h in Headers */,
+                               BD52398423F3BBCD004AC878 /* mdns_set.h in Headers */,
+                               BD7BFBDB236D5342000456BC /* mdns_symptoms.h in Headers */,
                                B7F1FEDE234F89C50081159C /* TestUtils.h in Headers */,
-                               BD2A15B9225ED30C00BEA50A /* mdns_private.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
-               D284BE520ADD80740027CCDF /* Headers */ = {
+               BF0E381524882B650028D528 /* Headers */ = {
                        isa = PBXHeadersBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
-                               D284BE540ADD80740027CCDF /* dnssd_ipc.h in Headers */,
-                               BDBF9B941ED74B9C001498A8 /* DNS64State.h in Headers */,
+                               BF0E381624882B650028D528 /* DNSMessage.h in Headers */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               D284BE520ADD80740027CCDF /* Headers */ = {
+                       isa = PBXHeadersBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               D284BE540ADD80740027CCDF /* dnssd_ipc.h in Headers */,
+                               BDFE400C2408E9E300C77011 /* mdns_tlv.h in Headers */,
+                               BDBF9B941ED74B9C001498A8 /* DNS64State.h in Headers */,
+                               BD52398623F3D17E004AC878 /* mdns_set.h in Headers */,
                                D4C2AED922050E8800B35685 /* xpc_client_dns_proxy.h in Headers */,
+                               D4ED20D023749DA900632A59 /* dnssec_v2_retrieval.h in Headers */,
                                D4C2AEDB22050F4E00B35685 /* xpc_client_log_utility.h in Headers */,
+                               D473A5112396F1F800D0F827 /* dnssec_v2_trust_anchor.h in Headers */,
                                2E96A5260C39BE480087C4D2 /* helper.h in Headers */,
                                BDF2D02E216A25E800D0DBF5 /* ApplePlatformFeatures.h in Headers */,
+                               D4ED20DC237CC90200632A59 /* dnssec_v2_validation.h in Headers */,
+                               D473A51B2397176A00D0F827 /* dnssec_v2_client.h in Headers */,
+                               D41DED3623DFC3C3009F9854 /* base_n.h in Headers */,
                                BDF2D02C2169B77C00D0DBF5 /* SymptomReporter.h in Headers */,
                                BD1628CF2168B02700020528 /* ClientRequests.h in Headers */,
+                               B7E8092D23F70EFE002CB309 /* mdns_trust_checks.h in Headers */,
                                BD11267121DB1B25006115E6 /* dnssd_server.h in Headers */,
                                D421B3DF22178BE700D35C20 /* system_utilities.h in Headers */,
+                               BDA9C3642370321200DC86BD /* mdns_dns_service.h in Headers */,
+                               B7F9AB7D237F4F5000C2BEA2 /* mdns_trust.h in Headers */,
                                2EDC5E750C39EA640092701B /* helper-server.h in Headers */,
+                               BDA9C3622370320000DC86BD /* mdns_symptoms.h in Headers */,
+                               B737059422CD65E500477EB9 /* dnssd_analytics.h in Headers */,
+                               BDA9C35D2370307600DC86BD /* QuerierSupport.h in Headers */,
                                2ECC11A60C4FEC3800CB1885 /* helpermsg-types.h in Headers */,
-                               21A57F4E145B2AE100939099 /* CryptoAlg.h in Headers */,
-                               21A57F55145B2B1400939099 /* CryptoSupport.h in Headers */,
-                               2124FA2C1471E98C0021D7BB /* nsec.h in Headers */,
                                D4CFA7D421E7BA9700F5AD0E /* mDNSFeatures.h in Headers */,
-                               D401238D22728506006C9BBE /* mdns_private.h in Headers */,
+                               BDA9C360237031E400DC86BD /* DNSMessage.h in Headers */,
                                D4BFF8DC22B1C52100A0BA86 /* posix_utilities.h in Headers */,
+                               D473A5202398895E00D0F827 /* dnssec_v2_log.h in Headers */,
+                               D4ED20BE236117F700632A59 /* dnssec_v2.h in Headers */,
                                897729B42202A5370018FAEB /* dso-transport.h in Headers */,
-                               2124FA301471E9B50021D7BB /* dnssec.h in Headers */,
+                               BDA37F2123471FE600B9266D /* mdns_private.h in Headers */,
                                D461F94F2203A8AA00A88910 /* xpc_service_dns_proxy.h in Headers */,
                                B7A8619B212B34CF00E81CC3 /* unittest.h in Headers */,
+                               BD0FFC702502E78A00B6DB73 /* mdns_managed_defaults.h in Headers */,
                                BD691B2B1ED2F4AB00E6F317 /* DNS64.h in Headers */,
+                               787E8B74239485D200DC9D01 /* HTTPUtilities.h in Headers */,
                                BD11267221DB1B29006115E6 /* dnssd_xpc.h in Headers */,
                                218E8E53156D8C0300720DA0 /* dnsproxy.h in Headers */,
-                               2127A47915C3C7B900A857FC /* nsec3.h in Headers */,
                                222A3C5B1C1B75F2003A6FFD /* DNSServiceDiscoveryDefines.h in Headers */,
+                               89511E4B23C5FE3B00D603D4 /* advertising_proxy_services.h in Headers */,
+                               BDFE40102408F62E00C77011 /* mdns_xpc.h in Headers */,
+                               D4ED20B92360D16900632A59 /* list.h in Headers */,
                                897729B52202A5370018FAEB /* dso.h in Headers */,
-                               21070E6116486B9000A69507 /* DNSSECSupport.h in Headers */,
+                               D4ED20D42374A03A00632A59 /* dnssec_v2_embedded.h in Headers */,
+                               D4ED20E1237DCD7000632A59 /* dnssec_v2_helper.h in Headers */,
                                BDA3F08E1C48DCA50054FB4B /* Metrics.h in Headers */,
+                               BD4FC0DA24A8D382000B0B21 /* mdns_message.h in Headers */,
                                22448EA71C90A837004F25CC /* coreBLE.h in Headers */,
                                220ABBF21D78F10F008423A7 /* D2D.h in Headers */,
+                               D43B121023A442CE00B8D941 /* dnssec_v2_crypto.h in Headers */,
                                22448EA41C90A7CB004F25CC /* BLE.h in Headers */,
+                               D4ED20D72375F99700632A59 /* dnssec_v2_structs.h in Headers */,
                                D461F94A2203A6B400A88910 /* xpc_services.h in Headers */,
+                               BD1970B523F90E94001BA06F /* mdns_powerlog.h in Headers */,
                                848DA5D616547F7200D2E8B4 /* xpc_clients.h in Headers */,
+                               BDA37F222347200900B9266D /* mdns_address.h in Headers */,
+                               5AF23B2C23F3732F004AB237 /* DNSHeuristics.h in Headers */,
+                               BDA37F232347200F00B9266D /* mdns_interface_monitor.h in Headers */,
+                               BDA37F242347201700B9266D /* mdns_object.h in Headers */,
+                               BDA37F252347201E00B9266D /* mdns_resolver.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        buildActionMask = 2147483647;
                        files = (
                                2E35529F0C3A9E7600CA1CB7 /* helper.h in Headers */,
-                               FFD52A9E1AF858DD00CAD3EC /* CryptoAlg.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        isa = PBXHeadersBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               BDFE40052408D4BD00C77011 /* dnssd_clientstub_apple.h in Headers */,
+                               BDFE40092408D4D800C77011 /* mdns_tlv.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        isa = PBXHeadersBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               BDFE40062408D4BE00C77011 /* dnssd_clientstub_apple.h in Headers */,
+                               BDFE400A2408D4D900C77011 /* mdns_tlv.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        files = (
                                BDA9A7881B3A924C00523835 /* dns_sd_private.h in Headers */,
                                BD41B27D203EBE6100A53629 /* dns_sd.h in Headers */,
+                               BDFE40022408C8D900C77011 /* dnssd_clientstub_apple.h in Headers */,
+                               B72BC06423ABFD8C0021E0C9 /* bundle_utilities.h in Headers */,
+                               BDFE3FFE2408C58B00C77011 /* mdns_tlv.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        productReference = 84C5B3351665529800C324A8 /* libdns_services.dylib */;
                        productType = "com.apple.product-type.library.dynamic";
                };
+               891F2F7E2397EA1C001EC153 /* srp-mdns-proxy */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = 891F2F852397EA1D001EC153 /* Build configuration list for PBXNativeTarget "srp-mdns-proxy" */;
+                       buildPhases = (
+                               891F2FA52397EE6A001EC153 /* Headers */,
+                               891F2F7B2397EA1C001EC153 /* Sources */,
+                               891F2F7C2397EA1C001EC153 /* Frameworks */,
+                               891F2F7D2397EA1C001EC153 /* CopyFiles */,
+                               89D3B57C2463357D00FA67EB /* Copy AppleInternal Logging Profile */,
+                               89D3B5802463366700FA67EB /* Copy Base Logging Profile */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                       );
+                       name = "srp-mdns-proxy";
+                       productName = "srp-mdns-proxy";
+                       productReference = 891F2F7F2397EA1C001EC153 /* srp-mdns-proxy */;
+                       productType = "com.apple.product-type.tool";
+               };
+               895E9DA423C68D5000DA0FE0 /* srputil */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = 895E9DAB23C68D5000DA0FE0 /* Build configuration list for PBXNativeTarget "srputil" */;
+                       buildPhases = (
+                               895E9DA123C68D5000DA0FE0 /* Sources */,
+                               895E9DA223C68D5000DA0FE0 /* Frameworks */,
+                               895E9DA323C68D5000DA0FE0 /* CopyFiles */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                       );
+                       name = srputil;
+                       productName = srputil;
+                       productReference = 895E9DA523C68D5000DA0FE0 /* srputil */;
+                       productType = "com.apple.product-type.tool";
+               };
+               89AFC1E223F5DE2B00538084 /* ra-tester */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = 89AFC1E923F5DE2B00538084 /* Build configuration list for PBXNativeTarget "ra-tester" */;
+                       buildPhases = (
+                               89AFC1DF23F5DE2B00538084 /* Sources */,
+                               89AFC1E023F5DE2B00538084 /* Frameworks */,
+                               89AFC1E123F5DE2B00538084 /* CopyFiles */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                       );
+                       name = "ra-tester";
+                       productName = "ra-tester";
+                       productReference = 89AFC1E323F5DE2B00538084 /* ra-tester */;
+                       productType = "com.apple.product-type.tool";
+               };
+               89D4BBE6239EA80E00FCFB7E /* srp-client */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = 89D4BBEB239EA80E00FCFB7E /* Build configuration list for PBXNativeTarget "srp-client" */;
+                       buildPhases = (
+                               89D4BBEE239EA82700FCFB7E /* Headers */,
+                               89D4BBE3239EA80E00FCFB7E /* Sources */,
+                               89D4BBE4239EA80E00FCFB7E /* Frameworks */,
+                               89D4BBE5239EA80E00FCFB7E /* CopyFiles */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                       );
+                       name = "srp-client";
+                       productName = "srp-client";
+                       productReference = 89D4BBE7239EA80E00FCFB7E /* srp-client */;
+                       productType = "com.apple.product-type.tool";
+               };
                B7325FED1DA47EBA00663834 /* DomainBrowser-iOS */ = {
                        isa = PBXNativeTarget;
                        buildConfigurationList = B7325FF31DA47EBA00663834 /* Build configuration list for PBXNativeTarget "DomainBrowser-iOS" */;
                        productReference = B7D6CA701D1076F3005E24CF /* DomainBrowser.framework */;
                        productType = "com.apple.product-type.framework";
                };
+               BD7BFBF2236FCF0E000456BC /* log_mdns */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = BD7BFBF4236FCF0E000456BC /* Build configuration list for PBXNativeTarget "log_mdns" */;
+                       buildPhases = (
+                               BD7BFBEF236FCF0E000456BC /* Headers */,
+                               BD7BFBF0236FCF0E000456BC /* Sources */,
+                               BD7BFBF1236FCF0E000456BC /* Frameworks */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                       );
+                       name = log_mdns;
+                       productName = log_mdns;
+                       productReference = BD7BFBF3236FCF0E000456BC /* liblog_mdns.dylib */;
+                       productType = "com.apple.product-type.library.dynamic";
+               };
                BD9BA7481EAF90E400658CCF /* dnssdutil */ = {
                        isa = PBXNativeTarget;
                        buildConfigurationList = BD9BA7501EAF90E400658CCF /* Build configuration list for PBXNativeTarget "dnssdutil" */;
                        productReference = BD9BA7531EAF90E400658CCF /* dnssdutil */;
                        productType = "com.apple.product-type.tool";
                };
+               BF0E381424882B650028D528 /* log_srp */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = BF0E381C24882B650028D528 /* Build configuration list for PBXNativeTarget "log_srp" */;
+                       buildPhases = (
+                               BF0E381524882B650028D528 /* Headers */,
+                               BF0E381724882B650028D528 /* Sources */,
+                               BF0E381A24882B650028D528 /* Frameworks */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                       );
+                       name = log_srp;
+                       productName = log_mdns;
+                       productReference = BF0E381F24882B650028D528 /* liblog_srp.dylib */;
+                       productType = "com.apple.product-type.library.dynamic";
+               };
                D284BE500ADD80740027CCDF /* mDNSResponder */ = {
                        isa = PBXNativeTarget;
                        buildConfigurationList = D284BE6D0ADD80740027CCDF /* Build configuration list for PBXNativeTarget "mDNSResponder" */;
                                D284BE520ADD80740027CCDF /* Headers */,
                                D284BE550ADD80740027CCDF /* Sources */,
                                D284BE640ADD80740027CCDF /* Frameworks */,
+                               B7A5C33B2399A34800615DBD /* Copy Feature Flags */,
                                D284BE6A0ADD80740027CCDF /* CopyFiles */,
                                D284BE6C0ADD80740027CCDF /* Run Script */,
                                21F51DC01B35418C0070B05C /* CopyFiles */,
                                8418673D15AB8BFF00BB7F70 /* Copy Base Logging Profile */,
-                               BD75E93F206ADEAD00656ED3 /* Copy AppleInternal Logging Profile */,
+                               BD75E93F206ADEAD00656ED3 /* Copy AppleInternal Logging Profiles */,
                                BD28AE8C207B888E00F0B257 /* Copy diagnose scripts */,
                                D448F7A5222DA8E20069E1D2 /* Copy script-based unit tests */,
                                BD98A797213A41240002EC47 /* Copy BATS test plist */,
                                FFB765800AEED9C700583A2C /* Headers */,
                                FFB765810AEED9C700583A2C /* Sources */,
                                FFB765820AEED9C700583A2C /* Frameworks */,
-                               21DE714D115831CB00DD4BD1 /* ShellScript */,
                        );
                        buildRules = (
                        );
                                                DevelopmentTeam = 63ZFQSB63Y;
                                                ProvisioningStyle = Automatic;
                                        };
+                                       891F2F7E2397EA1C001EC153 = {
+                                               CreatedOnToolsVersion = 11.0;
+                                               ProvisioningStyle = Automatic;
+                                       };
+                                       8941BE4C23E352C2004AF1CA = {
+                                               CreatedOnToolsVersion = 12.0;
+                                               ProvisioningStyle = Automatic;
+                                       };
+                                       895E9DA423C68D5000DA0FE0 = {
+                                               CreatedOnToolsVersion = 12.0;
+                                               ProvisioningStyle = Automatic;
+                                       };
+                                       89AFC1E223F5DE2B00538084 = {
+                                               CreatedOnToolsVersion = 12.0;
+                                               ProvisioningStyle = Automatic;
+                                       };
+                                       89D4BBE6239EA80E00FCFB7E = {
+                                               CreatedOnToolsVersion = 11.4;
+                                               ProvisioningStyle = Automatic;
+                                       };
                                        B70F38A5217AA6CE00612D3A = {
                                                ProvisioningStyle = Automatic;
                                        };
                                        B7DB589D215EB61C0054CD46 = {
                                                ProvisioningStyle = Automatic;
                                        };
+                                       BD7BFBF2236FCF0E000456BC = {
+                                               CreatedOnToolsVersion = 11.2;
+                                               ProvisioningStyle = Automatic;
+                                       };
+                                       BF0E381424882B650028D528 = {
+                                               ProvisioningStyle = Automatic;
+                                       };
                                        D4CFA7CB21E7B95E00F5AD0E = {
                                                CreatedOnToolsVersion = 11.0;
                                                ProvisioningStyle = Automatic;
                        projectRoot = "";
                        targets = (
                                03067D640C83A3700022BE1F /* Build Core */,
+                               B7E6DAC9244E28F800C898EF /* Build Services-macOS */,
                                B718416921F8D0A600CA42AD /* Build Services */,
                                B7DB589D215EB61C0054CD46 /* Build Extras-macOS */,
                                B7DB5895215EB4DD0054CD46 /* Build Extras-iOS */,
+                               8941BE4C23E352C2004AF1CA /* Build Extras-tvOS */,
                                B70F38A5217AA6CE00612D3A /* Build Extras */,
                                FFB7657B0AEED96B00583A2C /* Build All */,
                                D284BE500ADD80740027CCDF /* mDNSResponder */,
                                0C635A751E9418A60026C796 /* BonjourTop */,
                                BD9BA7481EAF90E400658CCF /* dnssdutil */,
                                D4CFA7CB21E7B95E00F5AD0E /* liblog_mdnsresponder */,
+                               BD7BFBF2236FCF0E000456BC /* log_mdns */,
+                               BF0E381424882B650028D528 /* log_srp */,
+                               891F2F7E2397EA1C001EC153 /* srp-mdns-proxy */,
+                               89D4BBE6239EA80E00FCFB7E /* srp-client */,
+                               895E9DA423C68D5000DA0FE0 /* srputil */,
+                               89AFC1E223F5DE2B00538084 /* ra-tester */,
                        );
                };
 /* End PBXProject section */
                        isa = PBXResourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               5A9E8E6423F4BE29003B4CAD /* mDNSResponderTests-Entitlements.plist in Resources */,
                                D4E219202268E09F00F06AA5 /* bats_test_state_dump.sh in Resources */,
                                894A55D722C438AF008CDEA1 /* bats_test_proxy.sh in Resources */,
                        );
                        shellPath = /bin/sh;
                        shellScript = "if [ -e \"${SDKROOT}/usr/local/include/vproc.h\" -o -e \"${SDKROOT}/usr/include/vproc.h\" ]\nthen\nrm -f \"${CONFIGURATION_TEMP_DIR}/vproc.h\"\nelse\ntouch \"${CONFIGURATION_TEMP_DIR}/vproc.h\"\nfi\n\nipsec=$(ls \"${SDKROOT}/usr/lib/libipsec.*\" 2> /dev/null | wc -l)\nif [ \"$ipsec\" != \"0\" ]\nthen\nrm -f \"${CONFIGURATION_TEMP_DIR}/ipsec_options.h\"\ntouch \"${CONFIGURATION_TEMP_DIR}/ipsec_options.h\"\nrm -f \"${CONFIGURATION_TEMP_DIR}/libipsec.a\"\nelse\necho \"#define MDNS_NO_IPSEC 1\" > ${CONFIGURATION_TEMP_DIR}/ipsec_options.h\ntouch \"${CONFIGURATION_TEMP_DIR}/empty.c\"\nfor i in ${ARCHS}\ndo\nccflags=\"-arch $i $ccflags\"\ndone\ncc ${ccflags} \"${CONFIGURATION_TEMP_DIR}/empty.c\" -c -o \"${CONFIGURATION_TEMP_DIR}/libipsec.a\"\nrm -f \"${CONFIGURATION_TEMP_DIR}/empty.c\"\nfi\n";
                };
-               21DE714D115831CB00DD4BD1 /* ShellScript */ = {
-                       isa = PBXShellScriptBuildPhase;
-                       buildActionMask = 8;
-                       files = (
-                       );
-                       inputPaths = (
-                       );
-                       outputPaths = (
-                       );
-                       runOnlyForDeploymentPostprocessing = 1;
-                       shellPath = "/bin/bash -e -x";
-                       shellScript = "DSTROOT=${DSTROOT}\n\nif [[ \"${ACTION}\" == \"installhdrs\" ]] || [[ \"${ACTION}\" == \"installapi\" ]]; then\n    exit 0\nfi\n\nif [[ \"${PLATFORM_NAME}\" =~ \"simulator\" ]]; then\n    ln -s libsystem_dnssd.dylib ${DSTROOT}${INSTALL_PATH}/libsystem_sim_dnssd.dylib\nfi\n";
-               };
                4A4EE3A413CB8E82005C624B /* Pre-generate dnsextd_parser.h file for dnsextd_lexer.c */ = {
                        isa = PBXShellScriptBuildPhase;
                        buildActionMask = 2147483647;
                                0C1596C01D77410A00E09998 /* GenLinkedList.c in Sources */,
                                0C1596BE1D7740E900E09998 /* mDNSDebug.c in Sources */,
                                0C1596BF1D7740EF00E09998 /* PlatformCommon.c in Sources */,
-                               0C1596BA1D7740D200E09998 /* CryptoAlg.c in Sources */,
                                0C1596B51D7740B500E09998 /* mDNSPosix.c in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                                215FFAEF124000F900470DE1 /* dnssd_clientlib.c in Sources */,
                                222A3C6A1C1B7777003A6FFD /* DNSServiceDiscovery.c in Sources */,
                                215FFAF0124000F900470DE1 /* dnssd_clientstub.c in Sources */,
+                               BDEF597A24FDD157001F8CB5 /* dnssd_clientstub_apple.c in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                215FFAF51240011800470DE1 /* dnssd_clientlib.c in Sources */,
                                222A3C6B1C1B7778003A6FFD /* DNSServiceDiscovery.c in Sources */,
                                215FFAF61240011800470DE1 /* dnssd_clientstub.c in Sources */,
+                               BDEF597B24FDD158001F8CB5 /* dnssd_clientstub_apple.c in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                215FFAFB1240013400470DE1 /* dnssd_clientlib.c in Sources */,
                                222A3C6C1C1B7779003A6FFD /* DNSServiceDiscovery.c in Sources */,
                                215FFAFC1240013400470DE1 /* dnssd_clientstub.c in Sources */,
+                               BDEF597C24FDD158001F8CB5 /* dnssd_clientstub_apple.c in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               B73C5CD024B78EE80002050A /* setup_assistant_helper.m in Sources */,
                                2E0405F50C3195F700F13B59 /* helper.c in Sources */,
                                2E96A51D0C39BDAC0087C4D2 /* helper-main.c in Sources */,
                                4BD2B63A134FE09F002B96D5 /* P2PPacketFilter.c in Sources */,
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               B7A5C343239B040900615DBD /* dnssd_xpc.c in Sources */,
+                               78BD2BFA23FC521C004A3D8B /* dnssd_svcb.c in Sources */,
                                84C5B33C166553F100C324A8 /* dns_services.c in Sources */,
-                               BD11267A21DB2A9B006115E6 /* dnssd.c in Sources */,
                                BD11267921DB2A9B006115E6 /* dnssd_object.m in Sources */,
-                               BD11267F21DB303F006115E6 /* dnssd_xpc.c in Sources */,
+                               89511E4E23C5FE3B00D603D4 /* advertising_proxy_services.c in Sources */,
+                               78A4903C23343B8500FA2CCA /* dnssd_descriptions.m in Sources */,
+                               B7F707A823971FD700A31B5A /* dnssd.c in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               891F2F7B2397EA1C001EC153 /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               8996E15623E21813004DB7FA /* sign-macos.c in Sources */,
+                               8996E15523E21808004DB7FA /* towire.c in Sources */,
+                               8996E15023E217E0004DB7FA /* posix.c in Sources */,
+                               8996E15123E217E0004DB7FA /* fromwire.c in Sources */,
+                               8996E15223E217E0004DB7FA /* macos-ioloop.c in Sources */,
+                               8996E15323E217E0004DB7FA /* verify-macos.c in Sources */,
+                               8996E15423E217E0004DB7FA /* wireutils.c in Sources */,
+                               8954B0372406F4A80032D78F /* cti-services.c in Sources */,
+                               89C38ECC23C93B9400800A42 /* route.c in Sources */,
+                               891F2F8A2397EA79001EC153 /* srp-mdns-proxy.c in Sources */,
+                               891F2F8B2397EA79001EC153 /* srp-parse.c in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               895E9DA123C68D5000DA0FE0 /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               89D2E11524297ABD002E4C8B /* advertising_proxy_services.c in Sources */,
+                               8998717723C6987A00C3AF39 /* srputil.c in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               89AFC1DF23F5DE2B00538084 /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               89AFC1F623F5E2BD00538084 /* wireutils.c in Sources */,
+                               89AFC1F523F5E2A900538084 /* sign-macos.c in Sources */,
+                               89AFC1F423F5E2A000538084 /* posix.c in Sources */,
+                               89AFC1ED23F5E01E00538084 /* towire.c in Sources */,
+                               89AFC1EC23F5E01600538084 /* fromwire.c in Sources */,
+                               89AFC1EB23F5E00C00538084 /* macos-ioloop.c in Sources */,
+                               89AFC1EA23F5E00500538084 /* route.c in Sources */,
+                               89AFC1E623F5DE2B00538084 /* ra-tester.c in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+               89D4BBE3239EA80E00FCFB7E /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               895A390424F47F6D0064C387 /* advertising_proxy_services.c in Sources */,
+                               895A278224CC6F6900697AB1 /* cti-services.c in Sources */,
+                               8996E15823E2185A004DB7FA /* macos-ioloop.c in Sources */,
+                               8996E15923E2185A004DB7FA /* sign-macos.c in Sources */,
+                               8996E15A23E2185A004DB7FA /* towire.c in Sources */,
+                               8996E15B23E2185A004DB7FA /* wireutils.c in Sources */,
+                               8996E15723E2182F004DB7FA /* posix.c in Sources */,
+                               89458DA123A1AA6100C3978D /* srp-client.c in Sources */,
+                               89458DA223A1AA6100C3978D /* srp-ioloop.c in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               895A278524D4660400697AB1 /* DebugServices.c in Sources */,
+                               895A278324D464C300697AB1 /* DNSMessage.c in Sources */,
                                898E98392203633800812DC6 /* dnssd_clientshim.c in Sources */,
+                               D41DED3F23E0F71D009F9854 /* DigestCalculationTest.m in Sources */,
                                D459F0D4222862BA0056AC5B /* HelperFunctionTest.m in Sources */,
                                B74BF931232701F600E35354 /* CacheOrderTest.m in Sources */,
                                898E983A2203633800812DC6 /* dso-transport.c in Sources */,
                                B7E06CE12329B0480021401F /* LocalOnlyWithInterfacesTest.m in Sources */,
                                898E983B2203633800812DC6 /* dso.c in Sources */,
+                               D4ED20D92375F99700632A59 /* dnssec_v2_structs.c in Sources */,
+                               D4F2AA44239ADFB10073E461 /* ListTMethodsTest.m in Sources */,
                                B7EEF7C1212601460093828F /* mDNSMacOSX.c in Sources */,
                                B7EEF7CC212604A80093828F /* LegacyNATTraversal.c in Sources */,
+                               D473A5132396F1F800D0F827 /* dnssec_v2_trust_anchor.c in Sources */,
                                B7A86199212B074500E81CC3 /* LocalOnlyTimeoutTest.m in Sources */,
                                B7EEF7CB2126048F0093828F /* SymptomReporter.c in Sources */,
-                               B7EEF7D6212606F50093828F /* uDNSPathEvalulation.c in Sources */,
+                               B756FA9122D502BC0052F12C /* dnssd_analytics.c in Sources */,
+                               B7EEF7D6212606F50093828F /* uDNSPathEvaluation.c in Sources */,
                                B7EEF7EA212613260093828F /* uds_daemon.c in Sources */,
                                B7A861952127806600E81CC3 /* unittest_common.c in Sources */,
-                               B74BF92E2322E97400E35354 /* SuspiciousReplyTest.m in Sources */,
-                               B7EEF7E921260E4C0093828F /* CryptoSupport.c in Sources */,
-                               B7EEF7D9212607C40093828F /* nsec.c in Sources */,
-                               D40123912272B6E3006C9BBE /* mdns_object.m in Sources */,
+                               D4ED20E3237DCD7000632A59 /* dnssec_v2_helper.c in Sources */,
                                D461F9512203A8AA00A88910 /* xpc_service_dns_proxy.c in Sources */,
+                               D473A51D2397176A00D0F827 /* dnssec_v2_client.c in Sources */,
                                B7A861972127845800E81CC3 /* CNameRecordTest.m in Sources */,
-                               B7EEF7C5212603D50093828F /* CryptoAlg.c in Sources */,
+                               AA48E83F240DEADD007918D7 /* mdns_xpc.c in Sources */,
                                D461F94C2203A6B400A88910 /* xpc_services.c in Sources */,
+                               D4ED20D223749DA900632A59 /* dnssec_v2_retrieval.c in Sources */,
                                B7EEF7EC212613D10093828F /* dnsproxy.c in Sources */,
+                               D4ED20DE237CC90200632A59 /* dnssec_v2_validation.c in Sources */,
                                B7EEF7D7212607520093828F /* mDNSDebug.c in Sources */,
+                               D4ED20C1236117F700632A59 /* dnssec_v2.c in Sources */,
                                B7EEF7E421260DC90093828F /* dnssd_ipc.c in Sources */,
                                B7EEF7E221260A610093828F /* PlatformCommon.c in Sources */,
                                B7EEF7E521260DE80093828F /* daemon.c in Sources */,
                                B7EEF7C2212602EC0093828F /* mDNS.c in Sources */,
+                               D41DED3C23E0CD3D009F9854 /* base_n.c in Sources */,
                                B7EEF7ED212613D50093828F /* DNSProxySupport.c in Sources */,
-                               D40123922272B7A7006C9BBE /* mdns.c in Sources */,
                                B7EEF7CA2126046A0093828F /* uDNS.c in Sources */,
                                D4F2BB2822CD21CB00234A38 /* posix_utilities.c in Sources */,
                                B77CAFCE234EADE5006706B4 /* PathEvaluationTest.m in Sources */,
-                               B7EEF7D82126076F0093828F /* dnssec.c in Sources */,
+                               D4ED20BB2360D16900632A59 /* list.c in Sources */,
                                B7EEF7E021260A1F0093828F /* helper-stubs.c in Sources */,
                                B79FA14B211CE8CA00B7861E /* DNSCommon.c in Sources */,
                                B7EEF7CD212604CB0093828F /* DNSDigest.c in Sources */,
+                               B7BAFB4E23A0453A0045705F /* system_utilities.m in Sources */,
+                               D41DED4323E21D65009F9854 /* NSEC3HashTest.m in Sources */,
                                B7A8618B21274FA200E81CC3 /* mDNSCoreReceiveTest.m in Sources */,
-                               B7EEF7DA212608F50093828F /* nsec3.c in Sources */,
-                               B7EEF7DB2126090D0093828F /* DNSSECSupport.c in Sources */,
+                               D43B121223A442CE00B8D941 /* dnssec_v2_crypto.c in Sources */,
                                BDF2D02A2169961900D0DBF5 /* ClientRequests.c in Sources */,
-                               B7EEF7D0212605170093828F /* D2D.c in Sources */,
                                B74F16F4211BA55400BEBE84 /* DNSMessageTest.m in Sources */,
-                               D448F7AF222DCC8F0069E1D2 /* system_utilities.c in Sources */,
                                D4C2AED72203E4F900B35685 /* xpc_service_log_utility.c in Sources */,
                                B7A8618921274BFC00E81CC3 /* ResourceRecordTest.m in Sources */,
+                               5A9E8E6223F4BC8A003B4CAD /* DNSHeuristics.m in Sources */,
+                               AA48E83E240DEADD007918D7 /* mdns_tlv.c in Sources */,
+                               5A9E8E5D23F47817003B4CAD /* DNSHeuristicsTest.m in Sources */,
+                               BDA37F2F2347387300B9266D /* mdns_interface_monitor.c in Sources */,
+                               BDA37F31234738D500B9266D /* mdns_helpers.c in Sources */,
+                               D41DED3A23E0CC40009F9854 /* BaseNEncodingDecodingTest.m in Sources */,
+                               D43B121623BEB1A200B8D941 /* CanonicalMethodsTest.m in Sources */,
+                               BDA37F302347389D00B9266D /* mdns_object.c in Sources */,
+                               BDA37F32234738F300B9266D /* mdns_objects.m in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
+               BD7BFBF0236FCF0E000456BC /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               BD7BFBF9236FD5F2000456BC /* DNSMessage.c in Sources */,
+                               BD7BFBF8236FD019000456BC /* log_mdns.m in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
                BD9BA74A1EAF90E400658CCF /* Sources */ = {
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
-                               BD97754C221D643600F68FFC /* dnssdutil.c in Sources */,
+                               5AF23B2B23F3731F004AB237 /* DNSHeuristics.m in Sources */,
                                BD97754F221D64BF00F68FFC /* DNSMessage.c in Sources */,
-                               BD2A15BA225ED31C00BEA50A /* mdns.c in Sources */,
-                               BD2A15BB225ED33C00BEA50A /* mdns_object.m in Sources */,
+                               BD12709324469F1C00BC84D8 /* DNSServerDNSSEC.c in Sources */,
+                               BD97754C221D643600F68FFC /* dnssdutil.c in Sources */,
+                               787E8B77239485D200DC9D01 /* HTTPUtilities.m in Sources */,
+                               BD1904F0235E83D500F146D6 /* mdns_address.c in Sources */,
+                               BD1904EF235E759500F146D6 /* mdns_dns_service.c in Sources */,
+                               BDA37F2D23472B4700B9266D /* mdns_helpers.c in Sources */,
+                               BDA37F2B23472B1500B9266D /* mdns_interface_monitor.c in Sources */,
+                               BD0FFC732502E78A00B6DB73 /* mdns_managed_defaults.c in Sources */,
+                               BD4FC0D724A81974000B0B21 /* mdns_message.c in Sources */,
+                               BDA37F2C23472B3B00B9266D /* mdns_object.c in Sources */,
+                               BDA37F2E23472B5700B9266D /* mdns_objects.m in Sources */,
+                               BD1904F2235E958900F146D6 /* mdns_resolver.c in Sources */,
+                               BD7BFBDC236D5342000456BC /* mdns_symptoms.c in Sources */,
+                               BD52398323F3BBCD004AC878 /* mdns_set.c in Sources */,
                                B7F1FEDF234F89C50081159C /* TestUtils.m in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
+               BF0E381724882B650028D528 /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               BF0E381824882B650028D528 /* DNSMessage.c in Sources */,
+                               BF0E382124882BB60028D528 /* log_srp.m in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
                D284BE550ADD80740027CCDF /* Sources */ = {
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
                                22C7633E1D777B940077AFCA /* D2D.c in Sources */,
                                227687F31C90AD580019382D /* coreBLE.m in Sources */,
+                               787E8B76239485D200DC9D01 /* HTTPUtilities.m in Sources */,
+                               89511E4D23C5FE3B00D603D4 /* advertising_proxy_services.c in Sources */,
                                BD11267421DB1B4D006115E6 /* dnssd_xpc.c in Sources */,
+                               D4ED20E2237DCD7000632A59 /* dnssec_v2_helper.c in Sources */,
+                               D41DED3723DFC3C3009F9854 /* base_n.c in Sources */,
                                897729B32202A5370018FAEB /* dso-transport.c in Sources */,
+                               BDFE400B2408E9DF00C77011 /* mdns_tlv.c in Sources */,
+                               5AF23B2923F37309004AB237 /* DNSHeuristics.m in Sources */,
+                               D4ED20D123749DA900632A59 /* dnssec_v2_retrieval.c in Sources */,
                                D284BE580ADD80740027CCDF /* mDNS.c in Sources */,
                                BD691B2A1ED2F47100E6F317 /* DNS64.c in Sources */,
                                BD11267321DB1B34006115E6 /* dnssd_server.c in Sources */,
                                D284BE5A0ADD80740027CCDF /* DNSCommon.c in Sources */,
                                D284BE5B0ADD80740027CCDF /* DNSDigest.c in Sources */,
                                D284BE5D0ADD80740027CCDF /* mDNSDebug.c in Sources */,
-                               D401238B227284FE006C9BBE /* mdns.c in Sources */,
+                               D473A5122396F1F800D0F827 /* dnssec_v2_trust_anchor.c in Sources */,
                                897729B22202A5370018FAEB /* dso.c in Sources */,
-                               D401238F227286BE006C9BBE /* mdns_object.m in Sources */,
                                D461F9502203A8AA00A88910 /* xpc_service_dns_proxy.c in Sources */,
+                               BD4FC0D924A8D37F000B0B21 /* mdns_message.c in Sources */,
                                D284BE5E0ADD80740027CCDF /* uds_daemon.c in Sources */,
                                22448EA31C90A7BE004F25CC /* BLE.c in Sources */,
+                               BDA9C35B2370304E00DC86BD /* QuerierSupport.c in Sources */,
+                               BD0FFC722502E78A00B6DB73 /* mdns_managed_defaults.c in Sources */,
+                               BDA9C3632370320D00DC86BD /* mdns_dns_service.c in Sources */,
                                D461F94B2203A6B400A88910 /* xpc_services.c in Sources */,
+                               D4ED20DD237CC90200632A59 /* dnssec_v2_validation.c in Sources */,
                                D284BE5F0ADD80740027CCDF /* dnssd_ipc.c in Sources */,
+                               BD1970B623F90E94001BA06F /* mdns_powerlog.c in Sources */,
+                               D4ED20D82375F99700632A59 /* dnssec_v2_structs.c in Sources */,
                                D284BE600ADD80740027CCDF /* PlatformCommon.c in Sources */,
+                               BDA9C35E237031BF00DC86BD /* mdns_resolver.c in Sources */,
+                               BDFE400F2408F62E00C77011 /* mdns_xpc.c in Sources */,
                                D284BE610ADD80740027CCDF /* mDNSMacOSX.c in Sources */,
+                               B7F9AB7B237F4F4300C2BEA2 /* mdns_trust.c in Sources */,
                                BDA3F08F1C48DCA50054FB4B /* Metrics.m in Sources */,
+                               BDA9C35F237031E000DC86BD /* DNSMessage.c in Sources */,
                                D284BE620ADD80740027CCDF /* LegacyNATTraversal.c in Sources */,
                                D284BE630ADD80740027CCDF /* daemon.c in Sources */,
+                               BD52398523F3D17B004AC878 /* mdns_set.c in Sources */,
+                               BDA37F2A2347258C00B9266D /* mdns_address.c in Sources */,
                                2E96A5320C39C1A50087C4D2 /* helper-stubs.c in Sources */,
+                               B7BAFB4C23A035150045705F /* system_utilities.m in Sources */,
+                               78BD2BF923FC521C004A3D8B /* dnssd_svcb.c in Sources */,
                                897729B72202A5480018FAEB /* dnssd_clientshim.c in Sources */,
-                               21A57F4C145B2AE100939099 /* CryptoAlg.c in Sources */,
                                BD1628D02168B02700020528 /* ClientRequests.c in Sources */,
-                               21A57F53145B2B1400939099 /* CryptoSupport.c in Sources */,
-                               2124FA331471E9DE0021D7BB /* nsec.c in Sources */,
-                               213BDC6D147319F400000896 /* dnssec.c in Sources */,
+                               D4ED20BA2360D16900632A59 /* list.c in Sources */,
                                218E8E51156D8C0300720DA0 /* dnsproxy.c in Sources */,
+                               D43B121423ABEFD300B8D941 /* dnssec_v2_crypto.c in Sources */,
                                D4BFF8DB22B1C52100A0BA86 /* posix_utilities.c in Sources */,
                                21DED43515702C0F0060B6B9 /* DNSProxySupport.c in Sources */,
-                               216D9ACE1720C9F5008066E1 /* uDNSPathEvalulation.c in Sources */,
+                               216D9ACE1720C9F5008066E1 /* uDNSPathEvaluation.c in Sources */,
+                               D473A51C2397176A00D0F827 /* dnssec_v2_client.c in Sources */,
                                BD03E88D1AD31278005E8A81 /* SymptomReporter.c in Sources */,
-                               2127A47715C3C7B900A857FC /* nsec3.c in Sources */,
-                               D421B3E022178BE700D35C20 /* system_utilities.c in Sources */,
-                               21070E5F16486B9000A69507 /* DNSSECSupport.c in Sources */,
+                               B737059322CD65E500477EB9 /* dnssd_analytics.c in Sources */,
                                D4C2AED62203E4F900B35685 /* xpc_service_log_utility.c in Sources */,
+                               BDA9C361237031FD00DC86BD /* mdns_symptoms.c in Sources */,
+                               BDA37F282347207200B9266D /* mdns_helpers.c in Sources */,
+                               B7BAFB4A23A033D80045705F /* mdns_trust_checks.m in Sources */,
+                               BDA37F262347204F00B9266D /* mdns_interface_monitor.c in Sources */,
+                               D4ED20C0236117F700632A59 /* dnssec_v2.c in Sources */,
+                               BDA37F272347206300B9266D /* mdns_object.c in Sources */,
+                               BDA37F29234720AC00B9266D /* mdns_objects.m in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                898E98362203621200812DC6 /* dnssd_clientshim.c in Sources */,
                                898E98372203621200812DC6 /* dso-transport.c in Sources */,
                                898E98382203621200812DC6 /* dso.c in Sources */,
-                               21DCD05C1461B23700702FC8 /* CryptoAlg.c in Sources */,
                                D284BEC50ADD80A20027CCDF /* DNSCommon.c in Sources */,
                                D284BEC60ADD80A20027CCDF /* DNSDigest.c in Sources */,
                                D284BEC70ADD80A20027CCDF /* dnsextd.c in Sources */,
                        buildActionMask = 2147483647;
                        files = (
                                FFA572330AF18F1C0055A0F1 /* dnssd_ipc.c in Sources */,
+                               B75B9DA323AD567D00349070 /* bundle_utilities.m in Sources */,
                                FFA572340AF18F1C0055A0F1 /* dnssd_clientlib.c in Sources */,
                                222A3C6E1C1B777B003A6FFD /* DNSServiceDiscovery.c in Sources */,
+                               BDFE40042408D4B900C77011 /* dnssd_clientstub_apple.c in Sources */,
+                               BDFE40072408D4D400C77011 /* mdns_tlv.c in Sources */,
                                FFA572350AF18F1C0055A0F1 /* dnssd_clientstub.c in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                        buildActionMask = 2147483647;
                        files = (
                                FFA5723F0AF18F450055A0F1 /* dnssd_ipc.c in Sources */,
+                               B75B9DA223AD566D00349070 /* bundle_utilities.m in Sources */,
                                FFA572400AF18F450055A0F1 /* dnssd_clientlib.c in Sources */,
                                222A3C6F1C1B777C003A6FFD /* DNSServiceDiscovery.c in Sources */,
+                               BDFE40032408D4B800C77011 /* dnssd_clientstub_apple.c in Sources */,
+                               BDFE40082408D4D500C77011 /* mdns_tlv.c in Sources */,
                                FFA572410AF18F450055A0F1 /* dnssd_clientstub.c in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                        buildActionMask = 2147483647;
                        files = (
                                FFFA38660AEEDB2B0065B80A /* dnssd_ipc.c in Sources */,
+                               B72BC06323ABFD8C0021E0C9 /* bundle_utilities.m in Sources */,
+                               BDFE40012408C8D900C77011 /* dnssd_clientstub_apple.c in Sources */,
                                FFFA38630AEEDB090065B80A /* dnssd_clientlib.c in Sources */,
                                222A3C6D1C1B777A003A6FFD /* DNSServiceDiscovery.c in Sources */,
+                               BDFE3FFD2408C58B00C77011 /* mdns_tlv.c in Sources */,
                                FFFA38650AEEDB130065B80A /* dnssd_clientstub.c in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                        target = 4AE471670EAFF81900A6C5AD /* dns_sd.jar */;
                        targetProxy = 4AE471690EAFF83800A6C5AD /* PBXContainerItemProxy */;
                };
+               8941BE5123E352DD004AF1CA /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = 891F2F7E2397EA1C001EC153 /* srp-mdns-proxy */;
+                       targetProxy = 8941BE5023E352DD004AF1CA /* PBXContainerItemProxy */;
+               };
+               8941BE5323E352DD004AF1CA /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = 895E9DA423C68D5000DA0FE0 /* srputil */;
+                       targetProxy = 8941BE5223E352DD004AF1CA /* PBXContainerItemProxy */;
+               };
+               8941BE5523E367C2004AF1CA /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = B70F38A5217AA6CE00612D3A /* Build Extras */;
+                       targetProxy = 8941BE5423E367C2004AF1CA /* PBXContainerItemProxy */;
+               };
+               89458D9523A1A66B00C3978D /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = 89D4BBE6239EA80E00FCFB7E /* srp-client */;
+                       targetProxy = 89458D9423A1A66B00C3978D /* PBXContainerItemProxy */;
+               };
+               89DC990423F72754009598E1 /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = 89AFC1E223F5DE2B00538084 /* ra-tester */;
+                       targetProxy = 89DC990323F72754009598E1 /* PBXContainerItemProxy */;
+               };
+               89DC990623F72765009598E1 /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = 89AFC1E223F5DE2B00538084 /* ra-tester */;
+                       targetProxy = 89DC990523F72765009598E1 /* PBXContainerItemProxy */;
+               };
                B70F38A6217AA6CE00612D3A /* PBXTargetDependency */ = {
                        isa = PBXTargetDependency;
                        target = B74F16EA2114E49C00BEBE84 /* Tests */;
                        target = B76783AB1E82D65900DA271E /* BonjourPrefsTool */;
                        targetProxy = B76783B81E82D83800DA271E /* PBXContainerItemProxy */;
                };
+               B7A41B3C245E33B500FA6163 /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = B7E6DAC9244E28F800C898EF /* Build Services-macOS */;
+                       targetProxy = B7A41B3B245E33B500FA6163 /* PBXContainerItemProxy */;
+               };
+               B7C90F602346D1A200AA89F1 /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = 03067D640C83A3700022BE1F /* Build Core */;
+                       targetProxy = B7C90F5F2346D1A200AA89F1 /* PBXContainerItemProxy */;
+               };
+               B7C90F682346D1B000AA89F1 /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = B70F38A5217AA6CE00612D3A /* Build Extras */;
+                       targetProxy = B7C90F672346D1B000AA89F1 /* PBXContainerItemProxy */;
+               };
                B7D566C81E81D9E700E43008 /* PBXTargetDependency */ = {
                        isa = PBXTargetDependency;
                        target = B7D566B91E81D8FD00E43008 /* RemoteViewService */;
                        target = 0C635A751E9418A60026C796 /* BonjourTop */;
                        targetProxy = B7DB58C1215F04490054CD46 /* PBXContainerItemProxy */;
                };
+               B7E6DACA244E28F800C898EF /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = 84C5B3341665529800C324A8 /* dns_services */;
+                       targetProxy = B7E6DACB244E28F800C898EF /* PBXContainerItemProxy */;
+               };
+               BD7BFBFC236FDCB2000456BC /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = BD7BFBF2236FCF0E000456BC /* log_mdns */;
+                       targetProxy = BD7BFBFB236FDCB2000456BC /* PBXContainerItemProxy */;
+               };
                D4528FFE21F91263004D61BF /* PBXTargetDependency */ = {
                        isa = PBXTargetDependency;
                        target = D4CFA7CB21E7B95E00F5AD0E /* liblog_mdnsresponder */;
                                CLANG_ANALYZER_NONNULL = YES;
                                CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
                                CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
                                CLANG_WARN_BOOL_CONVERSION = YES;
                                CLANG_WARN_CONSTANT_CONVERSION = YES;
                                CLANG_ANALYZER_NONNULL = YES;
                                CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
                                CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
                                CLANG_WARN_BOOL_CONVERSION = YES;
                                CLANG_WARN_CONSTANT_CONVERSION = YES;
                                CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
                                CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
                                CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
+                               CODE_SIGN_IDENTITY = "-";
                                CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/Debug";
                                CONFIGURATION_TEMP_DIR = "$(PROJECT_TEMP_DIR)/Debug";
                                COPY_PHASE_STRIP = NO;
                                );
                                GCC_TREAT_WARNINGS_AS_ERRORS = YES;
                                GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
+                               MACOSX_DEPLOYMENT_TARGET = 10.15;
                                MVERS = "\"(Engineering Build)\"";
                                OTHER_CFLAGS = (
                                        "-DUSE_SYSTEMCONFIGURATION_PRIVATE_HEADERS",
                                "APPLY_RULES_IN_COPY_FILES[sdk=iphoneos*]" = YES;
                                "APPLY_RULES_IN_COPY_FILES[sdk=watchos*]" = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES_ERROR;
+                               CLANG_WARN_COMMA = YES_ERROR;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES_ERROR;
+                               CLANG_WARN_CXX0X_EXTENSIONS = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES_ERROR;
+                               CLANG_WARN_FRAMEWORK_INCLUDE_PRIVATE_FROM_PUBLIC = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
                                CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR;
+                               CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES_ERROR;
+                               CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES;
+                               CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES_AGGRESSIVE;
+                               CLANG_WARN_PRAGMA_PACK = YES_ERROR;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES_ERROR;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+                               CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
                                CODE_SIGN_ENTITLEMENTS = "mDNSResponder-entitlements.plist";
                                CODE_SIGN_IDENTITY = "-";
                                FRAMEWORK_SEARCH_PATHS = (
                                GCC_PREPROCESSOR_DEFINITIONS = (
                                        "$(inherited)",
                                        "MDNSRESPONDER_PLATFORM_APPLE=1",
+                                       "MDNS_OBJECT_FORCE_NO_OBJC=1",
                                        DSO_USES_NETWORK_FRAMEWORK,
                                );
+                               GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
+                               GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES_ERROR;
+                               GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
+                               GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
+                               GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
+                               GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
+                               GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
+                               GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;
+                               GCC_WARN_SHADOW = YES;
+                               GCC_WARN_SIGN_COMPARE = YES;
+                               GCC_WARN_STRICT_SELECTOR_MATCH = YES;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNKNOWN_PRAGMAS = YES;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_LABEL = YES;
+                               GCC_WARN_UNUSED_PARAMETER = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
                                HEADER_SEARCH_PATHS = (
                                        ../mDNSShared,
                                        "${SYSTEM_LIBRARY_DIR}/Frameworks/System.framework/PrivateHeaders",
                                );
                                INSTALL_PATH = /usr/sbin;
                                LIBRARY_SEARCH_PATHS = "\"${CONFIGURATION_TEMP_DIR}\"";
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
                                ORDER_FILE = "${SRCROOT}/mDNSResponder.order";
                                OTHER_LDFLAGS = "";
                                "OTHER_LDFLAGS[sdk=iphoneos*][arch=*]" = (
                                        "-weak_framework",
                                        DeviceToDeviceManager,
+                                       "-weak_framework",
+                                       CoreAnalytics,
+                                       "-weak_framework",
+                                       NetworkExtension,
+                                       "-framework",
+                                       MobileWiFi,
                                );
                                "OTHER_LDFLAGS[sdk=macosx*][arch=*]" = (
                                        "-weak_framework",
                                        WebFilterDNS,
                                        "-weak_framework",
                                        DeviceToDeviceManager,
+                                       "-weak_framework",
+                                       CoreAnalytics,
+                                       "-weak_framework",
+                                       NetworkExtension,
+                                       "-framework",
+                                       UniformTypeIdentifiers,
                                );
                                "PLIST_FILE_OUTPUT_FORMAT[sdk=appletvos*]" = binary;
                                "PLIST_FILE_OUTPUT_FORMAT[sdk=iphoneos*]" = binary;
                                );
                                INSTALL_PATH = /usr/sbin;
                                LIBRARY_SEARCH_PATHS = "\"${CONFIGURATION_TEMP_DIR}\"";
-                               MACOSX_DEPLOYMENT_TARGET = 10.10;
+                               "OTHER_LDFLAGS[sdk=macosx*]" = (
+                                       "-weak_framework",
+                                       SetupAssistantFramework,
+                               );
                                "PLIST_FILE_OUTPUT_FORMAT[sdk=appletvos*]" = binary;
                                "PLIST_FILE_OUTPUT_FORMAT[sdk=iphoneos*]" = binary;
                                "PLIST_FILE_OUTPUT_FORMAT[sdk=watchos*]" = binary;
                                PRODUCT_NAME = mDNSResponderHelper;
                                PROVISIONING_PROFILE = "";
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
                        };
                        name = Debug;
                };
                                );
                                INSTALL_PATH = /usr/sbin;
                                LIBRARY_SEARCH_PATHS = "\"${CONFIGURATION_TEMP_DIR}\"";
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
                                OTHER_CFLAGS = "-UAPPLE_OSX_mDNSResponder";
                                PRODUCT_NAME = dnsextd;
                        };
                                GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
                                INFOPLIST_FILE = "PreferencePane/Info-PreferencePane.plist";
                                INSTALL_PATH = /AppleInternal/Library/PreferencePanes;
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
                                ONLY_ACTIVE_ARCH = YES;
                                PRODUCT_NAME = Bonjour;
                                SUPPORTED_PLATFORMS = macosx;
                0C419F241BA20DF600A70FF7 /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
+                               CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES;
                                CLANG_WARN_ASSIGN_ENUM = YES;
                                CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES_ERROR;
                                CLANG_WARN_BOOL_CONVERSION = YES_ERROR;
                                CLANG_WARN_FLOAT_CONVERSION = YES_ERROR;
                                CLANG_WARN_INFINITE_RECURSION = YES;
                                CLANG_WARN_INT_CONVERSION = YES_ERROR;
-                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR;
                                CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES;
                                CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
                                DYLIB_CURRENT_VERSION = "$(RC_ProjectSourceVersion)";
                                GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
                                GCC_WARN_SHADOW = YES;
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
-                               GCC_WARN_UNKNOWN_PRAGMAS = YES;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_LABEL = YES;
                                GCC_WARN_UNUSED_PARAMETER = YES;
                                        "-lsystem_blocks",
                                        "-ldispatch",
                                        "-lsystem_asl",
+                                       "-lxpc",
+                                       "-lsystem_featureflags",
+                                       "-Wl,-upward-lobjc",
                                );
                                OTHER_TAPI_FLAGS = "-umbrella System -extra-public-header $(SRCROOT)/DNSServiceDiscovery.h";
                                PRODUCT_NAME = libsystem_dnssd;
                                        "-lsystem_blocks",
                                        "-ldispatch",
                                        "-lsystem_asl",
+                                       "-lxpc",
+                                       "-lsystem_featureflags",
+                                       "-Wl,-upward-lobjc",
                                );
                                OTHER_TAPI_FLAGS = "-umbrella System -extra-public-header $(SRCROOT)/../mDNSShared/dns_sd.h -extra-public-header $(SRCROOT)/DNSServiceDiscovery.h -extra-private-header $(SRCROOT)/../mDNSShared/dns_sd_private.h";
                                PRODUCT_NAME = libsystem_dnssd_debug;
                                        "-lsystem_blocks",
                                        "-ldispatch",
                                        "-lsystem_asl",
+                                       "-lxpc",
+                                       "-lsystem_featureflags",
+                                       "-Wl,-upward-lobjc",
                                );
                                OTHER_TAPI_FLAGS = "-umbrella System -extra-public-header $(SRCROOT)/../mDNSShared/dns_sd.h -extra-public-header $(SRCROOT)/DNSServiceDiscovery.h -extra-private-header $(SRCROOT)/../mDNSShared/dns_sd_private.h";
                                PRODUCT_NAME = libsystem_dnssd_profile;
                                CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
                                CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
                                CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
+                               CLANG_LINK_OBJC_RUNTIME = NO;
                                CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES;
                                CLANG_WARN_ASSIGN_ENUM = YES;
                                CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
                                CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
                                EXECUTABLE_PREFIX = lib;
                                GCC_ENABLE_OBJC_EXCEPTIONS = YES;
-                               GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "DEBUG=1",
+                                       "$(inherited)",
+                               );
                                GCC_SYMBOLS_PRIVATE_EXTERN = YES;
                                GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
                                GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
                                MTL_ENABLE_DEBUG_INFO = NO;
                                OTHER_LDFLAGS = "-lncurses";
                                PRODUCT_NAME = bonjourtop;
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
                                MTL_ENABLE_DEBUG_INFO = YES;
                                ONLY_ACTIVE_ARCH = YES;
                                OTHER_LDFLAGS = "-lncurses";
                                );
                                INSTALL_PATH = /usr/sbin;
                                LIBRARY_SEARCH_PATHS = "\"${CONFIGURATION_TEMP_DIR}\"";
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
+                               "OTHER_LDFLAGS[sdk=macosx*]" = (
+                                       "-weak_framework",
+                                       SetupAssistantFramework,
+                               );
                                "PLIST_FILE_OUTPUT_FORMAT[sdk=appletvos*]" = binary;
                                "PLIST_FILE_OUTPUT_FORMAT[sdk=iphoneos*]" = binary;
                                "PLIST_FILE_OUTPUT_FORMAT[sdk=watchos*]" = binary;
                                PRODUCT_NAME = mDNSResponderHelper;
                                PROVISIONING_PROFILE = "";
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
                        };
                        name = Release;
                };
                                CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
                                CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
                                CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
+                               CLANG_LINK_OBJC_RUNTIME = NO;
                                CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES;
                                CLANG_WARN_ASSIGN_ENUM = YES;
                                CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
                                CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
                                EXECUTABLE_PREFIX = lib;
                                GCC_ENABLE_OBJC_EXCEPTIONS = YES;
-                               GCC_PREPROCESSOR_DEFINITIONS = "";
+                               GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
                                GCC_SYMBOLS_PRIVATE_EXTERN = YES;
                                GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
                                GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
                        };
                        name = Release;
                };
-               B70F38B1217AA6CE00612D3A /* Release */ = {
-                       isa = XCBuildConfiguration;
-                       buildSettings = {
-                               CODE_SIGN_STYLE = Automatic;
-                               PRODUCT_NAME = "$(TARGET_NAME)";
-                       };
-                       name = Release;
-               };
-               B70F38B2217AA6CE00612D3A /* Debug */ = {
-                       isa = XCBuildConfiguration;
-                       buildSettings = {
-                               CODE_SIGN_STYLE = Automatic;
-                               PRODUCT_NAME = "$(TARGET_NAME)";
-                       };
-                       name = Debug;
-               };
-               B718417321F8D0A600CA42AD /* Release */ = {
-                       isa = XCBuildConfiguration;
-                       buildSettings = {
-                               PRODUCT_NAME = "$(TARGET_NAME)";
-                       };
-                       name = Release;
-               };
-               B718417421F8D0A600CA42AD /* Debug */ = {
-                       isa = XCBuildConfiguration;
-                       buildSettings = {
-                               PRODUCT_NAME = "$(TARGET_NAME)";
-                       };
-                       name = Debug;
-               };
-               B7325FF41DA47EBA00663834 /* Release */ = {
+               891F2F832397EA1D001EC153 /* Release */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
                                CLANG_ANALYZER_NONNULL = YES;
-                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
                                CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CLANG_STATIC_ANALYZER_MODE = deep;
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
                                CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_COMMA = YES;
                                CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
                                CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
                                CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
                                CLANG_WARN_EMPTY_BODY = YES;
                                CLANG_WARN_ENUM_CONVERSION = YES;
                                CLANG_WARN_INFINITE_RECURSION = YES;
                                CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
                                CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-                               CLANG_WARN_SUSPICIOUS_MOVES = YES;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
                                CLANG_WARN_UNREACHABLE_CODE = YES;
                                CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-                               CODE_SIGN_IDENTITY = "";
-                               CURRENT_PROJECT_VERSION = 1;
-                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-                               DEFINES_MODULE = YES;
-                               DYLIB_COMPATIBILITY_VERSION = 1;
-                               DYLIB_CURRENT_VERSION = 1;
+                               CODE_SIGN_ENTITLEMENTS = "srp-mdns-proxy-entitlements.plist";
+                               CODE_SIGN_STYLE = Automatic;
+                               CREATE_INFOPLIST_SECTION_IN_BINARY = YES;
                                ENABLE_NS_ASSERTIONS = NO;
                                ENABLE_STRICT_OBJC_MSGSEND = YES;
-                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_C_LANGUAGE_STANDARD = gnu11;
                                GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "$(inherited)",
+                                       "USE_IPCONFIGURATION_SERVICE=1",
+                                       "IOLOOP_MACOS=1",
+                               );
                                GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
                                GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
                                GCC_WARN_UNDECLARED_SELECTOR = YES;
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               INFOPLIST_FILE = ../mDNSUI/DomainBrowser/iOS/Info.plist;
-                               INSTALL_PATH = "@rpath";
-                               IPHONEOS_DEPLOYMENT_TARGET = 10.0;
+                               INFOPLIST_FILE = "srp-mdns-proxy.plist";
+                               INSTALL_PATH = /usr/libexec;
+                               MACOSX_DEPLOYMENT_TARGET = 10.15;
                                MTL_ENABLE_DEBUG_INFO = NO;
-                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.coseos.network.bonjour.DomainBrowser;
-                               PRODUCT_NAME = DomainBrowser;
-                               SDKROOT = iphoneos.internal;
-                               SKIP_INSTALL = YES;
-                               SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
-                               TARGETED_DEVICE_FAMILY = "1,2";
-                               VALIDATE_PRODUCT = YES;
-                               VERSIONING_SYSTEM = "apple-generic";
-                               VERSION_INFO_PREFIX = "";
+                               MTL_FAST_MATH = YES;
+                               OTHER_LDFLAGS = "";
+                               PRODUCT_BUNDLE_IDENTIFIER = "com.apple.srp-mdns-proxy";
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               RUN_CLANG_STATIC_ANALYZER = NO;
+                               SDKROOT = macosx.internal;
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
+                               WARNING_CFLAGS = (
+                                       "-W",
+                                       "-Wall",
+                                       "-Wmissing-prototypes",
+                                       "-Wshadow",
+                                       "-Wformat-security",
+                               );
                        };
                        name = Release;
                };
-               B7325FF51DA47EBA00663834 /* Debug */ = {
+               891F2F842397EA1D001EC153 /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
                                CLANG_ANALYZER_NONNULL = YES;
-                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
                                CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CLANG_STATIC_ANALYZER_MODE = deep;
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
                                CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_COMMA = YES;
                                CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
                                CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
                                CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
                                CLANG_WARN_EMPTY_BODY = YES;
                                CLANG_WARN_ENUM_CONVERSION = YES;
                                CLANG_WARN_INFINITE_RECURSION = YES;
                                CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
                                CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-                               CLANG_WARN_SUSPICIOUS_MOVES = YES;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
                                CLANG_WARN_UNREACHABLE_CODE = YES;
                                CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-                               CODE_SIGN_IDENTITY = "";
-                               CURRENT_PROJECT_VERSION = 1;
-                               DEBUG_INFORMATION_FORMAT = dwarf;
-                               DEFINES_MODULE = YES;
-                               DYLIB_COMPATIBILITY_VERSION = 1;
-                               DYLIB_CURRENT_VERSION = 1;
+                               CODE_SIGN_ENTITLEMENTS = "srp-mdns-proxy-entitlements.plist";
+                               CODE_SIGN_STYLE = Automatic;
+                               CREATE_INFOPLIST_SECTION_IN_BINARY = YES;
                                ENABLE_STRICT_OBJC_MSGSEND = YES;
                                ENABLE_TESTABILITY = YES;
-                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_C_LANGUAGE_STANDARD = gnu11;
                                GCC_DYNAMIC_NO_PIC = NO;
                                GCC_NO_COMMON_BLOCKS = YES;
                                GCC_PREPROCESSOR_DEFINITIONS = (
                                        "DEBUG=1",
+                                       "USE_IPCONFIGURATION_SERVICE=1",
+                                       "IOLOOP_MACOS=1",
                                        "$(inherited)",
                                );
                                GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               INFOPLIST_FILE = ../mDNSUI/DomainBrowser/iOS/Info.plist;
-                               INSTALL_PATH = "@rpath";
-                               IPHONEOS_DEPLOYMENT_TARGET = 10.0;
-                               MTL_ENABLE_DEBUG_INFO = YES;
-                               ONLY_ACTIVE_ARCH = YES;
-                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.coseos.network.bonjour.DomainBrowser;
-                               PRODUCT_NAME = DomainBrowser;
-                               SDKROOT = iphoneos.internal;
-                               SKIP_INSTALL = YES;
-                               SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
-                               TARGETED_DEVICE_FAMILY = "1,2";
-                               VERSIONING_SYSTEM = "apple-generic";
-                               VERSION_INFO_PREFIX = "";
+                               INFOPLIST_FILE = "srp-mdns-proxy.plist";
+                               INSTALL_PATH = /usr/libexec;
+                               MACOSX_DEPLOYMENT_TARGET = 10.15;
+                               MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+                               MTL_FAST_MATH = YES;
+                               OTHER_LDFLAGS = "";
+                               PRODUCT_BUNDLE_IDENTIFIER = "com.apple.srp-mdns-proxy";
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               RUN_CLANG_STATIC_ANALYZER = NO;
+                               SDKROOT = macosx.internal;
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
+                               WARNING_CFLAGS = (
+                                       "-W",
+                                       "-Wall",
+                                       "-Wmissing-prototypes",
+                                       "-Wshadow",
+                                       "-Wformat-security",
+                               );
                        };
                        name = Debug;
                };
-               B7473E731EC3954400D31B9D /* Release */ = {
+               8941BE4E23E352C2004AF1CA /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CODE_SIGN_STYLE = Automatic;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                       };
+                       name = Release;
+               };
+               8941BE4F23E352C2004AF1CA /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CODE_SIGN_STYLE = Automatic;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                       };
+                       name = Debug;
+               };
+               895E9DA923C68D5000DA0FE0 /* Release */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
-                               ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
                                CLANG_ANALYZER_NONNULL = YES;
-                               CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
                                CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
                                CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CLANG_WARN_ASSIGN_ENUM = YES;
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
                                CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_COMMA = YES;
                                CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
                                CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
                                CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
                                CLANG_WARN_EMPTY_BODY = YES;
                                CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_FLOAT_CONVERSION = YES;
+                               CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
                                CLANG_WARN_INFINITE_RECURSION = YES;
                                CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
                                CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
                                CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES;
+                               CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
                                CLANG_WARN_SUSPICIOUS_MOVE = YES;
-                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
                                CLANG_WARN_UNREACHABLE_CODE = YES;
                                CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-                               CODE_SIGN_ENTITLEMENTS = "Bonjour Safari Menu/BonjourSafariMenu.entitlements";
-                               CODE_SIGN_IDENTITY = "-";
-                               COMBINE_HIDPI_IMAGES = YES;
-                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               CODE_SIGN_ENTITLEMENTS = "../Clients/srputil/srputil-entitlements.plist";
+                               CODE_SIGN_STYLE = Automatic;
+                               DEVELOPMENT_TEAM = "";
                                ENABLE_NS_ASSERTIONS = NO;
                                ENABLE_STRICT_OBJC_MSGSEND = YES;
                                GCC_C_LANGUAGE_STANDARD = gnu11;
                                GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "IOLOOP_MACOS=1",
+                                       "LOG_FPRINTF_STDERR=1",
+                                       "$(inherited)",
+                               );
+                               GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
+                               GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
                                GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
+                               GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
+                               GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
                                GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
+                               GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
+                               GCC_WARN_SHADOW = YES;
+                               GCC_WARN_SIGN_COMPARE = YES;
                                GCC_WARN_UNDECLARED_SELECTOR = YES;
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
                                GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_LABEL = YES;
+                               GCC_WARN_UNUSED_PARAMETER = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               INFOPLIST_FILE = "Bonjour Safari Menu/Info.plist";
-                               INSTALL_PATH = /AppleInternal/Applications;
-                               LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
+                               MACOSX_DEPLOYMENT_TARGET = 10.16;
                                MTL_ENABLE_DEBUG_INFO = NO;
-                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.bonjour.BonjourSafariMenu;
+                               MTL_FAST_MATH = YES;
                                PRODUCT_NAME = "$(TARGET_NAME)";
-                               SDKROOT = macosx;
+                               PROVISIONING_PROFILE_SPECIFIER = "";
                        };
                        name = Release;
                };
-               B7473E741EC3954400D31B9D /* Debug */ = {
+               895E9DAA23C68D5000DA0FE0 /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
-                               ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
                                CLANG_ANALYZER_NONNULL = YES;
-                               CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
                                CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
                                CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CLANG_WARN_ASSIGN_ENUM = YES;
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
                                CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_COMMA = YES;
                                CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
                                CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
                                CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
                                CLANG_WARN_EMPTY_BODY = YES;
                                CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_FLOAT_CONVERSION = YES;
+                               CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
                                CLANG_WARN_INFINITE_RECURSION = YES;
                                CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
                                CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
                                CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES;
+                               CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
                                CLANG_WARN_SUSPICIOUS_MOVE = YES;
-                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
                                CLANG_WARN_UNREACHABLE_CODE = YES;
                                CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-                               CODE_SIGN_ENTITLEMENTS = "Bonjour Safari Menu/BonjourSafariMenu.entitlements";
-                               CODE_SIGN_IDENTITY = "-";
-                               COMBINE_HIDPI_IMAGES = YES;
-                               DEBUG_INFORMATION_FORMAT = dwarf;
+                               CODE_SIGN_ENTITLEMENTS = "../Clients/srputil/srputil-entitlements.plist";
+                               CODE_SIGN_STYLE = Automatic;
+                               DEVELOPMENT_TEAM = "";
                                ENABLE_STRICT_OBJC_MSGSEND = YES;
                                ENABLE_TESTABILITY = YES;
                                GCC_C_LANGUAGE_STANDARD = gnu11;
                                GCC_DYNAMIC_NO_PIC = NO;
                                GCC_NO_COMMON_BLOCKS = YES;
                                GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "IOLOOP_MACOS=1",
+                                       "LOG_FPRINTF_STDERR=1",
                                        "DEBUG=1",
                                        "$(inherited)",
                                );
+                               GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
+                               GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
                                GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
+                               GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
+                               GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
                                GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
+                               GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
+                               GCC_WARN_SHADOW = YES;
+                               GCC_WARN_SIGN_COMPARE = YES;
                                GCC_WARN_UNDECLARED_SELECTOR = YES;
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
                                GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_LABEL = YES;
+                               GCC_WARN_UNUSED_PARAMETER = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               INFOPLIST_FILE = "Bonjour Safari Menu/Info.plist";
-                               INSTALL_PATH = /AppleInternal/Applications;
-                               LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
-                               MTL_ENABLE_DEBUG_INFO = YES;
-                               ONLY_ACTIVE_ARCH = YES;
-                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.bonjour.BonjourSafariMenu;
+                               MACOSX_DEPLOYMENT_TARGET = 10.16;
+                               MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+                               MTL_FAST_MATH = YES;
                                PRODUCT_NAME = "$(TARGET_NAME)";
-                               SDKROOT = macosx;
+                               PROVISIONING_PROFILE_SPECIFIER = "";
                        };
                        name = Debug;
                };
-               B7473E931EC395C300D31B9D /* Release */ = {
+               89AFC1E723F5DE2B00538084 /* Release */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
                                CLANG_ANALYZER_NONNULL = YES;
-                               CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
                                CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
                                CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
                                CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_COMMA = YES;
                                CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
                                CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
                                CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
                                CLANG_WARN_EMPTY_BODY = YES;
                                CLANG_WARN_ENUM_CONVERSION = YES;
                                CLANG_WARN_INFINITE_RECURSION = YES;
                                CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
                                CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
                                CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES;
                                CLANG_WARN_SUSPICIOUS_MOVE = YES;
-                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
                                CLANG_WARN_UNREACHABLE_CODE = YES;
                                CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-                               CODE_SIGN_ENTITLEMENTS = "Bonjour Safari Extension/BonjourSafariExtension.entitlements";
-                               CODE_SIGN_IDENTITY = "-";
-                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               CODE_SIGN_ENTITLEMENTS = "ra-tester-entitlements.plist";
+                               CODE_SIGN_STYLE = Automatic;
                                ENABLE_NS_ASSERTIONS = NO;
                                ENABLE_STRICT_OBJC_MSGSEND = YES;
                                GCC_C_LANGUAGE_STANDARD = gnu11;
                                GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "IOLOOP_MACOS=1",
+                                       "LOG_FPRINTF_STDERR=1",
+                                       "RA_TESTER=1",
+                                       "$(inherited)",
+                                       "USE_IPCONFIGURATION_SERVICE=1",
+                               );
                                GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
                                GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
                                GCC_WARN_UNDECLARED_SELECTOR = YES;
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               INFOPLIST_FILE = "Bonjour Safari Extension/Info.plist";
-                               LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks";
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
                                MTL_ENABLE_DEBUG_INFO = NO;
-                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.bonjour.BonjourSafariMenu.BonjourSafariExtension;
+                               MTL_FAST_MATH = YES;
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                SDKROOT = macosx.internal;
-                               SKIP_INSTALL = YES;
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
                        };
                        name = Release;
                };
-               B7473E941EC395C300D31B9D /* Debug */ = {
+               89AFC1E823F5DE2B00538084 /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
                                CLANG_ANALYZER_NONNULL = YES;
-                               CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
                                CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
                                CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
                                CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_COMMA = YES;
                                CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
                                CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
                                CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
                                CLANG_WARN_EMPTY_BODY = YES;
                                CLANG_WARN_ENUM_CONVERSION = YES;
                                CLANG_WARN_INFINITE_RECURSION = YES;
                                CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
                                CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
                                CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES;
                                CLANG_WARN_SUSPICIOUS_MOVE = YES;
-                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
                                CLANG_WARN_UNREACHABLE_CODE = YES;
                                CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-                               CODE_SIGN_ENTITLEMENTS = "Bonjour Safari Extension/BonjourSafariExtension.entitlements";
-                               CODE_SIGN_IDENTITY = "-";
-                               DEBUG_INFORMATION_FORMAT = dwarf;
+                               CODE_SIGN_ENTITLEMENTS = "ra-tester-entitlements.plist";
+                               CODE_SIGN_STYLE = Automatic;
                                ENABLE_STRICT_OBJC_MSGSEND = YES;
                                ENABLE_TESTABILITY = YES;
                                GCC_C_LANGUAGE_STANDARD = gnu11;
                                GCC_DYNAMIC_NO_PIC = NO;
                                GCC_NO_COMMON_BLOCKS = YES;
                                GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "LOG_FPRINTF_STDERR=1",
                                        "DEBUG=1",
+                                       "IOLOOP_MACOS=1",
                                        "$(inherited)",
+                                       "RA_TESTER=1",
+                                       "USE_IPCONFIGURATION_SERVICE=1",
                                );
                                GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
                                GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               INFOPLIST_FILE = "Bonjour Safari Extension/Info.plist";
-                               LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks";
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
-                               MTL_ENABLE_DEBUG_INFO = YES;
+                               MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+                               MTL_FAST_MATH = YES;
                                ONLY_ACTIVE_ARCH = YES;
-                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.bonjour.BonjourSafariMenu.BonjourSafariExtension;
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                SDKROOT = macosx.internal;
-                               SKIP_INSTALL = YES;
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
                        };
                        name = Debug;
                };
-               B74EC11C1D47FC7800A1D155 /* Release */ = {
+               89D4BBEC239EA80E00FCFB7E /* Release */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
-                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+                               CLANG_ANALYZER_NONNULL = YES;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
                                CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
-                               CODE_SIGN_IDENTITY = "-";
-                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_COMMA = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+                               CLANG_WARN_UNREACHABLE_CODE = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               CODE_SIGN_ENTITLEMENTS = "../ServiceRegistration/srp-client-entitlements.plist";
+                               CODE_SIGN_STYLE = Automatic;
                                ENABLE_NS_ASSERTIONS = NO;
                                ENABLE_STRICT_OBJC_MSGSEND = YES;
-                               FRAMEWORK_SEARCH_PATHS = (
-                                       "$(inherited)",
-                                       "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
-                               );
-                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_C_LANGUAGE_STANDARD = gnu11;
                                GCC_NO_COMMON_BLOCKS = YES;
-                               GCC_PRECOMPILE_PREFIX_HEADER = YES;
-                               INFOPLIST_FILE = SettingsBundle/Info.plist;
-                               INSTALL_PATH = /AppleInternal/Library/PreferenceBundles;
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "__APPLE_USE_RFC_3542=1",
+                                       "APPLE_OSX_mDNSResponder=1",
+                                       "__MigTypeCheck=1",
+                                       "mDNSResponderVersion=${MVERS}",
+                                       _LEGACY_NAT_TRAVERSAL_,
+                                       "_BUILDING_XCODE_PROJECT_=1",
+                                       "USE_LIBIDN=1",
+                                       "LOG_FPRINTF_STDERR=1",
+                                       "IOLOOP_MACOS=1",
+                               );
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
                                MTL_ENABLE_DEBUG_INFO = NO;
-                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.network.bonjour.BonjourSettings;
+                               MTL_FAST_MATH = YES;
+                               PRODUCT_BUNDLE_IDENTIFIER = "com.apple.srp-client";
                                PRODUCT_NAME = "$(TARGET_NAME)";
-                               SDKROOT = iphoneos.internal;
-                               SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
-                               TARGETED_DEVICE_FAMILY = "1,2";
-                               VALIDATE_PRODUCT = YES;
+                               SDKROOT = macosx.internal;
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
                        };
                        name = Release;
                };
-               B74EC11D1D47FC7800A1D155 /* Debug */ = {
+               89D4BBED239EA80E00FCFB7E /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
-                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+                               CLANG_ANALYZER_NONNULL = YES;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
                                CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
-                               CODE_SIGN_IDENTITY = "-";
-                               DEBUG_INFORMATION_FORMAT = dwarf;
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_COMMA = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+                               CLANG_WARN_UNREACHABLE_CODE = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               CODE_SIGN_ENTITLEMENTS = "../ServiceRegistration/srp-client-entitlements.plist";
+                               CODE_SIGN_STYLE = Automatic;
                                ENABLE_STRICT_OBJC_MSGSEND = YES;
                                ENABLE_TESTABILITY = YES;
-                               FRAMEWORK_SEARCH_PATHS = (
-                                       "$(inherited)",
-                                       "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
-                               );
-                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_C_LANGUAGE_STANDARD = gnu11;
                                GCC_DYNAMIC_NO_PIC = NO;
                                GCC_NO_COMMON_BLOCKS = YES;
-                               GCC_PRECOMPILE_PREFIX_HEADER = YES;
                                GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "IOLOOP_MACOS=1",
+                                       "LOG_FPRINTF_STDERR=1",
                                        "DEBUG=1",
-                                       "${inherited}",
+                                       "$(inherited)",
                                );
-                               INFOPLIST_FILE = SettingsBundle/Info.plist;
-                               INSTALL_PATH = /AppleInternal/Library/PreferenceBundles;
-                               MTL_ENABLE_DEBUG_INFO = YES;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+                               MTL_FAST_MATH = YES;
                                ONLY_ACTIVE_ARCH = YES;
-                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.network.bonjour.BonjourSettings;
+                               PRODUCT_BUNDLE_IDENTIFIER = "com.apple.srp-client";
                                PRODUCT_NAME = "$(TARGET_NAME)";
-                               SDKROOT = iphoneos.internal;
-                               SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
-                               TARGETED_DEVICE_FAMILY = "1,2";
+                               SDKROOT = macosx.internal;
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
                        };
                        name = Debug;
                };
-               B74F16F12114E49D00BEBE84 /* Release */ = {
+               B70F38B1217AA6CE00612D3A /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CODE_SIGN_STYLE = Automatic;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                       };
+                       name = Release;
+               };
+               B70F38B2217AA6CE00612D3A /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CODE_SIGN_STYLE = Automatic;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                       };
+                       name = Debug;
+               };
+               B718417321F8D0A600CA42AD /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                       };
+                       name = Release;
+               };
+               B718417421F8D0A600CA42AD /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                       };
+                       name = Debug;
+               };
+               B7325FF41DA47EBA00663834 /* Release */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
                                CLANG_ANALYZER_NONNULL = YES;
-                               CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+                               CLANG_CXX_LIBRARY = "libc++";
                                CLANG_ENABLE_OBJC_ARC = YES;
-                               CLANG_ENABLE_OBJC_WEAK = YES;
-                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
-                               CLANG_WARN_COMMA = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
                                CLANG_WARN_CONSTANT_CONVERSION = YES;
-                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
                                CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
                                CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
                                CLANG_WARN_EMPTY_BODY = YES;
-                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
-                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
                                CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
-                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_SUSPICIOUS_MOVES = YES;
+                               CLANG_WARN_UNREACHABLE_CODE = YES;
                                CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
                                CODE_SIGN_IDENTITY = "-";
-                               COMBINE_HIDPI_IMAGES = YES;
+                               CURRENT_PROJECT_VERSION = 1;
                                DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               DEFINES_MODULE = YES;
+                               DYLIB_COMPATIBILITY_VERSION = 1;
+                               DYLIB_CURRENT_VERSION = 1;
                                ENABLE_NS_ASSERTIONS = NO;
                                ENABLE_STRICT_OBJC_MSGSEND = YES;
-                               GCC_PREPROCESSOR_DEFINITIONS = (
-                                       "$(inherited)",
-                                       "UNIT_TEST=1",
-                                       "USE_XCTEST=1",
-                                       "MDNSRESPONDER_PLATFORM_APPLE=1",
-                                       "MDNSRESPONDER_SUPPORTS_APPLE_METRICS=0",
-                                       "MDNSRESPONDER_SUPPORTS_APPLE_DNS64=0",
-                                       "MDNSRESPONDER_SUPPORTS_APPLE_DNSSD_XPC_SERVICE=0",
-                                       "MDNSRESPONDER_SUPPORTS_COMMON_DNS_PUSH=0",
-                               );
-                               GCC_SYMBOLS_PRIVATE_EXTERN = YES;
-                               GCC_WARN_UNINITIALIZED_AUTOS = YES;
+                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               HEADER_SEARCH_PATHS = (
-                                       ../mDNSCore,
-                                       ../mDNSShared,
-                                       "${SYSTEM_LIBRARY_DIR}/Frameworks/System.framework/PrivateHeaders",
-                                       "$(SDKROOT)/usr/include/libxml2",
-                                       "${CONFIGURATION_TEMP_DIR}",
-                               );
-                               INFOPLIST_FILE = "Tests/Unit Tests/Info.plist";
-                               INSTALL_PATH = /AppleInternal/XCTests/com.apple.mDNSResponder/;
-                               LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks /AppleInternal/Developer/Library/Frameworks";
-                               MTL_FAST_MATH = YES;
-                               OTHER_CFLAGS = (
-                                       "-DUSE_SYSTEMCONFIGURATION_PRIVATE_HEADERS",
-                                       "-DNO_SECURITYFRAMEWORK",
-                                       "-fwrapv",
-                                       "-flto=full",
-                               );
-                               OTHER_LDFLAGS = (
-                                       "-weak_framework",
-                                       WebFilterDNS,
-                                       "-weak_framework",
-                                       DeviceToDeviceManager,
-                                       "-weak_framework",
-                                       XCTest,
-                               );
-                               "OTHER_LDFLAGS[sdk=iphoneos*]" = (
-                                       "-weak_framework",
-                                       DeviceToDeviceManager,
-                               );
-                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.bonjour.Tests;
-                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               INFOPLIST_FILE = ../mDNSUI/DomainBrowser/iOS/Info.plist;
+                               INSTALL_PATH = "@rpath";
+                               MTL_ENABLE_DEBUG_INFO = NO;
+                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.coseos.network.bonjour.DomainBrowser;
+                               PRODUCT_NAME = DomainBrowser;
                                SDKROOT = iphoneos.internal;
-                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks $(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
-                               WARNING_CFLAGS = (
-                                       "-W",
-                                       "-Wall",
-                                       "-Wmissing-prototypes",
-                                       "-Wno-four-char-constants",
-                                       "-Wno-unknown-pragmas",
-                                       "-Wshadow",
-                                       "-Wno-format",
-                                       "-Wformat-security",
-                               );
+                               SKIP_INSTALL = YES;
+                               SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
+                               TARGETED_DEVICE_FAMILY = "1,2";
+                               VALIDATE_PRODUCT = YES;
+                               VERSIONING_SYSTEM = "apple-generic";
+                               VERSION_INFO_PREFIX = "";
                        };
                        name = Release;
                };
-               B74F16F22114E49D00BEBE84 /* Debug */ = {
+               B7325FF51DA47EBA00663834 /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
                                CLANG_ANALYZER_NONNULL = YES;
-                               CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+                               CLANG_CXX_LIBRARY = "libc++";
                                CLANG_ENABLE_OBJC_ARC = YES;
-                               CLANG_ENABLE_OBJC_WEAK = YES;
-                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
-                               CLANG_WARN_COMMA = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
                                CLANG_WARN_CONSTANT_CONVERSION = YES;
-                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
                                CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
                                CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
                                CLANG_WARN_EMPTY_BODY = YES;
-                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
-                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
                                CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
-                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_SUSPICIOUS_MOVES = YES;
+                               CLANG_WARN_UNREACHABLE_CODE = YES;
                                CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
                                CODE_SIGN_IDENTITY = "-";
-                               COMBINE_HIDPI_IMAGES = YES;
+                               CURRENT_PROJECT_VERSION = 1;
                                DEBUG_INFORMATION_FORMAT = dwarf;
+                               DEFINES_MODULE = YES;
+                               DYLIB_COMPATIBILITY_VERSION = 1;
+                               DYLIB_CURRENT_VERSION = 1;
                                ENABLE_STRICT_OBJC_MSGSEND = YES;
                                ENABLE_TESTABILITY = YES;
+                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_DYNAMIC_NO_PIC = NO;
+                               GCC_NO_COMMON_BLOCKS = YES;
                                GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "DEBUG=1",
                                        "$(inherited)",
-                                       "UNIT_TEST=1",
-                                       "USE_XCTEST=1",
-                                       "MDNSRESPONDER_PLATFORM_APPLE=1",
-                                       "MDNSRESPONDER_SUPPORTS_APPLE_METRICS=0",
-                                       "MDNSRESPONDER_SUPPORTS_APPLE_DNS64=0",
-                                       "MDNSRESPONDER_SUPPORTS_APPLE_DNSSD_XPC_SERVICE=0",
-                                       "MDNSRESPONDER_SUPPORTS_COMMON_DNS_PUSH=0",
                                );
-                               GCC_SYMBOLS_PRIVATE_EXTERN = YES;
-                               GCC_WARN_UNINITIALIZED_AUTOS = YES;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               HEADER_SEARCH_PATHS = (
-                                       ../mDNSCore,
-                                       ../mDNSShared,
-                                       "${SYSTEM_LIBRARY_DIR}/Frameworks/System.framework/PrivateHeaders",
-                                       "$(SDKROOT)/usr/include/libxml2",
-                                       "${CONFIGURATION_TEMP_DIR}",
-                               );
-                               INFOPLIST_FILE = "Tests/Unit Tests/Info.plist";
-                               INSTALL_PATH = /AppleInternal/XCTests/com.apple.mDNSResponder/;
-                               LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks /AppleInternal/Developer/Library/Frameworks";
-                               MTL_FAST_MATH = YES;
+                               INFOPLIST_FILE = ../mDNSUI/DomainBrowser/iOS/Info.plist;
+                               INSTALL_PATH = "@rpath";
+                               MTL_ENABLE_DEBUG_INFO = YES;
                                ONLY_ACTIVE_ARCH = YES;
-                               OTHER_CFLAGS = (
-                                       "-DUSE_SYSTEMCONFIGURATION_PRIVATE_HEADERS",
-                                       "-DNO_SECURITYFRAMEWORK",
-                                       "-fwrapv",
-                               );
-                               OTHER_LDFLAGS = (
-                                       "-weak_framework",
-                                       WebFilterDNS,
-                                       "-weak_framework",
-                                       DeviceToDeviceManager,
-                                       "-weak_framework",
-                                       XCTest,
-                               );
-                               "OTHER_LDFLAGS[sdk=iphoneos*]" = (
-                                       "-weak_framework",
-                                       DeviceToDeviceManager,
-                               );
-                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.bonjour.Tests;
-                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.coseos.network.bonjour.DomainBrowser;
+                               PRODUCT_NAME = DomainBrowser;
                                SDKROOT = iphoneos.internal;
-                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks $(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
-                               WARNING_CFLAGS = (
-                                       "-W",
-                                       "-Wall",
-                                       "-Wmissing-prototypes",
-                                       "-Wno-four-char-constants",
-                                       "-Wno-unknown-pragmas",
-                                       "-Wshadow",
-                                       "-Wno-format",
-                                       "-Wformat-security",
-                               );
+                               SKIP_INSTALL = YES;
+                               SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
+                               TARGETED_DEVICE_FAMILY = "1,2";
+                               VERSIONING_SYSTEM = "apple-generic";
+                               VERSION_INFO_PREFIX = "";
                        };
                        name = Debug;
                };
-               B76783B51E82D65900DA271E /* Release */ = {
+               B7473E731EC3954400D31B9D /* Release */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
+                               ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
                                CLANG_ANALYZER_NONNULL = YES;
                                CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
-                               CLANG_CXX_LIBRARY = "compiler-default";
-                               CLANG_ENABLE_MODULES = YES;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+                               CLANG_CXX_LIBRARY = "libc++";
                                CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
                                CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
                                CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
                                CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
                                CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
                                CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
                                CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES;
                                CLANG_WARN_UNREACHABLE_CODE = YES;
                                CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-                               CODE_SIGN_ENTITLEMENTS = PreferencePane/BonjourPrefTool/entitlements.plist;
+                               CODE_SIGN_ENTITLEMENTS = "Bonjour Safari Menu/BonjourSafariMenu.entitlements";
                                CODE_SIGN_IDENTITY = "-";
                                COMBINE_HIDPI_IMAGES = YES;
                                DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
                                ENABLE_NS_ASSERTIONS = NO;
-                               FRAMEWORK_SEARCH_PATHS = (
-                                       "$(inherited)",
-                                       "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
-                               );
-                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               ENABLE_STRICT_OBJC_MSGSEND = YES;
+                               GCC_C_LANGUAGE_STANDARD = gnu11;
                                GCC_NO_COMMON_BLOCKS = YES;
-                               GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
                                GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               INFOPLIST_FILE = "$(SRCROOT)/PreferencePane/BonjourPrefTool/BonjourPrefTool-Info.plist";
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
+                               INFOPLIST_FILE = "Bonjour Safari Menu/Info.plist";
+                               INSTALL_PATH = /AppleInternal/Applications;
+                               LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
                                MTL_ENABLE_DEBUG_INFO = NO;
-                               PRODUCT_NAME = com.apple.preference.bonjour.tool;
-                               SDKROOT = macosx.internal;
-                               SKIP_INSTALL = YES;
-                               SUPPORTED_PLATFORMS = macosx;
+                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.bonjour.BonjourSafariMenu;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SDKROOT = macosx;
                        };
                        name = Release;
                };
-               B76783B61E82D65900DA271E /* Debug */ = {
+               B7473E741EC3954400D31B9D /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
+                               ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
                                CLANG_ANALYZER_NONNULL = YES;
                                CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
-                               CLANG_CXX_LIBRARY = "compiler-default";
-                               CLANG_ENABLE_MODULES = YES;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+                               CLANG_CXX_LIBRARY = "libc++";
                                CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
                                CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
                                CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
                                CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
                                CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
                                CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
                                CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES;
                                CLANG_WARN_UNREACHABLE_CODE = YES;
                                CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-                               CODE_SIGN_ENTITLEMENTS = PreferencePane/BonjourPrefTool/entitlements.plist;
+                               CODE_SIGN_ENTITLEMENTS = "Bonjour Safari Menu/BonjourSafariMenu.entitlements";
                                CODE_SIGN_IDENTITY = "-";
                                COMBINE_HIDPI_IMAGES = YES;
                                DEBUG_INFORMATION_FORMAT = dwarf;
+                               ENABLE_STRICT_OBJC_MSGSEND = YES;
                                ENABLE_TESTABILITY = YES;
-                               FRAMEWORK_SEARCH_PATHS = (
-                                       "$(inherited)",
-                                       "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
-                               );
-                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_C_LANGUAGE_STANDARD = gnu11;
                                GCC_DYNAMIC_NO_PIC = NO;
                                GCC_NO_COMMON_BLOCKS = YES;
                                GCC_PREPROCESSOR_DEFINITIONS = (
                                        "DEBUG=1",
                                        "$(inherited)",
                                );
-                               GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
                                GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               INFOPLIST_FILE = "$(SRCROOT)/PreferencePane/BonjourPrefTool/BonjourPrefTool-Info.plist";
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
+                               INFOPLIST_FILE = "Bonjour Safari Menu/Info.plist";
+                               INSTALL_PATH = /AppleInternal/Applications;
+                               LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
                                MTL_ENABLE_DEBUG_INFO = YES;
                                ONLY_ACTIVE_ARCH = YES;
-                               PRODUCT_NAME = com.apple.preference.bonjour.tool;
-                               SDKROOT = macosx.internal;
-                               SKIP_INSTALL = YES;
-                               SUPPORTED_PLATFORMS = macosx;
+                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.bonjour.BonjourSafariMenu;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SDKROOT = macosx;
                        };
                        name = Debug;
                };
-               B7D566C41E81D8FD00E43008 /* Release */ = {
+               B7473E931EC395C300D31B9D /* Release */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
                                CLANG_ANALYZER_NONNULL = YES;
                                CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
-                               CLANG_CXX_LIBRARY = "compiler-default";
-                               CLANG_ENABLE_MODULES = YES;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+                               CLANG_CXX_LIBRARY = "libc++";
                                CLANG_ENABLE_OBJC_ARC = YES;
                                CLANG_WARN_BOOL_CONVERSION = YES;
                                CLANG_WARN_CONSTANT_CONVERSION = YES;
                                CLANG_WARN_INFINITE_RECURSION = YES;
                                CLANG_WARN_INT_CONVERSION = YES;
                                CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
                                CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES;
                                CLANG_WARN_UNREACHABLE_CODE = YES;
                                CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-                               CODE_SIGN_ENTITLEMENTS = PreferencePane/RemoteViewService/entitlements.plist;
+                               CODE_SIGN_ENTITLEMENTS = "Bonjour Safari Extension/BonjourSafariExtension.entitlements";
                                CODE_SIGN_IDENTITY = "-";
-                               COMBINE_HIDPI_IMAGES = YES;
                                DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
                                ENABLE_NS_ASSERTIONS = NO;
                                ENABLE_STRICT_OBJC_MSGSEND = YES;
-                               FRAMEWORK_SEARCH_PATHS = (
-                                       "$(inherited)",
-                                       "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
-                               );
-                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_C_LANGUAGE_STANDARD = gnu11;
                                GCC_NO_COMMON_BLOCKS = YES;
                                GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
                                GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               INFOPLIST_FILE = "$(SRCROOT)/PreferencePane/RemoteViewService/BonjourPrefRemoteViewService-Info.plist";
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
+                               INFOPLIST_FILE = "Bonjour Safari Extension/Info.plist";
+                               LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks";
                                MTL_ENABLE_DEBUG_INFO = NO;
-                               PRODUCT_NAME = com.apple.preference.bonjour.remoteservice;
+                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.bonjour.BonjourSafariMenu.BonjourSafariExtension;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SDKROOT = macosx.internal;
                                SKIP_INSTALL = YES;
-                               SUPPORTED_PLATFORMS = macosx;
                        };
                        name = Release;
                };
-               B7D566C51E81D8FD00E43008 /* Debug */ = {
+               B7473E941EC395C300D31B9D /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
                                CLANG_ANALYZER_NONNULL = YES;
                                CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
-                               CLANG_CXX_LIBRARY = "compiler-default";
-                               CLANG_ENABLE_MODULES = YES;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+                               CLANG_CXX_LIBRARY = "libc++";
                                CLANG_ENABLE_OBJC_ARC = YES;
                                CLANG_WARN_BOOL_CONVERSION = YES;
                                CLANG_WARN_CONSTANT_CONVERSION = YES;
                                CLANG_WARN_INFINITE_RECURSION = YES;
                                CLANG_WARN_INT_CONVERSION = YES;
                                CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
                                CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES;
                                CLANG_WARN_UNREACHABLE_CODE = YES;
                                CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
-                               CODE_SIGN_ENTITLEMENTS = PreferencePane/RemoteViewService/entitlements.plist;
+                               CODE_SIGN_ENTITLEMENTS = "Bonjour Safari Extension/BonjourSafariExtension.entitlements";
                                CODE_SIGN_IDENTITY = "-";
-                               COMBINE_HIDPI_IMAGES = YES;
                                DEBUG_INFORMATION_FORMAT = dwarf;
                                ENABLE_STRICT_OBJC_MSGSEND = YES;
                                ENABLE_TESTABILITY = YES;
-                               FRAMEWORK_SEARCH_PATHS = (
-                                       "$(inherited)",
-                                       "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
-                               );
-                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_C_LANGUAGE_STANDARD = gnu11;
                                GCC_DYNAMIC_NO_PIC = NO;
                                GCC_NO_COMMON_BLOCKS = YES;
                                GCC_PREPROCESSOR_DEFINITIONS = (
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               INFOPLIST_FILE = "$(SRCROOT)/PreferencePane/RemoteViewService/BonjourPrefRemoteViewService-Info.plist";
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
+                               INFOPLIST_FILE = "Bonjour Safari Extension/Info.plist";
+                               LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks";
                                MTL_ENABLE_DEBUG_INFO = YES;
                                ONLY_ACTIVE_ARCH = YES;
-                               PRODUCT_NAME = com.apple.preference.bonjour.remoteservice;
+                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.bonjour.BonjourSafariMenu.BonjourSafariExtension;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SDKROOT = macosx.internal;
                                SKIP_INSTALL = YES;
-                               SUPPORTED_PLATFORMS = macosx;
                        };
                        name = Debug;
                };
-               B7D6CA761D1076F3005E24CF /* Release */ = {
+               B74EC11C1D47FC7800A1D155 /* Release */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
-                               ASSETCATALOG_COMPRESSION = "respect-asset-catalog";
-                               CLANG_ANALYZER_NONNULL = YES;
                                CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
                                CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
-                               CLANG_WARN_BOOL_CONVERSION = YES;
-                               CLANG_WARN_CONSTANT_CONVERSION = YES;
-                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
-                               CLANG_WARN_EMPTY_BODY = YES;
-                               CLANG_WARN_ENUM_CONVERSION = YES;
-                               CLANG_WARN_INT_CONVERSION = YES;
-                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-                               CLANG_WARN_UNREACHABLE_CODE = YES;
-                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
                                CODE_SIGN_IDENTITY = "-";
-                               COMBINE_HIDPI_IMAGES = YES;
-                               CURRENT_PROJECT_VERSION = 1;
                                DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
-                               DEFINES_MODULE = YES;
-                               DYLIB_COMPATIBILITY_VERSION = 1;
-                               DYLIB_CURRENT_VERSION = 1;
                                ENABLE_NS_ASSERTIONS = NO;
                                ENABLE_STRICT_OBJC_MSGSEND = YES;
-                               FRAMEWORK_VERSION = A;
+                               FRAMEWORK_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
+                               );
                                GCC_C_LANGUAGE_STANDARD = gnu99;
                                GCC_NO_COMMON_BLOCKS = YES;
-                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
-                               GCC_WARN_UNDECLARED_SELECTOR = YES;
-                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
-                               GCC_WARN_UNUSED_FUNCTION = YES;
-                               GCC_WARN_UNUSED_VARIABLE = YES;
-                               INFOPLIST_FILE = ../mDNSUI/DomainBrowser/macOS/Info.plist;
-                               INSTALL_PATH = "@rpath";
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
+                               GCC_PRECOMPILE_PREFIX_HEADER = YES;
+                               INFOPLIST_FILE = SettingsBundle/Info.plist;
+                               INSTALL_PATH = /AppleInternal/Library/PreferenceBundles;
                                MTL_ENABLE_DEBUG_INFO = NO;
-                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.coreos.network.bonjour.DomainBrowser;
-                               PRODUCT_NAME = DomainBrowser;
-                               SKIP_INSTALL = YES;
-                               SUPPORTED_PLATFORMS = macosx;
-                               VERSIONING_SYSTEM = "apple-generic";
-                               VERSION_INFO_PREFIX = "";
+                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.network.bonjour.BonjourSettings;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SDKROOT = iphoneos.internal;
+                               SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
+                               TARGETED_DEVICE_FAMILY = "1,2";
+                               VALIDATE_PRODUCT = YES;
                        };
                        name = Release;
                };
-               B7D6CA771D1076F3005E24CF /* Debug */ = {
+               B74EC11D1D47FC7800A1D155 /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
-                               ASSETCATALOG_COMPRESSION = lossless;
-                               CLANG_ANALYZER_NONNULL = YES;
                                CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
                                CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
-                               CLANG_WARN_BOOL_CONVERSION = YES;
-                               CLANG_WARN_CONSTANT_CONVERSION = YES;
-                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
-                               CLANG_WARN_EMPTY_BODY = YES;
-                               CLANG_WARN_ENUM_CONVERSION = YES;
-                               CLANG_WARN_INT_CONVERSION = YES;
-                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
-                               CLANG_WARN_UNREACHABLE_CODE = YES;
-                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
                                CODE_SIGN_IDENTITY = "-";
-                               COMBINE_HIDPI_IMAGES = YES;
-                               CURRENT_PROJECT_VERSION = 1;
                                DEBUG_INFORMATION_FORMAT = dwarf;
-                               DEFINES_MODULE = YES;
-                               DYLIB_COMPATIBILITY_VERSION = 1;
-                               DYLIB_CURRENT_VERSION = 1;
                                ENABLE_STRICT_OBJC_MSGSEND = YES;
                                ENABLE_TESTABILITY = YES;
-                               FRAMEWORK_VERSION = A;
+                               FRAMEWORK_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
+                               );
                                GCC_C_LANGUAGE_STANDARD = gnu99;
                                GCC_DYNAMIC_NO_PIC = NO;
                                GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_PRECOMPILE_PREFIX_HEADER = YES;
                                GCC_PREPROCESSOR_DEFINITIONS = (
                                        "DEBUG=1",
-                                       "$(inherited)",
+                                       "${inherited}",
                                );
-                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
-                               GCC_WARN_UNDECLARED_SELECTOR = YES;
-                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
-                               GCC_WARN_UNUSED_FUNCTION = YES;
-                               GCC_WARN_UNUSED_VARIABLE = YES;
-                               INFOPLIST_FILE = ../mDNSUI/DomainBrowser/macOS/Info.plist;
-                               INSTALL_PATH = "@rpath";
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
+                               INFOPLIST_FILE = SettingsBundle/Info.plist;
+                               INSTALL_PATH = /AppleInternal/Library/PreferenceBundles;
                                MTL_ENABLE_DEBUG_INFO = YES;
                                ONLY_ACTIVE_ARCH = YES;
-                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.coreos.network.bonjour.DomainBrowser;
-                               PRODUCT_NAME = DomainBrowser;
-                               SKIP_INSTALL = YES;
-                               SUPPORTED_PLATFORMS = macosx;
-                               VERSIONING_SYSTEM = "apple-generic";
-                               VERSION_INFO_PREFIX = "";
+                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.network.bonjour.BonjourSettings;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SDKROOT = iphoneos.internal;
+                               SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
+                               TARGETED_DEVICE_FAMILY = "1,2";
                        };
                        name = Debug;
                };
-               B7DB5896215EB4DD0054CD46 /* Release */ = {
+               B74F16F12114E49D00BEBE84 /* Release */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
-                               CODE_SIGN_STYLE = Automatic;
+                               CLANG_ANALYZER_NONNULL = YES;
+                               CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+                               CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+                               CLANG_WARN_COMMA = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               CODE_SIGN_ENTITLEMENTS = "Tests/mDNSResponderTests-Entitlements.plist";
+                               CODE_SIGN_IDENTITY = "-";
+                               COMBINE_HIDPI_IMAGES = YES;
+                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               ENABLE_NS_ASSERTIONS = NO;
+                               ENABLE_STRICT_OBJC_MSGSEND = YES;
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "$(inherited)",
+                                       "UNIT_TEST=1",
+                                       "USE_XCTEST=1",
+                                       "MDNSRESPONDER_PLATFORM_APPLE=1",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_D2D=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_DNS64=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_DNSSD_XPC_SERVICE=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_IPC_TLV=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_METRICS=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_QUERIER=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_SYMPTOMS=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_TRUST_ENFORCEMENT=0",
+                                       "MDNSRESPONDER_SUPPORTS_COMMON_DNS_PUSH=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_AUDIT_TOKEN=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_DNSSECv2=1",
+                               );
+                               GCC_SYMBOLS_PRIVATE_EXTERN = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               HEADER_SEARCH_PATHS = (
+                                       ../mDNSCore,
+                                       ../mDNSShared,
+                                       "${SYSTEM_LIBRARY_DIR}/Frameworks/System.framework/PrivateHeaders",
+                                       "$(SDKROOT)/usr/include/libxml2",
+                                       "${CONFIGURATION_TEMP_DIR}",
+                               );
+                               INFOPLIST_FILE = "Tests/Unit Tests/Info.plist";
+                               INSTALL_PATH = /AppleInternal/XCTests/com.apple.mDNSResponder/;
+                               LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks /AppleInternal/Developer/Library/Frameworks";
+                               MTL_FAST_MATH = YES;
+                               OTHER_CFLAGS = (
+                                       "-DUSE_SYSTEMCONFIGURATION_PRIVATE_HEADERS",
+                                       "-DNO_SECURITYFRAMEWORK",
+                                       "-fwrapv",
+                                       "-flto=full",
+                                       "-DDNS_XCTEST",
+                               );
+                               OTHER_LDFLAGS = (
+                                       "-weak_framework",
+                                       WebFilterDNS,
+                                       "-weak_framework",
+                                       DeviceToDeviceManager,
+                                       "-weak_framework",
+                                       CoreAnalytics,
+                                       "-weak_framework",
+                                       XCTest,
+                                       "-framework",
+                                       UniformTypeIdentifiers,
+                               );
+                               "OTHER_LDFLAGS[sdk=iphoneos*]" = (
+                                       "-weak_framework",
+                                       DeviceToDeviceManager,
+                                       "-weak_framework",
+                                       CoreAnalytics,
+                                       "-framework",
+                                       MobileWiFi,
+                                       "-weak_framework",
+                                       OCMock,
+                               );
+                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.bonjour.Tests;
                                PRODUCT_NAME = "$(TARGET_NAME)";
+                               SDKROOT = iphoneos.internal;
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks $(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
+                               WARNING_CFLAGS = (
+                                       "-W",
+                                       "-Wall",
+                                       "-Wmissing-prototypes",
+                                       "-Wno-four-char-constants",
+                                       "-Wno-unknown-pragmas",
+                                       "-Wshadow",
+                                       "-Wno-format",
+                                       "-Wformat-security",
+                               );
                        };
                        name = Release;
                };
-               B7DB5897215EB4DD0054CD46 /* Debug */ = {
+               B74F16F22114E49D00BEBE84 /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
-                               CODE_SIGN_STYLE = Automatic;
+                               CLANG_ANALYZER_NONNULL = YES;
+                               CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+                               CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+                               CLANG_WARN_COMMA = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               CODE_SIGN_ENTITLEMENTS = "Tests/mDNSResponderTests-Entitlements.plist";
+                               CODE_SIGN_IDENTITY = "-";
+                               COMBINE_HIDPI_IMAGES = YES;
+                               DEBUG_INFORMATION_FORMAT = dwarf;
+                               ENABLE_STRICT_OBJC_MSGSEND = YES;
+                               ENABLE_TESTABILITY = YES;
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "$(inherited)",
+                                       "UNIT_TEST=1",
+                                       "USE_XCTEST=1",
+                                       "MDNSRESPONDER_PLATFORM_APPLE=1",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_D2D=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_DNS64=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_DNSSD_XPC_SERVICE=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_IPC_TLV=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_METRICS=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_QUERIER=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_SYMPTOMS=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_TRUST_ENFORCEMENT=0",
+                                       "MDNSRESPONDER_SUPPORTS_COMMON_DNS_PUSH=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_AUDIT_TOKEN=0",
+                                       "MDNSRESPONDER_SUPPORTS_APPLE_DNSSECv2=1",
+                               );
+                               GCC_SYMBOLS_PRIVATE_EXTERN = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               HEADER_SEARCH_PATHS = (
+                                       ../mDNSCore,
+                                       ../mDNSShared,
+                                       "${SYSTEM_LIBRARY_DIR}/Frameworks/System.framework/PrivateHeaders",
+                                       "$(SDKROOT)/usr/include/libxml2",
+                                       "${CONFIGURATION_TEMP_DIR}",
+                               );
+                               INFOPLIST_FILE = "Tests/Unit Tests/Info.plist";
+                               INSTALL_PATH = /AppleInternal/XCTests/com.apple.mDNSResponder/;
+                               LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks /AppleInternal/Developer/Library/Frameworks";
+                               MTL_FAST_MATH = YES;
+                               ONLY_ACTIVE_ARCH = YES;
+                               OTHER_CFLAGS = (
+                                       "-DUSE_SYSTEMCONFIGURATION_PRIVATE_HEADERS",
+                                       "-DNO_SECURITYFRAMEWORK",
+                                       "-fwrapv",
+                                       "-DDNS_XCTEST",
+                               );
+                               OTHER_LDFLAGS = (
+                                       "-weak_framework",
+                                       WebFilterDNS,
+                                       "-weak_framework",
+                                       DeviceToDeviceManager,
+                                       "-weak_framework",
+                                       CoreAnalytics,
+                                       "-weak_framework",
+                                       XCTest,
+                                       "-framework",
+                                       UniformTypeIdentifiers,
+                               );
+                               "OTHER_LDFLAGS[sdk=iphoneos*]" = (
+                                       "-weak_framework",
+                                       DeviceToDeviceManager,
+                                       "-weak_framework",
+                                       CoreAnalytics,
+                                       "-framework",
+                                       MobileWiFi,
+                                       "-weak_framework",
+                                       OCMock,
+                               );
+                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.bonjour.Tests;
                                PRODUCT_NAME = "$(TARGET_NAME)";
+                               SDKROOT = iphoneos.internal;
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks $(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
+                               WARNING_CFLAGS = (
+                                       "-W",
+                                       "-Wall",
+                                       "-Wmissing-prototypes",
+                                       "-Wno-four-char-constants",
+                                       "-Wno-unknown-pragmas",
+                                       "-Wshadow",
+                                       "-Wno-format",
+                                       "-Wformat-security",
+                               );
                        };
                        name = Debug;
                };
-               B7DB58A3215EB61C0054CD46 /* Release */ = {
+               B76783B51E82D65900DA271E /* Release */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
-                               CODE_SIGN_STYLE = Automatic;
-                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               CLANG_ANALYZER_NONNULL = YES;
+                               CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+                               CLANG_CXX_LIBRARY = "compiler-default";
+                               CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNREACHABLE_CODE = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               CODE_SIGN_ENTITLEMENTS = PreferencePane/BonjourPrefTool/entitlements.plist;
+                               CODE_SIGN_IDENTITY = "-";
+                               COMBINE_HIDPI_IMAGES = YES;
+                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               ENABLE_NS_ASSERTIONS = NO;
+                               FRAMEWORK_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
+                               );
+                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               INFOPLIST_FILE = "$(SRCROOT)/PreferencePane/BonjourPrefTool/BonjourPrefTool-Info.plist";
+                               MTL_ENABLE_DEBUG_INFO = NO;
+                               PRODUCT_NAME = com.apple.preference.bonjour.tool;
+                               SDKROOT = macosx.internal;
+                               SKIP_INSTALL = YES;
+                               SUPPORTED_PLATFORMS = macosx;
                        };
                        name = Release;
                };
-               B7DB58A4215EB61C0054CD46 /* Debug */ = {
+               B76783B61E82D65900DA271E /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
-                               CODE_SIGN_STYLE = Automatic;
+                               CLANG_ANALYZER_NONNULL = YES;
+                               CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+                               CLANG_CXX_LIBRARY = "compiler-default";
+                               CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNREACHABLE_CODE = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               CODE_SIGN_ENTITLEMENTS = PreferencePane/BonjourPrefTool/entitlements.plist;
+                               CODE_SIGN_IDENTITY = "-";
+                               COMBINE_HIDPI_IMAGES = YES;
+                               DEBUG_INFORMATION_FORMAT = dwarf;
+                               ENABLE_TESTABILITY = YES;
+                               FRAMEWORK_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
+                               );
+                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_DYNAMIC_NO_PIC = NO;
+                               GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "DEBUG=1",
+                                       "$(inherited)",
+                               );
+                               GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               INFOPLIST_FILE = "$(SRCROOT)/PreferencePane/BonjourPrefTool/BonjourPrefTool-Info.plist";
+                               MTL_ENABLE_DEBUG_INFO = YES;
+                               ONLY_ACTIVE_ARCH = YES;
+                               PRODUCT_NAME = com.apple.preference.bonjour.tool;
+                               SDKROOT = macosx.internal;
+                               SKIP_INSTALL = YES;
+                               SUPPORTED_PLATFORMS = macosx;
+                       };
+                       name = Debug;
+               };
+               B7D566C41E81D8FD00E43008 /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CLANG_ANALYZER_NONNULL = YES;
+                               CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+                               CLANG_CXX_LIBRARY = "compiler-default";
+                               CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNREACHABLE_CODE = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               CODE_SIGN_ENTITLEMENTS = PreferencePane/RemoteViewService/entitlements.plist;
+                               CODE_SIGN_IDENTITY = "-";
+                               COMBINE_HIDPI_IMAGES = YES;
+                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               ENABLE_NS_ASSERTIONS = NO;
+                               ENABLE_STRICT_OBJC_MSGSEND = YES;
+                               FRAMEWORK_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
+                               );
+                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               INFOPLIST_FILE = "$(SRCROOT)/PreferencePane/RemoteViewService/BonjourPrefRemoteViewService-Info.plist";
+                               MTL_ENABLE_DEBUG_INFO = NO;
+                               PRODUCT_NAME = com.apple.preference.bonjour.remoteservice;
+                               SKIP_INSTALL = YES;
+                               SUPPORTED_PLATFORMS = macosx;
+                       };
+                       name = Release;
+               };
+               B7D566C51E81D8FD00E43008 /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CLANG_ANALYZER_NONNULL = YES;
+                               CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+                               CLANG_CXX_LIBRARY = "compiler-default";
+                               CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNREACHABLE_CODE = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               CODE_SIGN_ENTITLEMENTS = PreferencePane/RemoteViewService/entitlements.plist;
+                               CODE_SIGN_IDENTITY = "-";
+                               COMBINE_HIDPI_IMAGES = YES;
+                               DEBUG_INFORMATION_FORMAT = dwarf;
+                               ENABLE_STRICT_OBJC_MSGSEND = YES;
+                               ENABLE_TESTABILITY = YES;
+                               FRAMEWORK_SEARCH_PATHS = (
+                                       "$(inherited)",
+                                       "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
+                               );
+                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_DYNAMIC_NO_PIC = NO;
+                               GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "DEBUG=1",
+                                       "$(inherited)",
+                               );
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               INFOPLIST_FILE = "$(SRCROOT)/PreferencePane/RemoteViewService/BonjourPrefRemoteViewService-Info.plist";
+                               MTL_ENABLE_DEBUG_INFO = YES;
+                               ONLY_ACTIVE_ARCH = YES;
+                               PRODUCT_NAME = com.apple.preference.bonjour.remoteservice;
+                               SKIP_INSTALL = YES;
+                               SUPPORTED_PLATFORMS = macosx;
+                       };
+                       name = Debug;
+               };
+               B7D6CA761D1076F3005E24CF /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ASSETCATALOG_COMPRESSION = "respect-asset-catalog";
+                               CLANG_ANALYZER_NONNULL = YES;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+                               CLANG_CXX_LIBRARY = "libc++";
+                               CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_UNREACHABLE_CODE = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               CODE_SIGN_IDENTITY = "-";
+                               COMBINE_HIDPI_IMAGES = YES;
+                               CURRENT_PROJECT_VERSION = 1;
+                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               DEFINES_MODULE = YES;
+                               DYLIB_COMPATIBILITY_VERSION = 1;
+                               DYLIB_CURRENT_VERSION = 1;
+                               ENABLE_NS_ASSERTIONS = NO;
+                               ENABLE_STRICT_OBJC_MSGSEND = YES;
+                               FRAMEWORK_VERSION = A;
+                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               INFOPLIST_FILE = ../mDNSUI/DomainBrowser/macOS/Info.plist;
+                               INSTALL_PATH = "@rpath";
+                               MTL_ENABLE_DEBUG_INFO = NO;
+                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.coreos.network.bonjour.DomainBrowser;
+                               PRODUCT_NAME = DomainBrowser;
+                               SKIP_INSTALL = YES;
+                               SUPPORTED_PLATFORMS = macosx;
+                               VERSIONING_SYSTEM = "apple-generic";
+                               VERSION_INFO_PREFIX = "";
+                       };
+                       name = Release;
+               };
+               B7D6CA771D1076F3005E24CF /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ASSETCATALOG_COMPRESSION = lossless;
+                               CLANG_ANALYZER_NONNULL = YES;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+                               CLANG_CXX_LIBRARY = "libc++";
+                               CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_UNREACHABLE_CODE = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               CODE_SIGN_IDENTITY = "-";
+                               COMBINE_HIDPI_IMAGES = YES;
+                               CURRENT_PROJECT_VERSION = 1;
+                               DEBUG_INFORMATION_FORMAT = dwarf;
+                               DEFINES_MODULE = YES;
+                               DYLIB_COMPATIBILITY_VERSION = 1;
+                               DYLIB_CURRENT_VERSION = 1;
+                               ENABLE_STRICT_OBJC_MSGSEND = YES;
+                               ENABLE_TESTABILITY = YES;
+                               FRAMEWORK_VERSION = A;
+                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_DYNAMIC_NO_PIC = NO;
+                               GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "DEBUG=1",
+                                       "$(inherited)",
+                               );
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               INFOPLIST_FILE = ../mDNSUI/DomainBrowser/macOS/Info.plist;
+                               INSTALL_PATH = "@rpath";
+                               MTL_ENABLE_DEBUG_INFO = YES;
+                               ONLY_ACTIVE_ARCH = YES;
+                               PRODUCT_BUNDLE_IDENTIFIER = com.apple.coreos.network.bonjour.DomainBrowser;
+                               PRODUCT_NAME = DomainBrowser;
+                               SKIP_INSTALL = YES;
+                               SUPPORTED_PLATFORMS = macosx;
+                               VERSIONING_SYSTEM = "apple-generic";
+                               VERSION_INFO_PREFIX = "";
+                       };
+                       name = Debug;
+               };
+               B7DB5896215EB4DD0054CD46 /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CODE_SIGN_STYLE = Automatic;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SUPPORTED_PLATFORMS = iphoneos;
+                       };
+                       name = Release;
+               };
+               B7DB5897215EB4DD0054CD46 /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CODE_SIGN_STYLE = Automatic;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SUPPORTED_PLATFORMS = iphoneos;
+                       };
+                       name = Debug;
+               };
+               B7DB58A3215EB61C0054CD46 /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CODE_SIGN_STYLE = Automatic;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SUPPORTED_PLATFORMS = macosx;
+                       };
+                       name = Release;
+               };
+               B7DB58A4215EB61C0054CD46 /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CODE_SIGN_STYLE = Automatic;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SUPPORTED_PLATFORMS = macosx;
+                       };
+                       name = Debug;
+               };
+               B7E6DACF244E28F800C898EF /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                       };
+                       name = Release;
+               };
+               B7E6DAD0244E28F800C898EF /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                       };
+                       name = Debug;
+               };
+               BD7BFBF5236FCF0E000456BC /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CLANG_ANALYZER_NONNULL = YES;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+                               CLANG_CXX_LIBRARY = "libc++";
+                               CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CLANG_WARN_ASSIGN_ENUM = YES;
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_COMMA = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_CXX0X_EXTENSIONS = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_FLOAT_CONVERSION = YES_ERROR;
+                               CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES;
+                               CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
+                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES;
+                               CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES_ERROR;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+                               CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
+                               CODE_SIGN_IDENTITY = "-";
+                               CODE_SIGN_STYLE = Automatic;
+                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               DYLIB_COMPATIBILITY_VERSION = 1;
+                               DYLIB_CURRENT_VERSION = 1;
+                               ENABLE_NS_ASSERTIONS = NO;
+                               ENABLE_STRICT_OBJC_MSGSEND = YES;
+                               EXECUTABLE_PREFIX = lib;
+                               GCC_C_LANGUAGE_STANDARD = gnu11;
+                               GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
+                               GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
+                               GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
+                               GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
+                               GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
+                               GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
+                               GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;
+                               GCC_WARN_PEDANTIC = YES;
+                               GCC_WARN_SHADOW = YES;
+                               GCC_WARN_SIGN_COMPARE = YES;
+                               GCC_WARN_STRICT_SELECTOR_MATCH = YES;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+                               GCC_WARN_UNKNOWN_PRAGMAS = YES;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_LABEL = YES;
+                               GCC_WARN_UNUSED_PARAMETER = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               INSTALL_PATH = /usr/lib/log;
+                               MTL_ENABLE_DEBUG_INFO = NO;
+                               MTL_FAST_MATH = YES;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SDKROOT = macosx.internal;
+                               SKIP_INSTALL = NO;
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
+                               WARNING_CFLAGS = (
+                                       "-Wall",
+                                       "-Warc-repeated-use-of-weak",
+                                       "-Wassign-enum",
+                                       "-Wconditional-uninitialized",
+                                       "-Wconversion",
+                                       "-Werror",
+                                       "-Werror-implicit-function-declaration",
+                                       "-Wextra",
+                                       "-Wfour-char-constants",
+                                       "-Wnullable-to-nonnull-conversion",
+                                       "-Wsign-compare",
+                                       "-Wsign-conversion",
+                                       "-Wundef",
+                                       "-Wuninitialized",
+                                       "-Wunreachable-code",
+                                       "-Wunused-parameter",
+                                       "-Wunused-parameter",
+                                       "-Wstrict-selector-match",
+                                       "-Wexplicit-ownership-type",
+                                       "-Wpedantic",
+                                       "-pedantic",
+                                       "-Wno-nullability-extension",
+                                       "-Wno-fixed-enum-extension",
+                               );
+                       };
+                       name = Release;
+               };
+               BD7BFBF6236FCF0E000456BC /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CLANG_ANALYZER_NONNULL = YES;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+                               CLANG_CXX_LIBRARY = "libc++";
+                               CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_ENABLE_OBJC_WEAK = YES;
+                               CLANG_WARN_ASSIGN_ENUM = YES;
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_COMMA = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_CXX0X_EXTENSIONS = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_FLOAT_CONVERSION = YES_ERROR;
+                               CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+                               CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES;
+                               CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
+                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES;
+                               CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES_ERROR;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+                               CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
+                               CODE_SIGN_IDENTITY = "-";
+                               CODE_SIGN_STYLE = Automatic;
+                               DEBUG_INFORMATION_FORMAT = dwarf;
+                               DYLIB_COMPATIBILITY_VERSION = 1;
+                               DYLIB_CURRENT_VERSION = 1;
+                               ENABLE_STRICT_OBJC_MSGSEND = YES;
+                               ENABLE_TESTABILITY = YES;
+                               EXECUTABLE_PREFIX = lib;
+                               GCC_C_LANGUAGE_STANDARD = gnu11;
+                               GCC_DYNAMIC_NO_PIC = NO;
+                               GCC_NO_COMMON_BLOCKS = YES;
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "DEBUG=1",
+                                       "$(inherited)",
+                               );
+                               GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
+                               GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
+                               GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
+                               GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
+                               GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
+                               GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
+                               GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;
+                               GCC_WARN_PEDANTIC = YES;
+                               GCC_WARN_SHADOW = YES;
+                               GCC_WARN_SIGN_COMPARE = YES;
+                               GCC_WARN_STRICT_SELECTOR_MATCH = YES;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+                               GCC_WARN_UNKNOWN_PRAGMAS = YES;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_LABEL = YES;
+                               GCC_WARN_UNUSED_PARAMETER = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               INSTALL_PATH = /usr/lib/log;
+                               MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+                               MTL_FAST_MATH = YES;
+                               ONLY_ACTIVE_ARCH = YES;
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SDKROOT = macosx.internal;
+                               SKIP_INSTALL = NO;
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
+                               WARNING_CFLAGS = (
+                                       "-Wall",
+                                       "-Warc-repeated-use-of-weak",
+                                       "-Wassign-enum",
+                                       "-Wconditional-uninitialized",
+                                       "-Wconversion",
+                                       "-Werror",
+                                       "-Werror-implicit-function-declaration",
+                                       "-Wextra",
+                                       "-Wfour-char-constants",
+                                       "-Wnullable-to-nonnull-conversion",
+                                       "-Wsign-compare",
+                                       "-Wsign-conversion",
+                                       "-Wundef",
+                                       "-Wuninitialized",
+                                       "-Wunreachable-code",
+                                       "-Wunused-parameter",
+                                       "-Wunused-parameter",
+                                       "-Wstrict-selector-match",
+                                       "-Wexplicit-ownership-type",
+                                       "-Wpedantic",
+                                       "-pedantic",
+                                       "-Wno-nullability-extension",
+                                       "-Wno-fixed-enum-extension",
+                               );
+                       };
+                       name = Debug;
+               };
+               BD9BA7511EAF90E400658CCF /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CLANG_ANALYZER_GCD_PERFORMANCE = YES;
+                               CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
+                               CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
+                               CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
+                               CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_WARN_ASSIGN_ENUM = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_CXX0X_EXTENSIONS = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES;
+                               CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
+                               CODE_SIGN_ENTITLEMENTS = "../Clients/dnssdutil/dnssdutil-entitlements.plist";
+                               CODE_SIGN_IDENTITY = "-";
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "MDNSRESPONDER_PROJECT=1",
+                                       "DEBUG=0",
+                               );
+                               GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
+                               GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = YES;
+                               GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
+                               GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
+                               GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES;
+                               GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
+                               GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
+                               GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
+                               GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;
+                               GCC_WARN_PEDANTIC = YES;
+                               GCC_WARN_SHADOW = YES;
+                               GCC_WARN_SIGN_COMPARE = YES;
+                               GCC_WARN_STRICT_SELECTOR_MATCH = YES;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+                               GCC_WARN_UNKNOWN_PRAGMAS = YES;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_LABEL = YES;
+                               GCC_WARN_UNUSED_PARAMETER = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               HEADER_SEARCH_PATHS = "$(SDKROOT)${SYSTEM_LIBRARY_DIR}/Frameworks/System.framework/PrivateHeaders";
+                               INSTALL_PATH = /usr/local/bin;
+                               "OTHER_LDFLAGS[sdk=iphoneos*]" = (
+                                       "-framework",
+                                       MobileWiFi,
+                               );
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)/System/Library/PrivateFrameworks $(PLATFORM_DIR)/Developer/Library/Frameworks $(PLATFORM_DIR)/Developer/AppleInternal/Library/Frameworks";
+                               WARNING_CFLAGS = (
+                                       "-Wall",
+                                       "-Warc-repeated-use-of-weak",
+                                       "-Wassign-enum",
+                                       "-Wconditional-uninitialized",
+                                       "-Wconversion",
+                                       "-Werror",
+                                       "-Werror-implicit-function-declaration",
+                                       "-Wextra",
+                                       "-Wfour-char-constants",
+                                       "-Wnullable-to-nonnull-conversion",
+                                       "-Wsign-compare",
+                                       "-Wsign-conversion",
+                                       "-Wundef",
+                                       "-Wuninitialized",
+                                       "-Wunreachable-code",
+                                       "-Wunused-parameter",
+                                       "-Wunused-parameter",
+                                       "-Wstrict-selector-match",
+                                       "-Wexplicit-ownership-type",
+                                       "-Wpedantic",
+                                       "-pedantic",
+                                       "-Wno-nullability-extension",
+                                       "-Wno-fixed-enum-extension",
+                               );
+                       };
+                       name = Release;
+               };
+               BD9BA7521EAF90E400658CCF /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               CLANG_ANALYZER_GCD_PERFORMANCE = YES;
+                               CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
+                               CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
+                               CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
+                               CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_WARN_ASSIGN_ENUM = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_CXX0X_EXTENSIONS = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES;
+                               CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
+                               CODE_SIGN_ENTITLEMENTS = "../Clients/dnssdutil/dnssdutil-entitlements.plist";
+                               CODE_SIGN_IDENTITY = "-";
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "MDNSRESPONDER_PROJECT=1",
+                                       "DEBUG=1",
+                               );
+                               GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
+                               GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = YES;
+                               GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
+                               GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
+                               GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES;
+                               GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
+                               GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
+                               GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
+                               GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;
+                               GCC_WARN_PEDANTIC = YES;
+                               GCC_WARN_SHADOW = YES;
+                               GCC_WARN_SIGN_COMPARE = YES;
+                               GCC_WARN_STRICT_SELECTOR_MATCH = YES;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+                               GCC_WARN_UNKNOWN_PRAGMAS = YES;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_LABEL = YES;
+                               GCC_WARN_UNUSED_PARAMETER = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               HEADER_SEARCH_PATHS = "$(SDKROOT)${SYSTEM_LIBRARY_DIR}/Frameworks/System.framework/PrivateHeaders";
+                               INSTALL_PATH = /usr/local/bin;
+                               "OTHER_LDFLAGS[sdk=iphoneos*]" = (
+                                       "-framework",
+                                       MobileWiFi,
+                               );
                                PRODUCT_NAME = "$(TARGET_NAME)";
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)/System/Library/PrivateFrameworks $(PLATFORM_DIR)/Developer/Library/Frameworks $(PLATFORM_DIR)/Developer/AppleInternal/Library/Frameworks";
+                               WARNING_CFLAGS = (
+                                       "-Wall",
+                                       "-Warc-repeated-use-of-weak",
+                                       "-Wassign-enum",
+                                       "-Wconditional-uninitialized",
+                                       "-Wconversion",
+                                       "-Werror",
+                                       "-Werror-implicit-function-declaration",
+                                       "-Wextra",
+                                       "-Wfour-char-constants",
+                                       "-Wnullable-to-nonnull-conversion",
+                                       "-Wsign-compare",
+                                       "-Wsign-conversion",
+                                       "-Wundef",
+                                       "-Wuninitialized",
+                                       "-Wunreachable-code",
+                                       "-Wunused-parameter",
+                                       "-Wunused-parameter",
+                                       "-Wstrict-selector-match",
+                                       "-Wexplicit-ownership-type",
+                                       "-Wpedantic",
+                                       "-pedantic",
+                                       "-Wno-nullability-extension",
+                                       "-Wno-fixed-enum-extension",
+                               );
                        };
                        name = Debug;
                };
-               BD9BA7511EAF90E400658CCF /* Release */ = {
+               BF0E381D24882B650028D528 /* Release */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
-                               CLANG_ANALYZER_GCD_PERFORMANCE = YES;
-                               CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
-                               CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
-                               CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
+                               CLANG_ANALYZER_NONNULL = YES;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+                               CLANG_CXX_LIBRARY = "libc++";
                                CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_ENABLE_OBJC_WEAK = YES;
                                CLANG_WARN_ASSIGN_ENUM = YES;
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
                                CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_COMMA = YES;
                                CLANG_WARN_CONSTANT_CONVERSION = YES;
                                CLANG_WARN_CXX0X_EXTENSIONS = YES;
                                CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
-                               CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
+                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
                                CLANG_WARN_EMPTY_BODY = YES;
                                CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_FLOAT_CONVERSION = YES_ERROR;
                                CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
                                CLANG_WARN_INFINITE_RECURSION = YES;
                                CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
                                CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
                                CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
                                CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
                                CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES;
                                CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
+                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
                                CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
-                               CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES;
+                               CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES_ERROR;
                                CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
                                CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
                                CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
                                CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
-                               CODE_SIGN_ENTITLEMENTS = "../Clients/dnssdutil/dnssdutil-entitlements.plist";
                                CODE_SIGN_IDENTITY = "-";
-                               FRAMEWORK_SEARCH_PATHS = (
-                                       "$(inherited)",
-                                       "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
-                               );
-                               GCC_PREPROCESSOR_DEFINITIONS = (
-                                       "MDNSRESPONDER_PROJECT=1",
-                                       "DEBUG=0",
-                               );
+                               CODE_SIGN_STYLE = Automatic;
+                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               DYLIB_COMPATIBILITY_VERSION = 1;
+                               DYLIB_CURRENT_VERSION = 1;
+                               ENABLE_NS_ASSERTIONS = NO;
+                               ENABLE_STRICT_OBJC_MSGSEND = YES;
+                               EXECUTABLE_PREFIX = lib;
+                               GCC_C_LANGUAGE_STANDARD = gnu11;
+                               GCC_NO_COMMON_BLOCKS = YES;
                                GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
                                GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
                                GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-                               GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = YES;
                                GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
                                GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
                                GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
-                               GCC_WARN_ABOUT_RETURN_TYPE = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
                                GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
                                GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
                                GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
                                GCC_WARN_UNUSED_LABEL = YES;
                                GCC_WARN_UNUSED_PARAMETER = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               HEADER_SEARCH_PATHS = "$(SDKROOT)${SYSTEM_LIBRARY_DIR}/Frameworks/System.framework/PrivateHeaders";
-                               INSTALL_PATH = /usr/local/bin;
+                               INSTALL_PATH = /usr/lib/log;
+                               MTL_ENABLE_DEBUG_INFO = NO;
+                               MTL_FAST_MATH = YES;
                                PRODUCT_NAME = "$(TARGET_NAME)";
-                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)/System/Library/PrivateFrameworks $(PLATFORM_DIR)/Developer/Library/Frameworks $(PLATFORM_DIR)/Developer/AppleInternal/Library/Frameworks";
+                               SDKROOT = macosx.internal;
+                               SKIP_INSTALL = NO;
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
                                WARNING_CFLAGS = (
                                        "-Wall",
                                        "-Warc-repeated-use-of-weak",
                        };
                        name = Release;
                };
-               BD9BA7521EAF90E400658CCF /* Debug */ = {
+               BF0E381E24882B650028D528 /* Debug */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
-                               CLANG_ANALYZER_GCD_PERFORMANCE = YES;
-                               CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
-                               CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES;
-                               CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
+                               CLANG_ANALYZER_NONNULL = YES;
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+                               CLANG_CXX_LIBRARY = "libc++";
                                CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_ENABLE_OBJC_WEAK = YES;
                                CLANG_WARN_ASSIGN_ENUM = YES;
+                               CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
                                CLANG_WARN_BOOL_CONVERSION = YES;
+                               CLANG_WARN_COMMA = YES;
                                CLANG_WARN_CONSTANT_CONVERSION = YES;
                                CLANG_WARN_CXX0X_EXTENSIONS = YES;
                                CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
-                               CLANG_WARN_DOCUMENTATION_COMMENTS = NO;
+                               CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+                               CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
                                CLANG_WARN_EMPTY_BODY = YES;
                                CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_FLOAT_CONVERSION = YES_ERROR;
                                CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
                                CLANG_WARN_INFINITE_RECURSION = YES;
                                CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
                                CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
                                CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
                                CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
                                CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES;
                                CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES;
+                               CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
                                CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
-                               CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES;
+                               CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES_ERROR;
                                CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
                                CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
                                CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
                                CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
-                               CODE_SIGN_ENTITLEMENTS = "../Clients/dnssdutil/dnssdutil-entitlements.plist";
                                CODE_SIGN_IDENTITY = "-";
-                               FRAMEWORK_SEARCH_PATHS = (
-                                       "$(inherited)",
-                                       "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
-                               );
+                               CODE_SIGN_STYLE = Automatic;
+                               DEBUG_INFORMATION_FORMAT = dwarf;
+                               DYLIB_COMPATIBILITY_VERSION = 1;
+                               DYLIB_CURRENT_VERSION = 1;
+                               ENABLE_STRICT_OBJC_MSGSEND = YES;
+                               ENABLE_TESTABILITY = YES;
+                               EXECUTABLE_PREFIX = lib;
+                               GCC_C_LANGUAGE_STANDARD = gnu11;
+                               GCC_DYNAMIC_NO_PIC = NO;
+                               GCC_NO_COMMON_BLOCKS = YES;
                                GCC_PREPROCESSOR_DEFINITIONS = (
-                                       "MDNSRESPONDER_PROJECT=1",
                                        "DEBUG=1",
+                                       "$(inherited)",
                                );
                                GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
                                GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
                                GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
-                               GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO = YES;
                                GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
                                GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
                                GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
-                               GCC_WARN_ABOUT_RETURN_TYPE = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
                                GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
                                GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
                                GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
                                GCC_WARN_UNUSED_LABEL = YES;
                                GCC_WARN_UNUSED_PARAMETER = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
-                               HEADER_SEARCH_PATHS = "$(SDKROOT)${SYSTEM_LIBRARY_DIR}/Frameworks/System.framework/PrivateHeaders";
-                               INSTALL_PATH = /usr/local/bin;
+                               INSTALL_PATH = /usr/lib/log;
+                               MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+                               MTL_FAST_MATH = YES;
+                               ONLY_ACTIVE_ARCH = YES;
                                PRODUCT_NAME = "$(TARGET_NAME)";
-                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)/System/Library/PrivateFrameworks $(PLATFORM_DIR)/Developer/Library/Frameworks $(PLATFORM_DIR)/Developer/AppleInternal/Library/Frameworks";
+                               SDKROOT = macosx.internal;
+                               SKIP_INSTALL = NO;
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
                                WARNING_CFLAGS = (
                                        "-Wall",
                                        "-Warc-repeated-use-of-weak",
                                CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
                                CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES;
                                CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
+                               CODE_SIGN_IDENTITY = "-";
                                CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/Release";
                                CONFIGURATION_TEMP_DIR = "$(PROJECT_TEMP_DIR)/Release";
                                COPY_PHASE_STRIP = NO;
                                );
                                GCC_TREAT_WARNINGS_AS_ERRORS = YES;
                                GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
+                               MACOSX_DEPLOYMENT_TARGET = 10.15;
                                MVERS = "\"(Engineering Build)\"";
                                OTHER_CFLAGS = (
                                        "-DUSE_SYSTEMCONFIGURATION_PRIVATE_HEADERS",
                                "APPLY_RULES_IN_COPY_FILES[sdk=iphoneos*]" = YES;
                                "APPLY_RULES_IN_COPY_FILES[sdk=watchos*]" = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_WARN_BOOL_CONVERSION = YES_ERROR;
+                               CLANG_WARN_COMMA = YES_ERROR;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES_ERROR;
+                               CLANG_WARN_CXX0X_EXTENSIONS = YES;
+                               CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES_ERROR;
+                               CLANG_WARN_FRAMEWORK_INCLUDE_PRIVATE_FROM_PUBLIC = YES;
+                               CLANG_WARN_INFINITE_RECURSION = YES;
                                CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR;
+                               CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
+                               CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+                               CLANG_WARN_OBJC_LITERAL_CONVERSION = YES_ERROR;
+                               CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES;
+                               CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES_AGGRESSIVE;
+                               CLANG_WARN_PRAGMA_PACK = YES_ERROR;
+                               CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+                               CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES;
+                               CLANG_WARN_STRICT_PROTOTYPES = YES_ERROR;
+                               CLANG_WARN_SUSPICIOUS_MOVE = YES;
+                               CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+                               CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
                                CODE_SIGN_ENTITLEMENTS = "mDNSResponder-entitlements.plist";
                                CODE_SIGN_IDENTITY = "-";
                                FRAMEWORK_SEARCH_PATHS = (
                                GCC_PREPROCESSOR_DEFINITIONS = (
                                        "$(inherited)",
                                        "MDNSRESPONDER_PLATFORM_APPLE=1",
+                                       "MDNS_OBJECT_FORCE_NO_OBJC=1",
                                        DSO_USES_NETWORK_FRAMEWORK,
                                );
+                               GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
+                               GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES_ERROR;
+                               GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
+                               GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
+                               GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+                               GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES;
+                               GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES;
+                               GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
+                               GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES;
+                               GCC_WARN_SHADOW = YES;
+                               GCC_WARN_SIGN_COMPARE = YES;
+                               GCC_WARN_STRICT_SELECTOR_MATCH = YES;
+                               GCC_WARN_UNDECLARED_SELECTOR = YES;
+                               GCC_WARN_UNKNOWN_PRAGMAS = YES;
+                               GCC_WARN_UNUSED_FUNCTION = YES;
+                               GCC_WARN_UNUSED_LABEL = YES;
+                               GCC_WARN_UNUSED_PARAMETER = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
                                HEADER_SEARCH_PATHS = (
                                        ../mDNSShared,
                                        "${SYSTEM_LIBRARY_DIR}/Frameworks/System.framework/PrivateHeaders",
                                );
                                INSTALL_PATH = /usr/sbin;
                                LIBRARY_SEARCH_PATHS = "\"${CONFIGURATION_TEMP_DIR}\"";
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
                                ORDER_FILE = "${SRCROOT}/mDNSResponder.order";
                                OTHER_LDFLAGS = "";
                                "OTHER_LDFLAGS[sdk=iphoneos*][arch=*]" = (
                                        "-weak_framework",
                                        DeviceToDeviceManager,
+                                       "-weak_framework",
+                                       CoreAnalytics,
+                                       "-weak_framework",
+                                       NetworkExtension,
+                                       "-framework",
+                                       MobileWiFi,
                                );
                                "OTHER_LDFLAGS[sdk=macosx*][arch=*]" = (
                                        "-weak_framework",
                                        WebFilterDNS,
                                        "-weak_framework",
                                        DeviceToDeviceManager,
+                                       "-weak_framework",
+                                       CoreAnalytics,
+                                       "-weak_framework",
+                                       NetworkExtension,
+                                       "-framework",
+                                       UniformTypeIdentifiers,
                                );
                                "PLIST_FILE_OUTPUT_FORMAT[sdk=appletvos*]" = binary;
                                "PLIST_FILE_OUTPUT_FORMAT[sdk=iphoneos*]" = binary;
                                );
                                INSTALL_PATH = /usr/sbin;
                                LIBRARY_SEARCH_PATHS = "\"${CONFIGURATION_TEMP_DIR}\"";
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
                                OTHER_CFLAGS = "-UAPPLE_OSX_mDNSResponder";
                                PRODUCT_NAME = dnsextd;
                        };
                                GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
                                INFOPLIST_FILE = "PreferencePane/Info-PreferencePane.plist";
                                INSTALL_PATH = /AppleInternal/Library/PreferencePanes;
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
                                PRODUCT_NAME = Bonjour;
                                SUPPORTED_PLATFORMS = macosx;
                                WRAPPER_EXTENSION = prefPane;
                                CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
                                CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
                                CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
                                CLANG_ENABLE_OBJC_WEAK = YES;
                                CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
                                INSTALL_PATH = /usr/lib/log;
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
                                MTL_ENABLE_DEBUG_INFO = NO;
                                MTL_FAST_MATH = YES;
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                SDKROOT = macosx.internal;
                                SKIP_INSTALL = NO;
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
                        };
                        name = Release;
                };
                                CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
                                CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
                                CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
                                CLANG_ENABLE_OBJC_WEAK = YES;
                                CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_VARIABLE = YES;
                                INSTALL_PATH = /usr/lib/log;
-                               MACOSX_DEPLOYMENT_TARGET = 10.14;
                                MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
                                MTL_FAST_MATH = YES;
                                ONLY_ACTIVE_ARCH = YES;
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                SDKROOT = macosx.internal;
                                SKIP_INSTALL = NO;
+                               SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(inherited) $(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks";
                        };
                        name = Debug;
                };
                                        "-lsystem_blocks",
                                        "-ldispatch",
                                        "-lsystem_asl",
+                                       "-lxpc",
+                                       "-lsystem_featureflags",
+                                       "-Wl,-upward-lobjc",
                                );
                                OTHER_TAPI_FLAGS = "-umbrella System -extra-public-header $(SRCROOT)/../mDNSShared/dns_sd.h -extra-public-header $(SRCROOT)/DNSServiceDiscovery.h -extra-private-header $(SRCROOT)/../mDNSShared/dns_sd_private.h";
                                PRODUCT_NAME = libsystem_dnssd_debug;
                                        "-lsystem_blocks",
                                        "-ldispatch",
                                        "-lsystem_asl",
+                                       "-lxpc",
+                                       "-lsystem_featureflags",
+                                       "-Wl,-upward-lobjc",
                                );
                                OTHER_TAPI_FLAGS = "-umbrella System -extra-public-header $(SRCROOT)/../mDNSShared/dns_sd.h -extra-public-header $(SRCROOT)/DNSServiceDiscovery.h -extra-private-header $(SRCROOT)/../mDNSShared/dns_sd_private.h";
                                PRODUCT_NAME = libsystem_dnssd_profile;
                FFB7658A0AEED9FB00583A2C /* Release */ = {
                        isa = XCBuildConfiguration;
                        buildSettings = {
+                               CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES;
                                CLANG_WARN_ASSIGN_ENUM = YES;
                                CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES_ERROR;
                                CLANG_WARN_BOOL_CONVERSION = YES_ERROR;
                                CLANG_WARN_FLOAT_CONVERSION = YES_ERROR;
                                CLANG_WARN_INFINITE_RECURSION = YES;
                                CLANG_WARN_INT_CONVERSION = YES_ERROR;
-                               CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR;
                                CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES;
                                CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
                                DYLIB_CURRENT_VERSION = "$(RC_ProjectSourceVersion)";
                                GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES;
                                GCC_WARN_SHADOW = YES;
                                GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
-                               GCC_WARN_UNKNOWN_PRAGMAS = YES;
                                GCC_WARN_UNUSED_FUNCTION = YES;
                                GCC_WARN_UNUSED_LABEL = YES;
                                GCC_WARN_UNUSED_PARAMETER = YES;
                                        "-lsystem_blocks",
                                        "-ldispatch",
                                        "-lsystem_asl",
+                                       "-lxpc",
+                                       "-lsystem_featureflags",
+                                       "-Wl,-upward-lobjc",
                                );
                                OTHER_TAPI_FLAGS = "-umbrella System -extra-public-header $(SRCROOT)/DNSServiceDiscovery.h";
                                PRODUCT_NAME = libsystem_dnssd;
                        defaultConfigurationIsVisible = 0;
                        defaultConfigurationName = Release;
                };
+               891F2F852397EA1D001EC153 /* Build configuration list for PBXNativeTarget "srp-mdns-proxy" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               891F2F832397EA1D001EC153 /* Release */,
+                               891F2F842397EA1D001EC153 /* Debug */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               8941BE4D23E352C2004AF1CA /* Build configuration list for PBXAggregateTarget "Build Extras-tvOS" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               8941BE4E23E352C2004AF1CA /* Release */,
+                               8941BE4F23E352C2004AF1CA /* Debug */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               895E9DAB23C68D5000DA0FE0 /* Build configuration list for PBXNativeTarget "srputil" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               895E9DA923C68D5000DA0FE0 /* Release */,
+                               895E9DAA23C68D5000DA0FE0 /* Debug */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               89AFC1E923F5DE2B00538084 /* Build configuration list for PBXNativeTarget "ra-tester" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               89AFC1E723F5DE2B00538084 /* Release */,
+                               89AFC1E823F5DE2B00538084 /* Debug */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               89D4BBEB239EA80E00FCFB7E /* Build configuration list for PBXNativeTarget "srp-client" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               89D4BBEC239EA80E00FCFB7E /* Release */,
+                               89D4BBED239EA80E00FCFB7E /* Debug */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
                B70F38B0217AA6CE00612D3A /* Build configuration list for PBXAggregateTarget "Build Extras" */ = {
                        isa = XCConfigurationList;
                        buildConfigurations = (
                        defaultConfigurationIsVisible = 0;
                        defaultConfigurationName = Release;
                };
+               B7E6DACE244E28F800C898EF /* Build configuration list for PBXAggregateTarget "Build Services-macOS" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               B7E6DACF244E28F800C898EF /* Release */,
+                               B7E6DAD0244E28F800C898EF /* Debug */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               BD7BFBF4236FCF0E000456BC /* Build configuration list for PBXNativeTarget "log_mdns" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               BD7BFBF5236FCF0E000456BC /* Release */,
+                               BD7BFBF6236FCF0E000456BC /* Debug */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
                BD9BA7501EAF90E400658CCF /* Build configuration list for PBXNativeTarget "dnssdutil" */ = {
                        isa = XCConfigurationList;
                        buildConfigurations = (
                        defaultConfigurationIsVisible = 0;
                        defaultConfigurationName = Release;
                };
+               BF0E381C24882B650028D528 /* Build configuration list for PBXNativeTarget "log_srp" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               BF0E381D24882B650028D528 /* Release */,
+                               BF0E381E24882B650028D528 /* Debug */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
                D284BE2B0ADD78180027CCDF /* Build configuration list for PBXProject "mDNSResponder" */ = {
                        isa = XCConfigurationList;
                        buildConfigurations = (
index 6bc511e5e3cc7602d23918fb388a6d4781be321e..07c3d3ccd36c8b2f2e06822b19c2c82690507bdf 100644 (file)
@@ -29,8 +29,6 @@
       shouldUseLaunchSchemeArgsEnv = "YES">
       <Testables>
       </Testables>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Development"
@@ -51,8 +49,6 @@
             ReferencedContainer = "container:mDNSResponder.xcodeproj">
          </BuildableReference>
       </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Development"
index 16b8d0a4dd08af26c85ace66660932990cb82152..4d93a2be465f3c073f4ea2074e48a71ed015da2f 100644 (file)
       buildConfiguration = "Debug"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      codeCoverageEnabled = "YES"
-      shouldUseLaunchSchemeArgsEnv = "YES">
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      codeCoverageEnabled = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "D284BE500ADD80740027CCDF"
+            BuildableName = "mDNSResponder"
+            BlueprintName = "mDNSResponder"
+            ReferencedContainer = "container:mDNSResponder.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
       <Testables>
          <TestableReference
             skipped = "NO">
             </BuildableReference>
          </TestableReference>
       </Testables>
-      <MacroExpansion>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "D284BE500ADD80740027CCDF"
-            BuildableName = "mDNSResponder"
-            BlueprintName = "mDNSResponder"
-            ReferencedContainer = "container:mDNSResponder.xcodeproj">
-         </BuildableReference>
-      </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -82,8 +80,6 @@
             isEnabled = "YES">
          </CommandLineArgument>
       </CommandLineArguments>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Development"
diff --git a/mDNSMacOSX/mdns.c b/mDNSMacOSX/mdns.c
deleted file mode 100644 (file)
index 504b8d9..0000000
+++ /dev/null
@@ -1,807 +0,0 @@
-/*
- * Copyright (c) 2019 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "mdns_private.h"
-
-#include "mdns_object.h"
-
-#include <CoreUtils/CoreUtils.h>
-#include <network_information.h>
-#include <notify.h>
-#include <os/log.h>
-#include <os/object_private.h>
-
-//======================================================================================================================
-// MARK: - Kind Declarations
-
-#define MDNS_STRUCT(NAME)      struct mdns_ ## NAME ## _s
-
-// Note: The last check checks if the base's type is equal to that of the superkind. If it's not, then the pointer
-// comparison used as the argument to sizeof will cause a "comparison of distinct pointer types" warning, so long as
-// the warning hasn't been disabled.
-
-#define MDNS_BASE_CHECK(NAME, SUPER)                                                                                                                   \
-       check_compile_time(offsetof(MDNS_STRUCT(NAME), base) == 0);                                                                     \
-       check_compile_time(sizeof_field(MDNS_STRUCT(NAME), base) == sizeof(MDNS_STRUCT(SUPER)));        \
-       extern int _mdns_base_type_check[sizeof(&(((mdns_ ## NAME ## _t)0)->base) == ((mdns_ ## SUPER ## _t)0))]
-
-#define MDNS_OBJECT_SUBKIND_DEFINE(NAME)                                                                                                               \
-       static void                                                                                                                                                                     \
-       _mdns_ ## NAME ## _finalize(mdns_ ## NAME ## _t object);                                                                        \
-                                                                                                                                                                                               \
-       static char *                                                                                                                                                           \
-       _mdns_ ## NAME ## _copy_description(mdns_ ## NAME ## _t object, bool debug, bool privacy);      \
-                                                                                                                                                                                               \
-       static const struct mdns_kind_s _mdns_ ## NAME ## _kind = {                                                                     \
-               &_mdns_object_kind,                                                                                                                                             \
-               # NAME,                                                                                                                                                                 \
-               _mdns_ ## NAME ## _copy_description,                                                                                                    \
-               _mdns_ ## NAME ## _finalize                                                                                                                             \
-       };                                                                                                                                                                                      \
-                                                                                                                                                                                               \
-       static mdns_ ## NAME ## _t                                                                                                                                      \
-       _mdns_ ## NAME ## _alloc(void)                                                                                                                          \
-       {                                                                                                                                                                                       \
-               mdns_ ## NAME ## _t obj = mdns_object_ ## NAME ## _alloc(sizeof(*obj));                                 \
-               require_quiet(obj, exit);                                                                                                                               \
-                                                                                                                                                                                               \
-               const mdns_object_t base = (mdns_object_t)obj;                                                                                  \
-               base->kind = &_mdns_ ## NAME ## _kind;                                                                                                  \
-                                                                                                                                                                                               \
-       exit:                                                                                                                                                                           \
-               return obj;                                                                                                                                                             \
-       }                                                                                                                                                                                       \
-       MDNS_BASE_CHECK(NAME, object)
-
-typedef char * (*mdns_copy_description_f)(mdns_any_t object, bool debug, bool privacy);
-typedef void   (*mdns_finalize_f)(mdns_any_t object);
-
-typedef const struct mdns_kind_s *     mdns_kind_t;
-struct mdns_kind_s {
-       mdns_kind_t                             superkind;                      // This kind's superkind.
-       const char *                    name;                           // Name of this kind.
-       mdns_copy_description_f copy_description;       // Creates a textual description of object.
-       mdns_finalize_f                 finalize;                       // Releases object's resources right before the object is freed.
-};
-
-//======================================================================================================================
-// MARK: - mdns_object Kind Definition
-
-struct mdns_object_s {
-       _OS_OBJECT_HEADER(const void *_os_obj_isa, _os_obj_refcnt, _os_obj_xref_cnt);
-       mdns_kind_t     kind;   // Pointer to an object's kind.
-};
-
-static const struct mdns_kind_s _mdns_object_kind = {
-       NULL,           // No superkind.
-       "object",       // Name.
-       NULL,           // No copy_description method.
-       NULL            // No finalize method.
-};
-
-static const void *
-_mdns_cf_collection_callback_retain(CFAllocatorRef allocator, const void *object);
-
-static void
-_mdns_cf_collection_callback_release(CFAllocatorRef allocator, const void *object);
-
-static CFStringRef
-_mdns_cf_collection_callback_copy_description(const void *object);
-
-const CFArrayCallBacks mdns_cfarray_callbacks = {
-       0,                                                                                              // version
-       _mdns_cf_collection_callback_retain,                    // retain
-       _mdns_cf_collection_callback_release,                   // release
-       _mdns_cf_collection_callback_copy_description,  // copy description
-       NULL                                                                                    // equal (NULL for pointer equality)
-};
-
-//======================================================================================================================
-// MARK: - mdns_interface_monitor Kind Definition
-
-struct mdns_interface_monitor_s {
-       struct mdns_object_s                                    base;                                   // Object base.
-       mdns_interface_monitor_t                                next;                                   // Next monitor in list.
-       dispatch_queue_t                                                user_queue;                             // User's queue for invoking handlers.
-       nw_path_evaluator_t                                             path_evaluator;                 // Path evaluator for interface properties.
-       dispatch_source_t                                               update_source;                  // Data source for triggering user's update handler.
-       mdns_interface_monitor_update_handler_t update_handler;                 // User's update handler.
-       mdns_event_handler_t                                    event_handler;                  // User's event handler.
-       char *                                                                  ifname;                                 // Name of monitored interface.
-       uint32_t                                                                ifindex;                                // Index of monitored interface.
-       mdns_interface_flags_t                                  pending_flags;                  // The latest interface flags from path updates.
-       mdns_interface_flags_t                                  flags;                                  // The current interface flags made known to user.
-       bool                                                                    user_activated;                 // True if user called activate method.
-       bool                                                                    activated;                              // True if the monitor has been activated.
-       bool                                                                    invalidated;                    // True if the monitor has been invalidated.
-       bool                                                                    path_evaluator_started; // True if the path evaluator has been started.
-};
-
-MDNS_OBJECT_SUBKIND_DEFINE(interface_monitor);
-
-//======================================================================================================================
-// MARK: - Local Prototypes
-
-static dispatch_queue_t
-_mdns_internal_queue(void);
-
-static dispatch_queue_t
-_mdns_nwi_state_mutex_queue(void);
-
-static void
-_mdns_interface_monitor_activate_async(mdns_interface_monitor_t monitor);
-
-static void
-_mdns_interface_monitor_terminate(mdns_interface_monitor_t me, const OSStatus error);
-
-static mdns_interface_flags_t
-_mdns_get_interface_flags_from_nw_path(nw_path_t path, mdns_interface_flags_t current_flags);
-
-static mdns_interface_flags_t
-_mdns_get_interface_flags_from_nwi_state(const char *ifname, mdns_interface_flags_t current_flags);
-
-static int
-_mdns_snprintf_add(char **ptr, const char *lim, const char *fmt, ...);
-
-static void
-_mdns_start_nwi_state_monitoring(void);
-
-#if !defined(nw_forget)
-       #define nw_forget(X)    ForgetCustom(X, nw_release)
-#endif
-
-#if !defined(nw_release_null_safe)
-       #define nw_release_null_safe(X) do { if (X) { nw_release(X); } } while (0)
-#endif
-
-#if !defined(nwi_state_release_null_safe)
-       #define nwi_state_release_null_safe(X)  do { if (X) { nwi_state_release(X); } } while (0)
-#endif
-
-//======================================================================================================================
-// MARK: - Globals
-
-static mdns_interface_monitor_t        g_monitor_list  = NULL;
-static nwi_state_t                             g_nwi_state             = NULL;
-
-//======================================================================================================================
-// MARK: - Internals
-
-static dispatch_queue_t
-_mdns_internal_queue(void)
-{
-       static dispatch_once_t  s_once  = 0;
-       static dispatch_queue_t s_queue = NULL;
-       dispatch_once(&s_once,
-       ^{
-               s_queue = dispatch_queue_create("com.apple.mdns.internal_queue", DISPATCH_QUEUE_SERIAL);
-       });
-       return s_queue; 
-}
-
-//======================================================================================================================
-
-static dispatch_queue_t
-_mdns_nwi_state_mutex_queue(void)
-{
-       static dispatch_once_t  s_once  = 0;
-       static dispatch_queue_t s_queue = NULL;
-       dispatch_once(&s_once,
-       ^{
-               s_queue = dispatch_queue_create("com.apple.mdns.nwi_state_mutex", DISPATCH_QUEUE_SERIAL);
-       });
-       return s_queue;
-}
-
-//======================================================================================================================
-
-#define MDNS_LOG_CATEGORY_DEFINE(SHORT_NAME, CATEGORY_STR)                     \
-       static os_log_t                                                                                                 \
-       _mdns_ ## SHORT_NAME ## _log(void)                                                              \
-       {                                                                                                                               \
-               static dispatch_once_t  s_once  = 0;                                            \
-               static os_log_t                 s_log   = NULL;                                         \
-               dispatch_once(&s_once,                                                                          \
-               ^{                                                                                                                      \
-                       s_log = os_log_create("com.apple.mdns", CATEGORY_STR);  \
-               });                                                                                                                     \
-               return s_log;                                                                                           \
-       }                                                                                                                               \
-       extern int _mdns_dummy_variable
-
-MDNS_LOG_CATEGORY_DEFINE(ifmon, "interface_monitor");
-MDNS_LOG_CATEGORY_DEFINE(nwi,   "NWI");
-
-//======================================================================================================================
-// MARK: - mdns_object Public Methods
-
-void
-mdns_retain(mdns_any_t object)
-{
-       os_retain(object.base);
-}
-
-//======================================================================================================================
-
-void
-mdns_release(mdns_any_t object)
-{
-       os_release(object.base);
-}
-
-//======================================================================================================================
-
-char *
-mdns_copy_description(mdns_any_t object)
-{
-       return mdns_object_copy_description(object, false, false);
-}
-
-//======================================================================================================================
-// MARK: - mdns_object Private Methods
-
-char *
-mdns_object_copy_description(mdns_any_t object, bool debug, bool privacy)
-{
-       for (mdns_kind_t kind = object.base->kind; kind; kind = kind->superkind) {
-               if (kind->copy_description) {
-                       return kind->copy_description(object, debug, privacy);
-               }
-       }
-       return NULL;
-}
-
-//======================================================================================================================
-
-CFStringRef
-mdns_object_copy_description_as_cfstring(mdns_any_t object, bool debug, bool privacy)
-{
-       CFStringRef description = NULL;
-       char *cstring = mdns_object_copy_description(object, debug, privacy);
-       require_quiet(cstring, exit);
-
-       description = CFStringCreateWithCStringNoCopy(NULL, cstring, kCFStringEncodingUTF8, kCFAllocatorMalloc);
-       require_quiet(description, exit);
-       cstring = NULL;
-
-exit:
-       FreeNullSafe(cstring);
-       return description;
-}
-
-//======================================================================================================================
-
-void
-mdns_object_finalize(mdns_any_t object)
-{
-       for (mdns_kind_t kind = object.base->kind; kind; kind = kind->superkind) {
-               if (kind->finalize) {
-                       kind->finalize(object);
-               }
-       }
-}
-
-//======================================================================================================================
-
-static const void *
-_mdns_cf_collection_callback_retain(__unused CFAllocatorRef allocator, const void *object)
-{
-       mdns_retain((mdns_object_t)object);
-       return object;
-}
-
-//======================================================================================================================
-
-static void
-_mdns_cf_collection_callback_release(__unused CFAllocatorRef allocator, const void *object)
-{
-       mdns_release((mdns_object_t)object);
-}
-
-//======================================================================================================================
-
-static CFStringRef
-_mdns_cf_collection_callback_copy_description(const void *object)
-{
-       return mdns_object_copy_description_as_cfstring((mdns_object_t)object, false, false);
-}
-
-//======================================================================================================================
-// MARK: - mdns_interface_monitor Public Methods
-
-mdns_interface_monitor_t
-mdns_interface_monitor_create(uint32_t interface_index)
-{
-       mdns_interface_monitor_t        monitor         = NULL;
-       nw_interface_t                          interface       = NULL;
-       nw_parameters_t                         params          = NULL;
-
-       mdns_interface_monitor_t obj = _mdns_interface_monitor_alloc();
-       require_quiet(obj, exit);
-
-       obj->ifindex = interface_index;
-       char ifname[IF_NAMESIZE + 1];
-       if (if_indextoname(obj->ifindex, ifname) == NULL) {
-               os_log_error(_mdns_ifmon_log(), "if_indextoname returned NULL for index %u", obj->ifindex);
-               goto exit;
-       }
-       obj->ifname = strdup(ifname);
-       require_quiet(obj->ifname, exit);
-
-       interface = nw_interface_create_with_index(obj->ifindex);
-       if (!interface) {
-               os_log_error(_mdns_ifmon_log(), "nw_interface_create_with_index returned NULL for index %u", obj->ifindex);
-               goto exit;
-       }
-
-       params = nw_parameters_create();
-       require_quiet(params, exit);
-
-       nw_parameters_require_interface(params, interface);
-       obj->path_evaluator = nw_path_create_evaluator_for_endpoint(NULL, params);
-       if (!obj->path_evaluator) {
-               os_log_error(_mdns_ifmon_log(), "nw_path_create_evaluator_for_endpoint returned NULL for params: %@", params);
-               goto exit;
-       }
-
-       nw_path_t path = nw_path_evaluator_copy_path(obj->path_evaluator);
-       require_quiet(path, exit);
-
-       obj->pending_flags = _mdns_get_interface_flags_from_nw_path(path, mdns_interface_flag_null);
-       obj->pending_flags = _mdns_get_interface_flags_from_nwi_state(obj->ifname, obj->pending_flags);
-       obj->flags = obj->pending_flags;
-       nw_forget(&path);
-
-       monitor = obj;
-       obj = NULL;
-
-exit:
-       if (obj) {
-               mdns_release(obj);
-       }
-       nw_release_null_safe(interface);
-       nw_release_null_safe(params);
-       return monitor;
-}
-
-//======================================================================================================================
-
-void
-mdns_interface_monitor_activate(mdns_interface_monitor_t me)
-{
-       if (!me->user_activated) {
-               if (me->user_queue) {
-                       _mdns_interface_monitor_activate_async(me);
-               }
-               me->user_activated = true;
-       }
-}
-
-//======================================================================================================================
-
-void
-mdns_interface_monitor_invalidate(mdns_interface_monitor_t me)
-{
-       mdns_retain(me);
-       dispatch_async(_mdns_internal_queue(),
-       ^{
-               if (!me->invalidated) {
-                       _mdns_interface_monitor_terminate(me, kNoErr);
-                       me->invalidated = true;
-               }
-               mdns_release(me);
-       });
-}
-
-//======================================================================================================================
-
-void
-mdns_interface_monitor_set_queue(mdns_interface_monitor_t me, dispatch_queue_t queue)
-{
-       if (!me->user_activated) {
-               dispatch_retain(queue);
-               dispatch_release_null_safe(me->user_queue);
-               me->user_queue = queue;
-       } else if (!me->user_queue) {
-               me->user_queue = queue;
-               dispatch_retain(me->user_queue);
-               _mdns_interface_monitor_activate_async(me);
-       }
-}
-
-//======================================================================================================================
-
-void
-mdns_interface_monitor_set_event_handler(mdns_interface_monitor_t me, mdns_event_handler_t handler)
-{
-       mdns_event_handler_t const new_handler = handler ? Block_copy(handler) : NULL;
-       if (me->event_handler) {
-               Block_release(me->event_handler);
-       }
-       me->event_handler = new_handler;
-}
-
-//======================================================================================================================
-
-void
-mdns_interface_monitor_set_update_handler(mdns_interface_monitor_t me, mdns_interface_monitor_update_handler_t handler)
-{
-       mdns_interface_monitor_update_handler_t const new_handler = handler ? Block_copy(handler) : NULL;
-       if (me->update_handler) {
-               Block_release(me->update_handler);
-       }
-       me->update_handler = new_handler;
-}
-
-//======================================================================================================================
-
-uint32_t
-mdns_interface_monitor_get_interface_index(mdns_interface_monitor_t me)
-{
-       return me->ifindex;
-}
-
-//======================================================================================================================
-
-bool
-mdns_interface_monitor_has_ipv4_connectivity(mdns_interface_monitor_t me)
-{
-       return ((me->flags & mdns_interface_flag_ipv4_connectivity) ? true : false);
-}
-
-//======================================================================================================================
-
-bool
-mdns_interface_monitor_has_ipv6_connectivity(mdns_interface_monitor_t me)
-{
-       return ((me->flags & mdns_interface_flag_ipv6_connectivity) ? true : false);
-}
-
-//======================================================================================================================
-
-bool
-mdns_interface_monitor_is_expensive(mdns_interface_monitor_t me)
-{
-       return ((me->flags & mdns_interface_flag_expensive) ? true : false);
-}
-
-//======================================================================================================================
-
-bool
-mdns_interface_monitor_is_constrained(mdns_interface_monitor_t me)
-{
-       return ((me->flags & mdns_interface_flag_constrained) ? true : false);
-}
-
-//======================================================================================================================
-
-bool
-mdns_interface_monitor_is_clat46(mdns_interface_monitor_t me)
-{
-       return ((me->flags & mdns_interface_flag_clat46) ? true : false);
-}
-
-//======================================================================================================================
-// MARK: - mdns_interface_monitor Private Methods
-
-typedef struct {
-       mdns_interface_flags_t  flag;
-       const char *                    desc;
-} mdns_interface_flag_description_t;
-
-const mdns_interface_flag_description_t        mdns_interface_flag_descriptions[] = {
-       { mdns_interface_flag_ipv4_connectivity, "IPv4" },
-       { mdns_interface_flag_ipv6_connectivity, "IPv6" },
-       { mdns_interface_flag_expensive,         "expensive" },
-       { mdns_interface_flag_constrained,       "constrained" },
-       { mdns_interface_flag_clat46,            "CLAT46" }
-};
-
-static char *
-_mdns_interface_monitor_copy_description(mdns_interface_monitor_t me, const bool debug, __unused const bool privacy)
-{
-       char *                          description     = NULL;
-       char                            buffer[128];
-       char *                          dst                     = buffer;
-       const char * const      lim                     = &buffer[countof(buffer)];
-       int                                     n;
-
-       *dst = '\0';
-       if (debug) {
-               n = _mdns_snprintf_add(&dst, lim, "mdns_%s (%p): ", me->base.kind->name, me);
-               require_quiet(n >= 0, exit);
-       }
-       n = _mdns_snprintf_add(&dst, lim, "interface %s (%u): ", me->ifname, me->ifindex);
-       require_quiet(n >= 0, exit);
-
-       const char *separator = "";
-       for (size_t i = 0; i < countof(mdns_interface_flag_descriptions); ++i) {
-               const mdns_interface_flag_description_t * const flag_desc = &mdns_interface_flag_descriptions[i];
-               if (me->flags & flag_desc->flag) {
-                       n = _mdns_snprintf_add(&dst, lim, "%s%s", separator, flag_desc->desc);
-                       require_quiet(n >= 0, exit);
-                       separator = ", ";
-               }
-       }
-       description = strdup(buffer);
-
-exit:
-       return description;
-}
-
-//======================================================================================================================
-
-static void
-_mdns_interface_monitor_finalize(mdns_interface_monitor_t me)
-{
-       dispatch_forget(&me->user_queue);
-       nw_forget(&me->path_evaluator);
-       BlockForget(&me->update_handler);
-       BlockForget(&me->event_handler);
-       ForgetMem(&me->ifname);
-}
-
-//======================================================================================================================
-
-static void
-_mdns_interface_monitor_activate_internal(mdns_interface_monitor_t monitor);
-
-static void
-_mdns_interface_monitor_activate_async(mdns_interface_monitor_t me)
-{
-       mdns_retain(me);
-       dispatch_async(_mdns_internal_queue(),
-       ^{
-               _mdns_interface_monitor_activate_internal(me);
-               mdns_release(me);               
-       });
-}
-
-static void
-_mdns_interface_monitor_activate_internal(mdns_interface_monitor_t me)
-{
-       OSStatus err;
-       require_action_quiet(!me->activated && !me->invalidated, exit, err = kNoErr);
-       me->activated = true;
-
-       me->update_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_REPLACE, 0, 0, me->user_queue);
-       require_action_quiet(me->update_source, exit, err = kNoResourcesErr);
-
-       mdns_retain(me);
-       const dispatch_source_t update_source = me->update_source;
-       dispatch_source_set_event_handler(me->update_source,
-       ^{
-               const unsigned long data = dispatch_source_get_data(update_source);
-               const mdns_interface_flags_t new_flags = ((mdns_interface_flags_t)data) & ~mdns_interface_flag_reserved;
-               const mdns_interface_flags_t changed_flags = me->flags ^ new_flags;
-               if (changed_flags != 0) {
-                       me->flags = new_flags;
-                       if (me->update_handler) {
-                               me->update_handler(changed_flags);
-                       }
-               }
-       });
-       dispatch_source_set_cancel_handler(me->update_source,
-       ^{
-               mdns_release(me);
-       });
-       dispatch_activate(me->update_source);
-
-       mdns_retain(me);
-       nw_path_evaluator_set_update_handler(me->path_evaluator, _mdns_internal_queue(),
-       ^(nw_path_t path)
-       {
-               const mdns_interface_flags_t new_flags = _mdns_get_interface_flags_from_nw_path(path, me->pending_flags);
-               if (new_flags != me->pending_flags) {
-                       me->pending_flags = new_flags;
-                       if (me->update_source) {
-                               // Note: mdns_interface_flag_reserved is used to ensure that the data is non-zero. According to the
-                               // dispatch_source_create(3) man page, if the data value is zero, the source handler won't be invoked.
-                               dispatch_source_merge_data(me->update_source, me->pending_flags | mdns_interface_flag_reserved);
-                       }
-               }
-       });
-       nw_path_evaluator_set_cancel_handler(me->path_evaluator,
-       ^{
-               mdns_release(me);
-       });
-       nw_path_evaluator_start(me->path_evaluator);
-       me->path_evaluator_started = true;
-
-       mdns_interface_monitor_t *p = &g_monitor_list;
-       while (*p != NULL) {
-               p = &(*p)->next;
-       }
-       mdns_retain(me);
-       *p = me;
-
-       // This is called after adding the monitor to the global list to ensure that the initial NWI state check is aware
-       // that the interface monitor exists.
-       _mdns_start_nwi_state_monitoring();
-       err = kNoErr;
-
-exit:
-       if (err) {
-               _mdns_interface_monitor_terminate(me, err);
-       }
-}
-
-//======================================================================================================================
-
-static void
-_mdns_interface_monitor_terminate(mdns_interface_monitor_t me, const OSStatus error)
-{
-       dispatch_source_forget(&me->update_source);
-       if (me->path_evaluator) {
-               if (me->path_evaluator_started) {
-                       nw_path_evaluator_cancel(me->path_evaluator);
-               }
-               nw_forget(&me->path_evaluator);
-       }
-       for (mdns_interface_monitor_t *p = &g_monitor_list; *p; p = &(*p)->next) {
-               if (*p == me) {
-                       *p = me->next;
-                       me->next = NULL;
-                       mdns_release(me);
-                       break;
-               }
-       }
-       mdns_retain(me);
-       dispatch_async(me->user_queue,
-       ^{
-               if (me->event_handler) {
-                       me->event_handler(error ? mdns_event_error : mdns_event_invalidated, error);
-               }
-               mdns_release(me);
-       });
-}
-
-//======================================================================================================================
-// MARK: - NW Path Helpers
-
-#define MDNS_INTERFACE_FLAGS_FROM_NWPATH               \
-       (mdns_interface_flag_ipv4_connectivity |        \
-        mdns_interface_flag_ipv6_connectivity |        \
-        mdns_interface_flag_expensive         |        \
-        mdns_interface_flag_constrained)
-
-static mdns_interface_flags_t
-_mdns_get_interface_flags_from_nw_path(nw_path_t path, mdns_interface_flags_t current_flags)
-{
-       mdns_interface_flags_t flags = current_flags & ~MDNS_INTERFACE_FLAGS_FROM_NWPATH;
-       if (nw_path_has_ipv4(path)) {
-               flags |= mdns_interface_flag_ipv4_connectivity;
-       }
-       if (nw_path_has_ipv6(path)) {
-               flags |= mdns_interface_flag_ipv6_connectivity;
-       }
-       if (nw_path_is_expensive(path)) {
-               flags |= mdns_interface_flag_expensive;
-       }
-       if (__builtin_available(macOS 10.15, *)) {
-               if (nw_path_is_constrained(path)) {
-                       flags |= mdns_interface_flag_constrained;
-               }
-       }
-       return flags;
-}
-
-//======================================================================================================================
-// MARK: - NWI Helpers
-
-#if !defined(NWI_IFSTATE_FLAGS_HAS_CLAT46)
-       #define NWI_IFSTATE_FLAGS_HAS_CLAT46    0x0040
-#endif
-
-#define MDNS_INTERFACE_FLAGS_FROM_NWI_STATE            mdns_interface_flag_clat46
-
-static mdns_interface_flags_t
-_mdns_get_interface_flags_from_nwi_state(const char *ifname, mdns_interface_flags_t current_flags)
-{
-       __block nwi_ifstate_flags ifstate_flags = 0;
-       dispatch_sync(_mdns_nwi_state_mutex_queue(),
-       ^{
-               if (g_nwi_state) {
-                       const nwi_ifstate_t ifstate = nwi_state_get_ifstate(g_nwi_state, ifname);
-                       if (ifstate) {
-                               ifstate_flags = nwi_ifstate_get_flags(ifstate);
-                       }
-               }
-       });
-       mdns_interface_flags_t flags = current_flags & ~MDNS_INTERFACE_FLAGS_FROM_NWI_STATE;
-       if (ifstate_flags & NWI_IFSTATE_FLAGS_HAS_CLAT46) {
-               flags |= mdns_interface_flag_clat46;
-       }
-       return flags;
-}
-
-//======================================================================================================================
-
-static void
-_mdns_nwi_state_update(void);
-
-static void
-_mdns_start_nwi_state_monitoring(void)
-{
-       static int s_nwi_notify_token = NOTIFY_TOKEN_INVALID;
-       if (s_nwi_notify_token == NOTIFY_TOKEN_INVALID) {
-               const uint32_t status = notify_register_dispatch(nwi_state_get_notify_key(), &s_nwi_notify_token,
-                       _mdns_internal_queue(),
-               ^(__unused int token)
-               {
-                       _mdns_nwi_state_update();
-               });
-               if (s_nwi_notify_token == NOTIFY_TOKEN_INVALID) {
-                       os_log_error(_mdns_nwi_log(), "Failed to register for NWI state notifications (status %u)", status);
-               } else {
-                       _mdns_nwi_state_update();
-               }
-       }
-}
-
-static void
-_mdns_nwi_state_update(void)
-{
-       nwi_state_t new_state = nwi_state_copy();
-       if (!new_state) {
-               os_log_error(_mdns_nwi_log(), "Failed to copy NWI state");
-       }
-       __block nwi_state_t old_state;
-       dispatch_sync(_mdns_nwi_state_mutex_queue(),
-       ^{
-               old_state       = g_nwi_state;
-               g_nwi_state     = new_state;
-       });
-       nwi_state_release_null_safe(old_state);
-       for (mdns_interface_monitor_t m = g_monitor_list; m; m = m->next) {
-               const mdns_interface_flags_t new_flags = _mdns_get_interface_flags_from_nwi_state(m->ifname, m->pending_flags);
-               if (new_flags != m->pending_flags) {
-                       m->pending_flags = new_flags;
-                       if (m->update_source) {
-                               // Note: mdns_interface_flag_reserved is used to ensure that the data is non-zero. According to the
-                               // dispatch_source_create(3) man page, if the data value is zero, the source handler won't be invoked.
-                               dispatch_source_merge_data(m->update_source, m->pending_flags | mdns_interface_flag_reserved);
-                       }
-               }
-       }
-}
-
-//======================================================================================================================
-// MARK: - General Helpers
-
-static int
-_mdns_snprintf_add(char **ptr, const char *lim, const char *fmt, ...)
-{
-       char * const    dst = *ptr;
-       const size_t    len = (size_t)(lim - dst);
-       int                             n;
-
-       require_action_quiet(len > 0, exit, n = 0);
-
-       va_list args;
-       va_start(args, fmt);
-       n = vsnprintf(dst, len, fmt, args);
-       va_end(args);
-       require_quiet(n >= 0, exit);
-
-       if (((size_t)n) > len) {
-               n = (int)len;
-       }
-       *ptr = dst + n;
-
-exit:
-       return n;
-}
diff --git a/mDNSMacOSX/mdns_object.h b/mDNSMacOSX/mdns_object.h
deleted file mode 100644 (file)
index cb3301e..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (c) 2019 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __MDNS_OBJECT_H__
-#define __MDNS_OBJECT_H__
-
-#include "mdns_private.h"
-
-//======================================================================================================================
-// MARK: - mdns_object Private Method Declarations
-
-char *
-mdns_object_copy_description(mdns_any_t object, bool debug, bool privacy);
-
-CFStringRef
-mdns_object_copy_description_as_cfstring(mdns_any_t object, bool debug, bool privacy);
-
-void
-mdns_object_finalize(mdns_any_t object);
-
-#define MDNS_OBJECT_ALLOC_DECLARE(NAME)                \
-       mdns_ ## NAME ## _t                                             \
-       mdns_object_ ## NAME ## _alloc(size_t size)
-
-MDNS_OBJECT_ALLOC_DECLARE(interface_monitor);
-
-#endif // __MDNS_OBJECT_H__
diff --git a/mDNSMacOSX/mdns_object.m b/mDNSMacOSX/mdns_object.m
deleted file mode 100644 (file)
index bdfe665..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) 2019 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#import "mdns_object.h"
-
-#import <CoreUtils/CoreUtils.h>
-#import <Foundation/Foundation.h>
-#import <os/object_private.h>
-
-//======================================================================================================================
-// MARK: - Class Declarations
-
-#define MDNS_OBJECT_CLASS_DECLARE(NAME)                                                                \
-       _OS_OBJECT_DECL_SUBCLASS_INTERFACE(mdns_ ## NAME, mdns_object)  \
-       extern int _mdns_dummy_variable
-
-_OS_OBJECT_DECL_SUBCLASS_INTERFACE(mdns_object, object)
-
-MDNS_OBJECT_CLASS_DECLARE(interface_monitor);
-
-//======================================================================================================================
-// MARK: - Class Definitions
-
-@implementation OS_OBJECT_CLASS(mdns_object)
-- (void)dealloc
-{
-       mdns_object_finalize(self);
-       arc_safe_super_dealloc();
-}
-
-- (NSString *)description
-{
-       return arc_safe_autorelease((NSString *)mdns_object_copy_description_as_cfstring(self, false, false));
-}
-
-- (NSString *)debugDescription
-{
-       return arc_safe_autorelease((NSString *)mdns_object_copy_description_as_cfstring(self, true, false));
-}
-
-- (NSString *)redactedDescription
-{
-       return arc_safe_autorelease((NSString *)mdns_object_copy_description_as_cfstring(self, false, true));
-}
-@end
-
-#define MDNS_CLASS(NAME)       OS_OBJECT_CLASS(mdns_ ## NAME)
-#define MDNS_OBJECT_CLASS_DEFINE(NAME)                                                                                         \
-       @implementation MDNS_CLASS(NAME)                                                                                                \
-       @end                                                                                                                                                    \
-                                                                                                                                                                       \
-       mdns_ ## NAME ## _t                                                                                                                             \
-       mdns_object_ ## NAME ## _alloc(const size_t size)                                                               \
-       {                                                                                                                                                               \
-               return (mdns_## NAME ##_t)_os_object_alloc([MDNS_CLASS(NAME) class], size);     \
-       }                                                                                                                                                               \
-       extern int _mdns_dummy_variable
-
-MDNS_OBJECT_CLASS_DEFINE(interface_monitor);
diff --git a/mDNSMacOSX/mdns_objects/log_mdns.m b/mDNSMacOSX/mdns_objects/log_mdns.m
new file mode 100644 (file)
index 0000000..5b4840b
--- /dev/null
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "DNSMessage.h"
+
+#import <CoreUtils/CoreUtils.h>
+#import <Foundation/Foundation.h>
+#import <os/log_private.h>
+
+#if !COMPILER_ARC
+       #error "This file must be compiled with ARC."
+#endif
+
+//======================================================================================================================
+// MARK: - Specifiers
+//
+//     Data Type                               Specifier                                                       Arguments                                                       Notes
+//     DNS message                             %{mdns:dnsmsg}.*P                                       length (int), pointer (void *)          -
+//     DNS message header              %{mdns:dnshdr}.*P                                       length (int), pointer (void *)          -
+//     Error code integer              %{mdns:err}d, %{mdns:err}ld, etc.       error code (int, OSStatus, etc.)        1
+//     DNS record data                 %{mdns:rd.<record type>}.*P                     length (int), pointer (void *)          2,3,4
+//     DNS record type                 %{mdns:rrtype}d                                         record type (int)
+//     
+//     Notes:
+//     1. Formatting is handled by NSPrintTypedObject() from the CoreUtils framework, which handles a large variety of
+//        error codes, including errno and kDNSServiceErr_* error codes.
+//     2. The DNS record data must be fully expanded, i.e., it must not contain any compressed DNS domain names.
+//     3. The <record type> portion of the specifier is a case-insensitive DNS record type mnemonic, e.g., A, AAAA,
+//        DNSKEY, DS, etc.
+//     4. Formatting is handled by DNSRecordDataToString(), which currently only handles a subset of record types.
+//        This subset consists of the record types most commonly encountered by mDNSResponder. This subset may grow
+//        as needed.
+//     
+//     The specifiers for DNSSEC record data, %{mdns:dnskey}, %{mdns:ds}, %{mdns:nsec}, %{mdns:nsec3}, and
+//     %{mdns:rrsig}, have been deprecated in favor of the %{mdns:rd.<record type>} style of record data specifiers.
+
+#define LOG_MDNS_SPECIFIER_DNS_MESSAGE                 "dnsmsg"
+#define LOG_MDNS_SPECIFIER_DNS_MESSAGE_HEADER  "dnshdr"
+#define LOG_MDNS_SPECIFIER_ERROR                               "err"
+#define LOG_MDNS_SPECIFIER_RDATA_PREFIX                        "rd."
+#define LOG_MDNS_SPECIFIER_DNS_RECORD_TYPE             "rrtype"
+
+//======================================================================================================================
+// MARK: - Data Structures
+
+typedef NSAttributedString *
+(*log_mdns_data_formatter_f)(NSData *data);
+
+typedef struct {
+       const char *                            specifier;
+       log_mdns_data_formatter_f       formatter;
+} log_mdns_data_formatter_item_t;
+
+typedef NSAttributedString *
+(*log_mdns_number_formatter_f)(NSNumber *number);
+
+typedef struct {
+       const char *                            specifier;
+       log_mdns_number_formatter_f     formatter;
+} log_mdns_number_formatter_item_t;
+
+//======================================================================================================================
+// MARK: - Local Prototypes
+
+static NSAttributedString *
+_log_mdns_format_dns_message(NSData *data);
+
+static NSAttributedString *
+_log_mdns_format_dns_message_header(NSData *data);
+
+static NSAttributedString *
+_log_mdns_format_record_data(NSData *data, int record_type);
+
+static NSAttributedString *
+_log_mdns_format_err(NSNumber *number);
+
+static NSAttributedString *
+_log_mdns_format_dns_record_type(NSNumber *number);
+
+static NSAttributedString *
+_log_mdns_format_dns_message_ex(NSData *data, DNSMessageToStringFlags extra_flags);
+
+//======================================================================================================================
+// MARK: - DNS Record Data Formatters
+
+#define LOG_MDNS_DEFINE_RDATA_FORMATTER(RECORD_TYPE)                                                           \
+       static NSAttributedString *                                                                                                             \
+       _log_mdns_format_record_data_ ## RECORD_TYPE (NSData * const rdata)                             \
+       {                                                                                                                                                               \
+               return _log_mdns_format_record_data(rdata, kDNSRecordType_ ## RECORD_TYPE);     \
+       }                                                                                                                                                               \
+       extern int _log_mdns_dummy_variable
+
+LOG_MDNS_DEFINE_RDATA_FORMATTER(DNSKEY);
+LOG_MDNS_DEFINE_RDATA_FORMATTER(DS);
+LOG_MDNS_DEFINE_RDATA_FORMATTER(NSEC);
+LOG_MDNS_DEFINE_RDATA_FORMATTER(NSEC3);
+LOG_MDNS_DEFINE_RDATA_FORMATTER(RRSIG);
+
+//======================================================================================================================
+// MARK: - External Functions
+
+#define LOG_MDNS_RDATA_FORMATTER_ITEM(RECORD_TYPE)     { # RECORD_TYPE, _log_mdns_format_record_data_ ## RECORD_TYPE }
+
+NSAttributedString *
+OSLogCopyFormattedString(const char * const specifier, const id arg, __unused const os_log_type_info_t info)
+{
+       if ([arg isKindOfClass:[NSData class]]) {
+               NSData * const data = (NSData *)arg;
+               if (stricmp_prefix(specifier, LOG_MDNS_SPECIFIER_RDATA_PREFIX) == 0) {
+                       const char * const type_str = specifier + sizeof_string(LOG_MDNS_SPECIFIER_RDATA_PREFIX);
+                       const int type_value = DNSRecordTypeStringToValue(type_str);
+                       if (type_value != 0) {
+                               return _log_mdns_format_record_data(data, type_value);
+                       }
+               } else {
+                       const log_mdns_data_formatter_item_t log_mdns_data_formatter_table[] = {
+                               { LOG_MDNS_SPECIFIER_DNS_MESSAGE,                _log_mdns_format_dns_message },
+                               { LOG_MDNS_SPECIFIER_DNS_MESSAGE_HEADER, _log_mdns_format_dns_message_header },
+                               LOG_MDNS_RDATA_FORMATTER_ITEM(DNSKEY),
+                               LOG_MDNS_RDATA_FORMATTER_ITEM(DS),
+                               LOG_MDNS_RDATA_FORMATTER_ITEM(NSEC),
+                               LOG_MDNS_RDATA_FORMATTER_ITEM(NSEC3),
+                               LOG_MDNS_RDATA_FORMATTER_ITEM(RRSIG)
+                       };
+                       for (size_t i = 0; i < countof(log_mdns_data_formatter_table); ++i) {
+                               const log_mdns_data_formatter_item_t * const item = &log_mdns_data_formatter_table[i];
+                               if (strcasecmp(specifier, item->specifier) == 0) {
+                                       return item->formatter(data);
+                               }
+                       }
+               }
+       } else if ([arg isKindOfClass:[NSNumber class]]) {
+               const log_mdns_number_formatter_item_t log_mdns_number_formatter_table[] = {
+                       { LOG_MDNS_SPECIFIER_ERROR,                             _log_mdns_format_err },
+                       { LOG_MDNS_SPECIFIER_DNS_RECORD_TYPE,   _log_mdns_format_dns_record_type }
+               };
+               NSNumber * const number = (NSNumber *)arg;
+               for (size_t i = 0; i < countof(log_mdns_number_formatter_table); ++i) {
+                       const log_mdns_number_formatter_item_t * const item = &log_mdns_number_formatter_table[i];
+                       if (strcasecmp(specifier, item->specifier) == 0) {
+                               return item->formatter(number);
+                       }
+               }
+       }
+       return nil;
+}
+
+//======================================================================================================================
+// MARK: - Internal Functions
+
+static NSAttributedString *
+_log_mdns_format_dns_message(NSData * const data)
+{
+       return _log_mdns_format_dns_message_ex(data, kDNSMessageToStringFlag_Null);
+}
+
+//======================================================================================================================
+
+static NSAttributedString *
+_log_mdns_format_dns_message_header(NSData * const data)
+{
+       return _log_mdns_format_dns_message_ex(data, kDNSMessageToStringFlag_HeaderOnly);
+}
+
+//======================================================================================================================
+
+static NSAttributedString *
+_log_mdns_format_err(NSNumber * const number)
+{
+       return NSPrintTypedObject("err", number, NULL);
+}
+
+//======================================================================================================================
+
+static NSAttributedString *
+_log_mdns_format_dns_record_type(NSNumber * const number)
+{
+       unsigned long long value = [number unsignedLongLongValue];
+       require_return_value(value <= INT_MAX, nil);
+
+       NSString *nsstr;
+       const char *cstr = DNSRecordTypeValueToString((int)value);
+       if (cstr) {
+               nsstr = [[NSString alloc] initWithFormat:@"%s", cstr];
+               require_return_value(nsstr, nil);
+       } else {
+               nsstr = [[NSString alloc] initWithFormat:@"TYPE%u", (unsigned int)value];
+               require_return_value(nsstr, nil);
+       }
+       return [[NSAttributedString alloc] initWithString:nsstr];
+}
+
+//======================================================================================================================
+
+static NSAttributedString *
+_log_mdns_format_record_data(NSData * const rdata, const int record_type)
+{
+       char *cstr = NULL;
+       DNSRecordDataToString(rdata.bytes, rdata.length, record_type, &cstr);
+       if (!cstr) {
+               return nil;
+       }
+       const size_t len = strlen(cstr);
+       NSString * const nsstr = [[NSString alloc] initWithBytesNoCopy:cstr length:len encoding:NSUTF8StringEncoding
+               freeWhenDone:YES];
+       if (!nsstr) {
+               ForgetMem(&cstr);
+               return nil;
+       }
+       return [[NSAttributedString alloc] initWithString:nsstr];
+}
+
+//======================================================================================================================
+
+static NSAttributedString *
+_log_mdns_format_dns_message_ex(NSData * const data, const DNSMessageToStringFlags extra_flags)
+{
+       char *msg_cstr = NULL;
+       const DNSMessageToStringFlags flags = kDNSMessageToStringFlag_OneLine | extra_flags;
+       DNSMessageToString(data.bytes, data.length, flags, &msg_cstr);
+       require_return_value(msg_cstr, nil);
+
+       NSString *msg_nsstr = [[NSString alloc] initWithBytesNoCopy:msg_cstr length:strlen(msg_cstr)
+               encoding:NSUTF8StringEncoding freeWhenDone:YES];
+       require_return_value_action(msg_nsstr, nil, ForgetMem(&msg_cstr));
+
+       return [[NSAttributedString alloc] initWithString:msg_nsstr];
+}
diff --git a/mDNSMacOSX/mdns_objects/mdns_address.c b/mDNSMacOSX/mdns_objects/mdns_address.c
new file mode 100644 (file)
index 0000000..e37c586
--- /dev/null
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2019 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mdns_address.h"
+
+#include "mdns_helpers.h"
+#include "mdns_internal.h"
+#include "mdns_objects.h"
+
+#include <CoreUtils/CoreUtils.h>
+
+//======================================================================================================================
+// MARK: - Address Kind Definition
+
+struct mdns_address_s {
+       struct mdns_object_s    base;           // Object base.
+       sockaddr_ip                             sip;            // Underlying sockaddr structure.
+       char *                                  if_name;        // Interface name of IPv6 scope ID.
+};
+
+MDNS_OBJECT_SUBKIND_DEFINE_FULL(address);
+
+//======================================================================================================================
+// MARK: - Internals
+
+MDNS_LOG_CATEGORY_DEFINE(address, "address");
+
+//======================================================================================================================
+// MARK: - Address Public Methods
+
+mdns_address_t
+mdns_address_create_ipv4(uint32_t addr, uint16_t port)
+{
+       mdns_address_t obj = _mdns_address_alloc();
+       require_quiet(obj, exit);
+
+       struct sockaddr_in * const sa = &obj->sip.v4;
+       SIN_LEN_SET(sa);
+       sa->sin_family          = AF_INET;
+       sa->sin_port            = htons(port);
+       sa->sin_addr.s_addr     = htonl(addr);
+
+exit:
+       return obj;
+}
+
+//======================================================================================================================
+
+mdns_address_t
+mdns_address_create_ipv6(const uint8_t addr[static 16], uint16_t port, uint32_t scope_id)
+{
+       mdns_address_t obj = _mdns_address_alloc();
+       require_quiet(obj, exit);
+
+       struct sockaddr_in6 * const sa = &obj->sip.v6;
+       SIN6_LEN_SET(sa);
+       sa->sin6_family         = AF_INET6;
+       sa->sin6_port           = htons(port);
+       memcpy(sa->sin6_addr.s6_addr, addr, 16);
+       sa->sin6_scope_id       = scope_id;
+       if (sa->sin6_scope_id != 0) {
+               char name_buf[IF_NAMESIZE + 1];
+               const char *name_ptr = if_indextoname(sa->sin6_scope_id, name_buf);
+               const int name_err = map_global_value_errno(name_ptr, name_ptr);
+               if (!name_err) {
+                       obj->if_name = strdup(name_ptr);
+               } else {
+                       os_log_error(_mdns_address_log(), "if_indextoname() for %u failed with error %d: %s",
+                               sa->sin6_scope_id, name_err, strerror(name_err));
+               }
+       }
+
+exit:
+       return obj;
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mdns_address_parse_ipv6(const char * const start, const char *end, uint8_t out_addr_bytes[16],
+       uint32_t * const out_scope_id);
+
+mdns_address_t
+mdns_address_create_from_ip_address_string(const char *addr_str)
+{
+       mdns_address_t  address = NULL;
+       const char *    port_str;
+       uint32_t                scope_id;
+       uint8_t                 addr_bytes[16];
+       bool                    addr_is_ipv6;
+
+       // An opening bracket implies an IPv6 address, e.g., "[<IPv6 address>]:<port>" or "[<IPv6 address>]".
+       if (*addr_str == '[') {
+               const char *ptr = &addr_str[1];
+
+               // Look for closing bracket.
+               const char *end_bracket = strchr(ptr, ']');
+               require_quiet(end_bracket, exit);
+
+               const OSStatus err = _mdns_address_parse_ipv6(ptr, end_bracket, addr_bytes, &scope_id);
+               require_noerr_quiet(err, exit);
+
+               // Check for a port delimiter immediately after the closing bracket.
+               ptr = end_bracket + 1;
+               if (*ptr == ':') {
+                       port_str = ++ptr;
+               } else {
+                       require_quiet(*ptr == '\0', exit);
+                       port_str = NULL;
+               }
+               addr_is_ipv6 = true;
+       } else {
+               // Try to parse the string as an IPv6 address.
+               const OSStatus err = _mdns_address_parse_ipv6(addr_str, NULL, addr_bytes, &scope_id);
+               if (!err) {
+                       port_str = NULL;
+                       addr_is_ipv6 = true;
+               } else {
+                       const char *addr_ptr;
+                       char addr_buf[128];
+
+                       // Look for port delimiter.
+                       const char *delim = strchr(addr_str, ':');
+                       if (delim) {
+                               // Copy the substring up to the port delimiter.
+                               const size_t addr_len = (size_t)(delim - addr_str);
+                               require_quiet(addr_len < sizeof(addr_buf), exit);
+
+                               memcpy(addr_buf, addr_str, addr_len);
+                               addr_buf[addr_len] = '\0';
+                               addr_ptr = addr_buf;
+                               port_str = ++delim;
+                       } else {
+                               addr_ptr = addr_str;
+                               port_str = NULL;
+                       }
+                       // Try to parse the string or substring as an IPv4 address.
+                       const int result = inet_pton(AF_INET, addr_ptr, addr_bytes);
+                       require_quiet(result == 1, exit);
+                       addr_is_ipv6 = false;
+               }
+       }
+       // If there's a port substring, convert it to its numerical value.
+       uint32_t port = 0;
+       if (port_str) {
+               require_quiet(*port_str != '\0', exit);
+               const char *ptr;
+               for (ptr = port_str; isdigit_safe(*ptr); ++ptr) {
+                       const int c = *ptr;
+                       port = (10 * port) + (uint32_t)(c - '0');
+                       require_quiet(port <= UINT16_MAX, exit);
+               }
+               require_quiet(*ptr == '\0', exit);
+       }
+       if (addr_is_ipv6) {
+               address = mdns_address_create_ipv6(addr_bytes, (uint16_t)port, scope_id);
+       } else {
+               const uint32_t ipv4 = ReadBig32(addr_bytes);
+               address = mdns_address_create_ipv4(ipv4, (uint16_t)port);
+       }
+
+exit:
+       return address;
+}
+
+static OSStatus
+_mdns_address_parse_ipv6(const char * const start, const char *end, uint8_t out_addr_bytes[16],
+       uint32_t * const out_scope_id)
+{
+       OSStatus err;
+       if (!end) {
+               end = start;
+               while (*end != '\0') {
+                       ++end;
+               }
+       }
+       // Look for zone separator.
+       const char *ptr = start;
+       while ((ptr < end) && (*ptr != '%')) {
+               ++ptr;
+       }
+       const char * const zone_separator = (ptr < end) ? ptr : NULL;
+
+       // Copy substring enclosed in the brackets.
+       const char * const      addr_lim = zone_separator ? zone_separator : end;
+       const size_t            addr_len = (size_t)(addr_lim - start);
+       char addr_buf[128];
+       require_action_quiet(addr_len < sizeof(addr_buf), exit, err = kMalformedErr);
+
+       memcpy(addr_buf, start, addr_len);
+       addr_buf[addr_len] = '\0';
+
+       // Try to parse substring as an IPv6 address.
+       uint8_t addr_bytes[16];
+       const int result = inet_pton(AF_INET6, addr_buf, addr_bytes);
+       require_action_quiet(result == 1, exit, err = kMalformedErr);
+
+       uint32_t scope_id;
+       if (zone_separator) {
+               const char * const      zone_id         = zone_separator + 1;
+               const size_t            zone_id_len     = (size_t)(end - zone_id);
+               char *                          name_mem        = NULL;
+               char                            name_buf[IF_NAMESIZE + 1];
+
+               char *name_ptr;
+               if (zone_id_len < sizeof(name_buf)) {
+                       name_ptr = name_buf;
+               } else {
+                       name_mem = malloc(zone_id_len + 1);
+                       require_action_quiet(name_mem, exit, err = kNoMemoryErr);
+                       name_ptr = name_mem;
+               }
+               memcpy(name_ptr, zone_id, zone_id_len);
+               name_ptr[zone_id_len] = '\0';
+               scope_id = if_nametoindex(name_ptr);
+               ForgetMem(&name_mem);
+               if (scope_id == 0) {
+                       uint64_t u64 = 0;
+                       for (ptr = zone_id; (ptr < end) && isdigit_safe(*ptr); ++ptr) {
+                               const int c = *ptr;
+                               u64 = (10 * u64) + (uint32_t)(c - '0');
+                               require_action_quiet(u64 <= UINT32_MAX, exit, err = kMalformedErr);
+                       }
+                       require_action_quiet((ptr == end) && (ptr != zone_id), exit, err = kMalformedErr);
+
+                       scope_id = (uint32_t)u64;
+               }
+       } else {
+               scope_id = 0;
+       }
+       if (out_addr_bytes) {
+               memcpy(out_addr_bytes, addr_bytes, 16);
+       }
+       if (out_scope_id) {
+               *out_scope_id = scope_id;
+       }
+       err = kNoErr;
+
+exit:
+       return err;
+}
+
+//======================================================================================================================
+
+const struct sockaddr *
+mdns_address_get_sockaddr(mdns_address_t me)
+{
+       return &me->sip.sa;
+}
+
+//======================================================================================================================
+
+uint16_t
+mdns_address_get_port(const mdns_address_t me)
+{
+       switch (me->sip.sa.sa_family) {
+               case AF_INET:
+                       return ntohs(me->sip.v4.sin_port);
+
+               case AF_INET6:
+                       return ntohs(me->sip.v6.sin6_port);
+
+               default:
+                       return 0;
+       }
+}
+
+//======================================================================================================================
+// MARK: - Address Private Methods
+
+static char *
+_mdns_address_copy_description(mdns_address_t me, const bool debug, const bool privacy)
+{
+       char *                          description     = NULL;
+       char                            buffer[128];
+       char *                          dst                     = buffer;
+       const char * const      lim                     = &buffer[countof(buffer)];
+       int                                     n;
+
+       *dst = '\0';
+       if (debug) {
+               n = mdns_snprintf_add(&dst, lim, "<%s: %p>: ", me->base.kind->name, me);
+               require_quiet(n >= 0, exit);
+       }
+       switch (me->sip.sa.sa_family) {
+               case AF_INET: {
+                       const struct sockaddr_in * const sa = &me->sip.v4;
+                       const char *addr_str;
+                       char addr_buf[INET_ADDRSTRLEN];
+
+                       if (privacy) {
+                               addr_str = "<REDACTED IPv4 ADDRESS>";
+                       } else {
+                               addr_str = inet_ntop(AF_INET, &sa->sin_addr.s_addr, addr_buf, (socklen_t)sizeof(addr_buf));
+                       }
+                       n = mdns_snprintf_add(&dst, lim, "%s", addr_str);
+                       require_quiet(n >= 0, exit);
+
+                       const int port = ntohs(sa->sin_port);
+                       if (port != 0) {
+                               n = mdns_snprintf_add(&dst, lim, ":%d", port);
+                               require_quiet(n >= 0, exit);
+                       }
+                       break;
+               }
+               case AF_INET6: {
+                       const struct sockaddr_in6 * const sa = &me->sip.v6;
+                       const char *addr_str;
+                       char addr_buf[INET6_ADDRSTRLEN];
+
+                       if (privacy) {
+                               addr_str = "<REDACTED IPv6 ADDRESS>";
+                       } else {
+                               addr_str = inet_ntop(AF_INET6, sa->sin6_addr.s6_addr, addr_buf, (socklen_t)sizeof(addr_buf));
+                       }
+                       const int port = ntohs(sa->sin6_port);
+                       if (port != 0) {
+                               n = mdns_snprintf_add(&dst, lim, "[");
+                               require_quiet(n >= 0, exit);
+                       }
+                       n = mdns_snprintf_add(&dst, lim, "%s", addr_str);
+                       require_quiet(n >= 0, exit);
+
+                       if (sa->sin6_scope_id != 0) {
+                               if (me->if_name) {
+                                       n = mdns_snprintf_add(&dst, lim, "%%%s", me->if_name);
+                                       require_quiet(n >= 0, exit);
+                               } else {
+                                       n = mdns_snprintf_add(&dst, lim, "%%%u", sa->sin6_scope_id);
+                                       require_quiet(n >= 0, exit);
+                               }
+                       }
+                       if (port != 0) {
+                               n = mdns_snprintf_add(&dst, lim, "]:%d", port);
+                               require_quiet(n >= 0, exit);
+                       }
+                       break;
+               }
+               default:
+                       n = mdns_snprintf_add(&dst, lim, "<INVALID ADDRESS TYPE>");
+                       require_quiet(n >= 0, exit);
+                       break;
+       }
+       description = strdup(buffer);
+
+exit:
+       return description;
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_address_equal(mdns_address_t me, mdns_address_t other)
+{
+       const int family = me->sip.sa.sa_family;
+       if (family == other->sip.sa.sa_family) {
+               if (family == AF_INET) {
+                       const struct sockaddr_in * const me_sa          = &me->sip.v4;
+                       const struct sockaddr_in * const other_sa       = &other->sip.v4;
+                       if ((me_sa->sin_port        == other_sa->sin_port) &&
+                               (me_sa->sin_addr.s_addr == other_sa->sin_addr.s_addr)) {
+                               return true;
+                       }
+               } else if (family == AF_INET6) {
+                       const struct sockaddr_in6 * const me_sa         = &me->sip.v6;
+                       const struct sockaddr_in6 * const other_sa      = &other->sip.v6;
+                       if ((me_sa->sin6_port == other_sa->sin6_port) &&
+                               (memcmp(me_sa->sin6_addr.s6_addr, other_sa->sin6_addr.s6_addr, 16) == 0)) {
+                               return true;
+                       }
+               }
+       }
+       return false;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_address_finalize(__unused mdns_address_t me)
+{
+       ForgetMem(&me->if_name);
+}
diff --git a/mDNSMacOSX/mdns_objects/mdns_address.h b/mDNSMacOSX/mdns_objects/mdns_address.h
new file mode 100644 (file)
index 0000000..f31f171
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2019 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_ADDRESS_H__
+#define __MDNS_ADDRESS_H__
+
+#include "mdns_base.h"
+
+#include <stdint.h>
+
+MDNS_DECL(address);
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
+
+/*!
+ *     @brief
+ *             Creates an address object that represents an IPv4 address and port number.
+ *
+ *     @param addr
+ *             The IPv4 address as an unsigned 32-bit integer in host byte order.
+ *
+ *     @param port
+ *             The port number in host byte order.
+ *
+ *     @result
+ *             An address object or NULL if the system was out of memory.
+ */
+MDNS_RETURNS_RETAINED MDNS_WARN_RESULT _Nullable
+mdns_address_t
+mdns_address_create_ipv4(uint32_t addr, uint16_t port);
+
+/*!
+ *     @brief
+ *             Creates an address object that represents an IPv6 address and port number.
+ *
+ *     @param addr
+ *             The IPv6 address as an array of octets in network byte order.
+ *
+ *     @param port
+ *             The port number in host byte order.
+ *
+ *     @param scope_id
+ *             The scope ID.
+ *
+ *     @result
+ *             An address object or NULL if the system was out of memory.
+ */
+MDNS_RETURNS_RETAINED MDNS_WARN_RESULT _Nullable
+mdns_address_t
+mdns_address_create_ipv6(const uint8_t addr[static 16], uint16_t port, uint32_t scope_id);
+
+/*!
+ *     @brief
+ *             Creates an address object that represents the IPv4 or IPv6 address and optional port number specified by a
+ *             string.
+ *
+ *     @param ip_addr_str
+ *             The textual representation of the IPv4 or IPv6 address and optional port number as an ASCII C string.
+ *
+ *     @result
+ *             An address object or NULL if the system was out of memory.
+ *
+ *     @discussion
+ *             IPv4 addresses must be in dot-decimal notation, e.g., "192.0.2.1". A port can be specified for an IPv4 address
+ *             by appending a ':' character followed by the port number in decimal notation, e.g., "192.0.2.1:443".
+ *
+ *             IPv6 addresses must be in any of the three conventional forms described by
+ *             <https://tools.ietf.org/html/rfc3513#section-2.2>. For example, "2001:0db8:0000:0000:0000:0000:0000:0001",
+ *             "2001:db8::1", or "::ffff:192.0.2.1".
+ *
+ *             A port can be specified for an IPv6 address by enclosing the IPv6 address's textual representation in square
+ *             brackets, then appending a ':' character followed by the port number in decimal notation. For example,
+ *             "[2001:db8::1]:443".
+ *
+ *             If a port is not specified, a port number of zero is assumed.
+ */
+MDNS_RETURNS_RETAINED mdns_address_t _Nullable
+mdns_address_create_from_ip_address_string(const char *ip_addr_str);
+
+/*!
+ *     @brief
+ *             Return a pointer to a sockaddr structure that represents an address object's IPv4 or IPv6 address and port.
+ *
+ *     @param address
+ *             The address object.
+ *
+ *     @result
+ *             A pointer to a sockaddr structure.
+ *
+ *     @discussion
+ *             The pointer returned by this function can be safely cast to a pointer to a sockaddr_in structure if the value
+ *             of the sockaddr structure's sa_family member variable is AF_INET, which is the case for IPv4 addresses.
+ *
+ *             Likewise, the pointer can be safely cast to a pointer to a sockaddr_in6 structure if the value of the sockaddr
+ *             structure's sa_family member variable is AF_INET6, which is the case for IPv6 addresses.
+ */
+const struct sockaddr *
+mdns_address_get_sockaddr(mdns_address_t address);
+
+/*!
+ *     @brief
+ *             Returns an address's port number.
+ *
+ *     @param address
+ *             The address.
+ */
+uint16_t
+mdns_address_get_port(mdns_address_t address);
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#endif // __MDNS_ADDRESS_H__
diff --git a/mDNSMacOSX/mdns_objects/mdns_base.h b/mDNSMacOSX/mdns_objects/mdns_base.h
new file mode 100644 (file)
index 0000000..4bbaa50
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_BASE_H__
+#define __MDNS_BASE_H__
+
+#include <os/object.h>
+
+#if defined(MDNS_OBJECT_FORCE_NO_OBJC) && MDNS_OBJECT_FORCE_NO_OBJC
+       #define MDNS_OBJECT_USE_OBJC    0
+#else
+       #define MDNS_OBJECT_USE_OBJC    OS_OBJECT_USE_OBJC
+#endif
+
+#if MDNS_OBJECT_USE_OBJC
+       #define MDNS_RETURNS_RETAINED                   OS_OBJECT_RETURNS_RETAINED
+       #define MDNS_DECL(NAME)                                 OS_OBJECT_DECL_SUBCLASS(mdns_ ## NAME, mdns_object)
+       #define MDNS_DECL_SUBKIND(NAME, SUPER)  OS_OBJECT_DECL_SUBCLASS(mdns_ ## NAME, mdns_ ## SUPER)
+       OS_OBJECT_DECL(mdns_object,);
+#else
+       #define MDNS_RETURNS_RETAINED
+       #define MDNS_DECL(NAME)                                 typedef struct mdns_ ## NAME ## _s *    mdns_ ## NAME ## _t
+       #define MDNS_DECL_SUBKIND(NAME, SUPER)  MDNS_DECL(NAME)
+       MDNS_DECL(object);
+#endif
+
+#define MDNS_WARN_RESULT                                               OS_WARN_RESULT
+#define MDNS_ASSUME_NONNULL_BEGIN                              OS_ASSUME_NONNULL_BEGIN
+#define MDNS_ASSUME_NONNULL_END                                        OS_ASSUME_NONNULL_END
+#define MDNS_PRINTF_FORMAT(FMT_IDX, ARGS_IDX)  __attribute__((__format__ (__printf__, FMT_IDX, ARGS_IDX)))
+
+#define MDNS_UNION_MEMBER(NAME)        struct mdns_ ## NAME ## _s *    NAME
+
+#define mdns_forget_with_invalidation(X, NAME) \
+       do {                                                                            \
+               if (*(X)) {                                                             \
+                       mdns_ ## NAME ## _invalidate(*(X));     \
+                       mdns_release_arc_safe(*(X));            \
+                       *(X) = NULL;                                            \
+               }                                                                               \
+       } while (0)
+
+#endif // __MDNS_BASE_H__
diff --git a/mDNSMacOSX/mdns_objects/mdns_dns_service.c b/mDNSMacOSX/mdns_objects/mdns_dns_service.c
new file mode 100644 (file)
index 0000000..61ad5b0
--- /dev/null
@@ -0,0 +1,2901 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mdns_internal.h"
+#include "mdns_dns_service.h"
+#include "mdns_helpers.h"
+#include "mdns_interface_monitor.h"
+#include "mdns_objects.h"
+#include "mdns_resolver.h"
+
+#include "HTTPUtilities.h"
+#include "DNSMessage.h"
+#include <CoreUtils/CoreUtils.h>
+#include <stdatomic.h>
+
+//======================================================================================================================
+// MARK: - DNS Service Manager Kind Definition
+
+struct mdns_dns_service_manager_s {
+       struct mdns_object_s    base;                           // Object base.
+       CFMutableArrayRef               default_services;       // DNS services from configd.
+       CFMutableArrayRef               path_services;          // DNS services from path clients.
+       CFMutableArrayRef               discovered_services;// DNS services discovered from DNS records.
+       CFMutableArrayRef               custom_services;        // DNS services created for custom use.
+       CFMutableArrayRef               monitors;                       // Interface monitors.
+       dispatch_queue_t                queue;                          // Serial queue for interface monitor events.
+       dispatch_queue_t                user_queue;                     // User's queue for invoking user's event handler.
+       dispatch_source_t               update_source;          // Data source for triggering update events.
+       mdns_event_handler_t    event_handler;          // User's event handler.
+       bool                                    report_symptoms;        // True if resolvers should report DNS symptoms.
+       bool                                    invalidated;            // True if the manager has been invalidated.
+       bool                                    terminated;                     // True if the manager has been terminated.
+       bool                                    user_activated;         // True if user called mdns_dns_service_manager_activate().
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+       int                                             pqw_threshold;          // Threshold value for problematic QTYPE workaround.
+#endif
+};
+
+MDNS_OBJECT_SUBKIND_DEFINE(dns_service_manager);
+
+#define MDNS_DNS_SERVICE_MANAGER_ARRAYS(MANAGER)       \
+       (MANAGER)->default_services,                                    \
+       (MANAGER)->path_services,                                               \
+       (MANAGER)->discovered_services,                                 \
+       (MANAGER)->custom_services
+
+//======================================================================================================================
+// MARK: - DNS Service Kind Definition
+
+OS_CLOSED_ENUM(mdns_dns_service_source, int,
+       mdns_dns_service_source_sc              = 1,    // For DNS services defined by SystemConfiguration.
+       mdns_dns_service_source_nw              = 2,    // For DNS services defined system-wide by libnetwork.
+       mdns_dns_service_source_dns             = 3,    // For DNS services defined by SVCB/HTTPS DNS records.
+       mdns_dns_service_source_custom  = 4             // For DNS services defined by on a client using an XPC dictionary.
+);
+
+OS_CLOSED_OPTIONS(mdns_dns_service_flags, uint32_t,
+       mdns_dns_service_flag_null                                      = 0,                    // Null flag.
+       mdns_dns_service_flag_defunct                           = (1U <<  0),   // DNS service is defunct.
+       mdns_dns_service_flag_a_queries_advised         = (1U <<  1),   // Querying for A records is advised.
+       mdns_dns_service_flag_aaaa_queries_advised      = (1U <<  2),   // Querying for AAAA records is advised.
+       mdns_dns_service_flag_cellular                          = (1U <<  3),   // DNS service's interface is cellular.
+       mdns_dns_service_flag_ipv4_connectivity         = (1U <<  4),   // DNS service's interface has IPv4 connectivity.
+       mdns_dns_service_flag_ipv6_connectivity         = (1U <<  5),   // DNS service's interface has IPv6 connectivity.
+       mdns_dns_service_flag_expensive                         = (1U <<  6),   // DNS service's interface is expensive.
+       mdns_dns_service_flag_constrained                       = (1U <<  7),   // DNS service's interface is constrained.
+       mdns_dns_service_flag_clat46                            = (1U <<  8),   // DNS service's interface is CLAT46.
+       mdns_dns_service_flag_vpn                                       = (1U <<  9),   // DNS service's interface is VPN.
+       mdns_dns_service_flag_connection_problems       = (1U << 10)    // DNS service is currently having connection problems.
+);
+
+#define MDNS_DNS_SERVICE_FLAGS_FROM_DNS_CONFIG         \
+       (mdns_dns_service_flag_a_queries_advised        |       \
+        mdns_dns_service_flag_aaaa_queries_advised     |       \
+        mdns_dns_service_flag_cellular)                                \
+
+#define MDNS_DNS_SERVICE_FLAGS_FROM_INTERFACE_MONITOR (        \
+       mdns_dns_service_flag_ipv4_connectivity |                       \
+       mdns_dns_service_flag_ipv6_connectivity |                       \
+       mdns_dns_service_flag_expensive                 |                       \
+       mdns_dns_service_flag_constrained               |                       \
+       mdns_dns_service_flag_clat46                    |                       \
+       mdns_dns_service_flag_vpn                                                       \
+)
+
+// Make sure that the two sets of flags are mutually exclusive.
+check_compile_time((MDNS_DNS_SERVICE_FLAGS_FROM_DNS_CONFIG & MDNS_DNS_SERVICE_FLAGS_FROM_INTERFACE_MONITOR) == 0);
+
+struct _discovered_detail_s {
+       nw_endpoint_t                           url_endpoint;   // URL endpoint for discovered DNS services.
+       uint64_t                                        use_time;               // Recent use time for discovered DNS service.
+       uint64_t                                        expire_time;    // Expire time for discovered DNS service.
+       bool                                            squash_cnames;  // Should squash CNAMEs.
+};
+
+typedef struct _domain_item_s *        _domain_item_t;
+
+struct mdns_dns_service_s {
+       struct mdns_object_s            base;                           // Object base.
+       mdns_resolver_t                         resolver;                       // The DNS service's resolver.
+       CFMutableArrayRef                       addresses;                      // Addresses of servers that implement the DNS service.
+       _domain_item_t                          domain_list;            // List of domains that this DNS service should be used for.
+       nw_resolver_config_t            config;                         // Resolver config from nw_path.
+       char *                                          if_name;                        // Name of DNS service's network interface (for logging).
+       mdns_dns_service_id_t           ident;                          // The DNS service's process-wide unique identifier.
+       void *                                          context;                        // User-defined context.
+       mdns_context_finalizer_t        context_finalizer;      // Finalizer for user-defined context.
+       uint32_t                                        if_index;                       // Index of DNS service's network interface.
+       uint32_t                                        service_id;                     // Service ID for service-scoped DNS services.
+       int32_t                                         reg_count;                      // Count of outstanding registrations for a custom DNS service.
+       struct _discovered_detail_s discovered;                 // Details for discovered DNS services.
+       mdns_dns_service_source_t       source;                         // Service's source.
+       mdns_dns_service_scope_t        scope;                          // The DNS service's scope type.
+       mdns_dns_service_flags_t        flags;                          // Flags that represent the service's properties.
+       mdns_resolver_type_t            resolver_type;          // The resolver's type.
+       bool                                            defuncting;                     // True if the service becoming defunct is imminent.
+       bool                                            cannot_connect;         // True if we cannot connect to the DNS service.
+       bool                                            config_needs_cancel;// True if the new_resolver_config updates need to be cancelled.
+       bool                                            replace_resolver;       // True if resolver needs to be replaced on wake.
+};
+
+MDNS_OBJECT_SUBKIND_DEFINE_FULL(dns_service);
+
+struct _domain_item_s {
+       _domain_item_t  next;                   // Next item in list.
+       uint8_t *               name;                   // Domain name in label format.
+       char *                  name_str;               // Domain name as a C string.
+       int                             label_count;    // Domain name's label count for longest parent domain matching.
+       uint32_t                order;                  // Order value from associated dns_resolver_t object, if any.
+};
+
+//======================================================================================================================
+// MARK: - Local Prototypes
+
+static void
+_mdns_dns_service_manager_terminate(mdns_dns_service_manager_t manager, OSStatus error);
+
+static void
+_mdns_dns_service_manager_add_service(mdns_dns_service_manager_t manager, CFMutableArrayRef services,
+       mdns_dns_service_t service);
+
+static mdns_dns_service_t
+_mdns_dns_service_manager_get_service(mdns_dns_service_manager_t manager, const uint8_t *name,
+       mdns_dns_service_scope_t scope, uint32_t scoping_id);
+
+static mdns_dns_service_t
+_mdns_dns_service_manager_get_uuid_scoped_service(mdns_dns_service_manager_t manager, const uuid_t uuid);
+
+static mdns_dns_service_t
+_mdns_dns_service_manager_get_discovered_service(mdns_dns_service_manager_t manager, nw_endpoint_t url_endpoint);
+
+static void
+_mdns_dns_service_manager_update_interface_properties(mdns_dns_service_manager_t manager);
+
+static void
+_mdns_dns_service_manager_remove_unneeded_interface_monitors(mdns_dns_service_manager_t manager);
+
+static void
+_mdns_dns_service_manager_update_interface_properties_for_services(mdns_dns_service_manager_t manager,
+       CFArrayRef services);
+
+static void
+_mdns_dns_service_manager_update_interface_properties_for_service(mdns_dns_service_manager_t manager,
+       mdns_dns_service_t service);
+
+static mdns_dns_service_t
+_mdns_dns_service_create(mdns_dns_service_source_t source, mdns_dns_service_scope_t scope,
+       mdns_resolver_type_t resolver_type, OSStatus *out_error);
+
+static void
+_mdns_dns_service_manager_prepare_resolver(mdns_dns_service_manager_t manager, mdns_dns_service_t service);
+
+static void
+_mdns_dns_service_manager_start_defuncting(mdns_dns_service_manager_t manager, mdns_dns_service_t service);
+
+static mdns_dns_service_t
+_mdns_dns_service_manager_prepare_service(mdns_dns_service_manager_t manager, mdns_dns_service_t service);
+
+static void
+_mdns_dns_service_manager_trigger_update(mdns_dns_service_manager_t manager);
+
+typedef bool (^mdns_dns_service_array_applier_t)(CFMutableArrayRef service_array);
+
+static void
+_mdns_dns_service_manager_iterate_over_all_service_arrays(mdns_dns_service_manager_t manager,
+       mdns_dns_service_array_applier_t applier);
+
+static void
+_mdns_dns_service_make_defunct(mdns_dns_service_t service);
+
+static bool
+_mdns_dns_service_equal_ex(mdns_dns_service_t service, mdns_dns_service_t other, bool ignore_domains);
+
+static OSStatus
+_mdns_dns_service_add_domain(mdns_dns_service_t service, const char *name, uint32_t order);
+
+static int
+_mdns_dns_service_handles_domain_name(mdns_dns_service_t service, const uint8_t *name, uint32_t *out_order);
+
+static mdns_resolver_type_t
+_mdns_dns_service_get_resolver_type_safe(mdns_dns_service_t service);
+
+static CFMutableArrayRef
+_mdns_create_dns_service_array_from_config(const dns_config_t *config, OSStatus *out_error);
+
+static mdns_dns_service_t
+_mdns_dns_service_create_from_resolver_config(nw_resolver_config_t config, mdns_dns_service_source_t source,
+       OSStatus *out_error);
+
+static mdns_dns_service_id_t
+_mdns_dns_service_get_id_safe(mdns_dns_service_t service);
+
+static const uint8_t *
+_mdns_domain_name_get_parent(const uint8_t *name, int depth);
+
+static void
+_domain_item_free(_domain_item_t item);
+
+static int
+_domain_item_compare(const struct _domain_item_s *d1, const struct _domain_item_s *d2, bool ignore_order);
+
+//======================================================================================================================
+// MARK: - Internals
+
+MDNS_LOG_CATEGORY_DEFINE(dns_service, "dns_service");
+
+//======================================================================================================================
+// MARK: - DNS Service Manager Public Methods
+
+mdns_dns_service_manager_t
+mdns_dns_service_manager_create(const dispatch_queue_t user_queue, OSStatus * const out_error)
+{
+       OSStatus err;
+       mdns_dns_service_manager_t manager = NULL;
+       mdns_dns_service_manager_t obj = _mdns_dns_service_manager_alloc();
+       require_action_quiet(obj, exit, err = kNoMemoryErr);
+
+       obj->default_services = CFArrayCreateMutable(kCFAllocatorDefault, 0, &mdns_cfarray_callbacks);
+       require_action_quiet(obj->default_services, exit, err = kNoResourcesErr);
+
+       obj->path_services = CFArrayCreateMutable(kCFAllocatorDefault, 0, &mdns_cfarray_callbacks);
+       require_action_quiet(obj->path_services, exit, err = kNoResourcesErr);
+
+       obj->discovered_services = CFArrayCreateMutable(kCFAllocatorDefault, 0, &mdns_cfarray_callbacks);
+       require_action_quiet(obj->discovered_services, exit, err = kNoResourcesErr);
+
+       obj->custom_services = CFArrayCreateMutable(kCFAllocatorDefault, 0, &mdns_cfarray_callbacks);
+       require_action_quiet(obj->custom_services, exit, err = kNoResourcesErr);
+
+       obj->monitors = CFArrayCreateMutable(kCFAllocatorDefault, 0, &mdns_cfarray_callbacks);
+       require_action_quiet(obj->monitors, exit, err = kNoResourcesErr);
+
+       obj->queue = dispatch_queue_create("com.apple.mdns.dns-service-manager", DISPATCH_QUEUE_SERIAL);
+       require_action_quiet(obj->queue, exit, err = kNoResourcesErr);
+
+       obj->user_queue = user_queue;
+       dispatch_retain(obj->user_queue);
+
+       manager = obj;
+       obj = NULL;
+       err = kNoErr;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       mdns_release_null_safe(obj);
+       return manager;
+}
+
+//======================================================================================================================
+
+void
+mdns_dns_service_manager_set_report_symptoms(const mdns_dns_service_manager_t me, const bool report_symptoms)
+{
+       if (!me->user_activated) {
+               me->report_symptoms = report_symptoms;
+       }
+}
+
+//======================================================================================================================
+
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+void
+mdns_dns_service_manager_enable_problematic_qtype_workaround(const mdns_dns_service_manager_t me, const int threshold)
+{
+       require_return(!me->user_activated);
+       me->pqw_threshold = threshold;
+}
+#endif
+
+//======================================================================================================================
+
+void
+mdns_dns_service_manager_set_event_handler(const mdns_dns_service_manager_t me, const mdns_event_handler_t handler)
+{
+       if (!me->user_activated) {
+               const mdns_event_handler_t new_handler = handler ? Block_copy(handler) : NULL;
+               if (me->event_handler) {
+                       Block_release(me->event_handler);
+               }
+               me->event_handler = new_handler;
+       }
+}
+
+//======================================================================================================================
+
+void
+_mdns_dns_service_manager_activate_internal(mdns_dns_service_manager_t manager);
+
+void
+mdns_dns_service_manager_activate(const mdns_dns_service_manager_t me)
+{
+       require_return(!me->user_activated);
+
+       me->user_activated = true;
+       dispatch_sync(me->queue,
+       ^{
+               require_return(!me->terminated);
+               _mdns_dns_service_manager_activate_internal(me);
+       });
+}
+
+void
+_mdns_dns_service_manager_activate_internal(const mdns_dns_service_manager_t me)
+{
+       require_return(!me->update_source);
+       me->update_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0, me->user_queue);
+       if (me->update_source) {
+               mdns_retain(me);
+               dispatch_source_set_event_handler(me->update_source,
+               ^{
+                       if (me->event_handler) {
+                               me->event_handler(mdns_event_update, kNoErr);
+                       }
+               });
+               dispatch_source_set_cancel_handler(me->update_source,
+               ^{
+                       mdns_release(me);
+               });
+               dispatch_activate(me->update_source);
+       } else {
+               _mdns_dns_service_manager_terminate(me, kNoResourcesErr);
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_dns_service_manager_apply_dns_config_internal(mdns_dns_service_manager_t manager, const dns_config_t *config);
+
+void
+mdns_dns_service_manager_apply_dns_config(const mdns_dns_service_manager_t me, const dns_config_t * const config)
+{
+       dispatch_sync(me->queue,
+       ^{
+               require_return(!me->terminated);
+               _mdns_dns_service_manager_apply_dns_config_internal(me, config);
+       });
+}
+
+#define MDNS_TARGET_DISCOVERED_SERVICE_COUNT 4
+
+static CFComparisonResult
+_mdns_dns_service_compare_time(const void *val1, const void *val2, __unused void *context)
+{
+       const mdns_dns_service_t service1 = (const mdns_dns_service_t)val1;
+       const mdns_dns_service_t service2 = (const mdns_dns_service_t)val2;
+
+       if (service1->discovered.use_time > service2->discovered.use_time) {
+               return kCFCompareLessThan;
+       } else if (service1->discovered.use_time < service2->discovered.use_time) {
+               return kCFCompareGreaterThan;
+       } else {
+               return kCFCompareEqualTo;
+       }
+}
+
+static void
+_mdns_dns_service_manager_purge_discovered_services(const mdns_dns_service_manager_t me)
+{
+       const CFIndex n = CFArrayGetCount(me->discovered_services);
+       if (n < MDNS_TARGET_DISCOVERED_SERVICE_COUNT) {
+               return;
+       }
+
+       // Sort by recent use
+       CFMutableArrayRef recent_services = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, me->discovered_services);
+       CFArraySortValues(recent_services, CFRangeMake(0, n), _mdns_dns_service_compare_time, NULL);
+
+       // Reduce number of services down to target by defuncting them
+       for (CFIndex i = 0; i < n; ++i) {
+               if (i >= MDNS_TARGET_DISCOVERED_SERVICE_COUNT) {
+                       const mdns_dns_service_t service = (mdns_dns_service_t)CFArrayGetValueAtIndex(recent_services, i);
+                       _mdns_dns_service_make_defunct(service);
+               }
+       }
+       CFRelease(recent_services);
+}
+
+static void
+_mdns_dns_service_manager_apply_dns_config_internal(const mdns_dns_service_manager_t me,
+       const dns_config_t * const config)
+{
+       _mdns_dns_service_manager_purge_discovered_services(me);
+
+       OSStatus err;
+       CFMutableArrayRef new_services = _mdns_create_dns_service_array_from_config(config, &err);
+       require_noerr_quiet(err, exit);
+
+       // Keep DNS services that still exist, and mark those that no longer exist as defunct.
+       const CFRange full_range = CFRangeMake(0, CFArrayGetCount(new_services));
+       for (CFIndex i = CFArrayGetCount(me->default_services) - 1; i >= 0; --i) {
+               const mdns_dns_service_t service = (mdns_dns_service_t)CFArrayGetValueAtIndex(me->default_services, i);
+               const CFIndex j = CFArrayGetFirstIndexOfValue(new_services, full_range, service);
+               if (j >= 0) {
+                       // The service still exists, but its flags may have changed.
+                       const mdns_dns_service_t new_service = (mdns_dns_service_t)CFArrayGetValueAtIndex(new_services, j);
+                       service->flags &= ~MDNS_DNS_SERVICE_FLAGS_FROM_DNS_CONFIG;
+                       service->flags |= (new_service->flags & MDNS_DNS_SERVICE_FLAGS_FROM_DNS_CONFIG);
+
+                       // Replace the new service object with the established one.
+                       CFArraySetValueAtIndex(new_services, j, service);
+               } else {
+                       // The service no longer exists.
+                       _mdns_dns_service_make_defunct(service);
+               }
+       }
+       CFRelease(me->default_services);
+       me->default_services = new_services;
+       new_services = NULL;
+
+       _mdns_dns_service_manager_remove_unneeded_interface_monitors(me);
+       _mdns_dns_service_manager_update_interface_properties_for_services(me, me->default_services);
+
+exit:
+       if (err) {
+               _mdns_dns_service_manager_terminate(me, err);
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_dns_service_manager_register_path_resolver_internal(mdns_dns_service_manager_t manager,
+       const uuid_t resolver_config_uuid);
+
+static void
+_mdns_dns_service_manager_handle_resolver_config_removal(mdns_dns_service_manager_t manager,
+       nw_resolver_config_t config);
+
+static void
+_mdns_dns_service_manager_cancel_resolver_config_updates(mdns_dns_service_manager_t manager,
+       nw_resolver_config_t config);
+
+void
+mdns_dns_service_manager_register_path_resolver(const mdns_dns_service_manager_t me, const uuid_t resolver_config_uuid)
+{
+       dispatch_sync(me->queue,
+       ^{
+               require_return(!me->terminated);
+               _mdns_dns_service_manager_register_path_resolver_internal(me, resolver_config_uuid);
+       });
+}
+
+static void
+_mdns_dns_service_manager_register_path_resolver_internal(const mdns_dns_service_manager_t me,
+       const uuid_t config_uuid)
+{
+       mdns_dns_service_t service = _mdns_dns_service_manager_get_uuid_scoped_service(me, config_uuid);
+       require_return_action(!service, os_log_debug(_mdns_dns_service_log(),
+               "Already registered service -- service id: %llu, uuid: %{uuid_t}.16P", service->ident, config_uuid));
+
+       // Need a new service in the path array.
+       nw_resolver_config_t _Nonnull config = nw_resolver_config_create();
+       nw_resolver_config_set_identifier(config, config_uuid);
+
+       // Calling nw_resolver_config_watch_updates will fill out the contents of the resolver.
+       mdns_retain(me);
+       nw_retain(config);
+       nw_resolver_config_watch_updates(config, me->queue,
+       ^(const bool removed)
+       {
+               if (removed) {
+                       _mdns_dns_service_manager_handle_resolver_config_removal(me, config);
+               }
+       });
+       OSStatus err;
+       service = _mdns_dns_service_create_from_resolver_config(config, mdns_dns_service_source_nw, &err);
+       if (service) {
+               service->config_needs_cancel = true;
+               _mdns_dns_service_manager_add_service(me, me->path_services, service);
+               os_log(_mdns_dns_service_log(), "Registered service -- %@", service);
+               mdns_forget(&service);
+       } else {
+               _mdns_dns_service_manager_cancel_resolver_config_updates(me, config);
+               os_log_error(_mdns_dns_service_log(),
+                       "Failed to register service -- uuid: %{uuid_t}.16P, config: %@, error: %{mdns:err}ld",
+                       config_uuid, config, (long)err);
+       }
+       nw_release(config);
+}
+
+static void
+_mdns_dns_service_manager_handle_resolver_config_removal(const mdns_dns_service_manager_t me,
+       const nw_resolver_config_t config)
+{
+       const CFIndex n = CFArrayGetCount(me->path_services);
+       for (CFIndex i = 0; i < n; ++i) {
+               const mdns_dns_service_t service = (mdns_dns_service_t)CFArrayGetValueAtIndex(me->path_services, i);
+               if (service->config == config) {
+                       os_log(_mdns_dns_service_log(), "Removing service -- %@", service);
+                       // Cancel the config's updates now and mark the service as defuncting.
+                       // The service will ultimately be made defunct when the user accepts the next set of pending updates.
+                       if (service->config_needs_cancel) {
+                               service->config_needs_cancel = false;
+                               _mdns_dns_service_manager_cancel_resolver_config_updates(me, service->config);
+                       }
+                       _mdns_dns_service_manager_start_defuncting(me, service);
+                       break;
+               }
+       }
+}
+
+static void
+_mdns_dns_service_manager_cancel_resolver_config_updates(const mdns_dns_service_manager_t me,
+       const nw_resolver_config_t config)
+{
+       nw_resolver_config_cancel_updates(config, me->queue,
+       ^{
+               mdns_release(me);
+               nw_release(config);
+       });
+}
+
+//======================================================================================================================
+
+static mdns_dns_service_id_t
+_mdns_dns_service_manager_register_custom_service_internal(mdns_dns_service_manager_t manager,
+       xpc_object_t resolver_config_dict);
+
+static mdns_dns_service_t
+_mdns_dns_service_manager_get_custom_service_by_uuid(mdns_dns_service_manager_t manager, const uuid_t config_uuid);
+
+mdns_dns_service_id_t
+mdns_dns_service_manager_register_custom_service(const mdns_dns_service_manager_t me,
+       const xpc_object_t resolver_config_dict)
+{
+       __block mdns_dns_service_id_t ident;
+       dispatch_sync(me->queue,
+       ^{
+               require_return_action(!me->terminated, ident = 0);
+               ident = _mdns_dns_service_manager_register_custom_service_internal(me, resolver_config_dict);
+       });
+       return ident;
+}
+
+static mdns_dns_service_id_t
+_mdns_dns_service_manager_register_custom_service_internal(const mdns_dns_service_manager_t me,
+       const xpc_object_t resolver_config_dict)
+{
+       nw_resolver_config_t config = NULL;
+       mdns_dns_service_t service = NULL;
+
+       // Parse the dictionary
+       config = nw_resolver_config_create_with_dictionary(resolver_config_dict);
+       if (unlikely(!config)) {
+               char *dict_desc = xpc_copy_description(resolver_config_dict);
+               os_log_error(_mdns_dns_service_log(),
+                       "Failed to create nw_resolver_config for dictionary: %s", dict_desc ? dict_desc : "<NO DESC.>");
+               ForgetMem(&dict_desc);
+               goto exit;
+       }
+       uuid_t config_uuid = {0};
+       nw_resolver_config_get_identifier(config, config_uuid);
+       service = _mdns_dns_service_manager_get_custom_service_by_uuid(me, config_uuid);
+       if (!service) {
+               OSStatus err;
+               service = _mdns_dns_service_create_from_resolver_config(config, mdns_dns_service_source_custom, &err);
+               if (service) {
+                       _mdns_dns_service_manager_add_service(me, me->custom_services, service);
+                       os_log(_mdns_dns_service_log(), "Registered custom service -- %@", service);
+                       mdns_release(service);
+               } else {
+                       os_log_error(_mdns_dns_service_log(),
+                               "Failed to register custom service -- uuid: %{uuid_t}.16P, config: %@, error: %{mdns:err}ld",
+                               config_uuid, config, (long)err);
+               }
+       }
+       if (service) {
+               if (service->reg_count++ != 0) {
+                       os_log_info(_mdns_dns_service_log(),
+                               "Registered custom service -- service id: %llu, registration count: %d",
+                               service->ident, service->reg_count);
+               }
+       }
+
+exit:
+       nw_forget(&config);
+       return _mdns_dns_service_get_id_safe(service);
+}
+
+static mdns_dns_service_t
+_mdns_dns_service_manager_get_custom_service_by_uuid(const mdns_dns_service_manager_t me, const uuid_t config_uuid)
+{
+       const CFIndex n = CFArrayGetCount(me->custom_services);
+       for (CFIndex i = 0; i < n; ++i) {
+               const mdns_dns_service_t service = (mdns_dns_service_t)CFArrayGetValueAtIndex(me->custom_services, i);
+               if (service->config) {
+                       uuid_t match_uuid = {0};
+                       nw_resolver_config_get_identifier(service->config, match_uuid);
+                       if (uuid_compare(match_uuid, config_uuid) == 0) {
+                               return service;
+                       }
+               }
+       }
+       return NULL;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_dns_service_manager_deregister_custom_service_internal(mdns_dns_service_manager_t manager,
+       mdns_dns_service_id_t ident);
+
+void
+mdns_dns_service_manager_deregister_custom_service(const mdns_dns_service_manager_t me,
+       const mdns_dns_service_id_t ident)
+{
+       require_return(ident != 0);
+       dispatch_sync(me->queue,
+       ^{
+               require_return(!me->terminated);
+               _mdns_dns_service_manager_deregister_custom_service_internal(me, ident);
+       });
+}
+
+static void
+_mdns_dns_service_manager_deregister_custom_service_internal(const mdns_dns_service_manager_t me,
+       const mdns_dns_service_id_t ident)
+{
+       const CFIndex n = CFArrayGetCount(me->custom_services);
+       for (CFIndex i = 0; i < n; ++i) {
+               const mdns_dns_service_t service = (mdns_dns_service_t)CFArrayGetValueAtIndex(me->custom_services, i);
+               if (service->ident == ident) {
+                       --service->reg_count;
+                       os_log_with_type(_mdns_dns_service_log(),
+                               (service->reg_count == 0) ? OS_LOG_TYPE_DEFAULT : OS_LOG_TYPE_INFO,
+                               "Deregistered custom service -- service id: %llu, registration count: %d",
+                               service->ident, service->reg_count);
+                       if (service->reg_count == 0) {
+                               _mdns_dns_service_manager_start_defuncting(me, service);
+                       }
+                       break;
+               }
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_dns_service_manager_register_doh_uri_internal(mdns_dns_service_manager_t manager,
+       const char *doh_uri, const char *domain);
+
+static mdns_dns_service_t
+_mdns_dns_service_create_from_doh_uri(nw_endpoint_t url_endpoint, OSStatus *out_error);
+
+void
+mdns_dns_service_manager_register_doh_uri(const mdns_dns_service_manager_t me,
+       const char *doh_uri, const char *domain)
+{
+       dispatch_sync(me->queue,
+       ^{
+               require_return(!me->terminated);
+               _mdns_dns_service_manager_register_doh_uri_internal(me, doh_uri, domain);
+       });
+}
+
+static void
+_mdns_dns_service_manager_fetch_trusted_name_pvd(const mdns_dns_service_manager_t me,
+       mdns_dns_service_t service, nw_endpoint_t doh_endpoint,
+       xpc_object_t dns_zone_array, const char *trusted_name)
+{
+       if (doh_endpoint == NULL || trusted_name == NULL ||
+               dns_zone_array == NULL || xpc_get_type(dns_zone_array) != XPC_TYPE_ARRAY) {
+               return;
+       }
+
+       char *trusted_name_suffix = NULL;
+       asprintf(&trusted_name_suffix, ".%s", trusted_name);
+
+       xpc_object_t candidate_dns_zones = xpc_array_create(NULL, 0);
+       xpc_array_apply(dns_zone_array, ^bool(__unused size_t index, xpc_object_t _Nonnull array_value) {
+               if (xpc_get_type(array_value) == XPC_TYPE_STRING) {
+                       const char *dns_zone = xpc_string_get_string_ptr(array_value);
+
+                       if (strcmp(trusted_name, dns_zone) == 0) {
+                               // Exact match
+                               xpc_array_append_value(candidate_dns_zones, array_value);
+                       } else {
+                               size_t trusted_name_suffix_length = strlen(trusted_name_suffix);
+                               size_t dns_zone_length = strlen(dns_zone);
+                               if (trusted_name_suffix_length <= dns_zone_length) {
+                                       if (strcmp(trusted_name_suffix, dns_zone + (dns_zone_length - trusted_name_suffix_length)) == 0) {
+                                               // Suffix match
+                                               xpc_array_append_value(candidate_dns_zones, array_value);
+                                       }
+                               }
+                       }
+               }
+               return true;
+       });
+
+       free(trusted_name_suffix);
+
+       if (xpc_array_get_count(candidate_dns_zones) == 0) {
+               xpc_forget(&candidate_dns_zones);
+               return;
+       }
+
+       __block void *trusted_name_task = NULL;
+       nw_retain(doh_endpoint);
+       mdns_retain(service);
+       // candidate_dns_zones is already retained
+       nw_endpoint_t trusted_name_endpoint = nw_endpoint_create_host(trusted_name, "443");
+       trusted_name_task = http_task_create_pvd_query(me->queue,
+                                                                                                  trusted_name, "", ^(xpc_object_t trusted_name_json) {
+               do {
+                       if (trusted_name_json != NULL) {
+                               const char *trusted_doh_template = xpc_dictionary_get_string(trusted_name_json, "dohTemplate");
+                               if (trusted_doh_template == NULL) {
+                                       os_log_error(_mdns_dns_service_log(), "Trusted name %@ missing DoH template", trusted_name_endpoint);
+                                       break;
+                               }
+
+                               const char *inner_doh_string = nw_endpoint_get_url(doh_endpoint);
+                               if (inner_doh_string == NULL || strcasecmp(trusted_doh_template, inner_doh_string) != 0) {
+                                       os_log_error(_mdns_dns_service_log(), "DoH resolver for %@ does not match trusted DoH template %s for %@",
+                                                                doh_endpoint, trusted_doh_template, trusted_name_endpoint);
+                                       break;
+                               }
+
+                               os_log(_mdns_dns_service_log(), "DoH resolver at %@ is trusted for %@",
+                                          doh_endpoint, trusted_name_endpoint);
+
+                               xpc_array_apply(candidate_dns_zones, ^bool(__unused size_t index, xpc_object_t _Nonnull array_value) {
+                                       const char *dns_zone = xpc_string_get_string_ptr(array_value);
+
+                                       os_log(_mdns_dns_service_log(), "Adding domain %s to discovered DoH resolver for %@",
+                                                  dns_zone, doh_endpoint);
+                                       _mdns_dns_service_add_domain(service, dns_zone, 0);
+                                       return true;
+                               });
+                       } else {
+                               os_log_error(_mdns_dns_service_log(), "No PvD file found at %@ for DoH server %@",
+                                                        trusted_name_endpoint, doh_endpoint);
+                       }
+               } while (0);
+               http_task_cancel(trusted_name_task);
+               nw_release(trusted_name_endpoint);
+               nw_release(doh_endpoint);
+               xpc_release(candidate_dns_zones);
+               mdns_release(service);
+       });
+       http_task_start(trusted_name_task);
+}
+
+static uint64_t
+_mdns_get_future_continuous_time(uint64_t seconds)
+{
+       static dispatch_once_t onceToken;
+       static mach_timebase_info_data_t time_base = {0, 0};
+       dispatch_once(&onceToken, ^{
+               mach_timebase_info(&time_base);
+       });
+       uint64_t delta = seconds * NSEC_PER_SEC;
+       delta *= time_base.denom;
+       delta /= time_base.numer;
+       return mach_continuous_time() + delta;
+}
+
+static void
+_mdns_dns_service_manager_fetch_doh_pvd(const mdns_dns_service_manager_t me,
+       mdns_dns_service_t service)
+{
+       __block void *task = NULL;
+       mdns_retain(service);
+
+       nw_endpoint_t doh_endpoint = service->discovered.url_endpoint;
+       nw_retain(doh_endpoint);
+       task = http_task_create_pvd_query(me->queue,
+                                                                         nw_endpoint_get_hostname(doh_endpoint),
+                                                                         nw_endpoint_get_url_path(doh_endpoint), ^(xpc_object_t json_object) {
+               do {
+                       if (json_object != NULL) {
+                               const char *doh_template = xpc_dictionary_get_string(json_object, "dohTemplate");
+                               if (doh_template == NULL) {
+                                       os_log_error(_mdns_dns_service_log(), "DoH resolver for %@ missing DoH template", doh_endpoint);
+                                       break;
+                               }
+
+                               // If the string is suffixed by a string like "{?dns}", trim off that variable template
+                               size_t template_length = strlen(doh_template);
+                               const char *template_portion = strchr(doh_template, '{');
+                               if (template_portion != NULL) {
+                                       template_length = (size_t)(template_portion - doh_template);
+                               }
+
+                               const char *doh_string = nw_endpoint_get_url(doh_endpoint);
+                               if (doh_string == NULL ||
+                                       strlen(doh_string) != template_length ||
+                                       strncasecmp(doh_template, doh_string, template_length) != 0) {
+                                       os_log_error(_mdns_dns_service_log(), "DoH resolver for %@ does not match DoH template %s",
+                                                                doh_endpoint, doh_template);
+                                       break;
+                               }
+
+                               uint64_t seconds_remaining = xpc_dictionary_get_uint64(json_object, "secondsRemaining");
+                               if (seconds_remaining == 0) {
+                                       seconds_remaining = xpc_dictionary_get_uint64(json_object, "seconds-remaining");
+                               }
+                               if (seconds_remaining != 0) {
+                                       os_log_info(_mdns_dns_service_log(), "DoH resolver for %@ will expire in %llu seconds",
+                                                               doh_endpoint, seconds_remaining);
+                                       service->discovered.expire_time = _mdns_get_future_continuous_time(seconds_remaining);
+                               } else {
+                                       os_log_info(_mdns_dns_service_log(), "DoH resolver for %@ does not specify an expiration",
+                                                               doh_endpoint);
+                                       service->discovered.expire_time = 0;
+                               }
+
+                               xpc_object_t dns_zone_array = xpc_dictionary_get_value(json_object, "dnsZones");
+
+                               xpc_object_t trusted_names_array = xpc_dictionary_get_value(json_object, "trustedNames");
+                               if (trusted_names_array && xpc_get_type(trusted_names_array) == XPC_TYPE_ARRAY) {
+                                       xpc_array_apply(trusted_names_array, ^bool(__unused size_t index, xpc_object_t _Nonnull array_value) {
+                                               if (xpc_get_type(array_value) == XPC_TYPE_STRING) {
+                                                       const char *trusted_name = xpc_string_get_string_ptr(array_value);
+                                                       os_log(_mdns_dns_service_log(), "Query trusted name %s for DoH resolver for %@",
+                                                                  trusted_name, doh_endpoint);
+                                                       _mdns_dns_service_manager_fetch_trusted_name_pvd(me, service, doh_endpoint, dns_zone_array, trusted_name);
+                                               }
+                                               return true;
+                                       });
+                               }
+                       }
+               } while (0);
+               http_task_cancel(task);
+               mdns_release(service);
+               nw_release(doh_endpoint);
+       });
+       http_task_start(task);
+}
+
+static void
+_mdns_dns_service_manager_check_service_expiration(const mdns_dns_service_manager_t me,
+       mdns_dns_service_t service)
+{
+       // Check if a service is expired
+       if (service->discovered.expire_time != 0 &&
+               service->discovered.expire_time < mach_continuous_time()) {
+
+               os_log_info(_mdns_dns_service_log(), "DoH resolver for %@ has passed expiration",
+                                       service->discovered.url_endpoint);
+
+               service->discovered.expire_time = 0;
+
+               // Clear out domain list, in case they have changed
+               _domain_item_t item;
+               while ((item = service->domain_list) != NULL) {
+                       service->domain_list = item->next;
+                       _domain_item_free(item);
+               }
+
+               // Refresh PvD to rebuild the list
+               _mdns_dns_service_manager_fetch_doh_pvd(me, service);
+       }
+}
+
+static void
+_mdns_dns_service_manager_register_doh_uri_internal(const mdns_dns_service_manager_t me,
+       const char *doh_uri, const char *domain)
+{
+       mdns_dns_service_t service = NULL;
+       nw_endpoint_t doh_endpoint = NULL;
+       char *uri_string;
+       OSStatus err;
+
+       // Make a copy of the string in case it needs to be sanitized
+       uri_string = strdup(doh_uri);
+       require_quiet(uri_string, exit);
+
+       // If the string is suffixed by a string like "{?dns}", trim off that variable template
+       char *template_portion = strchr(uri_string, '{');
+       if (template_portion != NULL) {
+               template_portion[0] = '\0';
+       }
+
+       doh_endpoint = nw_endpoint_create_url(uri_string);
+       require_action_quiet(doh_endpoint, exit, err = kNoResourcesErr);
+
+       const char *scheme = nw_endpoint_get_url_scheme(doh_endpoint);
+       require_action_quiet((scheme != NULL && strcasecmp("https", scheme) == 0), exit, err = kMalformedErr);
+
+       service = _mdns_dns_service_manager_get_discovered_service(me, doh_endpoint);
+       if (service == NULL) {
+               os_log(_mdns_dns_service_log(), "Registering discovered DoH resolver at %s", uri_string);
+
+               service = _mdns_dns_service_create_from_doh_uri(doh_endpoint, NULL);
+               if (service) {
+                       _mdns_dns_service_manager_add_service(me, me->discovered_services, service);
+                       mdns_release(service);
+                       _mdns_dns_service_manager_fetch_doh_pvd(me, service);
+               }
+       }
+
+       if (service && domain) {
+               os_log(_mdns_dns_service_log(), "Adding domain %s to DoH resolver at %s", domain, uri_string);
+               _mdns_dns_service_add_domain(service, domain, 0);
+       }
+
+exit:
+       free(uri_string);
+       nw_forget(&doh_endpoint);
+       return;
+}
+
+static mdns_dns_service_t
+_mdns_dns_service_create_from_doh_uri(nw_endpoint_t url_endpoint, OSStatus * const out_error)
+{
+       nw_resolver_config_t config = nw_resolver_config_create();
+       nw_resolver_config_set_class(config, nw_resolver_class_designated);
+       nw_resolver_config_set_protocol(config, nw_resolver_protocol_doh);
+       nw_resolver_config_set_provider_name(config, nw_endpoint_get_hostname(url_endpoint));
+       nw_resolver_config_set_provider_path(config, nw_endpoint_get_url_path(url_endpoint));
+
+       uuid_t new_identifier;
+       uuid_generate(new_identifier);
+       nw_resolver_config_set_identifier(config, new_identifier);
+
+       OSStatus err;
+       const mdns_dns_service_t service = _mdns_dns_service_create(mdns_dns_service_source_dns,
+               mdns_dns_service_scope_uuid, mdns_resolver_type_null, &err);
+       require_noerr_quiet(err, exit);
+
+       service->discovered.url_endpoint = nw_retain(url_endpoint);
+       service->discovered.squash_cnames = true;
+       service->config = config;
+       config = NULL;
+       service->flags = (mdns_dns_service_flag_a_queries_advised | mdns_dns_service_flag_aaaa_queries_advised);
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       nw_forget(&config);
+       return service;
+}
+
+//======================================================================================================================
+
+void
+mdns_dns_service_manager_invalidate(const mdns_dns_service_manager_t me)
+{
+       dispatch_sync(me->queue,
+       ^{
+               require_return(!me->invalidated);
+               _mdns_dns_service_manager_terminate(me, kNoErr);
+               me->invalidated = true;
+       });
+}
+
+//======================================================================================================================
+
+mdns_dns_service_t
+mdns_dns_service_manager_get_unscoped_service(const mdns_dns_service_manager_t me, const uint8_t * const name)
+{
+       __block mdns_dns_service_t result;
+       dispatch_sync(me->queue,
+       ^{
+               require_return_action(!me->terminated, result = NULL);
+               const mdns_dns_service_scope_t scope = mdns_dns_service_scope_none;
+               const mdns_dns_service_t service = _mdns_dns_service_manager_get_service(me, name, scope, 0);
+               result = _mdns_dns_service_manager_prepare_service(me, service);
+       });
+       return result;
+}
+
+//======================================================================================================================
+
+mdns_dns_service_t
+mdns_dns_service_manager_get_interface_scoped_service(const mdns_dns_service_manager_t me, const uint8_t * const name,
+       const uint32_t if_index)
+{
+       __block mdns_dns_service_t result;
+       dispatch_sync(me->queue,
+       ^{
+               require_return_action(!me->terminated, result = NULL);
+               const mdns_dns_service_scope_t scope = mdns_dns_service_scope_interface;
+               const mdns_dns_service_t service = _mdns_dns_service_manager_get_service(me, name, scope, if_index);
+               result = _mdns_dns_service_manager_prepare_service(me, service);
+       });
+       return result;
+}
+
+//======================================================================================================================
+
+mdns_dns_service_t
+mdns_dns_service_manager_get_service_scoped_service(const mdns_dns_service_manager_t me, const uint8_t * const name,
+       const uint32_t service_id)
+{
+       __block mdns_dns_service_t result;
+       dispatch_sync(me->queue,
+       ^{
+               require_return_action(!me->terminated, result = NULL);
+               const mdns_dns_service_scope_t scope = mdns_dns_service_scope_service;
+               const mdns_dns_service_t service = _mdns_dns_service_manager_get_service(me, name, scope, service_id);
+               result = _mdns_dns_service_manager_prepare_service(me, service);
+       });
+       return result;
+}
+
+//======================================================================================================================
+
+static mdns_dns_service_t
+_mdns_dns_service_manager_get_custom_service(mdns_dns_service_manager_t manager, mdns_dns_service_id_t ident);
+
+mdns_dns_service_t
+mdns_dns_service_manager_get_custom_service(const mdns_dns_service_manager_t me, const mdns_dns_service_id_t ident)
+{
+       __block mdns_dns_service_t result;
+       dispatch_sync(me->queue,
+       ^{
+               require_return_action(!me->terminated, result = NULL);
+               const mdns_dns_service_t service = _mdns_dns_service_manager_get_custom_service(me, ident);
+               result = _mdns_dns_service_manager_prepare_service(me, service);
+       });
+       return result;
+}
+
+static mdns_dns_service_t
+_mdns_dns_service_manager_get_custom_service(const mdns_dns_service_manager_t me, const mdns_dns_service_id_t ident)
+{
+       const CFIndex n = CFArrayGetCount(me->custom_services);
+       for (CFIndex i = 0; i < n; ++i) {
+               const mdns_dns_service_t service = (mdns_dns_service_t)CFArrayGetValueAtIndex(me->custom_services, i);
+               if (service->ident == ident) {
+                       return service;
+               }
+       }
+       return NULL;
+}
+
+//======================================================================================================================
+
+mdns_dns_service_t
+mdns_dns_service_manager_get_uuid_scoped_service(const mdns_dns_service_manager_t me, const uuid_t uuid)
+{
+       __block mdns_dns_service_t result;
+       dispatch_sync(me->queue,
+       ^{
+               require_return_action(!me->terminated, result = NULL);
+               const mdns_dns_service_t service = _mdns_dns_service_manager_get_uuid_scoped_service(me, uuid);
+               result = _mdns_dns_service_manager_prepare_service(me, service);
+       });
+       return result;
+}
+
+//======================================================================================================================
+
+bool
+_mdns_dns_service_manager_fillout_discovered_service_for_name(mdns_dns_service_manager_t manager,
+       const uint8_t *name, uuid_t out_uuid);
+
+bool
+mdns_dns_service_manager_fillout_discovered_service_for_name(const mdns_dns_service_manager_t me,
+       const uint8_t * const name, uuid_t out_uuid)
+{
+       __block bool success = false;
+       dispatch_sync(me->queue,
+       ^{
+               require_return(!me->terminated);
+               success = _mdns_dns_service_manager_fillout_discovered_service_for_name(me, name, out_uuid);
+       });
+       return success;
+}
+
+bool
+_mdns_dns_service_manager_fillout_discovered_service_for_name(const mdns_dns_service_manager_t me,
+       const uint8_t * const name, uuid_t out_uuid)
+{
+       mdns_dns_service_t      service                         = NULL;
+       int                                     best_label_count        = -1;
+       const CFIndex n = CFArrayGetCount(me->discovered_services);
+       for (CFIndex i = 0; i < n; ++i) {
+               mdns_dns_service_t candidate = (mdns_dns_service_t)CFArrayGetValueAtIndex(me->discovered_services, i);
+               const int label_count = _mdns_dns_service_handles_domain_name(candidate, name, NULL);
+               if (candidate->config && (label_count > best_label_count)) {
+                       service                         = candidate;
+                       best_label_count        = label_count;
+               }
+
+               // Check if service details have expired while iterating the list
+               _mdns_dns_service_manager_check_service_expiration(me, candidate);
+       }
+
+       if (service) {
+               // Update the most recent use (approximate) time for this service
+               service->discovered.use_time = mach_continuous_approximate_time();
+               nw_resolver_config_get_identifier(service->config, out_uuid);
+               return true;
+       }
+       return false;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_dns_service_manager_apply_pending_updates_internal(mdns_dns_service_manager_t manager);
+
+static void
+_mdns_dns_service_manager_finish_defuncting_services(CFMutableArrayRef services);
+
+static void
+_mdns_dns_service_manager_update_service_usability(CFMutableArrayRef services);
+
+void
+mdns_dns_service_manager_apply_pending_updates(const mdns_dns_service_manager_t me)
+{
+       dispatch_sync(me->queue,
+       ^{
+               require_return(!me->terminated);
+               _mdns_dns_service_manager_apply_pending_updates_internal(me);
+       });
+}
+
+static void
+_mdns_dns_service_manager_apply_pending_updates_internal(const mdns_dns_service_manager_t me)
+{
+       _mdns_dns_service_manager_finish_defuncting_services(me->path_services);
+       _mdns_dns_service_manager_finish_defuncting_services(me->custom_services);
+       _mdns_dns_service_manager_update_service_usability(me->path_services);
+       _mdns_dns_service_manager_update_service_usability(me->discovered_services);
+       _mdns_dns_service_manager_update_service_usability(me->custom_services);
+       _mdns_dns_service_manager_remove_unneeded_interface_monitors(me);
+       _mdns_dns_service_manager_update_interface_properties(me);
+}
+
+static void
+_mdns_dns_service_manager_finish_defuncting_services(const CFMutableArrayRef services)
+{
+       for (CFIndex i = CFArrayGetCount(services) - 1; i >= 0; --i) {
+               const mdns_dns_service_t service = (mdns_dns_service_t)CFArrayGetValueAtIndex(services, i);
+               if (service->defuncting) {
+                       _mdns_dns_service_make_defunct(service);
+                       CFArrayRemoveValueAtIndex(services, i);
+               }
+       }
+}
+
+static void
+_mdns_dns_service_manager_update_service_usability(const CFMutableArrayRef services)
+{
+       const CFIndex n = CFArrayGetCount(services);
+       for (CFIndex i = 0; i < n; ++i) {
+               const mdns_dns_service_t service = (mdns_dns_service_t)CFArrayGetValueAtIndex(services, i);
+               if (service->cannot_connect) {
+                       if (!(service->flags & mdns_dns_service_flag_connection_problems)) {
+                               service->flags |= mdns_dns_service_flag_connection_problems;
+                       }
+               } else {
+                       if (service->flags & mdns_dns_service_flag_connection_problems) {
+                               service->flags &= ~mdns_dns_service_flag_connection_problems;
+                       }
+               }
+       }
+}
+
+//======================================================================================================================
+
+void
+mdns_dns_service_manager_iterate(const mdns_dns_service_manager_t me, const mdns_dns_service_applier_t applier)
+{
+       dispatch_sync(me->queue,
+       ^{
+               require_return(!me->terminated);
+               _mdns_dns_service_manager_iterate_over_all_service_arrays(me,
+               ^ bool (const CFMutableArrayRef service_array)
+               {
+                       bool stop = false;
+                       const CFIndex n = CFArrayGetCount(service_array);
+                       for (CFIndex i = 0; i < n; ++i) {
+                               const mdns_dns_service_t service = (mdns_dns_service_t)CFArrayGetValueAtIndex(service_array, i);
+                               stop = applier(service);
+                               if (stop) {
+                                       break;
+                               }
+                       }
+                       return stop;
+               });
+       });
+}
+
+//======================================================================================================================
+
+size_t
+mdns_dns_service_manager_get_count(const mdns_dns_service_manager_t me)
+{
+       __block size_t count = 0;
+       dispatch_sync(me->queue,
+       ^{
+               require_return(!me->terminated);
+               _mdns_dns_service_manager_iterate_over_all_service_arrays(me,
+               ^ bool (const CFMutableArrayRef service_array)
+               {
+                       count += (size_t)CFArrayGetCount(service_array);
+                       return false;
+               });
+       });
+       return count;
+}
+
+//======================================================================================================================
+
+void
+mdns_dns_service_manager_handle_sleep(const mdns_dns_service_manager_t me)
+{
+       mdns_dns_service_manager_iterate(me,
+       ^ bool (const mdns_dns_service_t service)
+       {
+               const mdns_resolver_type_t type = _mdns_dns_service_get_resolver_type_safe(service);
+               if ((type == mdns_resolver_type_tls) || (type == mdns_resolver_type_https)) {
+                       if (service->resolver) {
+                               mdns_resolver_forget(&service->resolver);
+                               service->replace_resolver = true;
+                       }
+               }
+               return false;
+       });
+}
+
+//======================================================================================================================
+
+void
+mdns_dns_service_manager_handle_wake(const mdns_dns_service_manager_t me)
+{
+       mdns_dns_service_manager_iterate(me,
+       ^ bool (const mdns_dns_service_t service)
+       {
+               if (service->replace_resolver) {
+                       _mdns_dns_service_manager_prepare_service(me, service);
+                       service->replace_resolver = false;
+               }
+               return false;
+       });
+}
+
+//======================================================================================================================
+// MARK: - DNS Service Manager Private Methods
+
+static void
+_mdns_dns_service_manager_finalize(mdns_dns_service_manager_t me)
+{
+       ForgetCF(&me->default_services);
+       ForgetCF(&me->path_services);
+       ForgetCF(&me->discovered_services);
+       ForgetCF(&me->custom_services);
+       ForgetCF(&me->monitors);
+       dispatch_forget(&me->queue);
+       dispatch_forget(&me->user_queue);
+       BlockForget(&me->event_handler);
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mdns_dns_service_manager_print_description(const mdns_dns_service_manager_t me, const bool debug, const bool privacy,
+       char * const buf_ptr, const size_t buf_len, size_t *out_len, size_t *out_true_len);
+
+static char *
+_mdns_dns_service_manager_copy_description(const mdns_dns_service_manager_t me, const bool debug, const bool privacy)
+{
+       char *description = NULL;
+
+       size_t true_len;
+       char buf[1024];
+       OSStatus err = _mdns_dns_service_manager_print_description(me, debug, privacy, buf, sizeof(buf), NULL, &true_len);
+       require_noerr_quiet(err, exit);
+
+       if (true_len < sizeof(buf)) {
+               description = strdup(buf);
+       } else {
+               const size_t buf_len = true_len + 1;
+               char *buf_ptr = malloc(buf_len);
+               require_quiet(buf_ptr, exit);
+
+               err = _mdns_dns_service_manager_print_description(me, debug, privacy, buf_ptr, buf_len, NULL, NULL);
+               if (!err) {
+                       description = buf_ptr;
+               } else {
+                       free(buf_ptr);
+               }
+       }
+
+exit:
+       return description;
+}
+
+static OSStatus
+_mdns_dns_service_print_description(const mdns_dns_service_t service, const bool debug, const bool privacy,
+       char * const buf_ptr, const size_t buf_len, size_t *out_len, size_t *out_true_len);
+
+static OSStatus
+_mdns_dns_service_manager_print_description(const mdns_dns_service_manager_t me, const bool debug, const bool privacy,
+       char * const buf_ptr, const size_t buf_len, size_t *out_len, size_t *out_true_len)
+{
+       OSStatus                        err;
+       char *                          dst                     = buf_ptr;
+       const char * const      lim                     = &buf_ptr[buf_len];
+       size_t                          true_len        = 0;
+       int                                     n;
+
+#define _do_appendf(...)                                                                               \
+       do {                                                                                                            \
+               n = mdns_snprintf_add(&dst, lim, __VA_ARGS__);                  \
+               require_action_quiet(n >= 0, exit, err = kUnknownErr);  \
+               true_len += (size_t)n;                                                                  \
+       } while(0)
+
+       if (debug) {
+               _do_appendf("<%s: %p>: ", me->base.kind->name, me);
+       }
+       const CFArrayRef service_arrays[] = {
+               MDNS_DNS_SERVICE_MANAGER_ARRAYS(me)
+       };
+       _do_appendf("{");
+       const char *sep = "";
+       for (size_t i = 0; i < countof(service_arrays); ++i) {
+               const CFArrayRef services = service_arrays[i];
+               const CFIndex service_count = CFArrayGetCount(services);
+               for (CFIndex j = 0; j < service_count; ++j) {
+                       _do_appendf("%s\n\t", sep);
+                       size_t len, true_len2;
+                       const mdns_dns_service_t service = (mdns_dns_service_t)CFArrayGetValueAtIndex(services, j);
+                       err = _mdns_dns_service_print_description(service, false, privacy, dst, (size_t)(lim - dst), &len,
+                               &true_len2);
+                       require_noerr_quiet(err, exit);
+
+                       dst += len;
+                       true_len += true_len2;
+                       sep = ",";
+               }
+       }
+       _do_appendf("\n}");
+#undef _do_appendf
+
+       if (out_len) {
+               *out_len = (size_t)(dst - buf_ptr);
+       }
+       if (out_true_len) {
+               *out_true_len = true_len;
+       }
+       err = kNoErr;
+
+exit:
+       return err;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_dns_service_manager_terminate_services(mdns_dns_service_manager_t manager, CFMutableArrayRef services);
+
+static void
+_mdns_dns_service_manager_terminate(const mdns_dns_service_manager_t me, const OSStatus error)
+{
+       require_return(!me->invalidated);
+
+       me->terminated = true;
+       dispatch_source_forget(&me->update_source);
+       CFIndex n = CFArrayGetCount(me->monitors);
+       for (CFIndex i = 0; i < n; ++i) {
+               mdns_interface_monitor_invalidate((mdns_interface_monitor_t)CFArrayGetValueAtIndex(me->monitors, i));
+       }
+       CFArrayRemoveAllValues(me->monitors);
+
+       _mdns_dns_service_manager_terminate_services(me, me->default_services);
+       _mdns_dns_service_manager_terminate_services(me, me->path_services);
+       _mdns_dns_service_manager_terminate_services(me, me->discovered_services);
+       _mdns_dns_service_manager_terminate_services(me, me->custom_services);
+
+       mdns_retain(me);
+       dispatch_async(me->user_queue,
+       ^{
+               if (me->event_handler) {
+                       me->event_handler(error ? mdns_event_error : mdns_event_invalidated, error);
+               }
+               mdns_release(me);
+       });
+}
+
+static void
+_mdns_dns_service_manager_terminate_services(const mdns_dns_service_manager_t me, const CFMutableArrayRef services)
+{
+       const CFIndex n = CFArrayGetCount(services);
+       for (CFIndex i = 0; i < n; ++i) {
+               const mdns_dns_service_t service = (mdns_dns_service_t)CFArrayGetValueAtIndex(services, i);
+               if (service->config && service->config_needs_cancel) {
+                       _mdns_dns_service_manager_cancel_resolver_config_updates(me, service->config);
+                       service->config_needs_cancel = false;
+               }
+               _mdns_dns_service_make_defunct((mdns_dns_service_t)CFArrayGetValueAtIndex(services, i));
+       }
+       CFArrayRemoveAllValues(services);
+}
+
+//======================================================================================================================
+
+static void
+_mdns_dns_service_manager_add_service(const mdns_dns_service_manager_t me, const CFMutableArrayRef services,
+       const mdns_dns_service_t service)
+{
+       CFArrayAppendValue(services, service);
+       _mdns_dns_service_manager_update_interface_properties_for_service(me, service);
+}
+
+//======================================================================================================================
+
+static mdns_dns_service_t
+_mdns_dns_service_manager_get_service(const mdns_dns_service_manager_t me, const uint8_t * const name,
+       const mdns_dns_service_scope_t scope, const uint32_t scoping_id)
+{
+       mdns_dns_service_t      best_service            = NULL;
+       int                                     best_label_count        = -1;
+       uint32_t                        best_order                      = 0;
+       // Find the best service.
+       const CFIndex n = CFArrayGetCount(me->default_services);
+       for (CFIndex i = 0; i < n; ++i) {
+               mdns_dns_service_t candidate = (mdns_dns_service_t)CFArrayGetValueAtIndex(me->default_services, i);
+               if (candidate->scope != scope) {
+                       continue;
+               }
+               switch (scope) {
+                       case mdns_dns_service_scope_interface:
+                               if (candidate->if_index != scoping_id) {
+                                       continue;
+                               }
+                               break;
+
+                       case mdns_dns_service_scope_service:
+                               if (candidate->service_id != scoping_id) {
+                                       continue;
+                               }
+                               break;
+
+                       default:
+                       case mdns_dns_service_scope_none:
+                               break;
+               }
+               uint32_t order = 0;
+               const int label_count = _mdns_dns_service_handles_domain_name(candidate, name, &order);
+               if (label_count < 0) {
+                       continue;
+               }
+               // The longer a service's parent domain match (in terms of label count), the better the service.
+               // If a service has a parent domain match with a label count equal to that of the best service so far,
+               // and its parent domain's order value is less than the current best service's parent domain's order
+               // value (i.e., it has a higher priority), then it's a better service.
+               if ((label_count > best_label_count) || ((label_count == best_label_count) && (order < best_order))) {
+                       best_service            = candidate;
+                       best_label_count        = label_count;
+                       best_order                      = order;
+               }
+       }
+       return best_service;
+}
+
+//======================================================================================================================
+
+static mdns_dns_service_t
+_mdns_dns_service_manager_get_service_by_config_uuid(CFArrayRef services, const uuid_t uuid);
+
+static mdns_dns_service_t
+_mdns_dns_service_manager_get_uuid_scoped_service(const mdns_dns_service_manager_t me, const uuid_t uuid)
+{
+       // First check in discovered services
+       mdns_dns_service_t service = _mdns_dns_service_manager_get_service_by_config_uuid(me->discovered_services, uuid);
+       if (!service) {
+               service = _mdns_dns_service_manager_get_service_by_config_uuid(me->path_services, uuid);
+       }
+       return service;
+}
+
+static mdns_dns_service_t
+_mdns_dns_service_manager_get_service_by_config_uuid(const CFArrayRef services, const uuid_t uuid)
+{
+       const CFIndex n = CFArrayGetCount(services);
+       for (CFIndex i = 0; i < n; ++i) {
+               const mdns_dns_service_t service = (mdns_dns_service_t)CFArrayGetValueAtIndex(services, i);
+               if (service->config) {
+                       uuid_t config_uuid = {0};
+                       nw_resolver_config_get_identifier(service->config, config_uuid);
+                       if (uuid_compare(uuid, config_uuid) == 0) {
+                               return service;
+                       }
+               }
+       }
+       return NULL;
+}
+
+//======================================================================================================================
+
+static mdns_dns_service_t
+_mdns_dns_service_manager_get_discovered_service(const mdns_dns_service_manager_t me, nw_endpoint_t url_endpoint)
+{
+       const char *hostname = nw_endpoint_get_hostname(url_endpoint);
+       const char *path = nw_endpoint_get_url_path(url_endpoint);
+       if (hostname == NULL || path == NULL) {
+               return NULL;
+       }
+
+       const CFIndex n = CFArrayGetCount(me->discovered_services);
+       for (CFIndex i = 0; i < n; ++i) {
+               const mdns_dns_service_t service = (mdns_dns_service_t)CFArrayGetValueAtIndex(me->discovered_services, i);
+               if (service->config &&
+                       nw_resolver_config_get_protocol(service->config) == nw_resolver_protocol_doh) {
+
+                       const char *config_hostname = nw_resolver_config_get_provider_name(service->config);
+                       const char *config_path = nw_resolver_config_get_provider_path(service->config);
+                       if (strcmp(hostname, config_hostname) == 0 ||
+                               strcmp(path, config_path) == 0) {
+                               return service;
+                       }
+               }
+       }
+       return NULL;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_dns_service_manager_update_interface_properties(const mdns_dns_service_manager_t me)
+{
+       _mdns_dns_service_manager_update_interface_properties_for_services(me, me->default_services);
+       _mdns_dns_service_manager_update_interface_properties_for_services(me, me->path_services);
+       _mdns_dns_service_manager_update_interface_properties_for_services(me, me->discovered_services);
+       _mdns_dns_service_manager_update_interface_properties_for_services(me, me->custom_services);
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_dns_service_manager_uses_interface(mdns_dns_service_manager_t manager, uint32_t if_index);
+
+static bool
+_mdns_dns_services_use_interface(CFArrayRef services, uint32_t if_index);
+
+static void
+_mdns_dns_service_manager_remove_unneeded_interface_monitors(const mdns_dns_service_manager_t me)
+{
+       for (CFIndex i = CFArrayGetCount(me->monitors) - 1; i >= 0; --i) {
+               const mdns_interface_monitor_t monitor = (mdns_interface_monitor_t)CFArrayGetValueAtIndex(me->monitors, i);
+               const uint32_t if_index = mdns_interface_monitor_get_interface_index(monitor);
+               const bool needed = _mdns_dns_service_manager_uses_interface(me, if_index);
+               if (!needed) {
+                       mdns_interface_monitor_invalidate(monitor);
+                       CFArrayRemoveValueAtIndex(me->monitors, i);
+               }
+       }
+}
+
+static bool
+_mdns_dns_service_manager_uses_interface(const mdns_dns_service_manager_t me, const uint32_t if_index)
+{
+       if (_mdns_dns_services_use_interface(me->default_services, if_index)    ||
+               _mdns_dns_services_use_interface(me->path_services, if_index)           ||
+               _mdns_dns_services_use_interface(me->discovered_services, if_index)     ||
+               _mdns_dns_services_use_interface(me->custom_services, if_index)) {
+               return true;
+       } else {
+               return false;
+       }
+}
+
+static bool
+_mdns_dns_services_use_interface(const CFArrayRef services, const uint32_t if_index)
+{
+       const CFIndex n = CFArrayGetCount(services);
+       for (CFIndex i = 0; i < n; ++i) {
+               const mdns_dns_service_t service = (mdns_dns_service_t)CFArrayGetValueAtIndex(services, i);
+               if (service->if_index == if_index) {
+                       return true;
+               }
+       }
+       return false;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_dns_service_manager_update_interface_properties_for_services(const mdns_dns_service_manager_t me,
+       const CFArrayRef services)
+{
+       const CFIndex n = CFArrayGetCount(services);
+       for (CFIndex i = 0; i < n; ++i) {
+               const mdns_dns_service_t service = (mdns_dns_service_t)CFArrayGetValueAtIndex(services, i);
+               _mdns_dns_service_manager_update_interface_properties_for_service(me, service);
+       }
+}
+
+//======================================================================================================================
+
+static mdns_interface_monitor_t
+_mdns_dns_service_manager_get_interface_monitor(mdns_dns_service_manager_t manager, uint32_t if_index);
+
+static void
+_mdns_dns_service_manager_update_interface_properties_for_service(const mdns_dns_service_manager_t me,
+       const mdns_dns_service_t service)
+{
+       require_quiet(service->if_index != 0, exit);
+
+       const mdns_interface_monitor_t monitor = _mdns_dns_service_manager_get_interface_monitor(me, service->if_index);
+       require_action_quiet(monitor, exit,
+               os_log_error(_mdns_dns_service_log(), "Failed to get interface monitor for interface %{public}s/%u",
+               service->if_name ? service->if_name : "", service->if_index));
+
+       service->flags &= ~MDNS_DNS_SERVICE_FLAGS_FROM_INTERFACE_MONITOR;
+       if (mdns_interface_monitor_has_ipv4_connectivity(monitor)) {
+               service->flags |= mdns_dns_service_flag_ipv4_connectivity;
+       }
+       if (mdns_interface_monitor_has_ipv6_connectivity(monitor)) {
+               service->flags |= mdns_dns_service_flag_ipv6_connectivity;
+       }
+       if (mdns_interface_monitor_is_expensive(monitor)) {
+               service->flags |= mdns_dns_service_flag_expensive;
+       }
+       if (mdns_interface_monitor_is_constrained(monitor)) {
+               service->flags |= mdns_dns_service_flag_constrained;
+       }
+       if (mdns_interface_monitor_is_clat46(monitor)) {
+               service->flags |= mdns_dns_service_flag_clat46;
+       }
+       if (mdns_interface_monitor_is_vpn(monitor)) {
+               service->flags |= mdns_dns_service_flag_vpn;
+       }
+
+exit:
+       return;
+}
+
+static mdns_interface_monitor_t
+_mdns_dns_service_manager_get_interface_monitor(const mdns_dns_service_manager_t me, const uint32_t if_index)
+{
+       mdns_interface_monitor_t monitor = NULL;
+       const CFIndex n = CFArrayGetCount(me->monitors);
+       for (CFIndex i = 0; i < n; ++i) {
+               mdns_interface_monitor_t candidate = (mdns_interface_monitor_t)CFArrayGetValueAtIndex(me->monitors, i);
+               if (mdns_interface_monitor_get_interface_index(candidate) == if_index) {
+                       monitor = candidate;
+                       break;
+               }
+       }
+       if (monitor) {
+               goto exit;
+       }
+       monitor = mdns_interface_monitor_create(if_index);
+       require_quiet(monitor, exit);
+
+       mdns_interface_monitor_set_queue(monitor, me->queue);
+       mdns_retain(me);
+       mdns_interface_monitor_set_update_handler(monitor,
+       ^(mdns_interface_flags_t change_flags)
+       {
+               const mdns_interface_flags_t relevant_flags =
+                       mdns_interface_flag_ipv4_connectivity   |
+                       mdns_interface_flag_ipv6_connectivity   |
+                       mdns_interface_flag_expensive                   |
+                       mdns_interface_flag_constrained                 |
+                       mdns_interface_flag_clat46                              |
+                       mdns_interface_flag_vpn;
+               if ((change_flags & relevant_flags) != 0) {
+                       const CFRange full_range = CFRangeMake(0, CFArrayGetCount(me->monitors));
+                       if (CFArrayContainsValue(me->monitors, full_range, monitor)) {
+                               _mdns_dns_service_manager_trigger_update(me);
+                       }
+               }
+       });
+       mdns_interface_monitor_set_event_handler(monitor,
+       ^(mdns_event_t event, __unused OSStatus error)
+       {
+               switch (event) {
+                       case mdns_event_invalidated:
+                               mdns_release(monitor);
+                               mdns_release(me);
+                               break;
+
+                       case mdns_event_error: {
+                               const CFRange full_range = CFRangeMake(0, CFArrayGetCount(me->monitors));
+                               const CFIndex i = CFArrayGetFirstIndexOfValue(me->monitors, full_range, monitor);
+                               if (i >= 0) {
+                                       CFArrayRemoveValueAtIndex(me->monitors, i);
+                               }
+                               mdns_interface_monitor_invalidate(monitor);
+                               break;
+                       }
+                       default:
+                               break;
+               }
+    });
+       mdns_interface_monitor_activate(monitor);
+       CFArrayAppendValue(me->monitors, monitor);
+
+exit:
+       return monitor;
+}
+
+//======================================================================================================================
+// MARK: - DNS Service Public Methods
+
+void
+mdns_dns_service_set_context(const mdns_dns_service_t me, void * const context)
+{
+       me->context = context;
+}
+
+//======================================================================================================================
+
+void *
+mdns_dns_service_get_context(const mdns_dns_service_t me)
+{
+       return me->context;
+}
+
+//======================================================================================================================
+
+void
+mdns_dns_service_set_context_finalizer(const mdns_dns_service_t me, const mdns_context_finalizer_t finalizer)
+{
+       me->context_finalizer = finalizer;
+}
+
+//======================================================================================================================
+
+mdns_querier_t
+mdns_dns_service_create_querier(const mdns_dns_service_t me, OSStatus * const out_error)
+{
+       if (me->resolver) {
+               return mdns_resolver_create_querier(me->resolver, out_error);
+       } else {
+               if (out_error) {
+                       *out_error = kNotInUseErr;
+               }
+               return NULL;
+       }
+}
+
+//======================================================================================================================
+
+mdns_dns_service_scope_t
+mdns_dns_service_get_scope(const mdns_dns_service_t me)
+{
+       return me->scope;
+}
+
+//======================================================================================================================
+
+uint32_t
+mdns_dns_service_get_interface_index(const mdns_dns_service_t me)
+{
+       return me->if_index;
+}
+
+//======================================================================================================================
+
+mdns_dns_service_id_t
+mdns_dns_service_get_id(const mdns_dns_service_t me)
+{
+       return _mdns_dns_service_get_id_safe(me);
+}
+
+//======================================================================================================================
+
+bool
+mdns_dns_service_is_defunct(const mdns_dns_service_t me)
+{
+       return ((me->flags & mdns_dns_service_flag_defunct) ? true : false);
+}
+
+//======================================================================================================================
+
+bool
+mdns_dns_service_is_encrypted(mdns_dns_service_t me)
+{
+       return mdns_resolver_type_uses_encryption(_mdns_dns_service_get_resolver_type_safe(me));
+}
+
+//======================================================================================================================
+
+bool
+mdns_dns_service_a_queries_advised(const mdns_dns_service_t me)
+{
+       return ((me->flags & mdns_dns_service_flag_a_queries_advised) ? true : false);
+}
+
+//======================================================================================================================
+
+bool
+mdns_dns_service_aaaa_queries_advised(const mdns_dns_service_t me)
+{
+       return ((me->flags & mdns_dns_service_flag_aaaa_queries_advised) ? true : false);
+}
+
+//======================================================================================================================
+
+bool
+mdns_dns_service_has_connection_problems(const mdns_dns_service_t me)
+{
+       return ((me->flags & mdns_dns_service_flag_connection_problems) ? true : false);
+}
+
+//======================================================================================================================
+
+bool
+mdns_dns_service_interface_has_ipv4_connectivity(const mdns_dns_service_t me)
+{
+       return ((me->flags & mdns_dns_service_flag_ipv4_connectivity) ? true : false);
+}
+
+//======================================================================================================================
+
+bool
+mdns_dns_service_interface_has_ipv6_connectivity(const mdns_dns_service_t me)
+{
+       return ((me->flags & mdns_dns_service_flag_ipv6_connectivity) ? true : false);
+}
+
+//======================================================================================================================
+
+bool
+mdns_dns_service_interface_is_cellular(const mdns_dns_service_t me)
+{
+       return ((me->flags & mdns_dns_service_flag_cellular) ? true : false);
+}
+
+//======================================================================================================================
+
+bool
+mdns_dns_service_interface_is_expensive(const mdns_dns_service_t me)
+{
+       return ((me->flags & mdns_dns_service_flag_expensive) ? true : false);
+}
+
+//======================================================================================================================
+
+bool
+mdns_dns_service_interface_is_constrained(const mdns_dns_service_t me)
+{
+       return ((me->flags & mdns_dns_service_flag_constrained) ? true : false);
+}
+
+//======================================================================================================================
+
+bool
+mdns_dns_service_interface_is_clat46(const mdns_dns_service_t me)
+{
+       return ((me->flags & mdns_dns_service_flag_clat46) ? true : false);
+}
+
+//======================================================================================================================
+
+bool
+mdns_dns_service_interface_is_vpn(const mdns_dns_service_t me)
+{
+       return ((me->flags & mdns_dns_service_flag_vpn) ? true : false);
+}
+
+//======================================================================================================================
+
+const char *
+mdns_dns_service_get_provider_name(const mdns_dns_service_t me)
+{
+       if (me->config) {
+               const char *provider_name = nw_resolver_config_get_provider_name(me->config);
+               if (provider_name) {
+                       return provider_name;
+               }
+       }
+       return NULL;
+}
+
+//======================================================================================================================
+
+mdns_resolver_type_t
+mdns_dns_service_get_resolver_type(const mdns_dns_service_t me)
+{
+       return me->resolver_type;
+}
+
+//======================================================================================================================
+// MARK: - DNS Service Private Methods
+
+static void
+_mdns_dns_service_finalize(const mdns_dns_service_t me)
+{
+       if (me->context) {
+               if (me->context_finalizer) {
+                       me->context_finalizer(me->context);
+               }
+               me->context = NULL;
+       }
+       ForgetCF(&me->addresses);
+       _domain_item_t item;
+       while ((item = me->domain_list) != NULL) {
+               me->domain_list = item->next;
+               _domain_item_free(item);
+       }
+       nw_forget(&me->discovered.url_endpoint);
+       nw_forget(&me->config);
+       ForgetMem(&me->if_name);
+}
+
+//======================================================================================================================
+
+static char *
+_mdns_dns_service_copy_description(const mdns_dns_service_t me, const bool debug, const bool privacy)
+{
+       char *description = NULL;
+
+       size_t true_len;
+       char buf[512];
+       OSStatus err = _mdns_dns_service_print_description(me, debug, privacy, buf, sizeof(buf), NULL, &true_len);
+       require_noerr_quiet(err, exit);
+
+       if (true_len < sizeof(buf)) {
+               description = strdup(buf);
+       } else {
+               const size_t buf_len = true_len + 1;
+               char *buf_ptr = malloc(buf_len);
+               require_quiet(buf_ptr, exit);
+
+               err = _mdns_dns_service_print_description(me, debug, privacy, buf_ptr, buf_len, NULL, NULL);
+               if (!err) {
+                       description = buf_ptr;
+               } else {
+                       free(buf_ptr);
+               }
+       }
+
+exit:
+       return description;
+}
+
+typedef struct {
+       mdns_dns_service_flags_t        flag;
+       const char *                            desc;
+} mdns_dns_service_flag_description_t;
+
+#define MDNS_DNS_SERVICE_REDACTED_STR  "<REDACTED>"
+
+static OSStatus
+_mdns_dns_service_print_description(const mdns_dns_service_t me, const bool debug, const bool privacy,
+       char * const buf_ptr, const size_t buf_len, size_t *out_len, size_t *out_true_len)
+{
+       OSStatus                        err;
+       char *                          dst                             = buf_ptr;
+       const char * const      lim                             = &buf_ptr[buf_len];
+       size_t                          true_len                = 0;
+       char *                          address_desc    = NULL;
+
+#define _do_appendf(...)                                                                                       \
+       do {                                                                                                                    \
+               const int n = mdns_snprintf_add(&dst, lim, __VA_ARGS__);        \
+               require_action_quiet(n >= 0, exit, err = kUnknownErr);          \
+               true_len += (size_t)n;                                                                          \
+       } while(0)
+
+       if (debug) {
+               _do_appendf("<%s: %p>: ", me->base.kind->name, me);
+       }
+
+       // Print ID.
+       _do_appendf("id: %llu", me->ident);
+
+       // Print DNS type.
+       _do_appendf(", type: ");
+       const mdns_resolver_type_t type = _mdns_dns_service_get_resolver_type_safe(me);
+       switch (type) {
+               case mdns_resolver_type_normal:
+                       _do_appendf("Do53");
+                       break;
+
+               case mdns_resolver_type_tls:
+                       _do_appendf("DoT");
+                       break;
+
+               case mdns_resolver_type_https:
+                       _do_appendf("DoH");
+                       break;
+
+               default:
+                       _do_appendf("<unknown type %d>", (int)type);
+                       break;
+       }
+
+       // Print source.
+       _do_appendf(", source: ");
+       switch (me->source) {
+               case mdns_dns_service_source_sc:
+                       _do_appendf("sc");
+                       break;
+
+               case mdns_dns_service_source_nw:
+                       _do_appendf("nw");
+                       break;
+
+               case mdns_dns_service_source_dns:
+                       _do_appendf("dns");
+                       break;
+
+               case mdns_dns_service_source_custom: {
+                       _do_appendf("custom");
+                       break;
+               }
+               default:
+                       _do_appendf("<unknown source %d>", (int)me->source);
+                       break;
+       }
+
+       // Print scope.
+       _do_appendf(", scope: ");
+       switch (me->scope) {
+               case mdns_dns_service_scope_none:
+                       _do_appendf("none");
+                       break;
+
+               case mdns_dns_service_scope_interface:
+                       _do_appendf("interface");
+                       break;
+
+               case mdns_dns_service_scope_service:
+                       _do_appendf("service (%u)", me->service_id);
+                       break;
+
+               case mdns_dns_service_scope_uuid: {
+                       _do_appendf("uuid");
+                       if (!privacy) {
+                               uuid_t uuid = {0};
+                               nw_resolver_config_get_identifier(me->config, uuid);
+                               uuid_string_t uuid_str;
+                               uuid_unparse(uuid, uuid_str);
+                               _do_appendf(" (%s)", uuid_str);
+                       }
+                       break;
+               }
+               default:
+                       _do_appendf("<ERROR: unknown scope %d>", (int)me->scope);
+                       break;
+       }
+
+       // Print interface index.
+       _do_appendf(", interface: %s/%u", me->if_name ? me->if_name : "", me->if_index);
+
+       // Print server addresses.
+       _do_appendf(", servers: {");
+
+       const char *sep = "";
+       const CFIndex address_count = CFArrayGetCount(me->addresses);
+       for (CFIndex i = 0; i < address_count; ++i) {
+               const mdns_address_t address = (mdns_address_t)CFArrayGetValueAtIndex(me->addresses, i);
+               if (privacy) {
+                       const char *str;
+                       char strbuf[64];
+                       const struct sockaddr * const sa = mdns_address_get_sockaddr(address);
+                       const int family = sa->sa_family;
+                       if ((family == AF_INET) || (family == AF_INET6)) {
+                               const int n = mdns_print_obfuscated_ip_address(strbuf, sizeof(strbuf), sa);
+                               if (n >= 0) {
+                                       str = strbuf;
+                               } else {
+                                       str = (family == AF_INET) ? "<IPv4>" : "<IPv6>";
+                               }
+                       } else {
+                               str = "<IPv?>";
+                       }
+                       _do_appendf("%s%s", sep, str);
+                       const int port = mdns_address_get_port(address);
+                       if (port != 0) {
+                               _do_appendf(":%d", port);
+                       }
+               } else {
+                       address_desc = mdns_object_copy_description(address, false, privacy);
+                       _do_appendf("%s%s", sep, address_desc ? address_desc : "<NO DESC.>");
+                       ForgetMem(&address_desc);
+               }
+               sep = ", ";
+       }
+       _do_appendf("}");
+
+       // Print domains.
+       _do_appendf(", domains: {");
+
+       sep = "";
+       for (const struct _domain_item_s *item = me->domain_list; item; item = item->next) {
+               const char *str;
+               char strbuf[64];
+               if (privacy) {
+                       const int n = DNSMessagePrintObfuscatedString(strbuf, sizeof(strbuf), item->name_str);
+                       str = (n >= 0) ? strbuf : MDNS_DNS_SERVICE_REDACTED_STR;
+               } else {
+                       str = item->name_str;
+               }
+               _do_appendf("%s%s", sep, str);
+               if (item->order != 0) {
+                       _do_appendf(" (%u)", item->order);
+               }
+               sep = ", ";
+       }
+       _do_appendf("}");
+
+       // Print attributes.
+       _do_appendf(", attributes: {");
+
+       const mdns_dns_service_flag_description_t mdns_dns_service_flag_service_descs[] = {
+               {mdns_dns_service_flag_defunct,                                 "defunct"},
+               {mdns_dns_service_flag_a_queries_advised,               "a-ok"},
+               {mdns_dns_service_flag_aaaa_queries_advised,    "aaaa-ok"},
+               {mdns_dns_service_flag_connection_problems,             "connection-problems"},
+       };
+       sep = "";
+       for (size_t i = 0; i < countof(mdns_dns_service_flag_service_descs); ++i) {
+               const mdns_dns_service_flag_description_t * const flag_desc = &mdns_dns_service_flag_service_descs[i];
+               if (me->flags & flag_desc->flag) {
+                       _do_appendf("%s%s", sep, flag_desc->desc);
+                       sep = ", ";
+               }
+       }
+       _do_appendf("}");
+
+       // Print interface properties.
+       _do_appendf(", interface properties: {");
+
+       const mdns_dns_service_flag_description_t mdns_dns_service_flag_interface_descs[] = {
+               {mdns_dns_service_flag_cellular,                        "cellular"},
+               {mdns_dns_service_flag_ipv4_connectivity,       "ipv4"},
+               {mdns_dns_service_flag_ipv6_connectivity,       "ipv6"},
+               {mdns_dns_service_flag_expensive,                       "expensive"},
+               {mdns_dns_service_flag_constrained,                     "constrained"},
+               {mdns_dns_service_flag_clat46,                          "clat46"},
+               {mdns_dns_service_flag_vpn,                                     "vpn"}
+       };
+       sep = "";
+       for (size_t i = 0; i < countof(mdns_dns_service_flag_interface_descs); ++i) {
+               const mdns_dns_service_flag_description_t * const flag_desc = &mdns_dns_service_flag_interface_descs[i];
+               if (me->flags & flag_desc->flag) {
+                       _do_appendf("%s%s", sep, flag_desc->desc);
+                       sep = ", ";
+               }
+       }
+       _do_appendf("}");
+
+       // Print additional information from resolver config object that isn't already printed.
+       if (me->config) {
+               _do_appendf(", resolver config: {");
+               const char *provider_name = nw_resolver_config_get_provider_name(me->config);
+               _do_appendf("provider name: ");
+               if (provider_name) {
+                       const char *str;
+                       char strbuf[64];
+                       if (privacy) {
+                               const int n = DNSMessagePrintObfuscatedString(strbuf, sizeof(strbuf), provider_name);
+                               str = (n >= 0) ? strbuf : MDNS_DNS_SERVICE_REDACTED_STR;
+                       } else {
+                               str = provider_name;
+                       }
+                       _do_appendf("%s", str);
+               }
+               const char *provider_path = nw_resolver_config_get_provider_path(me->config);
+               _do_appendf(", provider path: ");
+               if (provider_path) {
+                       const char *str;
+                       char strbuf[64];
+                       if (privacy) {
+                               const int n = DNSMessagePrintObfuscatedString(strbuf, sizeof(strbuf), provider_path);
+                               str = (n >= 0) ? strbuf : MDNS_DNS_SERVICE_REDACTED_STR;
+                       } else {
+                               str = provider_path;
+                       }
+                       _do_appendf("%s", str);
+               }
+               _do_appendf("}");
+       }
+#undef _do_appendf
+
+       if (out_len) {
+               *out_len = (size_t)(dst - buf_ptr);
+       }
+       if (out_true_len) {
+               *out_true_len = true_len;
+       }
+       err = kNoErr;
+
+exit:
+       ForgetMem(&address_desc);
+       return err;
+}
+//======================================================================================================================
+
+static bool
+_mdns_dns_service_equal(const mdns_dns_service_t me, const mdns_dns_service_t other)
+{
+       return _mdns_dns_service_equal_ex(me, other, false);
+}
+
+//======================================================================================================================
+
+static mdns_dns_service_id_t
+_mdns_get_next_dns_service_id(void);
+
+static mdns_dns_service_t
+_mdns_dns_service_create(const mdns_dns_service_source_t source, const mdns_dns_service_scope_t scope,
+       const mdns_resolver_type_t resolver_type, OSStatus * const out_error)
+{
+       OSStatus err;
+       mdns_dns_service_t service = NULL;
+       mdns_dns_service_t obj = _mdns_dns_service_alloc();
+       require_action_quiet(obj, exit, err = kNoMemoryErr);
+
+       obj->ident                      = _mdns_get_next_dns_service_id();
+       obj->source                     = source;
+       obj->scope                      = scope;
+       obj->resolver_type      = resolver_type;
+
+       obj->addresses = CFArrayCreateMutable(kCFAllocatorDefault, 0, &mdns_cfarray_callbacks);
+       require_action_quiet(obj->addresses, exit, err = kNoResourcesErr);
+
+       service = obj;
+       obj = NULL;
+       err = kNoErr;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       mdns_release_null_safe(obj);
+       return service;
+}
+
+static mdns_dns_service_id_t
+_mdns_get_next_dns_service_id(void)
+{
+       static _Atomic(mdns_dns_service_id_t) s_next_id = ATOMIC_VAR_INIT(1);
+       return atomic_fetch_add_explicit(&s_next_id, 1, memory_order_relaxed);
+}
+
+//======================================================================================================================
+
+#define MDNS_INITIAL_DGRAM_RTX_INTERVAL_NONCELLULAR_SECS       1
+#define MDNS_INITIAL_DGRAM_RTX_INTERVAL_CELLULAR_SECS          2
+
+static void
+_mdns_dns_service_manager_handle_resolver_event(mdns_dns_service_manager_t me, mdns_dns_service_t service,
+       mdns_resolver_t resolver, mdns_resolver_event_t event, xpc_object_t info);
+
+static void
+_mdns_dns_service_manager_prepare_resolver(const mdns_dns_service_manager_t me, const mdns_dns_service_t service)
+{
+       require_return(!service->resolver);
+
+       // Determine the appropriate resolver type.
+       const mdns_resolver_type_t type = _mdns_dns_service_get_resolver_type_safe(service);
+       require_return(type != mdns_resolver_type_null);
+
+       // Create the resolver.
+       OSStatus err;
+       mdns_resolver_t resolver = mdns_resolver_create(type, service->if_index, &err);
+       require_action_quiet(resolver, exit, os_log_error(_mdns_dns_service_log(),
+               "Failed to create resolver for service -- service id: %llu", service->ident));
+
+       // Set up the resolver.
+       if (service->config) {
+               mdns_resolver_set_provider_name(resolver, nw_resolver_config_get_provider_name(service->config));
+               mdns_resolver_set_url_path(resolver, nw_resolver_config_get_provider_path(service->config));
+       }
+       // Squash CNAMES if this discovered config requires it.
+       if (service->discovered.squash_cnames) {
+               mdns_resolver_set_squash_cnames(resolver, true);
+       }
+       const uint32_t interval_secs = mdns_dns_service_interface_is_cellular(service) ?
+               MDNS_INITIAL_DGRAM_RTX_INTERVAL_CELLULAR_SECS : MDNS_INITIAL_DGRAM_RTX_INTERVAL_NONCELLULAR_SECS;
+       mdns_resolver_set_initial_datagram_retransmission_interval(resolver, interval_secs);
+       mdns_resolver_enable_symptom_reporting(resolver, me->report_symptoms);
+       if (type == mdns_resolver_type_normal) {
+               mdns_resolver_disable_connection_reuse(resolver, true);
+       #if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+               mdns_resolver_enable_problematic_qtype_workaround(resolver, me->pqw_threshold);
+       #endif
+       }
+       const CFIndex n = CFArrayGetCount(service->addresses);
+       CFIndex add_count = 0;
+       for (CFIndex i = 0; i < n; ++i) {
+               const mdns_address_t address = (mdns_address_t)CFArrayGetValueAtIndex(service->addresses, i);
+               const OSStatus add_err = mdns_resolver_add_server_address(resolver, address);
+               if (likely(!add_err)) {
+                       ++add_count;
+               } else {
+                       os_log_error(_mdns_dns_service_log(),
+                               "Failed to add address to resolver -- service id: %llu, address: %@, error: %{mdns:err}ld",
+                               service->ident, address, (long)add_err);
+               }
+       }
+       require_quiet((n == 0) || (add_count > 0), exit);
+
+       mdns_resolver_set_queue(resolver, me->queue);
+       mdns_retain(me);
+       mdns_retain(resolver);
+       mdns_retain(service);
+       mdns_resolver_set_event_handler(resolver,
+       ^(const mdns_resolver_event_t event, const xpc_object_t info)
+       {
+               _mdns_dns_service_manager_handle_resolver_event(me, service, resolver, event, info);
+       });
+       service->resolver = resolver;
+       resolver = NULL;
+
+       // Reset the "cannot connect" state if the service used to have a resolver that couldn't connect.
+       if (service->cannot_connect) {
+               service->cannot_connect = false;
+               _mdns_dns_service_manager_trigger_update(me);
+       }
+       mdns_resolver_activate(service->resolver);
+
+exit:
+       mdns_release_null_safe(resolver);
+}
+
+static void
+_mdns_dns_service_manager_handle_resolver_event(const mdns_dns_service_manager_t me,
+       const mdns_dns_service_t service, const mdns_resolver_t resolver, const mdns_resolver_event_t event,
+       const xpc_object_t info)
+{
+       switch (event) {
+               case mdns_resolver_event_connection: {
+                       require_quiet(info, exit);
+                       require_quiet(service->resolver == resolver, exit);
+
+                       bool cannot_connect = xpc_dictionary_get_bool(info, MDNS_RESOLVER_EVENT_CONNECTION_INFO_KEY_CANNOT_CONNECT);
+                       os_log(_mdns_dns_service_log(),
+                               "Resolver can%{public}s connect -- service id: %llu, resolver: %@",
+                               cannot_connect ? "not" : "", service->ident, resolver);
+                       if (cannot_connect) {
+                               if (!service->cannot_connect) {
+                                       service->cannot_connect = true;
+                                       _mdns_dns_service_manager_trigger_update(me);
+                               }
+                       } else {
+                               if (service->cannot_connect) {
+                                       service->cannot_connect = false;
+                                       _mdns_dns_service_manager_trigger_update(me);
+                               }
+                       }
+                       break;
+               }
+               case mdns_resolver_event_invalidated: {
+                       os_log_info(_mdns_dns_service_log(),
+                               "Resolver has been invalidated -- service id: %llu, resolver: %@", service->ident, resolver);
+                       mdns_release(resolver);
+                       mdns_release(service);
+                       mdns_release(me);
+                       break;
+               }
+               default: {
+                       if (os_log_debug_enabled(_mdns_dns_service_log())) {
+                               char *info_desc = info ? xpc_copy_description(info) : NULL;
+                               os_log_debug(_mdns_dns_service_log(),
+                                       "DNS service (%@) got unhandled event: %s info: %{public}s",
+                                       service, mdns_resolver_event_to_string(event), info_desc);
+                               ForgetMem(&info_desc);
+                       }
+                       break;
+               }
+       }
+
+exit:
+       return;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_dns_service_manager_start_defuncting(const mdns_dns_service_manager_t me, const mdns_dns_service_t service)
+{
+       if (!service->defuncting) {
+               service->defuncting = true;
+               _mdns_dns_service_manager_trigger_update(me);
+       }
+}
+
+//======================================================================================================================
+
+static mdns_dns_service_t
+_mdns_dns_service_manager_prepare_service(const mdns_dns_service_manager_t me, const mdns_dns_service_t service)
+{
+       require_return_value(service, NULL);
+       _mdns_dns_service_manager_prepare_resolver(me, service);
+       require_return_value_action(service->resolver, NULL,
+               os_log_error(_mdns_dns_service_log(), "Failed to prepare resolver -- service id: %llu", service->ident));
+       return service;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_dns_service_manager_trigger_update(const mdns_dns_service_manager_t me)
+{
+       if (me->update_source) {
+               dispatch_source_merge_data(me->update_source, 1);
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_dns_service_manager_iterate_over_all_service_arrays(const mdns_dns_service_manager_t me,
+       const mdns_dns_service_array_applier_t applier)
+{
+       const CFMutableArrayRef all_arrays[] = {
+               MDNS_DNS_SERVICE_MANAGER_ARRAYS(me)
+       };
+       for (size_t i = 0; i < countof(all_arrays); ++i) {
+               const bool stop = applier(all_arrays[i]);
+               if (stop) {
+                       break;
+               }
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_dns_service_make_defunct(const mdns_dns_service_t me)
+{
+       me->flags |= mdns_dns_service_flag_defunct;
+       mdns_resolver_forget(&me->resolver);
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_dns_service_equal_ex(const mdns_dns_service_t me, const mdns_dns_service_t other, const bool ignore_domains)
+{
+       if (me == other) {
+               return true;
+       }
+       if (me->scope != other->scope) {
+               return false;
+       }
+       if (me->if_index != other->if_index) {
+               return false;
+       }
+       if ((me->scope == mdns_dns_service_scope_service) && (me->service_id != other->service_id)) {
+               return false;
+       }
+       if (!CFEqual(me->addresses, other->addresses)) {
+               return false;
+       }
+       if (!ignore_domains) {
+               const struct _domain_item_s *d1 = me->domain_list;
+               const struct _domain_item_s *d2 = other->domain_list;
+               while (d1 && d2) {
+                       if (_domain_item_compare(d1, d2, false) != 0) {
+                               return false;
+                       }
+                       d1 = d1->next;
+                       d2 = d2->next;
+               }
+               if (d1 || d2) {
+                       return false;
+               }
+       }
+       return true;
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mdns_dns_service_add_domain(const mdns_dns_service_t me, const char * const name_str, const uint32_t order)
+{
+       OSStatus err;
+       _domain_item_t new_item = (_domain_item_t)calloc(1, sizeof(*new_item));
+       require_action_quiet(new_item, exit, err = kNoMemoryErr);
+
+       uint8_t name[kDomainNameLengthMax];
+       err = DomainNameFromString(name, name_str, NULL);
+       require_noerr_quiet(err, exit);
+
+       char normalized_name_str[kDNSServiceMaxDomainName];
+       err = DomainNameToString(name, NULL, normalized_name_str, NULL);
+       require_noerr_quiet(err, exit);
+
+       new_item->name_str = strdup(normalized_name_str);
+       require_action_quiet(new_item->name_str, exit, err = kNoMemoryErr);
+
+       err = DomainNameDup(name, &new_item->name, NULL);
+       require_noerr_quiet(err, exit);
+
+       new_item->label_count = DomainNameLabelCount(new_item->name);
+       require_action_quiet(new_item->label_count >= 0, exit, err = kMalformedErr);
+
+       new_item->order = order;
+       _domain_item_t *ptr;
+       _domain_item_t item;
+       for (ptr = &me->domain_list; (item = *ptr) != NULL; ptr = &item->next) {
+               // Compare domain items, but ignore their order values.
+               const int cmp = _domain_item_compare(new_item, item, true);
+               if (cmp < 0) {
+                       break;
+               }
+               if (cmp == 0) {
+                       // The domain items are equal, but may have different order values. Keep the smaller order
+                       // (higher priority) value. The domain item with the larger order (lower priority) value is redundant.
+                       if (new_item->order < item->order) {
+                               item->order = new_item->order;
+                       }
+                       goto exit;
+               }
+       }
+       new_item->next = item;
+       *ptr = new_item;
+       new_item = NULL;
+
+exit:
+       if (new_item) {
+               _domain_item_free(new_item);
+       }
+       return err;
+}
+
+//======================================================================================================================
+
+static int
+_mdns_dns_service_handles_domain_name(const mdns_dns_service_t me, const uint8_t * const name,
+       uint32_t * const out_order)
+{
+       int result;
+       const int label_count = DomainNameLabelCount(name);
+       require_action_quiet(label_count >= 0, exit, result = -1);
+
+       const struct _domain_item_s *item;
+       for (item = me->domain_list; item; item = item->next) {
+               if (label_count < item->label_count) {
+                       continue;
+               }
+               const uint8_t * const ptr = _mdns_domain_name_get_parent(name, label_count - item->label_count);
+               if (DomainNameEqual(ptr, item->name)) {
+                       break;
+               }
+       }
+       require_action_quiet(item, exit, result = -1);
+
+       result = item->label_count;
+       if (out_order) {
+               *out_order = item->order;
+       }
+
+exit:
+       return result;
+}
+
+//======================================================================================================================
+
+static mdns_resolver_type_t
+_mdns_dns_service_get_resolver_type_safe(const mdns_dns_service_t me)
+{
+       if (me->config && (me->resolver_type == mdns_resolver_type_null)) {
+               const nw_resolver_protocol_t proto = nw_resolver_config_get_protocol(me->config);
+               switch (proto) {
+                       case nw_resolver_protocol_dns53:
+                               return mdns_resolver_type_normal;
+
+                       case nw_resolver_protocol_dot:
+                               return mdns_resolver_type_tls;
+
+                       case nw_resolver_protocol_doh:
+                               return mdns_resolver_type_https;
+
+                       default:
+                               return mdns_resolver_type_null;
+               }
+       } else {
+               return me->resolver_type;
+       }
+}
+
+//======================================================================================================================
+// MARK: - Local Helpers
+
+static OSStatus
+_mdns_append_dns_service_from_config_by_scope(CFMutableArrayRef services, const dns_config_t *config,
+       mdns_dns_service_scope_t scope);
+
+static CFMutableArrayRef
+_mdns_create_dns_service_array_from_config(const dns_config_t * const config, OSStatus * const out_error)
+{
+       OSStatus                        err;
+       CFMutableArrayRef       result = NULL;
+
+       CFMutableArrayRef services = CFArrayCreateMutable(kCFAllocatorDefault, 0, &mdns_cfarray_callbacks);
+       require_action_quiet(services, exit, err = kNoResourcesErr);
+
+       err = _mdns_append_dns_service_from_config_by_scope(services, config, mdns_dns_service_scope_none);
+       require_noerr_quiet(err, exit);
+
+       err = _mdns_append_dns_service_from_config_by_scope(services, config, mdns_dns_service_scope_interface);
+       require_noerr_quiet(err, exit);
+
+       err = _mdns_append_dns_service_from_config_by_scope(services, config, mdns_dns_service_scope_service);
+       require_noerr_quiet(err, exit);
+
+       result = services;
+       services = NULL;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       CFReleaseNullSafe(services);
+       return result;
+}
+
+#define MDNS_DNS_SERVICE_DNS_PORT      53
+#define MDNS_DNS_SERVICE_MDNS_PORT     5353
+
+static OSStatus
+_mdns_append_dns_service_from_config_by_scope(const CFMutableArrayRef services, const dns_config_t * const config,
+       const mdns_dns_service_scope_t scope)
+{
+       OSStatus                                        err;
+       mdns_dns_service_t                      new_service = NULL;
+       dns_resolver_t * const *        resolver_array;
+       int32_t                                         resolver_count;
+       switch (scope) {
+               case mdns_dns_service_scope_none:
+                       resolver_array = config->resolver;
+                       resolver_count = config->n_resolver;
+                       break;
+
+               case mdns_dns_service_scope_interface:
+                       resolver_array = config->scoped_resolver;
+                       resolver_count = config->n_scoped_resolver;
+                       break;
+
+               case mdns_dns_service_scope_service:
+                       resolver_array = config->service_specific_resolver;
+                       resolver_count = config->n_service_specific_resolver;
+                       break;
+
+               default:
+                       err = kNoErr;
+                       goto exit;
+       }
+       for (int32_t i = 0; i < resolver_count; ++i) {
+               const dns_resolver_t * const resolver = resolver_array[i];
+               if ((resolver->port == MDNS_DNS_SERVICE_MDNS_PORT) || (resolver->n_nameserver == 0)) {
+                       continue;
+               }
+               // Don't let a malformed domain name prevent parsing the remaining config.
+               if (resolver->domain) {
+                       uint8_t domain[kDomainNameLengthMax];
+                       if (DomainNameFromString(domain, resolver->domain, NULL) != kNoErr) {
+                               os_log_error(_mdns_dns_service_log(),
+                                       "Encountered invalid dns_config_t resolver domain name: %s", resolver->domain);
+                               continue;
+                       }
+               }
+               new_service = _mdns_dns_service_create(mdns_dns_service_source_sc, scope, mdns_resolver_type_normal, &err);
+               require_noerr_quiet(err, exit);
+
+               const uint16_t port = (resolver->port == 0) ? MDNS_DNS_SERVICE_DNS_PORT : resolver->port;
+               for (int32_t j = 0; j < resolver->n_nameserver; ++j) {
+                       const struct sockaddr * const sa = resolver->nameserver[j];
+                       mdns_address_t address;
+                       if (sa->sa_family == AF_INET) {
+                               const struct sockaddr_in * const sin = (const struct sockaddr_in *)sa;
+                               address = mdns_address_create_ipv4(ntohl(sin->sin_addr.s_addr), port);
+                               require_action_quiet(address, exit, err = kNoMemoryErr);
+                       } else if (sa->sa_family == AF_INET6) {
+                               struct sockaddr_in6 sin6_fixed;
+                               const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sa;
+                               if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) && (resolver->if_index != 0) &&
+                                       (sin6->sin6_scope_id != resolver->if_index)) {
+                                       sin6_fixed = *sin6;
+                                       sin6_fixed.sin6_scope_id = resolver->if_index;
+                                       os_log(_mdns_dns_service_log(),
+                                               "Corrected scope ID of link-local server address %{network:sockaddr}.*P from %u to %u",
+                                               (int)sizeof(*sin6), sin6, sin6->sin6_scope_id, sin6_fixed.sin6_scope_id);
+                                       sin6 = &sin6_fixed;
+                               }
+                               address = mdns_address_create_ipv6(sin6->sin6_addr.s6_addr, port, sin6->sin6_scope_id);
+                               require_action_quiet(address, exit, err = kNoMemoryErr);
+                       } else {
+                               continue;
+                       }
+                       CFArrayAppendValue(new_service->addresses, address);
+                       mdns_forget(&address);
+               }
+               new_service->if_index   = resolver->if_index;
+               new_service->service_id = (scope == mdns_dns_service_scope_service) ? resolver->service_identifier : 0;
+               new_service->flags              = mdns_dns_service_flag_null;
+
+               // Check if a service object that's identical in every way except domains and flags already exists.
+               const char * const domain_str = resolver->domain ? resolver->domain : ".";
+               const CFIndex n = CFArrayGetCount(services);
+               for (CFIndex j = 0; j < n; ++j) {
+                       const mdns_dns_service_t service = (mdns_dns_service_t)CFArrayGetValueAtIndex(services, j);
+                       if (_mdns_dns_service_equal_ex(service, new_service, true)) {
+                               // Simply add the domain to the existing service.
+                               err = _mdns_dns_service_add_domain(service, domain_str, resolver->search_order);
+                               require_noerr_quiet(err, exit);
+                               mdns_forget(&new_service);
+                               break;
+                       }
+               }
+
+               // If no existing service was found, add this one.
+               if (new_service) {
+                       if (resolver->flags & DNS_RESOLVER_FLAGS_REQUEST_A_RECORDS) {
+                               new_service->flags |= mdns_dns_service_flag_a_queries_advised;
+                       }
+                       if (resolver->flags & DNS_RESOLVER_FLAGS_REQUEST_AAAA_RECORDS) {
+                               new_service->flags |= mdns_dns_service_flag_aaaa_queries_advised;
+                       }
+               #if !(TARGET_OS_OSX)
+                       if (resolver->reach_flags & kSCNetworkReachabilityFlagsIsWWAN) {
+                               new_service->flags |= mdns_dns_service_flag_cellular;
+                       }
+               #endif
+                       if (new_service->if_index != 0) {
+                               const char *name_ptr;
+                               char name_buf[IF_NAMESIZE + 1];
+                               name_ptr = if_indextoname(new_service->if_index, name_buf);
+                               const int name_err = map_global_value_errno(name_ptr, name_ptr);
+                               if (!name_err) {
+                                       new_service->if_name = strdup(name_ptr);
+                               } else {
+                                       os_log_error(_mdns_dns_service_log(),
+                                               "if_indextoname() for %u failed: %{darwin.errno}d", new_service->if_index, name_err);
+                               }
+                       }
+                       err = _mdns_dns_service_add_domain(new_service, domain_str, resolver->search_order);
+                       require_noerr_quiet(err, exit);
+                       CFArrayAppendValue(services, new_service);
+                       mdns_forget(&new_service);
+               }
+       }
+       err = kNoErr;
+
+exit:
+       mdns_release_null_safe(new_service);
+       return err;
+}
+
+//======================================================================================================================
+
+static mdns_dns_service_t
+_mdns_dns_service_create_from_resolver_config(const nw_resolver_config_t config, const mdns_dns_service_source_t source,
+       OSStatus * const out_error)
+{
+       OSStatus err;
+       const mdns_dns_service_t service = _mdns_dns_service_create(source, mdns_dns_service_scope_uuid,
+               mdns_resolver_type_null, &err);
+       require_noerr_quiet(err, exit);
+
+       nw_resolver_config_enumerate_name_servers(config,
+       ^ bool (const char * _Nonnull name_server)
+       {
+               mdns_address_t address = mdns_address_create_from_ip_address_string(name_server);
+               if (address) {
+                       CFArrayAppendValue(service->addresses, address);
+                       mdns_forget(&address);
+               }
+               return true;
+       });
+       nw_resolver_config_enumerate_match_domains(config,
+       ^ bool (const char * _Nonnull match_domain)
+       {
+               _mdns_dns_service_add_domain(service, match_domain, 0);
+               return true;
+       });
+       service->config = config;
+       nw_retain(service->config);
+       const char * const interface_name = nw_resolver_config_get_interface_name(config);
+       if (interface_name) {
+               service->if_name        = strdup(interface_name);
+               service->if_index       = if_nametoindex(interface_name);
+       }
+       service->flags = (mdns_dns_service_flag_a_queries_advised | mdns_dns_service_flag_aaaa_queries_advised);
+       err = kNoErr;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       return service;
+}
+
+//======================================================================================================================
+
+static mdns_dns_service_id_t
+_mdns_dns_service_get_id_safe(const mdns_dns_service_t me)
+{
+       require_return_value(me, 0);
+       return me->ident;
+}
+
+//======================================================================================================================
+
+static const uint8_t *
+_mdns_domain_name_get_parent(const uint8_t * const name, const int depth)
+{
+       int current_depth = 0;
+       const uint8_t *ptr = name;
+       while ((*ptr != 0) && (current_depth < depth)) {
+               ptr += (1 + *ptr);
+               ++current_depth;
+       }
+       return ptr;
+}
+
+//======================================================================================================================
+
+static void
+_domain_item_free(const _domain_item_t item)
+{
+       ForgetMem(&item->name);
+       ForgetMem(&item->name_str);
+       free(item);
+}
+
+//======================================================================================================================
+
+static int
+_domain_item_compare(const struct _domain_item_s * const d1, const struct _domain_item_s * const d2,
+       const bool ignore_order)
+{
+       // The domain name with the greater label count precedes the other.
+       int diff = d1->label_count - d2->label_count;
+       if (diff > 0) {
+               return -1;
+       }
+       if (diff < 0) {
+               return 1;
+       }
+       // The domain name with the lexicographically lesser rightmost label precedes the other.
+       // Compare each pair of non-root labels from right to left.
+       for (int depth = d1->label_count; depth-- > 0; ) {
+               const uint8_t * const label1 = _mdns_domain_name_get_parent(d1->name, depth);
+               const uint8_t * const label2 = _mdns_domain_name_get_parent(d2->name, depth);
+               const int length1 = label1[0];
+               const int length2 = label2[0];
+               const int n = Min(length1, length2);
+               for (int i = 1; i <= n; ++i) {
+                       diff = tolower_safe(label1[i]) - tolower_safe(label2[i]);
+                       if (diff < 0) {
+                               return -1;
+                       }
+                       if (diff > 0) {
+                               return 1;
+                       }
+               }
+               diff = length1 - length2;
+               if (diff < 0) {
+                       return -1;
+               }
+               if (diff > 0) {
+                       return 1;
+               }
+       }
+       if (!ignore_order) {
+               // The domain name with the smaller order (higher priority) precedes the other.
+               if (d1->order < d2->order) {
+                       return -1;
+               }
+               if (d1->order > d2->order) {
+                       return 1;
+               }
+       }
+       return 0;
+}
diff --git a/mDNSMacOSX/mdns_objects/mdns_dns_service.h b/mDNSMacOSX/mdns_objects/mdns_dns_service.h
new file mode 100644 (file)
index 0000000..4bc76ff
--- /dev/null
@@ -0,0 +1,790 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_DNS_SERVICE_H__
+#define __MDNS_DNS_SERVICE_H__
+
+#include "mdns_base.h"
+#include "mdns_object.h"
+#include "mdns_resolver.h"
+
+#include <dnsinfo.h>
+#include <MacTypes.h>
+#include <stdint.h>
+#include <uuid/uuid.h>
+#include <xpc/xpc.h>
+
+MDNS_DECL(dns_service);
+MDNS_DECL(dns_service_manager);
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
+
+/*!
+ *     @brief
+ *             Creates a DNS service manager, which manages DNS services from a DNS configuration.
+ *
+ *     @param queue
+ *             Dispatch queue for event handler.
+ *
+ *     @param out_error
+ *             Pointer to an OSStatus variable, which will be set to the error that was encountered during creation.
+ *
+ *     @result
+ *             A new DNS service manager object or NULL if there was a lack of resources.
+ */
+MDNS_RETURNS_RETAINED MDNS_WARN_RESULT
+mdns_dns_service_manager_t _Nullable
+mdns_dns_service_manager_create(dispatch_queue_t queue, OSStatus * _Nullable out_error);
+
+/*!
+ *     @brief
+ *             Sets whether a DNS service manager is to report DNS server responsiveness symptoms.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @param report_symptoms
+ *             Whether or not a DNS service manager is to report DNS server responsiveness symptoms.
+ *
+ *     @discussion
+ *             This function has no effect on a manager after a call to
+ *             <code>mdns_dns_service_manager_activate()</code>.
+ */
+void
+mdns_dns_service_manager_set_report_symptoms(mdns_dns_service_manager_t manager, bool report_symptoms);
+
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+/*!
+ *     @brief
+ *             For each Do53 DNS service managed by a DNS service manager, enables or disables a workaround where the
+ *             DNS service's queriers will refrain from sending queries of type SVCB and HTTPS to a server if the
+ *             server has been determined to not respond to queries of those types.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @param threshold
+ *             If greater than zero, the workaround is enabled. Otherwise, the workaround is disabled.
+ *
+ *     @discussion
+ *             This is a workaround for DNS servers that don't respond to SVCB and HTTPS queries and then become less
+ *             responsive to queries of other types as more SVCB and HTTPS retry queries are sent.
+ *
+ *             The workaround is disabled by default.
+ *
+ *             This function has no effect on a DNS service manager afer a call to mdns_dns_service_manager_activate().
+ */
+void
+mdns_dns_service_manager_enable_problematic_qtype_workaround(mdns_dns_service_manager_t manager, int threshold);
+#endif
+
+/*!
+ *     @brief
+ *             Sets a DNS service manager's event handler.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @param handler
+ *             The event handler.
+ *
+ *     @discussion
+ *             The event handler will never be invoked prior to a call to either
+ *             <code>mdns_dns_service_manager_activate()()</code> or
+ *             <code>mdns_dns_service_manager_invalidate()</code>.
+ *
+ *             The event handler will be invoked on the dispatch queue specified by
+ *             <code>mdns_dns_service_manager_create()</code> with event <code>mdns_event_error</code> when a fatal error
+ *             occurs, with event <code>mdns_event_invalidated</code> when the interface monitor has been invalidated, and
+ *             with <code>mdns_event_update</code> when there are pending DNS service updates.
+ *
+ *             After an <code>mdns_event_invalidated</code> event, the event handler will never be invoked again.
+ */
+void
+mdns_dns_service_manager_set_event_handler(mdns_dns_service_manager_t manager, mdns_event_handler_t handler);
+
+/*!
+ *     @brief
+ *             Activates a DNS service manager.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @discussion
+ *             This function should be called on a new DNS service manager after after setting its properties and event
+ *             handler.
+ */
+void
+mdns_dns_service_manager_activate(mdns_dns_service_manager_t manager);
+
+/*!
+ *     @brief
+ *             Synchronously processes a DNS configuration.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @param config
+ *             A dnsinfo DNS configuration. See <dnsinfo.h>.
+ *
+ *     @discussion
+ *             This function ensures that a DNS service object is instantiated for each DNS service contained in this DNS
+ *             configuration. DNS service objects that were created for previous DNS configurations, but that are not
+ *             present in this configuration, are marked as defunct.
+ */
+void
+mdns_dns_service_manager_apply_dns_config(mdns_dns_service_manager_t manager, const dns_config_t *config);
+
+/*!
+ *     @brief
+ *             Add a dynamic resolver configuration to the service manager based on a resolver UUID.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @param resolver_config_uuid
+ *             A UUID of a resolver configuration registered with the system.
+ *
+ *     @discussion
+ *             This function registers a UUID with the service manager if it does not exist already. The UUID will be used
+ *             to look up the details of the resolver configuration.
+ */
+void
+mdns_dns_service_manager_register_path_resolver(mdns_dns_service_manager_t manager,
+       const uuid_t _Nonnull resolver_config_uuid);
+
+/*!
+ * @typedef mdns_dns_service_id_t
+ *
+ * @abstract
+ *             A unique per-process identifier for DNS service objects.
+ *
+ * @discussion
+ *             Useful as an alternative to a pointer to a DNS service when the DNS service itself isn't actually
+ *             needed. This identifier can be used to safely distinguish one DNS service object from another even after
+ *             one or both have been released.
+ *
+ *             The zero value is reserved as an invalid ID.
+ */
+typedef uint64_t mdns_dns_service_id_t;
+
+#define MDNS_DNS_SERVICE_MAX_ID        ((mdns_dns_service_id_t)-1)
+
+/*!
+ *     @brief
+ *             Registers a custom DNS service based on an nw_resolver_config dictionary with a DNS service manager.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @param resolver_config_dict
+ *             The nw_resolver_config dictionary.
+ *
+ *     @result
+ *             If the registration is successful, this function returns a non-zero identifier for the custom DNS
+ *             service.
+ *
+ *     @discussion
+ *             When the custom DNS service is no longer needed by the entity that registered it, it should be
+ *             deregistered with <code>mdns_dns_service_manager_deregister_custom_service()</code>.
+ */
+MDNS_WARN_RESULT
+mdns_dns_service_id_t
+mdns_dns_service_manager_register_custom_service(mdns_dns_service_manager_t manager, xpc_object_t resolver_config_dict);
+
+/*!
+ *     @brief
+ *             Deregisters a custom DNS service from a DNS service manager.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @param ident
+ *             The identifier returned by <code>mdns_dns_service_manager_register_custom_service()</code> when the
+ *             custom DNS service was registered.
+ */
+void
+mdns_dns_service_manager_deregister_custom_service(mdns_dns_service_manager_t manager, mdns_dns_service_id_t ident);
+
+/*!
+ *     @brief
+ *             Add a custom resolver configuration to the service manager associated with a particular handle.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @param doh_uri
+ *             A URI of a DoH server, as a string.
+ *
+ *     @param domain
+ *             A domain to link to a DoH server.
+ *
+ *     @discussion
+ *             This function registers a DoH URI with the service manager if it does not exist already.
+ */
+void
+mdns_dns_service_manager_register_doh_uri(const mdns_dns_service_manager_t manager,
+       const char *doh_uri, const char * _Nullable domain);
+
+/*!
+ *     @brief
+ *             Asynchronously invalidates a DNS service manager.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @discussion
+ *             This function should be called when a DNS service manager is no longer needed.
+ *
+ *             As a result of calling this function, the DNS service manager's event handler will be invoked with a
+ *             <code>mdns_event_invalidated</code> event, after which the DNS service manager's event handler will never
+ *             be invoked again.
+ *
+ *             This function has no effect on a DNS service manager that has already been invalidated.
+ */
+void
+mdns_dns_service_manager_invalidate(mdns_dns_service_manager_t manager);
+
+/*!
+ *     @brief
+ *             Gets the most suitable unscoped DNS service that can be used to query for a record with the given domain
+ *             name.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @param name
+ *             The domain name in label format.
+ *
+ *     @discussion
+ *             This function returns the most suitable unscoped DNS service from the latest DNS configuration that can be
+ *             used to query for a record with the given domain name if such a service even exists.
+ *
+ *             If a service is returned, there's no guarantee that the reference will be valid after the next call to
+ *             either <code>mdns_dns_service_manager_apply_dns_config()</code> or
+ *             <code>mdns_dns_service_manager_apply_pending_updates()</code> unless the service is retained by the caller.
+ *
+ *     @result
+ *             A non-retained service if a suitable service exists. Otherwise, NULL.
+ */
+mdns_dns_service_t _Nullable
+mdns_dns_service_manager_get_unscoped_service(mdns_dns_service_manager_t manager, const uint8_t *name);
+
+/*!
+ *     @brief
+ *             Gets the most suitable interface-scoped DNS service that can be used to query for a record with the given
+ *             domain name.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @param name
+ *             The domain name in label format.
+ *
+ *     @param if_index
+ *             The index of the interface to which the interface-scoped service must be scoped.
+ *
+ *     @discussion
+ *             This function returns the most suitable interface-scoped DNS service from the latest DNS configuration that
+ *             can be used to query for a record with the given domain name if such a service even exists.
+ *
+ *             If a service is returned, there's no guarantee that the reference will be valid after the next call to
+ *             either <code>mdns_dns_service_manager_apply_dns_config()</code> or
+ *             <code>mdns_dns_service_manager_apply_pending_updates()</code> unless the service is retained by the caller.
+ *
+ *     @result
+ *             A non-retained service if a suitable service exists. Otherwise, NULL.
+ */
+mdns_dns_service_t _Nullable
+mdns_dns_service_manager_get_interface_scoped_service(mdns_dns_service_manager_t manager, const uint8_t *name,
+       uint32_t if_index);
+
+/*!
+ *     @brief
+ *             Gets the most suitable service-scoped DNS service that can be used to query for a record with the given
+ *             domain name.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @param name
+ *             The domain name in label format.
+ *
+ *     @param service_id
+ *             The ID of the service for which the service-scoped service must be scoped.
+ *
+ *     @discussion
+ *             This function returns the most suitable service-scoped DNS service from the latest DNS configuration that
+ *             can be used to query for a record with the given domain name if such a service even exists.
+ *
+ *             Note: service-scoped DNS services are for specialized VPN applications, such as Per-App VPN.
+ *
+ *             If a service is returned, there's no guarantee that the reference will be valid after the next call to
+ *             either <code>mdns_dns_service_manager_apply_dns_config()</code> or
+ *             <code>mdns_dns_service_manager_apply_pending_updates()</code> unless the service is retained by the caller.
+ *
+ *     @result
+ *             A non-retained service if a suitable service exists. Otherwise, NULL.
+ */
+mdns_dns_service_t _Nullable
+mdns_dns_service_manager_get_service_scoped_service(mdns_dns_service_manager_t manager, const uint8_t *name,
+       uint32_t service_id);
+
+/*!
+ *     @brief
+ *             Gets a custom DNS service with a given identifier from a DNS service manager.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @param ident
+ *             The custom DNS service's identifier, i.e., the identifier returned by
+ *             <code>mdns_dns_service_manager_register_custom_service()</code> when the custom DNS service was
+ *             registered.
+ *
+ *     @result
+ *             A non-retained reference to the custom DNS service if the custom DNS service is still registered.
+ *             Otherwise, NULL.
+ */
+mdns_dns_service_t _Nullable
+mdns_dns_service_manager_get_custom_service(mdns_dns_service_manager_t manager, mdns_dns_service_id_t ident);
+
+/*!
+ *     @brief
+ *             Gets the config-specified DNS service that can be used to query for a record with the given
+ *             domain name.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @param uuid
+ *             The UUID of the resolver config to select.
+ *
+ *     @discussion
+ *             If a service is returned, there's no guarantee that the reference will be valid after the next call to
+ *             either <code>mdns_dns_service_manager_apply_dns_config()</code> or
+ *             <code>mdns_dns_service_manager_apply_pending_updates()</code> unless the service is retained by the caller.
+ *
+ *     @result
+ *             A non-retained service if a suitable service exists. Otherwise, NULL.
+ */
+mdns_dns_service_t _Nullable
+mdns_dns_service_manager_get_uuid_scoped_service(mdns_dns_service_manager_t manager, const uuid_t _Nonnull uuid);
+
+/*!
+ *     @brief
+ *             Fills out the UUID of a DNS service that should be used to query for a record with the given
+ *             domain name.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @param name
+ *             The domain name in label format.
+ *
+ *     @param out_uuid
+ *             The UUID of the resolver config to select.
+ *
+ *     @result
+ *             Returns true if the UUID was filled out.
+ */
+bool
+mdns_dns_service_manager_fillout_discovered_service_for_name(mdns_dns_service_manager_t manager, const uint8_t * const name,
+                                                                                                                        uuid_t _Nonnull out_uuid);
+
+/*!
+ *     @brief
+ *             Applies pending updates to the DNS service manager's DNS services.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @discussion
+ *             This function applies pending updates having to do with each managed DNS service's interface properties,
+ *             e.g., expensive, constrained, and clat46.
+ *
+ *             This function should be called when handling an <code>mdns_event_update</code> event.
+ */
+void
+mdns_dns_service_manager_apply_pending_updates(mdns_dns_service_manager_t manager);
+
+/*!
+ *     @brief
+ *             The type for a block that handles a DNS service when iterating over a DNS service manager's services.
+ *
+ *     @param service
+ *             The DNS service.
+ *
+ *     @result
+ *             If true, then iteration will stop prematurely. If false, then iteration will continue.
+ */
+typedef bool
+(^mdns_dns_service_applier_t)(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             Iterates over each DNS service managed by a DNS service manager.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ *
+ *     @param applier
+ *             Block to invoke for each DNS service.
+ */
+void
+mdns_dns_service_manager_iterate(mdns_dns_service_manager_t manager, mdns_dns_service_applier_t applier);
+
+/*!
+ *     @brief
+ *             Returns the number of DNS services being managed by a DNS service manager.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ */
+size_t
+mdns_dns_service_manager_get_count(mdns_dns_service_manager_t manager);
+
+/*!
+ *     @brief
+ *             Performs tasks necessary for a DNS service manager to prepare for system sleep.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ */
+void
+mdns_dns_service_manager_handle_sleep(mdns_dns_service_manager_t manager);
+
+/*!
+ *     @brief
+ *             Performs tasks necessary for a DNS service manager to prepare for system wake.
+ *
+ *     @param manager
+ *             The DNS service manager.
+ */
+void
+mdns_dns_service_manager_handle_wake(mdns_dns_service_manager_t manager);
+
+/*!
+ *     @brief
+ *             Sets a DNS service's user-defined context.
+ *
+ *     @param service
+ *             The DNS service.
+ *
+ *     @param context
+ *             The user-defined context.
+ *
+ *     @discussion
+ *             The last context set with this function can be retrieved with mdns_dns_service_get_context().
+ *
+ *             A DNS service's context is NULL by default.
+ */
+void
+mdns_dns_service_set_context(mdns_dns_service_t service, void *context);
+
+/*!
+ *     @brief
+ *             Gets a DNS service's user-defined context.
+ *
+ *     @param service
+ *             The DNS service.
+ *
+ *     @result
+ *             Returns the last context set with mdns_dns_service_set_context().
+ */
+void * _Nonnull
+mdns_dns_service_get_context(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             The type for a function that finalizes a user-defined context.
+ *
+ *     @param context
+ *             The user-defined context.
+ */
+typedef void
+(*mdns_context_finalizer_t)(void *context);
+
+/*!
+ *     @brief
+ *             Sets a DNS service's context finalizer function.
+ *
+ *     @param service
+ *             The DNS service.
+ *
+ *     @param finalizer
+ *             The finalizer.
+ *
+ *     @discussion
+ *             If a DNS service's context finalizer is not NULL and the service's context, which can be set with
+ *             mdns_dns_service_set_context(), is not NULL when the service's last reference is released, then the
+ *             finalizer will be invoked exactly once using the DNS service's context as an argument. The finalizer
+ *             will be invoked under no other conditions.
+ */
+void
+mdns_dns_service_set_context_finalizer(mdns_dns_service_t service, mdns_context_finalizer_t _Nullable finalizer);
+
+/*!
+ *     @brief
+ *             Creates a querier to query a DNS service represented by a DNS service object.
+ *
+ *     @param service
+ *             The DNS service.
+ *
+ *     @param out_error
+ *             Pointer to an OSStatus variable, which will be set to the error that was encountered during creation.
+ *
+ *     @discussion
+ *             If the DNS service is defunct, then no querier will be created.
+ *
+ *     @result
+ *             A new querier object if the DNS service is not defunct and resources are available. Otherwise, NULL.
+ */
+MDNS_RETURNS_RETAINED MDNS_WARN_RESULT
+mdns_querier_t _Nullable
+mdns_dns_service_create_querier(mdns_dns_service_t service, OSStatus * _Nullable out_error);
+
+/*!
+ *     @brief
+ *             Indicates a DNS service's scoping.
+ *
+ *     @const mdns_dns_service_scope_null
+ *             An invalid scope to be used only as a placeholder.
+ *
+ *     @const mdns_dns_service_scope_none
+ *             For unscoped DNS services.
+ *
+ *     @const mdns_dns_service_scope_interface
+ *             For interface-scoped DNS services.
+ *
+ *     @const mdns_dns_service_scope_service
+ *             For services-scoped DNS services.
+ *
+ *     @const mdns_dns_service_scope_uuid
+ *             For UUID-scoped DNS services.
+ *
+ *     @const mdns_dns_service_scope_custom
+ *             For custom DNS services.
+ */
+OS_CLOSED_ENUM(mdns_dns_service_scope, int,
+       mdns_dns_service_scope_null                     = 0,
+       mdns_dns_service_scope_none                     = 1,
+       mdns_dns_service_scope_interface        = 2,
+       mdns_dns_service_scope_service          = 3,
+       mdns_dns_service_scope_uuid                     = 4,
+       mdns_dns_service_scope_custom           = 5
+);
+
+/*!
+ *     @brief
+ *             Gets a DNS service's scope.
+ *
+ *     @param service
+ *             The DNS service.
+ */
+mdns_dns_service_scope_t
+mdns_dns_service_get_scope(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             Gets the index of the network interface used to access a DNS service.
+ *
+ *     @param service
+ *             The DNS service.
+ */
+uint32_t
+mdns_dns_service_get_interface_index(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             Gets a DNS service's unique per-process ID.
+ *
+ *     @param service
+ *             The DNS service.
+ *
+ *     @result
+ *             If service is non-NULL, then the service's ID is returned. Otherwise, 0, which is an invalid ID, is
+ *             returned.
+ */
+mdns_dns_service_id_t
+mdns_dns_service_get_id(mdns_dns_service_t _Nullable service);
+
+/*!
+ *     @brief
+ *             Determines whether or not a DNS service is defunct.
+ *
+ *     @param service
+ *             The DNS service.
+ *
+ *     @discussion
+ *             A DNS service becomes defunct when the DNS service manager that created it later applies a DNS
+ *             configuration (with <code>mdns_dns_service_manager_apply_dns_config()</code>) that doesn't contain the DNS
+ *             service.
+ *
+ *             When a DNS service is defunct, it is no longer usable, i.e., it is no longer capable of creating queriers.
+ */
+bool
+mdns_dns_service_is_defunct(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             Check if a DNS service uses an encrypted protocol.
+ *
+ *     @param service
+ *             The DNS service.
+ */
+bool
+mdns_dns_service_is_encrypted(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             Determines whether or not A record queries are advised for a DNS service.
+ *
+ *     @param service
+ *             The DNS service.
+ *
+ *     @discussion
+ *             Mirrors the meaning of the DNS_RESOLVER_FLAGS_REQUEST_A_RECORDS flag in a DNS configuration.
+ */
+bool
+mdns_dns_service_a_queries_advised(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             Determines whether or not AAAA record queries are advised for a DNS service.
+ *
+ *     @param service
+ *             The DNS service.
+ *
+ *     @discussion
+ *             Mirrors the meaning of the DNS_RESOLVER_FLAGS_REQUEST_AAAA_RECORDS flag in a DNS configuration.
+ */
+bool
+mdns_dns_service_aaaa_queries_advised(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             Determines whether or not a DNS service is currently experiencing connection problems.
+ *
+ *     @param service
+ *             The DNS service.
+ *
+ *     @discussion
+ *             This function currently only applies to DNS services that use DNS over HTTPS.
+ *
+ *             Since connection problems may be transient, a service with connection problems may still be used to
+ *             create queriers.
+ */
+bool
+mdns_dns_service_has_connection_problems(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             Determines whether or not a DNS service's interface has IPv4 connectivity.
+ *
+ *     @param service
+ *             The DNS service.
+ */
+bool
+mdns_dns_service_interface_has_ipv4_connectivity(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             Determines whether or not a DNS service's interface has IPv6 connectivity.
+ *
+ *     @param service
+ *             The DNS service.
+ */
+bool
+mdns_dns_service_interface_has_ipv6_connectivity(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             Determines whether or not a DNS service's interface is a cellular interface.
+ *
+ *     @param service
+ *             The DNS service.
+ */
+bool
+mdns_dns_service_interface_is_cellular(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             Determines whether or not a DNS service's interface is expensive.
+ *
+ *     @param service
+ *             The DNS service.
+ */
+bool
+mdns_dns_service_interface_is_expensive(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             Determines whether or not a DNS service's interface is constrained.
+ *
+ *     @param service
+ *             The DNS service.
+ */
+bool
+mdns_dns_service_interface_is_constrained(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             Determines whether or not a DNS service's interface is clat46.
+ *
+ *     @param service
+ *             The DNS service.
+ */
+bool
+mdns_dns_service_interface_is_clat46(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             Determines whether or not a DNS service's interface is a VPN interface.
+ *
+ *     @param service
+ *             The DNS service.
+ */
+bool
+mdns_dns_service_interface_is_vpn(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             Access the provider name, if applicable, used by this service.
+ *
+ *     @param service
+ *             The DNS service.
+ */
+const char * _Nullable
+mdns_dns_service_get_provider_name(mdns_dns_service_t service);
+
+/*!
+ *     @brief
+ *             Gets the resolver type used by a DNS service.
+ *
+ *     @param service
+ *             The DNS service.
+ */
+mdns_resolver_type_t
+mdns_dns_service_get_resolver_type(mdns_dns_service_t service);
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#endif // __MDNS_DNS_SERVICE_H__
diff --git a/mDNSMacOSX/mdns_objects/mdns_helpers.c b/mDNSMacOSX/mdns_objects/mdns_helpers.c
new file mode 100644 (file)
index 0000000..79921be
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mdns_helpers.h"
+
+#include <CoreUtils/CoreUtils.h>
+#include "DNSMessage.h"
+
+//======================================================================================================================
+// MARK: - Internals
+
+MDNS_LOG_CATEGORY_DEFINE(helpers, "helpers");
+
+//======================================================================================================================
+
+int
+mdns_snprintf_add(char **ptr, const char *lim, const char *fmt, ...)
+{
+       va_list args;
+       char * const dst = ptr ? *ptr : NULL;
+       const size_t len = (size_t)(lim - dst);
+       va_start(args, fmt);
+       const int n = vsnprintf(dst, len, fmt, args);
+       va_end(args);
+       require_quiet(n >= 0, exit);
+
+       if (ptr) {
+               if (((size_t)n) > len) {
+                       *ptr = dst + len;
+               } else {
+                       *ptr = dst + n;
+               }
+       }
+
+exit:
+       return n;
+}
+
+//======================================================================================================================
+
+OSStatus
+mdns_replace_string(char **string_ptr, const char *replacement)
+{
+       OSStatus err;
+       char *new_string;
+       if (replacement) {
+               new_string = strdup(replacement);
+               require_action_quiet(new_string, exit, err = kNoMemoryErr);
+       } else {
+               new_string = NULL;
+       }
+       FreeNullSafe(*string_ptr);
+       *string_ptr = new_string;
+       err = kNoErr;
+
+exit:
+       return err;
+}
+
+//======================================================================================================================
+
+OSStatus
+mdns_make_socket_nonblocking(int sock)
+{
+       int flags = fcntl(sock, F_GETFL, 0);
+       flags |= O_NONBLOCK;
+       OSStatus err = fcntl(sock, F_SETFL, flags);
+       err = map_global_value_errno(err != -1, err);
+    return err;
+}
+
+//======================================================================================================================
+
+uint64_t
+mdns_mach_ticks_per_second(void)
+{
+       static dispatch_once_t  s_once = 0;
+       static uint64_t                 s_ticks_per_second = 0;
+       dispatch_once(&s_once,
+       ^{
+               mach_timebase_info_data_t info;
+               const kern_return_t err = mach_timebase_info(&info);
+               if (!err && (info.numer != 0) && (info.denom != 0)) {
+                       s_ticks_per_second = (info.denom * UINT64_C_safe(kNanosecondsPerSecond)) / info.numer;
+               } else {
+                       os_log_error(_mdns_helpers_log(),
+                               "Unexpected results from mach_timebase_info: err %d numer %u denom %u", err, info.numer, info.denom);
+                       s_ticks_per_second = kNanosecondsPerSecond;
+               }
+       });
+       return s_ticks_per_second;
+}
+
+//======================================================================================================================
+
+int
+mdns_print_obfuscated_ip_address(char * const buf_ptr, const size_t buf_len, const struct sockaddr * const sa)
+{
+       int n;
+       char strbuf[64];
+       switch (sa->sa_family) {
+               case AF_INET: {
+                       const struct sockaddr_in * const sin = (const struct sockaddr_in *)sa;
+                       n = DNSMessagePrintObfuscatedIPv4Address(strbuf, sizeof(strbuf), ntohl(sin->sin_addr.s_addr));
+                       require_return_value(n >= 0, n);
+                       return snprintf(buf_ptr, buf_len, "<IPv4:%s>", strbuf);
+               }
+               case AF_INET6: {
+                       const struct sockaddr_in6 * const sin6 = (const struct sockaddr_in6 *)sa;
+                       n = DNSMessagePrintObfuscatedIPv6Address(strbuf, sizeof(strbuf), sin6->sin6_addr.s6_addr);
+                       require_return_value(n >= 0, n);
+                       return snprintf(buf_ptr, buf_len, "<IPv6:%s>", strbuf);
+               }
+               default: {
+                       return kTypeErr;
+               }
+       }
+}
diff --git a/mDNSMacOSX/mdns_objects/mdns_helpers.h b/mDNSMacOSX/mdns_objects/mdns_helpers.h
new file mode 100644 (file)
index 0000000..b39ca6a
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_HELPERS_H__
+#define __MDNS_HELPERS_H__
+
+#include "mdns_base.h"
+
+#include <MacTypes.h>
+#include <netinet/in.h>
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
+
+int
+mdns_snprintf_add(char * _Nonnull * _Nonnull ptr, const char * _Nullable lim, const char *fmt, ...);
+
+OSStatus
+mdns_replace_string(char * _Nullable * _Nonnull string_ptr, const char * _Nullable new_string);
+
+OSStatus
+mdns_make_socket_nonblocking(int sock);
+
+uint64_t
+mdns_mach_ticks_per_second(void);
+
+int
+mdns_print_obfuscated_ip_address(char *buf_ptr, size_t buf_len, const struct sockaddr *sa);
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#define MDNS_LOG_CATEGORY_DEFINE(SHORT_NAME, CATEGORY_STR)                     \
+       static os_log_t                                                                                                 \
+       _mdns_ ## SHORT_NAME ## _log(void)                                                              \
+       {                                                                                                                               \
+               static dispatch_once_t  s_once  = 0;                                            \
+               static os_log_t                 s_log   = NULL;                                         \
+               dispatch_once(&s_once,                                                                          \
+               ^{                                                                                                                      \
+                       s_log = os_log_create("com.apple.mdns", CATEGORY_STR);  \
+               });                                                                                                                     \
+               return s_log;                                                                                           \
+       }                                                                                                                               \
+       extern int _mdns_dummy_variable
+
+#if !defined(nw_forget)
+       #define nw_forget(X)    ForgetCustom(X, nw_release)
+#endif
+
+#if !defined(nw_release_null_safe)
+       #define nw_release_null_safe(X) do { if (X) { nw_release(X); } } while (0)
+#endif
+
+#if !defined(nwi_state_release_null_safe)
+       #define nwi_state_release_null_safe(X)  do { if (X) { nwi_state_release(X); } } while (0)
+#endif
+
+#define _mdns_socket_forget(PTR)               \
+       do {                                                            \
+               if (IsValidSocket(*(PTR))) {    \
+                       close(*(PTR));                          \
+                       *(PTR) = kInvalidSocketRef;     \
+               }                                                               \
+       } while (0)
+
+#endif // __MDNS_HELPERS_H__
diff --git a/mDNSMacOSX/mdns_objects/mdns_interface_monitor.c b/mDNSMacOSX/mdns_objects/mdns_interface_monitor.c
new file mode 100644 (file)
index 0000000..57352bc
--- /dev/null
@@ -0,0 +1,579 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mdns_internal.h"
+#include "mdns_interface_monitor.h"
+#include "mdns_helpers.h"
+#include "mdns_objects.h"
+
+#include <CoreUtils/CoreUtils.h>
+#include <network_information.h>
+#include <notify.h>
+#include <os/log.h>
+#include <os/object_private.h>
+
+//======================================================================================================================
+// MARK: - Interface Monitor Kind Definition
+
+struct mdns_interface_monitor_s {
+       struct mdns_object_s                                    base;                                   // Object base.
+       mdns_interface_monitor_t                                next;                                   // Next monitor in list.
+       dispatch_queue_t                                                user_queue;                             // User's queue for invoking handlers.
+       nw_path_evaluator_t                                             path_evaluator;                 // Path evaluator for interface properties.
+       dispatch_source_t                                               update_source;                  // Data source for triggering user's update handler.
+       mdns_interface_monitor_update_handler_t update_handler;                 // User's update handler.
+       mdns_event_handler_t                                    event_handler;                  // User's event handler.
+       char *                                                                  ifname;                                 // Name of monitored interface.
+       uint32_t                                                                ifindex;                                // Index of monitored interface.
+       mdns_interface_flags_t                                  pending_flags;                  // The latest interface flags from path updates.
+       mdns_interface_flags_t                                  flags;                                  // The current interface flags made known to user.
+       bool                                                                    user_activated;                 // True if user called activate method.
+       bool                                                                    activated;                              // True if the monitor has been activated.
+       bool                                                                    invalidated;                    // True if the monitor has been invalidated.
+       bool                                                                    path_evaluator_started; // True if the path evaluator has been started.
+};
+
+MDNS_OBJECT_SUBKIND_DEFINE(interface_monitor);
+
+//======================================================================================================================
+// MARK: - Local Prototypes
+
+static dispatch_queue_t
+_mdns_internal_queue(void);
+
+static dispatch_queue_t
+_mdns_nwi_state_mutex_queue(void);
+
+static void
+_mdns_interface_monitor_activate_async(mdns_interface_monitor_t monitor);
+
+static void
+_mdns_interface_monitor_terminate(mdns_interface_monitor_t me, const OSStatus error);
+
+static mdns_interface_flags_t
+_mdns_get_interface_flags_from_nw_path(nw_path_t path, mdns_interface_flags_t current_flags);
+
+static mdns_interface_flags_t
+_mdns_get_interface_flags_from_nwi_state(const char *ifname, mdns_interface_flags_t current_flags);
+
+static void
+_mdns_start_nwi_state_monitoring(void);
+
+//======================================================================================================================
+// MARK: - Globals
+
+static mdns_interface_monitor_t        g_monitor_list  = NULL;
+static nwi_state_t                             g_nwi_state             = NULL;
+
+//======================================================================================================================
+// MARK: - Internals
+
+static dispatch_queue_t
+_mdns_internal_queue(void)
+{
+       static dispatch_once_t  s_once  = 0;
+       static dispatch_queue_t s_queue = NULL;
+       dispatch_once(&s_once,
+       ^{
+               s_queue = dispatch_queue_create("com.apple.mdns.internal_queue", DISPATCH_QUEUE_SERIAL);
+       });
+       return s_queue; 
+}
+
+//======================================================================================================================
+
+static dispatch_queue_t
+_mdns_nwi_state_mutex_queue(void)
+{
+       static dispatch_once_t  s_once  = 0;
+       static dispatch_queue_t s_queue = NULL;
+       dispatch_once(&s_once,
+       ^{
+               s_queue = dispatch_queue_create("com.apple.mdns.nwi_state_mutex", DISPATCH_QUEUE_SERIAL);
+       });
+       return s_queue;
+}
+
+//======================================================================================================================
+
+MDNS_LOG_CATEGORY_DEFINE(ifmon, "interface_monitor");
+MDNS_LOG_CATEGORY_DEFINE(nwi,   "NWI");
+
+//======================================================================================================================
+// MARK: - Interface Monitor Public Methods
+
+mdns_interface_monitor_t
+mdns_interface_monitor_create(uint32_t interface_index)
+{
+       mdns_interface_monitor_t        monitor         = NULL;
+       nw_interface_t                          interface       = NULL;
+       nw_parameters_t                         params          = NULL;
+
+       mdns_interface_monitor_t obj = _mdns_interface_monitor_alloc();
+       require_quiet(obj, exit);
+
+       obj->ifindex = interface_index;
+       char ifname[IF_NAMESIZE + 1];
+       if (if_indextoname(obj->ifindex, ifname) == NULL) {
+               os_log_error(_mdns_ifmon_log(), "if_indextoname returned NULL for index %u", obj->ifindex);
+               goto exit;
+       }
+       obj->ifname = strdup(ifname);
+       require_quiet(obj->ifname, exit);
+
+       interface = nw_interface_create_with_index(obj->ifindex);
+       if (!interface) {
+               os_log_error(_mdns_ifmon_log(), "nw_interface_create_with_index returned NULL for index %u", obj->ifindex);
+               goto exit;
+       }
+
+       params = nw_parameters_create();
+       require_quiet(params, exit);
+
+       nw_parameters_require_interface(params, interface);
+       obj->path_evaluator = nw_path_create_evaluator_for_endpoint(NULL, params);
+       if (!obj->path_evaluator) {
+               os_log_error(_mdns_ifmon_log(), "nw_path_create_evaluator_for_endpoint returned NULL for params: %@", params);
+               goto exit;
+       }
+
+       nw_path_t path = nw_path_evaluator_copy_path(obj->path_evaluator);
+       require_quiet(path, exit);
+
+       obj->pending_flags = _mdns_get_interface_flags_from_nw_path(path, mdns_interface_flag_null);
+       obj->pending_flags = _mdns_get_interface_flags_from_nwi_state(obj->ifname, obj->pending_flags);
+       obj->flags = obj->pending_flags;
+       nw_forget(&path);
+
+       monitor = obj;
+       obj = NULL;
+
+exit:
+       if (obj) {
+               mdns_release(obj);
+       }
+       nw_release_null_safe(interface);
+       nw_release_null_safe(params);
+       return monitor;
+}
+
+//======================================================================================================================
+
+void
+mdns_interface_monitor_activate(mdns_interface_monitor_t me)
+{
+       if (!me->user_activated) {
+               if (me->user_queue) {
+                       _mdns_interface_monitor_activate_async(me);
+               }
+               me->user_activated = true;
+       }
+}
+
+//======================================================================================================================
+
+void
+mdns_interface_monitor_invalidate(mdns_interface_monitor_t me)
+{
+       mdns_retain(me);
+       dispatch_async(_mdns_internal_queue(),
+       ^{
+               if (!me->invalidated) {
+                       _mdns_interface_monitor_terminate(me, kNoErr);
+                       me->invalidated = true;
+               }
+               mdns_release(me);
+       });
+}
+
+//======================================================================================================================
+
+void
+mdns_interface_monitor_set_queue(mdns_interface_monitor_t me, dispatch_queue_t queue)
+{
+       if (!me->user_activated) {
+               dispatch_retain(queue);
+               dispatch_release_null_safe(me->user_queue);
+               me->user_queue = queue;
+       } else if (!me->user_queue) {
+               me->user_queue = queue;
+               dispatch_retain(me->user_queue);
+               _mdns_interface_monitor_activate_async(me);
+       }
+}
+
+//======================================================================================================================
+
+void
+mdns_interface_monitor_set_event_handler(mdns_interface_monitor_t me, mdns_event_handler_t handler)
+{
+       mdns_event_handler_t const new_handler = handler ? Block_copy(handler) : NULL;
+       if (me->event_handler) {
+               Block_release(me->event_handler);
+       }
+       me->event_handler = new_handler;
+}
+
+//======================================================================================================================
+
+void
+mdns_interface_monitor_set_update_handler(mdns_interface_monitor_t me, mdns_interface_monitor_update_handler_t handler)
+{
+       mdns_interface_monitor_update_handler_t const new_handler = handler ? Block_copy(handler) : NULL;
+       if (me->update_handler) {
+               Block_release(me->update_handler);
+       }
+       me->update_handler = new_handler;
+}
+
+//======================================================================================================================
+
+uint32_t
+mdns_interface_monitor_get_interface_index(mdns_interface_monitor_t me)
+{
+       return me->ifindex;
+}
+
+//======================================================================================================================
+
+bool
+mdns_interface_monitor_has_ipv4_connectivity(mdns_interface_monitor_t me)
+{
+       return ((me->flags & mdns_interface_flag_ipv4_connectivity) ? true : false);
+}
+
+//======================================================================================================================
+
+bool
+mdns_interface_monitor_has_ipv6_connectivity(mdns_interface_monitor_t me)
+{
+       return ((me->flags & mdns_interface_flag_ipv6_connectivity) ? true : false);
+}
+
+//======================================================================================================================
+
+bool
+mdns_interface_monitor_is_expensive(mdns_interface_monitor_t me)
+{
+       return ((me->flags & mdns_interface_flag_expensive) ? true : false);
+}
+
+//======================================================================================================================
+
+bool
+mdns_interface_monitor_is_constrained(mdns_interface_monitor_t me)
+{
+       return ((me->flags & mdns_interface_flag_constrained) ? true : false);
+}
+
+//======================================================================================================================
+
+bool
+mdns_interface_monitor_is_clat46(mdns_interface_monitor_t me)
+{
+       return ((me->flags & mdns_interface_flag_clat46) ? true : false);
+}
+
+//======================================================================================================================
+
+bool
+mdns_interface_monitor_is_vpn(const mdns_interface_monitor_t me)
+{
+       return ((me->flags & mdns_interface_flag_vpn) ? true : false);
+}
+
+//======================================================================================================================
+// MARK: - Interface Monitor Private Methods
+
+typedef struct {
+       mdns_interface_flags_t  flag;
+       const char *                    desc;
+} mdns_interface_flag_description_t;
+
+static char *
+_mdns_interface_monitor_copy_description(mdns_interface_monitor_t me, const bool debug, __unused const bool privacy)
+{
+       char *                          description     = NULL;
+       char                            buffer[128];
+       char *                          dst                     = buffer;
+       const char * const      lim                     = &buffer[countof(buffer)];
+       int                                     n;
+
+       *dst = '\0';
+       if (debug) {
+               n = mdns_snprintf_add(&dst, lim, "<%s: %p>: ", me->base.kind->name, me);
+               require_quiet(n >= 0, exit);
+       }
+       n = mdns_snprintf_add(&dst, lim, "interface %s (%u): ", me->ifname, me->ifindex);
+       require_quiet(n >= 0, exit);
+
+       const mdns_interface_flag_description_t mdns_interface_flag_descriptions[] = {
+               {mdns_interface_flag_ipv4_connectivity, "ipv4"},
+               {mdns_interface_flag_ipv6_connectivity, "ipv6"},
+               {mdns_interface_flag_expensive,         "expensive"},
+               {mdns_interface_flag_constrained,       "constrained"},
+               {mdns_interface_flag_clat46,            "clat46"},
+               {mdns_interface_flag_vpn,               "vpn"}
+       };
+       const char *separator = "";
+       for (size_t i = 0; i < countof(mdns_interface_flag_descriptions); ++i) {
+               const mdns_interface_flag_description_t * const flag_desc = &mdns_interface_flag_descriptions[i];
+               if (me->flags & flag_desc->flag) {
+                       n = mdns_snprintf_add(&dst, lim, "%s%s", separator, flag_desc->desc);
+                       require_quiet(n >= 0, exit);
+                       separator = ", ";
+               }
+       }
+       description = strdup(buffer);
+
+exit:
+       return description;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_interface_monitor_finalize(mdns_interface_monitor_t me)
+{
+       dispatch_forget(&me->user_queue);
+       nw_forget(&me->path_evaluator);
+       BlockForget(&me->update_handler);
+       BlockForget(&me->event_handler);
+       ForgetMem(&me->ifname);
+}
+
+//======================================================================================================================
+
+static void
+_mdns_interface_monitor_activate_internal(mdns_interface_monitor_t monitor);
+
+static void
+_mdns_interface_monitor_activate_async(mdns_interface_monitor_t me)
+{
+       mdns_retain(me);
+       dispatch_async(_mdns_internal_queue(),
+       ^{
+               _mdns_interface_monitor_activate_internal(me);
+               mdns_release(me);               
+       });
+}
+
+static void
+_mdns_interface_monitor_activate_internal(mdns_interface_monitor_t me)
+{
+       OSStatus err;
+       require_action_quiet(!me->activated && !me->invalidated, exit, err = kNoErr);
+       me->activated = true;
+
+       me->update_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_REPLACE, 0, 0, me->user_queue);
+       require_action_quiet(me->update_source, exit, err = kNoResourcesErr);
+
+       mdns_retain(me);
+       const dispatch_source_t update_source = me->update_source;
+       dispatch_source_set_event_handler(me->update_source,
+       ^{
+               const unsigned long data = dispatch_source_get_data(update_source);
+               const mdns_interface_flags_t new_flags = ((mdns_interface_flags_t)data) & ~mdns_interface_flag_reserved;
+               const mdns_interface_flags_t changed_flags = me->flags ^ new_flags;
+               if (changed_flags != 0) {
+                       me->flags = new_flags;
+                       if (me->update_handler) {
+                               me->update_handler(changed_flags);
+                       }
+               }
+       });
+       dispatch_source_set_cancel_handler(me->update_source,
+       ^{
+               mdns_release(me);
+       });
+       dispatch_activate(me->update_source);
+
+       mdns_retain(me);
+       nw_path_evaluator_set_update_handler(me->path_evaluator, _mdns_internal_queue(),
+       ^(nw_path_t path)
+       {
+               const mdns_interface_flags_t new_flags = _mdns_get_interface_flags_from_nw_path(path, me->pending_flags);
+               if (new_flags != me->pending_flags) {
+                       me->pending_flags = new_flags;
+                       if (me->update_source) {
+                               // Note: mdns_interface_flag_reserved is used to ensure that the data is non-zero. According to the
+                               // dispatch_source_create(3) man page, if the data value is zero, the source handler won't be invoked.
+                               dispatch_source_merge_data(me->update_source, me->pending_flags | mdns_interface_flag_reserved);
+                       }
+               }
+       });
+       nw_path_evaluator_set_cancel_handler(me->path_evaluator,
+       ^{
+               mdns_release(me);
+       });
+       nw_path_evaluator_start(me->path_evaluator);
+       me->path_evaluator_started = true;
+
+       mdns_interface_monitor_t *p = &g_monitor_list;
+       while (*p != NULL) {
+               p = &(*p)->next;
+       }
+       mdns_retain(me);
+       *p = me;
+
+       // This is called after adding the monitor to the global list to ensure that the initial NWI state check is aware
+       // that the interface monitor exists.
+       _mdns_start_nwi_state_monitoring();
+       err = kNoErr;
+
+exit:
+       if (err) {
+               _mdns_interface_monitor_terminate(me, err);
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_interface_monitor_terminate(mdns_interface_monitor_t me, const OSStatus error)
+{
+       dispatch_source_forget(&me->update_source);
+       if (me->path_evaluator) {
+               if (me->path_evaluator_started) {
+                       nw_path_evaluator_cancel(me->path_evaluator);
+               }
+               nw_forget(&me->path_evaluator);
+       }
+       for (mdns_interface_monitor_t *p = &g_monitor_list; *p; p = &(*p)->next) {
+               if (*p == me) {
+                       *p = me->next;
+                       me->next = NULL;
+                       mdns_release(me);
+                       break;
+               }
+       }
+       mdns_retain(me);
+       dispatch_async(me->user_queue,
+       ^{
+               if (me->event_handler) {
+                       me->event_handler(error ? mdns_event_error : mdns_event_invalidated, error);
+               }
+               mdns_release(me);
+       });
+}
+
+//======================================================================================================================
+// MARK: - NW Path Helpers
+
+#define MDNS_INTERFACE_FLAGS_FROM_NWPATH               \
+       (mdns_interface_flag_ipv4_connectivity |        \
+        mdns_interface_flag_ipv6_connectivity |        \
+        mdns_interface_flag_expensive         |        \
+        mdns_interface_flag_constrained)
+
+static mdns_interface_flags_t
+_mdns_get_interface_flags_from_nw_path(nw_path_t path, mdns_interface_flags_t current_flags)
+{
+       mdns_interface_flags_t flags = current_flags & ~MDNS_INTERFACE_FLAGS_FROM_NWPATH;
+       if (nw_path_has_ipv4(path)) {
+               flags |= mdns_interface_flag_ipv4_connectivity;
+       }
+       if (nw_path_has_ipv6(path)) {
+               flags |= mdns_interface_flag_ipv6_connectivity;
+       }
+       if (nw_path_is_expensive(path)) {
+               flags |= mdns_interface_flag_expensive;
+       }
+       if (nw_path_is_constrained(path)) {
+               flags |= mdns_interface_flag_constrained;
+       }
+       return flags;
+}
+
+//======================================================================================================================
+// MARK: - NWI Helpers
+
+#define MDNS_INTERFACE_FLAGS_FROM_NWI_STATE    (       \
+       mdns_interface_flag_clat46 |                            \
+       mdns_interface_flag_vpn                                         \
+)
+
+static mdns_interface_flags_t
+_mdns_get_interface_flags_from_nwi_state(const char * const ifname, const mdns_interface_flags_t current_flags)
+{
+       __block mdns_interface_flags_t flags = current_flags;
+       dispatch_sync(_mdns_nwi_state_mutex_queue(),
+       ^{
+               require_return(g_nwi_state);
+               const nwi_ifstate_t ifstate = nwi_state_get_ifstate(g_nwi_state, ifname);
+               flags &= ~MDNS_INTERFACE_FLAGS_FROM_NWI_STATE;
+               require_return(ifstate);
+               const nwi_ifstate_flags ifstate_flags = nwi_ifstate_get_flags(ifstate);
+               if (ifstate_flags & NWI_IFSTATE_FLAGS_HAS_CLAT46) {
+                       flags |= mdns_interface_flag_clat46;
+               }
+               if (nwi_ifstate_get_vpn_server(ifstate)) {
+                       flags |= mdns_interface_flag_vpn;
+               }
+       });
+       return flags;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_nwi_state_update(void);
+
+static void
+_mdns_start_nwi_state_monitoring(void)
+{
+       static int s_nwi_notify_token = NOTIFY_TOKEN_INVALID;
+       if (s_nwi_notify_token == NOTIFY_TOKEN_INVALID) {
+               const uint32_t status = notify_register_dispatch(nwi_state_get_notify_key(), &s_nwi_notify_token,
+                       _mdns_internal_queue(),
+               ^(__unused int token)
+               {
+                       _mdns_nwi_state_update();
+               });
+               if (s_nwi_notify_token == NOTIFY_TOKEN_INVALID) {
+                       os_log_error(_mdns_nwi_log(), "Failed to register for NWI state notifications (status %u)", status);
+               } else {
+                       _mdns_nwi_state_update();
+               }
+       }
+}
+
+static void
+_mdns_nwi_state_update(void)
+{
+       nwi_state_t new_state = nwi_state_copy();
+       if (!new_state) {
+               os_log_error(_mdns_nwi_log(), "Failed to copy NWI state");
+       }
+       __block nwi_state_t old_state;
+       dispatch_sync(_mdns_nwi_state_mutex_queue(),
+       ^{
+               old_state       = g_nwi_state;
+               g_nwi_state     = new_state;
+       });
+       nwi_state_release_null_safe(old_state);
+       for (mdns_interface_monitor_t m = g_monitor_list; m; m = m->next) {
+               const mdns_interface_flags_t new_flags = _mdns_get_interface_flags_from_nwi_state(m->ifname, m->pending_flags);
+               if (new_flags != m->pending_flags) {
+                       m->pending_flags = new_flags;
+                       if (m->update_source) {
+                               // Note: mdns_interface_flag_reserved is used to ensure that the data is non-zero. According to the
+                               // dispatch_source_create(3) man page, if the data value is zero, the source handler won't be invoked.
+                               dispatch_source_merge_data(m->update_source, m->pending_flags | mdns_interface_flag_reserved);
+                       }
+               }
+       }
+}
diff --git a/mDNSMacOSX/mdns_objects/mdns_interface_monitor.h b/mDNSMacOSX/mdns_objects/mdns_interface_monitor.h
new file mode 100644 (file)
index 0000000..4c82582
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_INTERFACE_MONITOR_H__
+#define __MDNS_INTERFACE_MONITOR_H__
+
+#include "mdns_base.h"
+#include "mdns_object.h"
+
+#include <stdint.h>
+
+#include <dispatch/dispatch.h>
+#include <MacTypes.h>
+
+MDNS_DECL(interface_monitor);
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
+
+/*!
+ *     @brief
+ *             Creates an interface monitor.
+ *
+ *     @param interface_index
+ *             Index of the interface to monitor.
+ *
+ *     @result
+ *             A new interface monitor or NULL if there was a lack of resources.
+ *
+ *     @discussion
+ *             An interface monitor provides up-to-date information about an interface's properties, such as IPv4
+ *             connectivity, IPv6 connectivity, whether the interface is expensive, and whether the interface is constrained.
+ *
+ *             If this function returns non-NULL, then the caller has an ownership reference to the newly created interface
+ *             monitor, which can be relinquished with <code>mdns_release()</code>.
+ */
+MDNS_RETURNS_RETAINED mdns_interface_monitor_t _Nullable
+mdns_interface_monitor_create(uint32_t interface_index);
+
+/*!
+ *     @brief
+ *             Activates an interface monitor.
+ *
+ *     @param monitor
+ *             The interface monitor.
+ *
+ *     @discussion
+ *             Successful activation enables interface monitor updates.
+ *
+ *             This function has no effect on an interface monitor that has already been activated or one that has been
+ *             invalidated.
+ */
+void
+mdns_interface_monitor_activate(mdns_interface_monitor_t monitor);
+
+/*!
+ *     @brief
+ *             Asynchronously invalidates an interface monitor.
+ *
+ *     @param monitor
+ *             The interface monitor.
+ *
+ *     @discussion
+ *             This function should be called when the interface monitor is no longer needed.
+ *
+ *             As a result of calling this function, the interface monitor's event handler will be invoked with a
+ *             <code>mdns_event_invalidated</code> event, after which the interface monitor's event and update handlers will
+ *             never be invoked again.
+ *
+ *             This function has no effect on an interface monitor that has already been invalidated.
+ */
+void
+mdns_interface_monitor_invalidate(mdns_interface_monitor_t monitor);
+
+/*!
+ *     @brief
+ *             Specifies the queue on which to invoke the interface monitor's event and update handlers.
+ *
+ *     @param monitor
+ *             The interface monitor.
+ *
+ *     @param queue
+ *             A serial queue.
+ *
+ *     @discussion
+ *             This function must be called before activating the interface monitor.
+ *
+ *             This function has no effect on an interface monitor that has been activated or invalidated.
+ */
+void
+mdns_interface_monitor_set_queue(mdns_interface_monitor_t monitor, dispatch_queue_t queue);
+
+/*!
+ *     @brief
+ *             Sets an interface monitor's event handler.
+ *
+ *     @param monitor
+ *             The interface monitor.
+ *
+ *     @param handler
+ *             The event handler.
+ *
+ *     @discussion
+ *             The event handler will never be invoked prior to activation.
+ *
+ *             The event handler will be invoked on the dispatch queue specified by
+ *             <code>mdns_interface_monitor_set_queue()</code> with event <code>mdns_event_error</code> when a fatal error
+ *             occurs and with event <code>mdns_event_invalidated</code> when the interface monitor has been invalidated.
+ *
+ *             After an <code>mdns_event_invalidated</code> event, the event handler will never be invoked again.
+ */
+void
+mdns_interface_monitor_set_event_handler(mdns_interface_monitor_t monitor, mdns_event_handler_t _Nullable handler);
+
+/*!
+ *     @brief
+ *             Flags that represent the properties of a monitored interface.
+ *
+ *     @discussion
+ *             These flags don't represent the actual values of properties. The meaning of these flags depends on the
+ *             context in which they're used. For example, as a parameter of mdns_interface_monitor_update_handler_t, a set
+ *             flag means that the value of a given property has changed.
+ */
+OS_CLOSED_OPTIONS(mdns_interface_flags, uint32_t,
+       mdns_interface_flag_null                                = 0,
+       mdns_interface_flag_ipv4_connectivity   = (1U <<  0),
+       mdns_interface_flag_ipv6_connectivity   = (1U <<  1),
+       mdns_interface_flag_expensive                   = (1U <<  2),
+       mdns_interface_flag_constrained                 = (1U <<  3),
+       mdns_interface_flag_clat46                              = (1U <<  4),
+       mdns_interface_flag_vpn                                 = (1U <<  5),
+       mdns_interface_flag_reserved                    = (1U << 31)
+);
+
+/*!
+ *     @brief
+ *             Update handler for an interface monitor.
+ *
+ *     @param change_flags
+ *             Each flag bit represents a property of a monitored interface. If the bit is set, then the value of that
+ *             property has changed. If the bit is clear, then the value of that property has not changed.
+ */
+typedef void (^mdns_interface_monitor_update_handler_t)(mdns_interface_flags_t change_flags);
+
+/*!
+ *     @brief
+ *             Sets an interface monitor's update handler.
+ *
+ *     @param monitor
+ *             The interface monitor.
+ *
+ *     @param handler
+ *             The update handler.
+ *
+ *     @discussion
+ *             The update handler will never be invoked prior to activation.
+ *
+ *             The update handler will be invoked on the dispatch queue specified by
+ *             <code>mdns_interface_monitor_set_queue()</code> when any of the monitored interface's properties have been
+ *             updated.
+ *
+ *             After an <code>mdns_event_invalidated</code> event, the update handler will ever be invoked again.
+ */
+void
+mdns_interface_monitor_set_update_handler(mdns_interface_monitor_t monitor,
+       mdns_interface_monitor_update_handler_t _Nullable handler);
+
+/*!
+ *     @brief
+ *             Returns the index of the monitored interface.
+ *
+ *     @param monitor
+ *             The interface monitor.
+ */
+uint32_t
+mdns_interface_monitor_get_interface_index(mdns_interface_monitor_t monitor);
+
+/*!
+ *     @brief
+ *             Determines whether the monitored interface currently has IPv4 connectivity.
+ *
+ *     @param monitor
+ *             The interface monitor.
+ *
+ *     @discussion
+ *             mdns_interface_flag_ipv4_connectivity will be set in the update handler's change_flags argument when the
+ *             value of this property has changed.
+ */
+bool
+mdns_interface_monitor_has_ipv4_connectivity(mdns_interface_monitor_t monitor);
+
+/*!
+ *     @brief
+ *             Determines whether the monitored interface currently has IPv6 connectivity.
+ *
+ *     @param monitor
+ *             The interface monitor.
+ *
+ *     @discussion
+ *             mdns_interface_flag_ipv6_connectivity will be set in the update handler's change_flags argument when the
+ *             value of this property has changed.
+ */
+bool
+mdns_interface_monitor_has_ipv6_connectivity(mdns_interface_monitor_t monitor);
+
+/*!
+ *     @brief
+ *             Determines whether the monitored interface is currently expensive.
+ *
+ *     @param monitor
+ *             The interface monitor.
+ *
+ *     @discussion
+ *             mdns_interface_flag_expensive will be set in the update handler's change_flags argument when the value
+ *             of this property has changed.
+ */
+bool
+mdns_interface_monitor_is_expensive(mdns_interface_monitor_t monitor);
+
+/*!
+ *     @brief
+ *             Determines whether the monitored interface is currently constrained.
+ *
+ *     @param monitor
+ *             The interface monitor.
+ *
+ *     @discussion
+ *             mdns_interface_flag_constrained will be set in the update handler's change_flags argument when the value
+ *             of this property has changed.
+ */
+bool
+mdns_interface_monitor_is_constrained(mdns_interface_monitor_t monitor);
+
+/*!
+ *     @brief
+ *             Determines whether the monitored interface has CLAT46 support.
+ *
+ *     @param monitor
+ *             The interface monitor.
+ *
+ *     @discussion
+ *             mdns_interface_flag_clat46 will be set in the update handler's change_flags argument when the value
+ *             of this property has changed.
+ */
+bool
+mdns_interface_monitor_is_clat46(mdns_interface_monitor_t monitor);
+
+/*!
+ *     @brief
+ *             Determines whether the monitored interface is used for VPN.
+ *
+ *     @param monitor
+ *             The interface monitor.
+ *
+ *     @discussion
+ *             mdns_interface_flag_vpn will be set in the update handler's change_flags argument when the value of this
+ *             property has changed.
+ */
+bool
+mdns_interface_monitor_is_vpn(mdns_interface_monitor_t monitor);
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#define mdns_interface_monitor_forget(X)       mdns_forget_with_invalidation(X, interface_monitor)
+
+#endif // __MDNS_INTERFACE_MONITOR_H__
diff --git a/mDNSMacOSX/mdns_objects/mdns_internal.h b/mDNSMacOSX/mdns_objects/mdns_internal.h
new file mode 100644 (file)
index 0000000..d034b2d
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_INTERNAL_H__
+#define __MDNS_INTERNAL_H__
+
+#include "mdns_base.h"
+
+#define MDNS_ANY_TYPE_INTERNAL_MEMBERS         \
+       MDNS_UNION_MEMBER(normal_resolver);             \
+       MDNS_UNION_MEMBER(tcp_resolver);                \
+       MDNS_UNION_MEMBER(tls_resolver);                \
+       MDNS_UNION_MEMBER(https_resolver);              \
+       MDNS_UNION_MEMBER(server);                              \
+       MDNS_UNION_MEMBER(session);                             \
+       MDNS_UNION_MEMBER(connection_session);  \
+       MDNS_UNION_MEMBER(udp_socket_session);  \
+       MDNS_UNION_MEMBER(url_session);
+
+#endif // __MDNS_INTERNAL_H__
+
diff --git a/mDNSMacOSX/mdns_objects/mdns_managed_defaults.c b/mDNSMacOSX/mdns_objects/mdns_managed_defaults.c
new file mode 100644 (file)
index 0000000..5fedabe
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mdns_managed_defaults.h"
+
+#include "mdns_helpers.h"
+
+#include <CoreUtils/CoreUtils.h>
+
+//======================================================================================================================
+// MARK: - Internals
+
+// Managed defaults is currently only for iOS-like OSes.
+#if !defined(TARGET_OS_IOS_LIKE_DEVICE)
+       #error "TARGET_OS_IOS_LIKE_DEVICE is not defined"
+#endif
+
+MDNS_LOG_CATEGORY_DEFINE(managed_defaults, "managed_defaults");
+
+//======================================================================================================================
+// MARK: - Local Prototypes
+
+#if TARGET_OS_IOS_LIKE_DEVICE
+static CFURLRef
+_mdns_managed_defaults_create_domain_url(const char *domain, OSStatus *out_error);
+
+static CFReadStreamRef
+_mdns_managed_defaults_create_opened_read_stream(CFURLRef url, OSStatus *out_error);
+
+static CFDictionaryRef
+_mdns_managed_defaults_create_dictionary_from_stream(CFReadStreamRef stream, OSStatus *out_error);
+#endif
+
+static int64_t
+_mdns_managed_defaults_get_int64(CFDictionaryRef defaults, CFStringRef key, OSStatus *out_error);
+
+#define _assign_null_safe(PTR, VALUE)  \
+       do {                                                            \
+               if ((PTR)) {                                    \
+                       *(PTR) = (VALUE);                       \
+               }                                                               \
+       } while(0)
+
+//======================================================================================================================
+// MARK: - Public Functions
+
+#if TARGET_OS_IOS_LIKE_DEVICE
+CFDictionaryRef
+mdns_managed_defaults_create(const char * const domain, OSStatus * const out_error)
+{
+       CFDictionaryRef result = NULL;
+       OSStatus err, tmp_err;
+       CFURLRef url = _mdns_managed_defaults_create_domain_url(domain, &tmp_err);
+       require_action_quiet(url, exit, err = tmp_err; os_log_error(_mdns_managed_defaults_log(),
+               "Failed to create URL -- domain: %{public}s, error: %{mdns:err}ld", domain, (long)err));
+
+       CFReadStreamRef stream = _mdns_managed_defaults_create_opened_read_stream(url, &tmp_err);
+       require_action_quiet(stream, exit, err = tmp_err; os_log_error(_mdns_managed_defaults_log(),
+               "Failed to create read stream -- url: %{public}@, error: %{mdns:err}ld", url, (long)err));
+
+       CFDictionaryRef plist = _mdns_managed_defaults_create_dictionary_from_stream(stream, &tmp_err);
+       ForgetCF(&stream);
+       require_action_quiet(plist, exit, err = tmp_err; os_log_error(_mdns_managed_defaults_log(),
+               "Failed to create dictionary -- url: %{public}@, error: %{mdns:err}ld", url, (long)err));
+
+       result = plist;
+       err = kNoErr;
+
+exit:
+       ForgetCF(&url);
+       _assign_null_safe(out_error, err);
+       return result;
+}
+#else
+CFDictionaryRef
+mdns_managed_defaults_create(__unused const char * const domain, OSStatus * const out_error)
+{
+       os_log_info(_mdns_managed_defaults_log(), "Managed defaults is not supported on this OS");
+       _assign_null_safe(out_error, kUnsupportedErr);
+       return NULL;
+}
+#endif // TARGET_OS_IOS_LIKE_DEVICE
+
+//======================================================================================================================
+
+int
+mdns_managed_defaults_get_int_clamped(const CFDictionaryRef defaults, const CFStringRef key, const int fallback_value,
+       OSStatus * const out_error)
+{
+       int result;
+       OSStatus err;
+       const int64_t value = _mdns_managed_defaults_get_int64(defaults, key, &err);
+       require_noerr_action_quiet(err, exit, result = fallback_value);
+
+       result = (int)Clamp(value, INT_MIN, INT_MAX);
+
+exit:
+       _assign_null_safe(out_error, err);
+       return result;
+}
+
+//======================================================================================================================
+// MARK: - Private Functions
+
+#if TARGET_OS_IOS_LIKE_DEVICE
+static CFURLRef
+_mdns_managed_defaults_create_domain_url(const char * const domain, OSStatus * const out_error)
+{
+       OSStatus err;
+       CFURLRef result = NULL;
+       char *path_cstr = NULL;
+       asprintf(&path_cstr, "/Library/Managed Preferences/mobile/%s.plist", domain);
+       require_action_quiet(path_cstr, exit, err = kNoMemoryErr);
+
+       CFStringRef path = CFStringCreateWithCStringNoCopy(NULL, path_cstr, kCFStringEncodingUTF8, kCFAllocatorMalloc);
+       require_action_quiet(path, exit, ForgetMem(&path_cstr); err = kNoResourcesErr);
+       path_cstr = NULL;
+
+       CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path, kCFURLPOSIXPathStyle, false);
+       ForgetCF(&path);
+       require_action_quiet(url, exit, err = kNoResourcesErr);
+
+       result = url;
+       err = kNoErr;
+
+exit:
+       _assign_null_safe(out_error, err);
+       return result;
+}
+
+//======================================================================================================================
+
+static CFReadStreamRef
+_mdns_managed_defaults_create_opened_read_stream(const CFURLRef url, OSStatus * const out_error)
+{
+       OSStatus err;
+       CFReadStreamRef result = NULL;
+       CFReadStreamRef stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
+       require_action_quiet(stream, exit, err = kNoResourcesErr);
+
+       const Boolean ok = CFReadStreamOpen(stream);
+       require_action_quiet(ok, exit, ForgetCF(&stream); err = kOpenErr);
+
+       result = stream;
+       err = kNoErr;
+
+exit:
+       _assign_null_safe(out_error, err);
+       return result;
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mdns_get_cferror_code(CFErrorRef error);
+
+static CFDictionaryRef
+_mdns_managed_defaults_create_dictionary_from_stream(const CFReadStreamRef stream, OSStatus * const out_error)
+{
+       OSStatus err;
+       CFErrorRef error = NULL;
+       CFDictionaryRef result = NULL;
+       CFPropertyListRef plist = CFPropertyListCreateWithStream(NULL, stream, 0, kCFPropertyListImmutable, NULL, &error);
+       require_action_quiet(plist, exit, err = _mdns_get_cferror_code(error); os_log_error(_mdns_managed_defaults_log(),
+               "CFPropertyListCreateWithStream failed: %{public}@", error));
+       require_action_quiet(CFIsType(plist, CFDictionary), exit, ForgetCF(&plist); err = kTypeErr);
+
+       result = plist;
+       err = kNoErr;
+
+exit:
+       ForgetCF(&error);
+       _assign_null_safe(out_error, err);
+       return result;
+}
+
+static OSStatus
+_mdns_get_cferror_code(const CFErrorRef error)
+{
+       return (error ? ((OSStatus)CFErrorGetCode(error)) : kUnknownErr);
+}
+#endif // TARGET_OS_IOS_LIKE_DEVICE
+
+//======================================================================================================================
+
+static int64_t
+_mdns_managed_defaults_get_int64(const CFDictionaryRef defaults, const CFStringRef key, OSStatus * const out_error)
+{
+       OSStatus err;
+       int64_t result = 0;
+       const CFNumberRef number = CFDictionaryGetValue(defaults, key);
+       require_action_quiet(number != NULL, exit, err = kNotFoundErr);
+       require_action_quiet(CFIsType(number, CFNumber), exit, err = kTypeErr);
+       require_action_quiet(!CFNumberIsFloatType(number), exit, err = kTypeErr);
+
+       int64_t value;
+       const Boolean ok = CFNumberGetValue(number, kCFNumberSInt64Type, &value);
+       require_action_quiet(ok, exit, err = kUnknownErr);
+
+       result = value;
+       err = kNoErr;
+
+exit:
+       _assign_null_safe(out_error, err);
+       return result;
+}
diff --git a/mDNSMacOSX/mdns_objects/mdns_managed_defaults.h b/mDNSMacOSX/mdns_objects/mdns_managed_defaults.h
new file mode 100644 (file)
index 0000000..5578db2
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_MANAGED_DEFAUTS_H__
+#define __MDNS_MANAGED_DEFAUTS_H__
+
+#include "mdns_base.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <MacTypes.h>
+#include <stdint.h>
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
+
+/*!
+ *     @brief
+ *             Creates a defaults dictionary based on a managed defaults domain.
+ *
+ *     @param domain
+ *             The managed defaults domain as a UTF-8 C string.
+ *
+ *     @param out_error
+ *             Gets set to an error code that indicates the error that was encountered, if any.
+ *
+ *     @result
+ *             A reference to a dictionary if successful. Otherwise, NULL.
+ */
+CFDictionaryRef _Nullable
+mdns_managed_defaults_create(const char *domain, OSStatus * _Nullable out_error);
+
+/*!
+ *     @brief
+ *             Gets the integer value of a key from a defaults dictionary.
+ *
+ *     @param defaults
+ *             The defaults dictionary.
+ *
+ *     @param key
+ *             The key.
+ *
+ *     @param fallback_value
+ *             A fallback value to return if the key is not present in the dictionary, or if the key's value is not an
+ *             integer.
+ *
+ *     @param out_error
+ *             Gets set to an error code that indicates the error that was encountered, if any.
+ *
+ *     @result
+ *             If the key is present in the dictionary and the key's value is an integer, then the value is returned
+ *             after clamping it in the [INT_MIN, INT_MAX] range. Otherwise, the specified fallback value is returned.
+ *             
+ */
+int
+mdns_managed_defaults_get_int_clamped(CFDictionaryRef defaults, CFStringRef key, int fallback_value,
+       OSStatus * _Nullable out_error);
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#endif // __MDNS_MANAGED_DEFAUTS_H__
diff --git a/mDNSMacOSX/mdns_objects/mdns_message.c b/mDNSMacOSX/mdns_objects/mdns_message.c
new file mode 100644 (file)
index 0000000..235613e
--- /dev/null
@@ -0,0 +1,472 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mdns_message.h"
+
+#include "mdns_objects.h"
+
+#include "DNSMessage.h"
+#include <CoreUtils/CoreUtils.h>
+
+//======================================================================================================================
+// MARK: - Message Kind Definition
+
+struct mdns_message_s {
+       struct mdns_object_s    base;                           // Object base.
+       dispatch_data_t                 msg_data;                       // Underlying object for message data.
+       const uint8_t *                 msg_ptr;                        // Pointer to first byte of message data.
+       size_t                                  msg_len;                        // Length of message.
+       bool                                    print_body_only;        // True if only the message body should be printed in description.
+};
+
+MDNS_OBJECT_SUBKIND_DEFINE(message);
+
+typedef const struct mdns_message_kind_s *     mdns_message_kind_t;
+struct mdns_message_kind_s {
+       struct mdns_kind_s      base;
+       const char *            name;
+};
+
+#define MDNS_MESSAGE_SUBKIND_DEFINE(NAME)                                                                                                                      \
+       static void                                                                                                                                                                             \
+       _mdns_ ## NAME ## _message_finalize(mdns_ ## NAME ## _message_t message);                                               \
+                                                                                                                                                                                                       \
+       static const struct mdns_message_kind_s _mdns_ ## NAME ## _message_kind = {                                             \
+               .base = {                                                                                                                                                                       \
+                       .superkind      = &_mdns_message_kind,                                                                                                          \
+                       .name           = "mdns_" # NAME "_message",                                                                                            \
+                       .finalize       = _mdns_ ## NAME ## _message_finalize                                                                           \
+               },                                                                                                                                                                                      \
+               .name = # NAME "_message"                                                                                                                                       \
+       };                                                                                                                                                                                              \
+                                                                                                                                                                                                       \
+       static mdns_ ## NAME ## _message_t                                                                                                                              \
+       _mdns_ ## NAME ## _message_alloc(void)                                                                                                                  \
+       {                                                                                                                                                                                               \
+               mdns_ ## NAME ## _message_t obj;                                                                                                                        \
+               obj = mdns_ ## NAME ## _message_object_alloc(sizeof(struct mdns_ ## NAME ## _message_s));       \
+               require_return_value(obj, NULL);                                                                                                                        \
+                                                                                                                                                                                                       \
+               const mdns_object_t object = (mdns_object_t)obj;                                                                                        \
+               object->kind = &_mdns_ ## NAME ## _message_kind.base;                                                                           \
+               return obj;                                                                                                                                                                     \
+       }                                                                                                                                                                                               \
+       MDNS_BASE_CHECK(NAME ## _message, message)
+
+//======================================================================================================================
+// MARK: - Query Message Kind Definition
+
+struct mdns_query_message_s {
+       struct mdns_message_s   base;                           // Message object base.
+       uint8_t *                               qname;                          // Question's QNAME.
+       uint16_t                                qtype;                          // Question's QTYPE.
+       uint16_t                                qclass;                         // Question's QCLASS.
+       uint16_t                                msg_id;                         // Message ID.
+       bool                                    set_ad_bit;                     // True if the AD (authentic data) bit is to be set.
+       bool                                    set_cd_bit;                     // True if the CD (checking disabled) bit is to be set.
+       bool                                    set_do_bit;                     // True if the DO (DNSSEC OK) bit is to be set in OPT record.
+       bool                                    use_edns0_padding;      // True if the query uses EDNS0 padding.
+       bool                                    constructed;            // True if the message has been constructed.
+};
+
+MDNS_MESSAGE_SUBKIND_DEFINE(query);
+
+//======================================================================================================================
+// MARK: - Local Prototypes
+
+static OSStatus
+_mdns_message_init(mdns_any_message_t message, dispatch_data_t msg_data, mdns_message_init_options_t options);
+
+static OSStatus
+_mdns_message_set_msg_data(mdns_any_message_t message, dispatch_data_t msg_data);
+
+const uint8_t *
+_mdns_query_message_get_qname_safe(mdns_query_message_t query_message);
+
+//======================================================================================================================
+// MARK: - Messge Public Methods
+
+mdns_message_t
+mdns_message_create_with_dispatch_data(const dispatch_data_t data, const mdns_message_init_options_t options)
+{
+       mdns_message_t message = NULL;
+       mdns_message_t obj = _mdns_message_alloc();
+       require_quiet(obj, exit);
+
+       const OSStatus err = _mdns_message_init(obj, data, options);
+       require_noerr_quiet(err, exit);
+
+       message = obj;
+       obj = NULL;
+
+exit:
+       mdns_release_null_safe(obj);
+       return message;
+}
+
+//======================================================================================================================
+
+dispatch_data_t
+mdns_message_get_dispatch_data(const mdns_message_t me)
+{
+       return me->msg_data;
+}
+
+//======================================================================================================================
+
+const uint8_t *
+mdns_message_get_byte_ptr(const mdns_message_t me)
+{
+       return me->msg_ptr;
+}
+
+//======================================================================================================================
+
+size_t
+mdns_message_get_length(const mdns_message_t me)
+{
+       return me->msg_len;
+}
+
+//======================================================================================================================
+// MARK: - Message Private Methods
+
+static char *
+_mdns_message_copy_description(mdns_message_t me, __unused const bool debug, const bool privacy)
+{
+       char *description = NULL;
+       if (me->msg_ptr) {
+               DNSMessageToStringFlags flags = kDNSMessageToStringFlag_OneLine;
+               if (me->print_body_only) {
+                       flags |= kDNSMessageToStringFlag_BodyOnly;
+               }
+               if (privacy) {
+                       flags |= kDNSMessageToStringFlag_Privacy;
+               }
+               DNSMessageToString(me->msg_ptr, me->msg_len, flags, &description);
+       }
+       return description;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_message_finalize(const mdns_message_t me)
+{
+       me->msg_ptr = NULL;
+       dispatch_forget(&me->msg_data);
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mdns_message_init(const mdns_any_message_t any, const dispatch_data_t msg_data,
+       const mdns_message_init_options_t options)
+{
+       const mdns_message_t me = any.message;
+       if (options & mdns_message_init_option_disable_header_printing) {
+               me->print_body_only = true;
+       }
+       return _mdns_message_set_msg_data(me, msg_data);
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mdns_message_set_msg_data(const mdns_any_message_t any, const dispatch_data_t msg_data)
+{
+       dispatch_data_t msg_data_new;
+       const uint8_t * msg_ptr;
+       size_t                  msg_len;
+       if (msg_data) {
+               msg_data_new = dispatch_data_create_map(msg_data, (const void **)&msg_ptr, &msg_len);
+               require_return_value(msg_data_new, kNoMemoryErr);
+       } else {
+               msg_data_new = dispatch_data_empty;
+               msg_ptr = NULL;
+               msg_len = 0;
+       }
+       const mdns_message_t me = any.message;
+       dispatch_release_null_safe(me->msg_data);
+       me->msg_data = msg_data_new;
+       me->msg_ptr  = msg_ptr;
+       me->msg_len  = msg_len;
+       return kNoErr;
+}
+
+//======================================================================================================================
+// MARK: - Query Messge Public Methods
+
+mdns_query_message_t
+mdns_query_message_create(const mdns_message_init_options_t options)
+{
+       mdns_query_message_t message = NULL;
+       mdns_query_message_t obj = _mdns_query_message_alloc();
+       require_quiet(obj, exit);
+
+       const OSStatus err = _mdns_message_init(obj, NULL, options);
+       require_noerr_quiet(err, exit);
+
+       message = obj;
+       obj = NULL;
+
+exit:
+       mdns_release_null_safe(obj);
+       return message;
+}
+
+//======================================================================================================================
+
+OSStatus
+mdns_query_message_set_qname(const mdns_query_message_t me, const uint8_t * const qname)
+{
+       require_return_value(!me->constructed, kNoErr);
+
+       uint8_t *qname_dup = NULL;
+       OSStatus err = DomainNameDup(qname, &qname_dup, NULL);
+       require_noerr_quiet(err, exit);
+
+       FreeNullSafe(me->qname);
+       me->qname = qname_dup;
+       qname_dup = NULL;
+       err = kNoErr;
+
+exit:
+       return err;
+}
+
+//======================================================================================================================
+
+void
+mdns_query_message_set_qtype(const mdns_query_message_t me, const uint16_t qtype)
+{
+       require_return(!me->constructed);
+       me->qtype = qtype;
+}
+
+//======================================================================================================================
+
+void
+mdns_query_message_set_qclass(const mdns_query_message_t me, const uint16_t qclass)
+{
+       require_return(!me->constructed);
+       me->qclass = qclass;
+}
+
+//======================================================================================================================
+
+void
+mdns_query_message_set_message_id(const mdns_query_message_t me, const uint16_t msg_id)
+{
+       require_return(!me->constructed);
+       me->msg_id = msg_id;
+}
+
+//======================================================================================================================
+
+void
+mdns_query_message_set_ad_bit(const mdns_query_message_t me, const bool set)
+{
+       require_return(!me->constructed);
+       me->set_ad_bit = set;
+}
+
+//======================================================================================================================
+
+void
+mdns_query_message_set_cd_bit(const mdns_query_message_t me, const bool set)
+{
+       require_return(!me->constructed);
+       me->set_cd_bit = set;
+}
+
+//======================================================================================================================
+
+void
+mdns_query_message_set_do_bit(const mdns_query_message_t me, const bool set)
+{
+       require_return(!me->constructed);
+       me->set_do_bit = set;
+}
+
+//======================================================================================================================
+
+void
+mdns_query_message_use_edns0_padding(const mdns_query_message_t me, const bool use)
+{
+       require_return(!me->constructed);
+       me->use_edns0_padding = use;
+}
+
+//======================================================================================================================
+
+#define MDNS_EDNS0_PADDING_OVERHEAD             15     // Size of OPT pseudo-RR with OPTION-CODE and OPTION-LENGTH
+#define MDNS_EDNS0_PADDING_BLOCK_SIZE  128     // <https://tools.ietf.org/html/rfc8467#section-4.1>
+
+#define MDNS_QUERY_MESSAGE_BUFFER_SIZE \
+       RoundUp(kDNSQueryMessageMaxLen + MDNS_EDNS0_PADDING_OVERHEAD, MDNS_EDNS0_PADDING_BLOCK_SIZE)
+
+static OSStatus
+_mdns_query_message_add_edns0_padding(uint8_t query_buf[static MDNS_QUERY_MESSAGE_BUFFER_SIZE], size_t query_len,
+       bool set_do_bit, size_t *out_len);
+
+static OSStatus
+_mdns_query_message_add_edns0_dnssec_ok(uint8_t query_buf[static MDNS_QUERY_MESSAGE_BUFFER_SIZE], size_t query_len,
+       size_t *out_len);
+
+OSStatus
+mdns_query_message_construct(const mdns_query_message_t me)
+{
+       uint16_t flags = kDNSHeaderFlag_RecursionDesired;
+       if (me->set_ad_bit) {
+               flags |= kDNSHeaderFlag_AuthenticData;
+       }
+       if (me->set_cd_bit) {
+               flags |= kDNSHeaderFlag_CheckingDisabled;
+       }
+       uint8_t query_buf[MDNS_QUERY_MESSAGE_BUFFER_SIZE];
+       size_t  query_len;
+       const uint8_t * const qname = _mdns_query_message_get_qname_safe(me);
+       OSStatus err = DNSMessageWriteQuery(me->msg_id, flags, qname, me->qtype, me->qclass, query_buf, &query_len);
+       require_noerr_quiet(err, exit);
+
+       if (me->use_edns0_padding) {
+               err = _mdns_query_message_add_edns0_padding(query_buf, query_len, me->set_do_bit, &query_len);
+               require_noerr_quiet(err, exit);
+       } else if (me->set_do_bit) {
+               err = _mdns_query_message_add_edns0_dnssec_ok(query_buf, query_len, &query_len);
+               require_noerr_quiet(err, exit);
+       }
+       dispatch_data_t query_data = dispatch_data_create(query_buf, query_len, NULL, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
+       require_action_quiet(query_data, exit, err = kNoMemoryErr);
+
+       err = _mdns_message_set_msg_data(me, query_data);
+       dispatch_forget(&query_data);
+       require_noerr_quiet(err, exit);
+
+       me->constructed = true;
+
+exit:
+       return err;
+}
+
+static OSStatus
+_mdns_query_message_add_edns0_padding(uint8_t query_buf[static MDNS_QUERY_MESSAGE_BUFFER_SIZE], const size_t query_len,
+       const bool set_do_bit, size_t * const out_len)
+{
+       const size_t new_len = RoundUp(query_len + MDNS_EDNS0_PADDING_OVERHEAD, MDNS_EDNS0_PADDING_BLOCK_SIZE);
+       require_return_value(new_len <= MDNS_QUERY_MESSAGE_BUFFER_SIZE, kSizeErr);
+
+       uint8_t * const                 end             = &query_buf[query_len];
+       const uint8_t * const   new_end = &query_buf[new_len];
+       memset(end, 0, (size_t)(new_end - end));
+
+       check_compile_time_code(MDNS_EDNS0_PADDING_OVERHEAD == sizeof(dns_fixed_fields_opt1));
+
+       dns_fixed_fields_opt1 * const   pad_opt         = (dns_fixed_fields_opt1 *)end;
+       const uint8_t * const                   pad_start       = (const uint8_t *)&pad_opt[1];
+       dns_fixed_fields_opt1_set_type(pad_opt, kDNSRecordType_OPT);
+       dns_fixed_fields_opt1_set_udp_payload_size(pad_opt, 512);
+       dns_fixed_fields_opt1_set_rdlen(pad_opt, (uint16_t)(new_end - pad_opt->option_code));
+       dns_fixed_fields_opt1_set_option_code(pad_opt, kDNSEDNS0OptionCode_Padding);
+       dns_fixed_fields_opt1_set_option_length(pad_opt, (uint16_t)(new_end - pad_start));
+       if (set_do_bit) {
+               dns_fixed_fields_opt1_set_extended_flags(pad_opt, kDNSExtendedFlag_DNSSECOK);
+       }
+       DNSHeaderSetAdditionalCount((DNSHeader *)&query_buf[0], 1);
+       if (out_len) {
+               *out_len = new_len;
+       }
+       return kNoErr;
+}
+
+static OSStatus
+_mdns_query_message_add_edns0_dnssec_ok(uint8_t query_buf[static MDNS_QUERY_MESSAGE_BUFFER_SIZE],
+       const size_t query_len, size_t * const out_len)
+{
+       dns_fixed_fields_opt *opt;
+       const size_t new_len = query_len + sizeof(*opt);
+       require_return_value(new_len <= MDNS_QUERY_MESSAGE_BUFFER_SIZE, kSizeErr);
+
+       opt = (dns_fixed_fields_opt *)&query_buf[query_len];
+       memset(opt, 0, sizeof(*opt));
+       dns_fixed_fields_opt_set_type(opt, kDNSRecordType_OPT);
+       dns_fixed_fields_opt_set_udp_payload_size(opt, 512);
+       dns_fixed_fields_opt_set_extended_flags(opt, kDNSExtendedFlag_DNSSECOK);
+
+       DNSHeaderSetAdditionalCount((DNSHeader *)&query_buf[0], 1);
+       if (out_len) {
+               *out_len = new_len;
+       }
+       return kNoErr;
+}
+
+//======================================================================================================================
+
+const uint8_t *
+mdns_query_message_get_qname(const mdns_query_message_t me)
+{
+       return _mdns_query_message_get_qname_safe(me);
+}
+
+//======================================================================================================================
+
+uint16_t
+mdns_query_message_get_qtype(const mdns_query_message_t me)
+{
+       return me->qtype;
+}
+
+//======================================================================================================================
+
+uint16_t
+mdns_query_message_get_qclass(const mdns_query_message_t me)
+{
+       return me->qclass;
+}
+
+//======================================================================================================================
+
+uint16_t
+mdns_query_message_get_message_id(const mdns_query_message_t me)
+{
+       return me->msg_id;
+}
+
+//======================================================================================================================
+
+bool
+mdns_query_message_do_bit_is_set(const mdns_query_message_t me)
+{
+       return me->set_do_bit;
+}
+
+//======================================================================================================================
+// MARK: - Query Message Private Methods
+
+static void
+_mdns_query_message_finalize(const mdns_query_message_t me)
+{
+       ForgetMem(&me->qname);
+}
+
+//======================================================================================================================
+
+const uint8_t *
+_mdns_query_message_get_qname_safe(const mdns_query_message_t me)
+{
+       return (me->qname ? me->qname : (const uint8_t *)"");
+}
diff --git a/mDNSMacOSX/mdns_objects/mdns_message.h b/mDNSMacOSX/mdns_objects/mdns_message.h
new file mode 100644 (file)
index 0000000..b2dffc0
--- /dev/null
@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_MESSAGE_H__
+#define __MDNS_MESSAGE_H__
+
+#include "mdns_base.h"
+
+#include <dispatch/dispatch.h>
+#include <MacTypes.h>
+
+#define MDNS_MESSAGE_SUBKIND_DECLARE(NAME)     MDNS_DECL_SUBKIND(NAME ## _message, message)
+
+MDNS_DECL(message);
+MDNS_MESSAGE_SUBKIND_DECLARE(query);
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+#if OS_OBJECT_USE_OBJC
+       typedef mdns_message_t  mdns_any_message_t;
+#else
+       #if defined(__cplusplus)
+               typedef void *  mdns_any_message_t;
+       #else
+               typedef union {
+                       MDNS_UNION_MEMBER(message);
+                       MDNS_UNION_MEMBER(query_message);
+               } mdns_any_message_t __attribute__((__transparent_union__));
+       #endif
+#endif
+
+/*!
+ *     @typedef mdns_message_init_options_t
+ *
+ *     @brief
+ *             Creates a DNS message object from a dispatch_data object.
+ *
+ *     @constant mdns_message_init_option_null
+ *             Represents no option.
+ *
+ *     @constant mdns_message_init_option_disable_header_printing
+ *             Disable message header printing in description.
+ */
+OS_CLOSED_OPTIONS(mdns_message_init_options, uint32_t,
+       mdns_message_init_option_null                                           = 0,            // Null option.
+       mdns_message_init_option_disable_header_printing        = (1U << 0)     // Disable message header printing in description.
+);
+
+__BEGIN_DECLS
+
+/*!
+ *     @brief
+ *             Creates a DNS message object from a dispatch_data object.
+ *
+ *     @param data
+ *             A dispatch_data object containing a DNS message in wire format.
+ *
+ *     @param options
+ *             Message initialization options.
+ *
+ *     @result
+ *             A new message object, or NULL if there was a lack of resources.
+ */
+MDNS_RETURNS_RETAINED MDNS_WARN_RESULT
+mdns_message_t _Nullable
+mdns_message_create_with_dispatch_data(dispatch_data_t data, mdns_message_init_options_t options);
+
+/*!
+ *     @brief
+ *             Gets a message's data in wire format as a dispatch_data object.
+ *
+ *     @param message
+ *             The message.
+ */
+dispatch_data_t
+mdns_message_get_dispatch_data(mdns_any_message_t message);
+
+/*!
+ *     @brief
+ *             Returns a pointer to the first byte of a message's data in wire format.
+ *
+ *     @param message
+ *             The message.
+ */
+const uint8_t * _Nullable
+mdns_message_get_byte_ptr(mdns_any_message_t message);
+
+/*!
+ *     @brief
+ *             Returns the length of a message's data in wire format.
+ *
+ *     @param message
+ *             The message.
+ */
+size_t
+mdns_message_get_length(mdns_any_message_t message);
+
+/*!
+ *     @brief
+ *             Creates a DNS query message object.
+ *
+ *     @param options
+ *             Message initialization options.
+ *
+ *     @result
+ *             A new query message object, or NULL if there was a lack of resources.
+ *
+ *     @discussion
+ *             A query message starts out in a mutable state. Functions such as mdns_query_message_set_qname(),
+ *             mdns_query_message_set_qtype(), and mdns_query_message_set_qclass() can be used to modify the message.
+ *             After all the desired modifications are made, the mdns_query_message_construct() function should be used
+ *             to finalize them.
+ */
+MDNS_RETURNS_RETAINED MDNS_WARN_RESULT
+mdns_query_message_t _Nullable
+mdns_query_message_create(mdns_message_init_options_t options);
+
+/*!
+ *     @brief
+ *             Sets the QNAME of a query message's question.
+ *
+ *     @param message
+ *             The query message.
+ *
+ *     @param qname
+ *             The QNAME in wire format.
+ *
+ *     @result
+ *             kNoErr if the QNAME was successfully set. Otherwise, an error code that indicates the error that was
+ *             encountered.
+ *
+ *     @discussion
+ *             This function has no effect on a query message that has been constructed.
+ */
+OSStatus
+mdns_query_message_set_qname(mdns_query_message_t message, const uint8_t *qname);
+
+/*!
+ *     @brief
+ *             Sets the QTYPE of a query message's question.
+ *
+ *     @param message
+ *             The query message.
+ *
+ *     @param qtype
+ *             The QTYPE.
+ *
+ *     @discussion
+ *             This function has no effect on a query message that has been constructed.
+ */
+void
+mdns_query_message_set_qtype(mdns_query_message_t message, uint16_t qtype);
+
+/*!
+ *     @brief
+ *             Sets the QCLASS of a query message's question.
+ *
+ *     @param message
+ *             The query message.
+ *
+ *     @param qclass
+ *             The QCLASS.
+ *
+ *     @discussion
+ *             This function has no effect on a query message that has been constructed.
+ */
+void
+mdns_query_message_set_qclass(mdns_query_message_t message, uint16_t qclass);
+
+/*!
+ *     @brief
+ *             Sets a query message's ID.
+ *
+ *     @param message
+ *             The query message.
+ *
+ *     @param msg_id
+ *             The message ID.
+ *
+ *     @discussion
+ *             The default message ID is 0.
+ *
+ *             This function has no effect on a query message that has been constructed.
+ */
+void
+mdns_query_message_set_message_id(mdns_query_message_t message, uint16_t msg_id);
+
+/*!
+ *     @brief
+ *             Sets or clears a query message's AD (authentic data) bit.
+ *
+ *     @param message
+ *             The query message.
+ *
+ *     @param set
+ *             If true, the AD bit is set. If false, the AD bit is cleared.
+ *
+ *     @discussion
+ *             The AD bit is clear by default.
+ *
+ *             See <https://tools.ietf.org/html/rfc6840#section-5.7>.
+ *
+ *             This function has no effect on a query message that has been constructed.
+ */
+void
+mdns_query_message_set_ad_bit(mdns_query_message_t message, bool set);
+
+/*!
+ *     @brief
+ *             Sets or clears a query message's CD (checking disabled) bit.
+ *
+ *     @param message
+ *             The query message.
+ *
+ *     @param set
+ *             If true, the CD bit is set. If false, the CD bit is cleared.
+ *
+ *     @discussion
+ *             The CD bit is clear by default.
+ *
+ *             See <https://tools.ietf.org/html/rfc2535#section-6.1>.
+ *
+ *             This function has no effect on a query message that has been constructed.
+ */
+void
+mdns_query_message_set_cd_bit(mdns_query_message_t message, bool set);
+
+/*!
+ *     @brief
+ *             Sets or clears a query message's DO (DNSSEC OK) bit in its EDNS0 header.
+ *
+ *     @param message
+ *             The query message.
+ *
+ *     @param set
+ *             If true, the DO bit is set. If false, the DO bit is cleared.
+ *
+ *     @discussion
+ *             The DO bit is clear by default.
+ *
+ *             See <https://tools.ietf.org/html/rfc3225#section-3>.
+ *
+ *             This function has no effect on a query message that has been constructed.
+ */
+void
+mdns_query_message_set_do_bit(mdns_query_message_t message, bool set);
+
+/*!
+ *     @brief
+ *             Specifies whether a query message should be constructed with EDNS0 padding.
+ *
+ *     @param message
+ *             The query message.
+ *
+ *     @param use
+ *             If true, EDNS0 padding will be used. If false, EDNS0 padding will not be used.
+ *
+ *     @discussion
+ *             By default, EDNS0 padding is not used.
+ *
+ *             The padding strategy that will be utilized during construction is the Block-Length Padding strategy, as
+ *             recommended by <https://tools.ietf.org/html/rfc8467#section-4.1>.
+ *
+ *             This function has no effect on a query message that has been constructed.
+ */
+void
+mdns_query_message_use_edns0_padding(mdns_query_message_t message, bool use);
+
+/*!
+ *     @brief
+ *             Constructs a query message's data in wire format.
+ *
+ *     @param message
+ *             The query message.
+ *
+ *     @result
+ *             kNoErr if the query message data was successfully constructed. Otherwise, an error code that indicates
+ *             the error that was encountered during construction.
+ *
+ *     @discussion
+ *             After a successful call of this function, the return values of mdns_message_get_dispatch_data(),
+ *             mdns_message_get_byte_ptr(), and mdns_message_get_length() will reflect the newly constructed message
+ *             data.
+ *
+ *             This function has no effect on a query message that has been constructed.
+ */
+OSStatus
+mdns_query_message_construct(mdns_query_message_t message);
+
+/*!
+ *     @brief
+ *             Gets a query message's question's QNAME in wire format.
+ *
+ *     @param message
+ *             The query message.
+ *
+ *     @discussion
+ *             The pointer returned by this function is guaranteed to be valid during the lifetime of the query message
+ *             object.
+ */
+const uint8_t *
+mdns_query_message_get_qname(mdns_query_message_t message);
+
+/*!
+ *     @brief
+ *             Gets a query message's question's QTYPE value.
+ *
+ *     @param message
+ *             The query message.
+ */
+uint16_t
+mdns_query_message_get_qtype(mdns_query_message_t message);
+
+/*!
+ *     @brief
+ *             Gets a query message's question's QCLASS value.
+ *
+ *     @param message
+ *             The query message.
+ */
+uint16_t
+mdns_query_message_get_qclass(mdns_query_message_t message);
+
+/*!
+ *     @brief
+ *             Gets a query message's ID.
+ *
+ *     @param message
+ *             The query message.
+ */
+uint16_t
+mdns_query_message_get_message_id(mdns_query_message_t message);
+
+/*!
+ *     @brief
+ *             Determines whether a query message's the DO bit is set.
+ *
+ *     @param message
+ *             The query message.
+ */
+bool
+mdns_query_message_do_bit_is_set(mdns_query_message_t message);
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#endif // __MDNS_MESSAGE_H__
diff --git a/mDNSMacOSX/mdns_objects/mdns_object.c b/mDNSMacOSX/mdns_objects/mdns_object.c
new file mode 100644 (file)
index 0000000..80ff2f2
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2019 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mdns_internal.h"
+#include "mdns_object.h"
+
+#include "mdns_objects.h"
+
+#include <CoreUtils/CoreUtils.h>
+
+//======================================================================================================================
+// MARK: - Object Kind Definition
+
+static char *
+_mdns_object_copy_description(mdns_object_t object, bool debug, bool privacy);
+
+const struct mdns_kind_s _mdns_object_kind = {
+       NULL,           // No superkind.
+       "object",       // Name.
+       _mdns_object_copy_description,
+       NULL,           // No equal method.
+       NULL            // No finalize method.
+};
+
+static const void *
+_mdns_cf_collection_callback_retain(CFAllocatorRef allocator, const void *object);
+
+static void
+_mdns_cf_collection_callback_release(CFAllocatorRef allocator, const void *object);
+
+static CFStringRef
+_mdns_cf_collection_callback_copy_description(const void *object);
+
+static Boolean
+_mdns_cf_collection_callback_equal(const void *object1, const void *object2);
+
+const CFArrayCallBacks mdns_cfarray_callbacks = {
+       0,                                                                                              // version
+       _mdns_cf_collection_callback_retain,                    // retain
+       _mdns_cf_collection_callback_release,                   // release
+       _mdns_cf_collection_callback_copy_description,  // copy description
+       _mdns_cf_collection_callback_equal                              // equal
+};
+
+//======================================================================================================================
+// MARK: - Object Public Methods
+
+mdns_object_t
+mdns_retain(mdns_object_t me)
+{
+       return os_retain(me);
+}
+
+//======================================================================================================================
+
+void
+mdns_release(mdns_object_t me)
+{
+       os_release(me);
+}
+
+//======================================================================================================================
+
+char *
+mdns_copy_description(mdns_object_t me)
+{
+       return mdns_object_copy_description(me, false, false);
+}
+
+//======================================================================================================================
+
+bool
+mdns_equal(mdns_object_t me, mdns_object_t other)
+{
+       if (me == other) {
+               return true;
+       }
+       if (me->kind != other->kind) {
+               return false;
+       }
+       if (me->kind->equal) {
+               return me->kind->equal(me, other);
+       }
+       return false;
+}
+
+//======================================================================================================================
+// MARK: - Object Private Methods
+
+static char *
+_mdns_object_copy_description(mdns_object_t me, __unused bool debug, __unused bool privacy)
+{
+       char *description = NULL;
+       asprintf(&description, "<%s: %p>", me->kind->name, (void *)me);
+       return description;
+}
+
+//======================================================================================================================
+
+char *
+mdns_object_copy_description(mdns_object_t me, bool debug, bool privacy)
+{
+       for (mdns_kind_t kind = me->kind; kind; kind = kind->superkind) {
+               if (kind->copy_description) {
+                       return kind->copy_description(me, debug, privacy);
+               }
+       }
+       return NULL;
+}
+
+//======================================================================================================================
+
+CFStringRef
+mdns_object_copy_description_as_cfstring_ex(mdns_object_t me, bool debug, bool privacy)
+{
+       CFStringRef description = NULL;
+       char *cstring = mdns_object_copy_description(me, debug, privacy);
+       require_quiet(cstring, exit);
+
+       description = CFStringCreateWithCStringNoCopy(NULL, cstring, kCFStringEncodingUTF8, kCFAllocatorMalloc);
+       require_quiet(description, exit);
+       cstring = NULL;
+
+exit:
+       FreeNullSafe(cstring);
+       return description;
+}
+
+//======================================================================================================================
+
+void
+mdns_object_finalize(mdns_object_t me)
+{
+       for (mdns_kind_t kind = me->kind; kind; kind = kind->superkind) {
+               if (kind->finalize) {
+                       kind->finalize(me);
+               }
+       }
+}
+
+//======================================================================================================================
+
+static const void *
+_mdns_cf_collection_callback_retain(__unused CFAllocatorRef allocator, const void *obj)
+{
+       mdns_retain((mdns_object_t)obj);
+       return obj;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_cf_collection_callback_release(__unused CFAllocatorRef allocator, const void *obj)
+{
+       mdns_release((mdns_object_t)obj);
+}
+
+//======================================================================================================================
+
+static CFStringRef
+_mdns_cf_collection_callback_copy_description(const void *obj)
+{
+       return mdns_object_copy_description_as_cfstring((mdns_object_t)obj);
+}
+
+//======================================================================================================================
+
+static Boolean
+_mdns_cf_collection_callback_equal(const void *object1, const void *object2)
+{
+       return mdns_equal((mdns_object_t)object1, (mdns_object_t)object2);
+}
diff --git a/mDNSMacOSX/mdns_objects/mdns_object.h b/mDNSMacOSX/mdns_objects/mdns_object.h
new file mode 100644 (file)
index 0000000..f93f48d
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_OBJECT_H__
+#define __MDNS_OBJECT_H__
+
+#include "mdns_base.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+
+/*!
+ *     @brief
+ *             CFArray callbacks for mdns objects.
+ */
+extern const CFArrayCallBacks mdns_cfarray_callbacks;
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+/*!
+ *     @typedef        mdns_any_t
+ *     @brief
+ *             A pointer to an mdns object.
+ */
+#if OS_OBJECT_USE_OBJC
+       typedef mdns_object_t   mdns_any_t;
+#else
+       #if defined(__cplusplus)
+               typedef void *  mdns_any_t;
+       #else
+               #if !defined(MDNS_ANY_TYPE_INTERNAL_MEMBERS)
+                       #define MDNS_ANY_TYPE_INTERNAL_MEMBERS
+               #endif
+               typedef union {
+                       MDNS_UNION_MEMBER(object);
+                       MDNS_UNION_MEMBER(address);
+                       MDNS_UNION_MEMBER(dns_service);
+                       MDNS_UNION_MEMBER(dns_service_manager);
+                       MDNS_UNION_MEMBER(interface_monitor);
+                       MDNS_UNION_MEMBER(message);
+                       MDNS_UNION_MEMBER(query_message);
+                       MDNS_UNION_MEMBER(querier);
+                       MDNS_UNION_MEMBER(resolver);
+                       MDNS_UNION_MEMBER(set);
+                       MDNS_UNION_MEMBER(trust);
+                       MDNS_ANY_TYPE_INTERNAL_MEMBERS
+               } mdns_any_t __attribute__((__transparent_union__));
+       #endif
+#endif
+
+__BEGIN_DECLS
+
+/*!
+ *     @brief
+ *             Increments the reference count of an mdns object.
+ *
+ *     @param object
+ *             The mdns object.
+ */
+mdns_object_t
+mdns_retain(mdns_any_t object);
+#if OS_OBJECT_USE_OBJC_RETAIN_RELEASE
+       #undef mdns_retain
+       #define mdns_retain(object)     [(object) retain]
+#endif
+
+/*!
+ *     @brief
+ *             Decrements the reference count of an mdns object.
+ *
+ *     @param object
+ *             The mdns object.
+ */
+void
+mdns_release(mdns_any_t object);
+#if OS_OBJECT_USE_OBJC_RETAIN_RELEASE
+       #undef mdns_release
+       #define mdns_release(object)    [(object) release]
+#endif
+
+/*!
+ *     @brief
+ *             Creates a human-readable description of an mdns object as a C string encoded in UTF-8.
+ *
+ *     @param object
+ *             The mdns object.
+ *
+ *     @result
+ *             A C string that must be freed with free(3).
+ */
+MDNS_WARN_RESULT
+char * _Nullable
+mdns_copy_description(mdns_any_t object);
+
+/*!
+ *     @brief
+ *             Determines whether two mdns objects are equal.
+ *
+ *     @param object1
+ *             The first object.
+ *
+ *     @param object2
+ *             The second object.
+ *
+ *     @result
+ *             Returns true if the two objects are equal, otherwise false.
+ */
+bool
+mdns_equal(mdns_any_t object1, mdns_any_t object2);
+
+/*!
+ *     @brief
+ *             Generic events that may occur during the lifetime of some mdns objects.
+ *
+ *     @const mdns_event_error
+ *             A fatal error has occurred.
+ *
+ *     @const mdns_event_invalidated
+ *             The object has been invalidated.
+ *
+ *     @const mdns_event_update
+ *             Some aspect of the object has been updated.
+ */
+OS_CLOSED_ENUM(mdns_event, int,
+       mdns_event_error                = 1,
+       mdns_event_invalidated  = 2,
+       mdns_event_update               = 3
+);
+
+static inline const char *
+mdns_event_to_string(mdns_event_t event)
+{
+       switch (event) {
+               case mdns_event_error:                  return "Error";
+               case mdns_event_invalidated:    return "Invalidated";
+               default:                                                return "?";
+       }
+}
+
+/*!
+ *     @brief
+ *             Generic event handler for mdns objects.
+ *
+ *     @param event
+ *             The event.
+ *
+ *     @param error
+ *             The error associated with a <code>mdns_event_error</code> event. This argument should be ignored for all
+ *             other types of events.
+ *
+ *     @discussion
+ *             After an <code>mdns_event_invalidated</code> event, none of the object's handlers will ever be invoked again.
+ */
+typedef void (^mdns_event_handler_t)(mdns_event_t event, OSStatus error);
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#if OS_OBJECT_USE_OBJC && __has_feature(objc_arc)
+       #define mdns_retain_arc_safe(OBJ)       (OBJ)
+       #define mdns_release_arc_safe(OBJ)      do {} while (0)
+#else
+       #define mdns_retain_arc_safe(OBJ)       mdns_retain(OBJ)
+       #define mdns_release_arc_safe(OBJ)      mdns_release(OBJ)
+#endif
+
+#define mdns_retain_null_safe(OBJ)             \
+       do {                                                            \
+               if (OBJ) {                                              \
+                       mdns_retain_arc_safe(OBJ);      \
+               }                                                               \
+       } while (0)                                                     \
+
+#define mdns_release_null_safe(OBJ)            \
+       do {                                                            \
+               if (OBJ) {                                              \
+                       mdns_release_arc_safe(OBJ);     \
+               }                                                               \
+       } while (0)                                                     \
+
+#define mdns_forget(PTR)                                       \
+       do {                                                                    \
+               if (*(PTR)) {                                           \
+                       mdns_release_arc_safe(*(PTR));  \
+                       *(PTR) = NULL;                                  \
+               }                                                                       \
+       } while(0)
+
+#define mdns_replace(PTR, OBJ)                         \
+       do {                                                                    \
+               if (OBJ) {                                                      \
+                       mdns_retain_arc_safe(OBJ);              \
+               }                                                                       \
+               if (*(PTR)) {                                           \
+                       mdns_release_arc_safe(*(PTR));  \
+               }                                                                       \
+               *(PTR) = (OBJ);                                         \
+       } while(0)
+
+#endif // __MDNS_OBJECT_H__
diff --git a/mDNSMacOSX/mdns_objects/mdns_objects.h b/mDNSMacOSX/mdns_objects/mdns_objects.h
new file mode 100644 (file)
index 0000000..610eb1c
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_OBJECTS_H__
+#define __MDNS_OBJECTS_H__
+
+#include "mdns_private.h"
+
+#include <os/object_private.h>
+
+MDNS_DECL(server);
+MDNS_DECL(session);
+
+//======================================================================================================================
+// MARK: - Kind Declarations
+
+#define MDNS_OBJECT_SUBKIND_DEFINE_ABSTRACT(NAME)      _MDNS_OBJECT_SUBKIND_DEFINE_ABSTRACT(mdns_ ## NAME)
+#define MDNS_OBJECT_SUBKIND_DEFINE(NAME)                       _MDNS_OBJECT_SUBKIND_DEFINE(mdns_ ## NAME)
+#define MDNS_OBJECT_SUBKIND_DEFINE_FULL(NAME)          _MDNS_OBJECT_SUBKIND_DEFINE_FULL(mdns_ ## NAME)
+#define MDNS_OBJECT_SUBKIND_DEFINE_TEST(NAME)          _MDNS_OBJECT_SUBKIND_DEFINE_TEST(mdns_ ## NAME)
+
+// Note: The last check checks if the base's type is equal to that of the superkind. If it's not, then the pointer
+// comparison used as the argument to sizeof will cause a "comparison of distinct pointer types" warning, so long as
+// the warning hasn't been disabled.
+
+#define MDNS_BASE_CHECK(NAME, SUPER)           _MDNS_BASE_CHECK(mdns_ ## NAME, mdns_ ## SUPER)
+#define _MDNS_BASE_CHECK(NAME, SUPER)                                                                                                                  \
+       check_compile_time(offsetof(struct NAME ## _s, base) == 0);                                                                     \
+       check_compile_time(sizeof_field(struct NAME ## _s, base) == sizeof(struct SUPER ## _s));        \
+       extern int _mdns_base_type_check[sizeof(&(((NAME ## _t)0)->base) == ((SUPER ## _t)0))]
+
+#define _MDNS_OBJECT_SUBKIND_DEFINE_TEST(NAME, ...)                    \
+       static const struct mdns_kind_s _ ## NAME ## _kind = {  \
+               .superkind      = &_mdns_object_kind,                                   \
+               .name           = # NAME,                                                               \
+               __VA_ARGS__                                                                                     \
+       };                                                                                                              \
+       _MDNS_OBJECT_SUBKIND_DEFINE_ALLOC(NAME)
+
+#define _MDNS_OBJECT_SUBKIND_DEFINE_ABSTRACT(NAME)                                                                     \
+       static char *                                                                                                                                   \
+       _ ## NAME ## _copy_description(NAME ## _t object, bool debug, bool privacy);    \
+                                                                                                                                                                       \
+       static void                                                                                                                                             \
+       _ ## NAME ## _finalize(NAME ## _t object);                                                                              \
+                                                                                                                                                                       \
+       _MDNS_OBJECT_SUBKIND_DEFINE_STRUCT(                                                                                             \
+               NAME,                                                                                                                                           \
+               _ ## NAME ## _copy_description,                                                                                         \
+               NULL,                                                                                                                                           \
+               _ ## NAME ## _finalize                                                                                                          \
+       )
+
+#define _MDNS_OBJECT_SUBKIND_DEFINE(NAME)              \
+       _MDNS_OBJECT_SUBKIND_DEFINE_ABSTRACT(NAME);     \
+       _MDNS_OBJECT_SUBKIND_DEFINE_ALLOC(NAME)
+
+#define _MDNS_OBJECT_SUBKIND_DEFINE_FULL(NAME)                                                                         \
+       static char *                                                                                                                                   \
+       _ ## NAME ## _copy_description(NAME ## _t object, bool debug, bool privacy);    \
+                                                                                                                                                                       \
+       static bool                                                                                                                                             \
+       _ ## NAME ## _equal(NAME ## _t object1, NAME ## _t object2);                                    \
+                                                                                                                                                                       \
+       static void                                                                                                                                             \
+       _ ## NAME ## _finalize(NAME ## _t object);                                                                              \
+                                                                                                                                                                       \
+       _MDNS_OBJECT_SUBKIND_DEFINE_STRUCT(                                                                                             \
+               NAME,                                                                                                                                           \
+               _ ## NAME ## _copy_description,                                                                                         \
+               _ ## NAME ## _equal,                                                                                                            \
+               _ ## NAME ## _finalize                                                                                                          \
+       );                                                                                                                                                              \
+       _MDNS_OBJECT_SUBKIND_DEFINE_ALLOC(NAME)
+
+#define _MDNS_OBJECT_SUBKIND_DEFINE_STRUCT(NAME, COPY_DESCRIPTION_METHOD, EQUAL_METHOD, FINALIZE_METHOD)       \
+       static const struct mdns_kind_s _ ## NAME ## _kind = {                                                                                                  \
+               &_mdns_object_kind,                                                                                                                                                                     \
+               # NAME,                                                                                                                                                                                         \
+               (COPY_DESCRIPTION_METHOD),                                                                                                                                                      \
+               (EQUAL_METHOD),                                                                                                                                                                         \
+               (FINALIZE_METHOD)                                                                                                                                                                       \
+       };                                                                                                                                                                                                              \
+       _MDNS_BASE_CHECK(NAME, mdns_object)
+
+#define _MDNS_OBJECT_SUBKIND_DEFINE_ALLOC(NAME)                                        \
+       static NAME ## _t                                                                                       \
+       _ ## NAME ## _alloc(void)                                                                       \
+       {                                                                                                                       \
+               NAME ## _t obj = NAME ## _object_alloc(sizeof(*obj));   \
+               require_quiet(obj, exit);                                                               \
+                                                                                                                               \
+               const mdns_object_t base = (mdns_object_t)obj;                  \
+               base->kind = &_ ## NAME ## _kind;                                               \
+                                                                                                                               \
+       exit:                                                                                                           \
+               return obj;                                                                                             \
+       }                                                                                                                       \
+       extern int _mdns_dummy_variable
+
+typedef char * (*mdns_copy_description_f)(mdns_any_t object, bool debug, bool privacy);
+typedef bool   (*mdns_equal_f)(mdns_any_t object1, mdns_any_t object2);
+typedef void   (*mdns_finalize_f)(mdns_any_t object);
+
+typedef const struct mdns_kind_s *     mdns_kind_t;
+struct mdns_kind_s {
+       mdns_kind_t                             superkind;                      // This kind's superkind.
+       const char *                    name;                           // Name of this kind.
+       mdns_copy_description_f copy_description;       // Creates a textual description of an object as a UTF-8 C string.
+       mdns_equal_f                    equal;                          // Compares two objects for equality.
+       mdns_finalize_f                 finalize;                       // Releases object's resources right before the object is freed.
+};
+
+//======================================================================================================================
+// MARK: - Object Kind Definition
+
+struct mdns_object_s {
+       _OS_OBJECT_HEADER(const void * __ptrauth_objc_isa_pointer _os_obj_isa, _os_obj_refcnt, _os_obj_xref_cnt);
+       mdns_kind_t     kind;   // Pointer to an object's kind.
+};
+
+extern const struct mdns_kind_s _mdns_object_kind;
+
+//======================================================================================================================
+// MARK: - Object Private Method Declarations
+
+char *
+mdns_object_copy_description(mdns_any_t object, bool debug, bool privacy);
+
+CFStringRef
+mdns_object_copy_description_as_cfstring_ex(mdns_any_t object, bool debug, bool privacy);
+
+#define mdns_object_copy_description_as_cfstring(OBJ) \
+       mdns_object_copy_description_as_cfstring_ex(OBJ, false, false)
+
+#define mdns_object_copy_debug_description_as_cfstring(OBJ) \
+       mdns_object_copy_description_as_cfstring_ex(OBJ, true, false)
+
+#define mdns_object_copy_redacted_description_as_cfstring(OBJ) \
+       mdns_object_copy_description_as_cfstring_ex(OBJ, false, true)
+
+void
+mdns_object_finalize(mdns_any_t object);
+
+#define MDNS_OBJECT_ALLOC_DECLARE(NAME)        \
+       mdns_ ## NAME ## _t                                     \
+       mdns_ ## NAME ## _object_alloc(size_t size)
+
+MDNS_OBJECT_ALLOC_DECLARE(address);
+MDNS_OBJECT_ALLOC_DECLARE(dns_service);
+MDNS_OBJECT_ALLOC_DECLARE(dns_service_manager);
+MDNS_OBJECT_ALLOC_DECLARE(interface_monitor);
+MDNS_OBJECT_ALLOC_DECLARE(message);
+MDNS_OBJECT_ALLOC_DECLARE(query_message);
+MDNS_OBJECT_ALLOC_DECLARE(querier);
+MDNS_OBJECT_ALLOC_DECLARE(resolver);
+MDNS_OBJECT_ALLOC_DECLARE(server);
+MDNS_OBJECT_ALLOC_DECLARE(session);
+MDNS_OBJECT_ALLOC_DECLARE(set);
+MDNS_OBJECT_ALLOC_DECLARE(trust);
+
+//======================================================================================================================
+// MARK: - Class Declarations
+
+_OS_OBJECT_DECL_SUBCLASS_INTERFACE(mdns_object, object)
+
+#endif // __MDNS_OBJECTS_H__
diff --git a/mDNSMacOSX/mdns_objects/mdns_objects.m b/mDNSMacOSX/mdns_objects/mdns_objects.m
new file mode 100644 (file)
index 0000000..a9e923b
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef MDNS_OBJECT_FORCE_NO_OBJC
+#define MDNS_OBJECT_FORCE_NO_OBJC      0
+#import "mdns_objects.h"
+
+#import <CoreUtils/CoreUtils.h>
+#import <Foundation/Foundation.h>
+#import <os/object_private.h>
+
+//======================================================================================================================
+// MARK: - Class Definitions
+
+#define MDNS_CLASS(NAME)       OS_OBJECT_CLASS(mdns_ ## NAME)
+
+@implementation OS_OBJECT_CLASS(mdns_object)
+- (void)dealloc
+{
+       mdns_object_finalize(self);
+       arc_safe_super_dealloc();
+}
+
+- (NSString *)description
+{
+       NSString * const nsstr = (NSString *)CFBridgingTransfer(mdns_object_copy_description_as_cfstring(self));
+       return arc_safe_autorelease(nsstr);
+}
+
+- (NSString *)debugDescription
+{
+       NSString * const nsstr = (NSString *)CFBridgingTransfer(mdns_object_copy_debug_description_as_cfstring(self));
+       return arc_safe_autorelease(nsstr);
+}
+
+- (NSString *)redactedDescription
+{
+       NSString * const nsstr = (NSString *)CFBridgingTransfer(mdns_object_copy_redacted_description_as_cfstring(self));
+       return arc_safe_autorelease(nsstr);
+}
+
+- (BOOL)isEqual:(id)other
+{
+       if (self == other) {
+               return YES;
+       }
+       if (![other isKindOfClass:[MDNS_CLASS(object) class]]) {
+               return NO;
+       }
+       return (mdns_equal(self, (mdns_object_t)other) ? YES : NO);
+}
+@end
+
+#define MDNS_OBJECT_CLASS_DEFINE(NAME)                                                                                                 \
+       _OS_OBJECT_DECL_SUBCLASS_INTERFACE(mdns_ ## NAME, mdns_object)                                          \
+                                                                                                                                                                               \
+       @implementation MDNS_CLASS(NAME)                                                                                                        \
+       @end                                                                                                                                                            \
+                                                                                                                                                                               \
+       mdns_ ## NAME ## _t                                                                                                                                     \
+       mdns_ ## NAME ## _object_alloc(const size_t size)                                                                       \
+       {                                                                                                                                                                       \
+               return (mdns_ ## NAME ## _t)_os_object_alloc([MDNS_CLASS(NAME) class], size);   \
+       }                                                                                                                                                                       \
+       extern int _mdns_dummy_variable
+
+MDNS_OBJECT_CLASS_DEFINE(address);
+MDNS_OBJECT_CLASS_DEFINE(dns_service);
+MDNS_OBJECT_CLASS_DEFINE(dns_service_manager);
+MDNS_OBJECT_CLASS_DEFINE(interface_monitor);
+MDNS_OBJECT_CLASS_DEFINE(message);
+MDNS_OBJECT_CLASS_DEFINE(query_message);
+MDNS_OBJECT_CLASS_DEFINE(querier);
+MDNS_OBJECT_CLASS_DEFINE(resolver);
+MDNS_OBJECT_CLASS_DEFINE(server);
+MDNS_OBJECT_CLASS_DEFINE(session);
+MDNS_OBJECT_CLASS_DEFINE(set);
+MDNS_OBJECT_CLASS_DEFINE(trust);
diff --git a/mDNSMacOSX/mdns_objects/mdns_powerlog.c b/mDNSMacOSX/mdns_objects/mdns_powerlog.c
new file mode 100644 (file)
index 0000000..b06e86e
--- /dev/null
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mdns_powerlog.h"
+#include "mdns_helpers.h"
+
+#include "DNSMessage.h"
+
+#include <CoreUtils/CoreUtils.h>
+#include <libproc.h>
+#include <os/log.h>
+#include <PowerLog/PowerLog.h>
+#include <SoftLinking/WeakLinking.h>
+#include <sys/proc_info.h>
+
+//======================================================================================================================
+// MARK: - Weak Linking
+
+WEAK_LINK_FORCE_IMPORT(PLLogRegisteredEvent); // The PowerLog framework isn't present in the BaseSystem
+
+//======================================================================================================================
+// MARK: - PowerLog Event Names
+
+#define MDNS_POWERLOG_EVENT_AWDL_BROWSE_START          CFSTR("startAWDLBrowse")
+#define MDNS_POWERLOG_EVENT_AWDL_BROWSE_STOP           CFSTR("stopAWDLBrowse")
+#define MDNS_POWERLOG_EVENT_AWDL_ADVERTISE_START       CFSTR("startAWDLAdvertise")
+#define MDNS_POWERLOG_EVENT_AWDL_ADVERTISE_STOP                CFSTR("stopAWDLAdvertise")
+#define MDNS_POWERLOG_EVENT_AWDL_RESOLVE_START         CFSTR("startAWDLResolve")
+#define MDNS_POWERLOG_EVENT_AWDL_RESOLVE_STOP          CFSTR("stopAWDLResolve")
+
+//======================================================================================================================
+// MARK: - PowerLog Event Dictionary Keys
+
+#define MDNS_POWERLOG_EVENT_KEY_SERVICE_TYPE   CFSTR("service")        // String: Service name w/protocol, e.g., _ssh._tcp.
+#define MDNS_POWERLOG_EVENT_KEY_RECORD_TYPE            CFSTR("recordType")     // String: Record type, e.g., PTR, SRV, A.
+#define MDNS_POWERLOG_EVENT_KEY_CLIENT_PID             CFSTR("clientPID")      // Number: Client's PID.
+#define MDNS_POWERLOG_EVENT_KEY_CLIENT_NAME            CFSTR("clientName")     // String: Client's process name.
+
+//======================================================================================================================
+// MARK: - Helper Prototypes
+
+static void
+_mdns_powerlog_bonjour_event(CFStringRef event_name, const uint8_t *record_name, int record_type, pid_t client_pid);
+
+//======================================================================================================================
+// MARK: - Debug Logging
+
+// The purpose of this os_log category is to log debug messages about logging to PowerLog.
+// It has nothing to do with actually logging to PowerLog.
+MDNS_LOG_CATEGORY_DEFINE(powerlog, "powerlog");
+
+//======================================================================================================================
+// MARK: - External Functions
+
+void
+mdns_powerlog_awdl_browse_start(const uint8_t * const record_name, const int record_type, const pid_t client_pid)
+{
+       _mdns_powerlog_bonjour_event(MDNS_POWERLOG_EVENT_AWDL_BROWSE_START, record_name, record_type, client_pid);
+}
+
+//======================================================================================================================
+
+void
+mdns_powerlog_awdl_browse_stop(const uint8_t * const record_name, const int record_type, const pid_t client_pid)
+{
+       _mdns_powerlog_bonjour_event(MDNS_POWERLOG_EVENT_AWDL_BROWSE_STOP, record_name, record_type, client_pid);
+}
+
+//======================================================================================================================
+
+void
+mdns_powerlog_awdl_advertise_start(const uint8_t * const record_name, const int record_type, const pid_t client_pid)
+{
+       _mdns_powerlog_bonjour_event(MDNS_POWERLOG_EVENT_AWDL_ADVERTISE_START, record_name, record_type, client_pid);
+}
+
+//======================================================================================================================
+
+void
+mdns_powerlog_awdl_advertise_stop(const uint8_t * const record_name, const int record_type, const pid_t client_pid)
+{
+       _mdns_powerlog_bonjour_event(MDNS_POWERLOG_EVENT_AWDL_ADVERTISE_STOP, record_name, record_type, client_pid);
+}
+
+//======================================================================================================================
+
+void
+mdns_powerlog_awdl_resolve_start(const uint8_t * const record_name, const int record_type, const pid_t client_pid)
+{
+       _mdns_powerlog_bonjour_event(MDNS_POWERLOG_EVENT_AWDL_RESOLVE_START, record_name, record_type, client_pid);
+}
+
+//======================================================================================================================
+
+void
+mdns_powerlog_awdl_resolve_stop(const uint8_t * const record_name, const int record_type, const pid_t client_pid)
+{
+       _mdns_powerlog_bonjour_event(MDNS_POWERLOG_EVENT_AWDL_RESOLVE_STOP, record_name, record_type, client_pid);
+}
+
+//======================================================================================================================
+// MARK: - Helpers
+
+static CFDictionaryRef
+_mdns_powerlog_create_event_dictionary(const uint8_t *record_name, int record_type, pid_t client_pid);
+
+static void
+_mdns_powerlog_bonjour_event(const CFStringRef event_name, const uint8_t * const record_name, const int record_type,
+       const pid_t client_pid)
+{
+       require_quiet(PLLogRegisteredEvent, exit);
+
+       const PLClientID plc_id = PLClientIDMDNSResponder;
+       const CFDictionaryRef event_dict = _mdns_powerlog_create_event_dictionary(record_name, record_type, client_pid);
+       os_log_debug(_mdns_powerlog_log(),
+               "Logging to powerlog -- id: %ld, event: %@, dictionary: %@", (long)plc_id, event_name, event_dict);
+       PLLogRegisteredEvent(plc_id, event_name, event_dict, NULL);
+       CFReleaseNullSafe(event_dict);
+
+exit:
+       return;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_powerlog_event_dictionary_add_service_type(CFMutableDictionaryRef event_dict, const uint8_t *record_name);
+
+static void
+_mdns_powerlog_event_dictionary_add_record_type(CFMutableDictionaryRef event_dict, int record_type);
+
+static void
+_mdns_powerlog_event_dictionary_add_client_info(CFMutableDictionaryRef event_dict, pid_t client_pid);
+
+static CFDictionaryRef
+_mdns_powerlog_create_event_dictionary(const uint8_t * const record_name, const int record_type,
+       const pid_t client_pid)
+{
+       CFMutableDictionaryRef event_dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
+               &kCFTypeDictionaryValueCallBacks);
+       require_quiet(event_dict, exit);
+
+       _mdns_powerlog_event_dictionary_add_service_type(event_dict, record_name);
+       _mdns_powerlog_event_dictionary_add_record_type(event_dict, record_type);
+       _mdns_powerlog_event_dictionary_add_client_info(event_dict, client_pid);
+
+exit:
+       return event_dict;
+}
+
+//======================================================================================================================
+
+static char *
+_mdns_get_service_name_string_from_domain_name(const uint8_t *name,
+       char service_name_str[static kDNSServiceMaxDomainName]);
+
+static void
+_mdns_powerlog_event_dictionary_add_service_type(const CFMutableDictionaryRef event_dict,
+       const uint8_t * const record_name)
+{
+       char service_type[kDNSServiceMaxDomainName];
+       if (_mdns_get_service_name_string_from_domain_name(record_name, service_type)) {
+               CFStringRef service_type_value = CFStringCreateWithCString(NULL, service_type, kCFStringEncodingUTF8);
+               if (service_type_value) {
+                       CFDictionaryAddValue(event_dict, MDNS_POWERLOG_EVENT_KEY_SERVICE_TYPE, service_type_value);
+                       ForgetCF(&service_type_value);
+               }
+       }
+}
+
+#define MDNS_TCP_PROTOCOL_LABEL        ((const uint8_t *)"\x4" "_tcp")
+#define MDNS_UDP_PROTOCOL_LABEL        ((const uint8_t *)"\x4" "_udp")
+#define MDNS_LOCAL_DOMAIN_NAME ((const uint8_t *)"\x5" "local")
+
+static char *
+_mdns_get_service_name_string_from_domain_name(const uint8_t * const record_name,
+       char service_name_str[static kDNSServiceMaxDomainName])
+{
+       size_t len;
+       const uint8_t *l1 = NULL;
+       const uint8_t *l2 = NULL;
+       const uint8_t *l3 = NULL;
+       // Get the three labels leading up to the root label.
+       for (const uint8_t *ptr = record_name; (len = *ptr) != 0; ptr = &ptr[1 + len]) {
+               l1 = l2;
+               l2 = l3;
+               l3 = ptr;
+       }
+       // Make sure the first label begins with '_', the second label is '_tcp' or '_udp', and the third label is 'local'.
+       // See <https://tools.ietf.org/html/rfc6763#section-4.1.2>.
+       char *result = NULL;
+       if (l1 && (l1[1] == '_') &&
+               l2 && (DomainLabelEqual(l2, MDNS_TCP_PROTOCOL_LABEL) || DomainLabelEqual(l2, MDNS_UDP_PROTOCOL_LABEL)) &&
+               l3 && DomainNameEqual(l3, MDNS_LOCAL_DOMAIN_NAME)) {
+               uint8_t service_name[kDomainNameLengthMax];
+               uint8_t *ptr = service_name;
+               const uint8_t * const limit = &service_name[countof(service_name)];
+
+               // Append service name label.
+               len = 1 + l1[0];
+               require_quiet(((size_t)(limit - ptr)) >= len, exit);
+               memcpy(ptr, l1, len);
+               ptr += len;
+
+               // Append protocol label, i.e., '_tcp' or '_udp'.
+               len = 1 + l2[0];
+               require_quiet(((size_t)(limit - ptr)) >= len, exit);
+               memcpy(ptr, l2, len);
+               ptr += len;
+
+               // Append root label.
+               require_quiet((limit - ptr) >= 1, exit);
+               *ptr = 0;
+
+               // Convert to string.
+               const OSStatus err = DomainNameToString(service_name, limit, service_name_str, NULL);
+               if (!err) {
+                       // Remove trailing root dot.
+                       len = strlen(service_name_str);
+                       if (len > 0) {
+                               char * const cptr = &service_name_str[len - 1];
+                               if (*cptr == '.') {
+                                       *cptr = '\0';
+                               }
+                       }
+                       result = service_name_str;
+               }
+       }
+
+exit:
+       return result;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_powerlog_event_dictionary_add_record_type(const CFMutableDictionaryRef event_dict, const int record_type)
+{
+       char qtype_str_buf[32];
+       const char *qtype_str = DNSRecordTypeValueToString(record_type);
+       if (!qtype_str) {
+               snprintf(qtype_str_buf, sizeof(qtype_str_buf), "TYPE%d", record_type);
+               qtype_str = qtype_str_buf;
+       }
+       CFStringRef qtype_str_value = CFStringCreateWithCString(NULL, qtype_str, kCFStringEncodingUTF8);
+       if (qtype_str_value) {
+               CFDictionaryAddValue(event_dict, MDNS_POWERLOG_EVENT_KEY_RECORD_TYPE, qtype_str_value);
+               ForgetCF(&qtype_str_value);
+       }
+}
+
+//======================================================================================================================
+
+static char *
+_mdns_pid_to_name(pid_t pid, char name[STATIC_PARAM MAXCOMLEN]);
+
+static void
+_mdns_powerlog_event_dictionary_add_client_info(const CFMutableDictionaryRef event_dict, const pid_t client_pid)
+{
+       const long long client_pid_ll = client_pid;
+       CFNumberRef client_pid_value = CFNumberCreate(NULL, kCFNumberLongLongType, &client_pid_ll);
+       if (client_pid_value != NULL) {
+               CFDictionaryAddValue(event_dict, MDNS_POWERLOG_EVENT_KEY_CLIENT_PID, client_pid_value);
+               ForgetCF(&client_pid_value);
+       }
+       char client_name[MAXCOMLEN];
+       if (_mdns_pid_to_name(client_pid, client_name)) {
+               CFStringRef client_name_value = CFStringCreateWithCString(NULL, client_name, kCFStringEncodingUTF8);
+               if (client_name_value) {
+                       CFDictionaryAddValue(event_dict, MDNS_POWERLOG_EVENT_KEY_CLIENT_NAME, client_name_value);
+                       ForgetCF(&client_name_value);
+               }
+       }
+}
+
+static char *
+_mdns_pid_to_name(const pid_t pid, char name[STATIC_PARAM MAXCOMLEN])
+{
+       if (pid != 0) {
+               struct proc_bsdshortinfo info;
+               const int n = proc_pidinfo(pid, PROC_PIDT_SHORTBSDINFO, 1, &info, PROC_PIDT_SHORTBSDINFO_SIZE);
+               if (n == (int)sizeof(info)) {
+                       strlcpy(name, info.pbsi_comm, MAXCOMLEN);
+                       return name;
+               }
+       }
+       return NULL;
+}
diff --git a/mDNSMacOSX/mdns_objects/mdns_powerlog.h b/mDNSMacOSX/mdns_objects/mdns_powerlog.h
new file mode 100644 (file)
index 0000000..141750c
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_POWERLOG_H__
+#define __MDNS_POWERLOG_H__
+
+#include "mdns_base.h"
+
+#include <stdint.h>
+#include <sys/types.h>
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
+
+void
+mdns_powerlog_awdl_browse_start(const uint8_t *record_name, int record_type, pid_t client_pid);
+
+void
+mdns_powerlog_awdl_browse_stop(const uint8_t *record_name, int record_type, pid_t client_pid);
+
+void
+mdns_powerlog_awdl_advertise_start(const uint8_t *record_name, int record_type, pid_t client_pid);
+
+void
+mdns_powerlog_awdl_advertise_stop(const uint8_t *record_name, int record_type, pid_t client_pid);
+
+void
+mdns_powerlog_awdl_resolve_start(const uint8_t *record_name, int record_type, pid_t client_pid);
+
+void
+mdns_powerlog_awdl_resolve_stop(const uint8_t *record_name, int record_type, pid_t client_pid);
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#endif // __MDNS_POWERLOG_H__
diff --git a/mDNSMacOSX/mdns_objects/mdns_private.h b/mDNSMacOSX/mdns_objects/mdns_private.h
new file mode 100644 (file)
index 0000000..9111455
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_PRIVATE_H__
+#define __MDNS_PRIVATE_H__
+
+#include "mdns_address.h"
+#include "mdns_dns_service.h"
+#include "mdns_interface_monitor.h"
+#include "mdns_message.h"
+#include "mdns_object.h"
+#include "mdns_resolver.h"
+#include "mdns_set.h"
+#include "mdns_trust.h"
+
+#endif // __MDNS_PRIVATE_H__
diff --git a/mDNSMacOSX/mdns_objects/mdns_resolver.c b/mDNSMacOSX/mdns_objects/mdns_resolver.c
new file mode 100644 (file)
index 0000000..b1e7b37
--- /dev/null
@@ -0,0 +1,5619 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mdns_internal.h"
+#include "mdns_resolver.h"
+
+#include "mdns_helpers.h"
+#include "mdns_message.h"
+#include "mdns_objects.h"
+#include "mdns_symptoms.h"
+
+#include "DNSMessage.h"
+#include "HTTPUtilities.h"
+#include <CFNetwork/CFNetworkErrors.h>
+#include <CoreUtils/CoreUtils.h>
+#include <ne_session.h>
+#include <stdatomic.h>
+
+//======================================================================================================================
+// MARK: - Resolver Kind Definition
+
+struct mdns_resolver_s {
+       struct mdns_object_s                    base;                                           // Object base.
+       mdns_server_t                                   server_list;                            // Dynamic list of servers that implement DNS service.
+       mdns_querier_t                                  querier_list;                           // List of active queriers.
+       nw_interface_t                                  interface;                                      // If non-NULL, interface to use for network traffic.
+       CFMutableArrayRef                               server_array;                           // Array of servers that implement DNS service.
+       char *                                                  interface_log_str;                      // Logging string for network interface.
+       dispatch_queue_t                                user_queue;                                     // User's event queue.
+       mdns_resolver_event_handler_t   event_handler;                          // User's event handler.
+       uint64_t                                                suspicious_mode_expiry;         // When suspicious mode expires in ticks.
+       dispatch_source_t                               probe_timer;                            // Periodic timer for restarting probe querier.
+       mdns_querier_t                                  probe_querier;                          // Querier to detect when DNS service is usable again.
+       uint32_t                                                probe_querier_id;                       // ID number of current probe querier.
+       uint32_t                                                initial_dgram_rtx_ms;           // Initial datagram retransmission interval in ms.
+       bool                                                    report_symptoms;                        // True if this resolver should report symptoms.
+       bool                                                    squash_cnames;                          // True if this resolver should squash CNAMEs.
+       bool                                                    suspicious_mode;                        // True if currently in suspicious mode.
+       bool                                                    activated;                                      // True if resolver has been activated.
+       bool                                                    invalidated;                            // True if resolver has bee invalidated.
+       bool                                                    user_activated;                         // True if user called activate method.
+       bool                                                    force_no_stream_sharing;        // True to force queriers to not share stream sessions.
+       bool                                                    cannot_connect;                         // True if all usable servers have connection problems.
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+       unsigned int                                    pqw_threshold;                          // Threshold value for problematic QTYPE workaround. [1]
+#endif
+};
+
+// Notes:
+// 1. If a server don't send prompt responses to queries for problematic QTYPEs (SVCB and HTTPS), then queries for
+//    such QTYPEs will no longer be sent to that server as a workaround. Instead, a NotImp response message will be
+//    fabricated. If the threshold value is zero, the workaround never goes into effect. If the threshold value is
+//    greater than zero, then the value specifies the number of unique QNAME queries that must go without a prompt
+//    response before the workaround goes into effect.
+
+MDNS_OBJECT_SUBKIND_DEFINE_ABSTRACT(resolver);
+
+typedef union {
+       MDNS_UNION_MEMBER(resolver);
+       MDNS_UNION_MEMBER(normal_resolver);
+       MDNS_UNION_MEMBER(tcp_resolver);
+       MDNS_UNION_MEMBER(tls_resolver);
+       MDNS_UNION_MEMBER(https_resolver);
+} mdns_any_resolver_t __attribute__((__transparent_union__));
+
+typedef OSStatus
+(*mdns_resolver_set_provider_name_f)(mdns_any_resolver_t resolver, const char *provider_name);
+
+typedef void
+(*mdns_resolver_set_port_f)(mdns_any_resolver_t resolver, uint16_t port);
+
+typedef OSStatus
+(*mdns_resolver_set_url_path_f)(mdns_any_resolver_t resolver, const char *url_path);
+
+typedef nw_parameters_t
+(*mdns_resolver_get_datagram_params_f)(mdns_any_resolver_t resolver, OSStatus *out_error);
+
+typedef nw_parameters_t
+(*mdns_resolver_get_stream_params_f)(mdns_any_resolver_t resolver, OSStatus *out_error);
+
+typedef nw_endpoint_t
+(*mdns_resolver_create_hostname_endpoint_f)(mdns_any_resolver_t resolver);
+
+typedef const struct mdns_resolver_kind_s *    mdns_resolver_kind_t;
+struct mdns_resolver_kind_s {
+       struct mdns_kind_s                                                      base;
+       const char *                                                            name;
+       mdns_resolver_set_provider_name_f                       set_provider_name;
+       mdns_resolver_set_port_f                                        set_port;
+       mdns_resolver_set_url_path_f                            set_url_path;
+       mdns_resolver_get_datagram_params_f                     get_datagram_params;
+       mdns_resolver_get_stream_params_f                       get_stream_params;
+       mdns_resolver_create_hostname_endpoint_f        create_hostname_endpoint;
+       const char *                                                            datagram_protocol_str;
+       const char *                                                            bytestream_protocol_str;
+       mdns_resolver_type_t                                            type;
+       uint16_t                                                                        default_port;
+       bool                                                                            stream_only;
+       bool                                                                            needs_edns0_padding;
+       bool                                                                            needs_zero_ids;
+       bool                                                                            suspicious_reply_defense;
+       bool                                                                            no_stream_session_sharing;
+};
+
+#define MDNS_RESOLVER_SUBKIND_DECLARE(NAME)    MDNS_DECL_SUBKIND(NAME ## _resolver, resolver)
+#define MDNS_RESOLVER_SUBKIND_DEFINE(NAME, ...)                                                                                                                        \
+       static void                                                                                                                                                                                     \
+       _mdns_ ## NAME ## _resolver_finalize(mdns_ ## NAME ## _resolver_t resolver);                                            \
+                                                                                                                                                                                                               \
+       static const struct mdns_resolver_kind_s _mdns_ ## NAME ## _resolver_kind = {                                           \
+               .base = {                                                                                                                                                                               \
+                       .superkind      = &_mdns_resolver_kind,                                                                                                                 \
+                       .name           = "mdns_" # NAME "_resolver",                                                                                                   \
+                       .finalize       = _mdns_ ## NAME ## _resolver_finalize                                                                                  \
+               },                                                                                                                                                                                              \
+               .name = # NAME "_resolver",                                                                                                                                             \
+               .type = mdns_resolver_type_ ## NAME,                                                                                                                    \
+               __VA_ARGS__                                                                                                                                                                             \
+       };                                                                                                                                                                                                      \
+                                                                                                                                                                                                               \
+       static mdns_resolver_t                                                                                                                                                          \
+       _mdns_ ## NAME ## _resolver_alloc(void)                                                                                                                         \
+       {                                                                                                                                                                                                       \
+               mdns_resolver_t obj = mdns_resolver_object_alloc(sizeof(struct mdns_ ## NAME ## _resolver_s));  \
+               require_quiet(obj, exit);                                                                                                                                               \
+                                                                                                                                                                                                               \
+               const mdns_object_t object = (mdns_object_t)obj;                                                                                                \
+               object->kind = &_mdns_ ## NAME ## _resolver_kind.base;                                                                                  \
+                                                                                                                                                                                                               \
+       exit:                                                                                                                                                                                           \
+               return obj;                                                                                                                                                                             \
+       }                                                                                                                                                                                                       \
+       MDNS_BASE_CHECK(NAME ## _resolver, resolver)
+
+#define MDNS_RESOLVER_SERVER_COUNT_MAX 32
+
+//======================================================================================================================
+// MARK: - Normal Resolver Kind Definition
+
+// As recommended by libnetwork team, use sockets for UDP.
+#if !defined(MDNS_USE_NW_CONNECTION_FOR_UDP_INSTEAD_OF_SOCKETS)
+       #define MDNS_USE_NW_CONNECTION_FOR_UDP_INSTEAD_OF_SOCKETS       0
+#endif
+
+MDNS_RESOLVER_SUBKIND_DECLARE(normal);
+
+struct mdns_normal_resolver_s {
+       struct mdns_resolver_s  base;           // Resolver object base.
+       nw_parameters_t                 udp_params;     // UDP parameters.
+       nw_parameters_t                 tcp_params;     // TCP parameters.
+};
+
+static nw_parameters_t
+_mdns_normal_resolver_get_datagram_params(mdns_normal_resolver_t resolver, OSStatus *out_error);
+
+static nw_parameters_t
+_mdns_normal_resolver_get_stream_params(mdns_normal_resolver_t resolver, OSStatus *out_error);
+
+MDNS_RESOLVER_SUBKIND_DEFINE(normal,
+       .get_datagram_params            = _mdns_normal_resolver_get_datagram_params,
+       .get_stream_params                      = _mdns_normal_resolver_get_stream_params,
+       .datagram_protocol_str          = "UDP",
+       .bytestream_protocol_str        = "TCP",
+       .default_port                           = 53,   // See <https://tools.ietf.org/html/rfc1035#section-4.2>.
+       .suspicious_reply_defense       = true
+);
+
+//======================================================================================================================
+// MARK: - TCP-Only Resolver Kind Definition
+
+MDNS_RESOLVER_SUBKIND_DECLARE(tcp);
+
+struct mdns_tcp_resolver_s {
+       struct mdns_resolver_s  base;   // Resolver object base.
+       nw_parameters_t                 params; // TCP parameters.
+};
+
+static nw_parameters_t
+_mdns_tcp_resolver_get_stream_params(mdns_tcp_resolver_t resolver, OSStatus *out_error);
+
+MDNS_RESOLVER_SUBKIND_DEFINE(tcp,
+       .get_stream_params                      = _mdns_tcp_resolver_get_stream_params,
+       .bytestream_protocol_str        = "TCP",
+       .default_port                           = 53,   // See <https://tools.ietf.org/html/rfc1035#section-4.2.2>.
+       .stream_only                            = true,
+);
+
+//======================================================================================================================
+// MARK: - TLS Resolver Kind Definition
+
+MDNS_RESOLVER_SUBKIND_DECLARE(tls);
+
+struct mdns_tls_resolver_s {
+       struct mdns_resolver_s  base;           // Resolver object base.
+       char *                                  hostname;       // Hostname to use for TLS.
+       nw_parameters_t                 params;         // TLS parameters.
+       uint16_t                                port;           // Port to use if no server addresses are specified.
+};
+
+static OSStatus
+_mdns_tls_resolver_set_provider_name(mdns_tls_resolver_t resolver, const char *provider_name);
+
+static void
+_mdns_tls_resolver_set_port(mdns_tls_resolver_t resolver, uint16_t port);
+
+static nw_parameters_t
+_mdns_tls_resolver_get_stream_params(mdns_tls_resolver_t resolver, OSStatus *out_error);
+
+static nw_endpoint_t
+_mdns_tls_resolver_create_hostname_endpoint(mdns_tls_resolver_t resolver);
+
+MDNS_RESOLVER_SUBKIND_DEFINE(tls,
+       .set_provider_name                      = _mdns_tls_resolver_set_provider_name,
+       .set_port                                       = _mdns_tls_resolver_set_port,
+       .get_stream_params                      = _mdns_tls_resolver_get_stream_params,
+       .create_hostname_endpoint       = _mdns_tls_resolver_create_hostname_endpoint,
+       .bytestream_protocol_str        = "TLS",
+       .default_port                           = 853,  // See <https://tools.ietf.org/html/rfc7858#section-3.1>.
+       .stream_only                            = true,
+       .needs_edns0_padding            = true
+);
+
+//======================================================================================================================
+// MARK: - HTTPS Resolver Kind Definition
+
+MDNS_RESOLVER_SUBKIND_DECLARE(https);
+
+struct mdns_https_resolver_s {
+       struct mdns_resolver_s  base;                           // Resolver object base.
+       char *                                  provider_name;          // Hostname to use for HTTPS.
+       char *                                  url_path;                       // Path to use for HTTPS queries.
+       nw_parameters_t                 params;                         // HTTPS parameters.
+       uint16_t                                port;                           // Port to use if no server addresses are specified.
+};
+
+static OSStatus
+_mdns_https_resolver_set_provider_name(mdns_https_resolver_t resolver, const char *provider_name);
+
+static void
+_mdns_https_resolver_set_port(mdns_https_resolver_t resolver, uint16_t port);
+
+static OSStatus
+_mdns_https_resolver_set_url_path(mdns_https_resolver_t resolver, const char *url_path);
+
+static nw_parameters_t
+_mdns_https_resolver_get_stream_params(mdns_https_resolver_t resolver, OSStatus *out_error);
+
+static nw_endpoint_t
+_mdns_https_resolver_create_hostname_endpoint(mdns_https_resolver_t resolver);
+
+MDNS_RESOLVER_SUBKIND_DEFINE(https,
+       .set_provider_name                      = _mdns_https_resolver_set_provider_name,
+       .set_port                                       = _mdns_https_resolver_set_port,
+       .set_url_path                           = _mdns_https_resolver_set_url_path,
+       .get_stream_params                      = _mdns_https_resolver_get_stream_params,
+       .create_hostname_endpoint       = _mdns_https_resolver_create_hostname_endpoint,
+       .bytestream_protocol_str        = "HTTPS",
+       .default_port                           = 443,  // See <https://tools.ietf.org/html/rfc8484#section-8.1>.
+       .stream_only                            = true,
+       .needs_edns0_padding            = true,
+       .needs_zero_ids                         = true, // See <https://tools.ietf.org/html/rfc8484#section-4.1>.
+       .no_stream_session_sharing      = true  // For DoH, each mdns_session uses an NSURLSessionDataTask.
+);
+
+//======================================================================================================================
+// MARK: - Server Kind Definition
+
+#define MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND    1
+
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+typedef struct pqw_qname_item_s pqw_qname_item_t;
+
+struct pqw_qname_item_s {
+       pqw_qname_item_t *      next;   // Next item in list.
+       uint8_t *                       qname;  // QNAME.
+};
+
+typedef struct {
+       pqw_qname_item_t *      qname_list;             // List of unique QNAMEs whose problematic query didn't get a prompt response.
+       unsigned int            qname_count;    // Current number QNAMEs on list. [1]
+       unsigned int            threshold;              // The maximum number of unique QNAMEs to allow on list.
+} pqw_info_t;
+
+// Notes:
+// 1. When the threshold has been reached, problematic QTYPEs will no longer be sent to the server that owns this
+//    data structure.
+
+#endif
+
+struct mdns_server_s {
+       struct mdns_object_s    base;                                           // Object base.
+       mdns_server_t                   next;                                           // Next server in list.
+       mdns_session_t                  shared_stream_session;          // Shared byte-stream connection to server.
+       nw_endpoint_t                   endpoint;                                       // Endpoint that represents server's IP address and port.
+       nw_path_evaluator_t             path_evaluator;                         // Path evaluator for monitoring server's reachability.
+       uint64_t                                penalty_expiry;                         // If currently penalized, time when penalization will end.
+       uint64_t                                latest_session_start_ticks;     // Latest start time, in ticks, of sessions that got a response.
+       uint64_t                                last_stream_error_ticks;        // When stream_error_count was last incremented. [1]
+       uint32_t                                stream_error_count;                     // Numer of errors experienced by bytestream sessions. [2,3]
+       unsigned int                    rank;                                           // Ordinal rank number of this server.
+       bool                                    usable;                                         // True if the server is currently potentially usable.
+       bool                                    penalized;                                      // True if the server is currently penalized.
+       bool                                    stream_lateness;                        // True if a bytestream sessions are experiencing lateness.
+       bool                                    reported_unresponsiveness;      // True if an unresponsiveness symptom has been reported.
+       bool                                    uses_default_port;                      // True if the endpoint's port is a default port.
+#if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND
+       bool                                    mixes_up_responses;                     // True if server mixes up A/AAAA and HTTPS/SVCB responses.
+#endif
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+       bool                                    responds_to_problematics;       // True if server responds to queries with problematic QTYPEs.
+       uint16_t                                test_query_qtype;                       // The QTYPE that should be used for a test query.
+       pqw_info_t *                    pqw_info;                                       // Information about problematic QTYPEs.
+#endif
+};
+
+// Notes:
+// 1. last_stream_error_ticks is used to track bursts of errors, which can occur if multiple outstanding queriers
+//    experience the same underlying error at once.
+// 2. stream_error_count is incremented whenever a bystream session experiences an error, so long as
+//    stream_error_count was last incremented at least one second ago.
+// 3. stream_error_count is reset to zero whenever an acceptable response is received.
+
+MDNS_OBJECT_SUBKIND_DEFINE(server);
+
+// MDNS_SERVER_STREAM_ERROR_COUNT_THRESHOLD is the number of consecutive errors, as tracked by stream_error_count,
+// that have to be experienced by bytestream sessions to a server, before the server is considered to be
+// problematic (in the absence of bytestream lateness). In the absence of bytestream lateness, when the threshold
+// is reached, a cannot-connect event is generated, so value should be greater than one to not overreact to the
+// case where there's a one-off error and the the subsequent retry is error-free. Three consecutive errors seems
+// small enough to be reactive to longer-term problems, such as a firewall that blocks connections using TCP
+// resets, or a bad server certificate, but not too sensitive to transient errors.
+#define MDNS_SERVER_STREAM_ERROR_COUNT_THRESHOLD       3
+
+// MDNS_SERVER_STREAM_ERROR_BURST_WINDOW_SECS is the width of a bytestream session error burst window in seconds.
+#define MDNS_SERVER_STREAM_ERROR_BURST_WINDOW_SECS     1
+
+//======================================================================================================================
+// MARK: - Delegation
+
+OS_CLOSED_ENUM(mdns_delegation_type, int,
+       mdns_delegation_type_none       = 0,    // No delegation.
+       mdns_delegation_type_pid        = 1,    // Delegation by PID.
+       mdns_delegation_type_uuid       = 2             // Delegation by UUID.
+);
+
+typedef struct {
+       mdns_delegation_type_t  type;   // Type of delegation.
+       union {
+               pid_t                           pid;    // Delegator's PID if type is mdns_delegation_type_pid.
+               uuid_t                          uuid;   // Delegator's UUID if type is mdns_delegation_type_uuid.
+       } ident;                                                // Delegator's identifier.
+} mdns_delegation_t;
+
+//======================================================================================================================
+// MARK: - Session Kind Definition
+
+OS_CLOSED_ENUM(mdns_session_event, int,
+       mdns_session_event_null                         = 0,
+       mdns_session_event_ready                        = 1,
+       mdns_session_event_lateness_warning     = 2,
+       mdns_session_event_terminated           = 3
+);
+
+typedef void
+(*mdns_session_handle_event_f)(mdns_session_t session, mdns_session_event_t event, OSStatus error, void *context);
+
+typedef void
+(*mdns_session_receive_f)(mdns_session_t session, dispatch_data_t response, void *context);
+
+typedef void
+(*mdns_session_finalize_context_f)(void *context);
+
+typedef struct {
+       mdns_session_handle_event_f             handle_event;
+       mdns_session_receive_f                  receive;
+       mdns_session_finalize_context_f finalize_context;
+} mdns_session_callbacks_t;
+
+OS_CLOSED_ENUM(mdns_session_state, int,
+       mdns_session_state_nascent              = 0,
+       mdns_session_state_activated    = 1,
+       mdns_session_state_failed               = 2,
+       mdns_session_state_done                 = 3
+);
+
+struct mdns_session_s {
+       struct mdns_object_s                            base;                           // Object base.
+       mdns_session_t                                          next;                           // Next session in list.
+       mdns_server_t                                           server;                         // Server associated with this session.
+       dispatch_source_t                                       lateness_timer;         // Lateness timer.
+       void *                                                          context;                        // User object's context for callbacks.
+       const mdns_session_callbacks_t *        callbacks;                      // User object's callbacks.
+       uint64_t                                                        start_ticks;            // Time, in ticks, when the session was activated.
+       uint64_t                                                        last_send_ticks;        // Time, in ticks, of the last send.
+       mdns_session_state_t                            state;                          // Current state.
+       uint32_t                                                        lateness_time_ms;       // Time in ms after activation before lateness timer fires.
+       uint32_t                                                        receive_count;          // Number of messages received.
+       bool                                                            is_stream;                      // True if session is bytestream instead of datagram.
+       bool                                                            is_ready;                       // True if session is ready for sending (while activated).
+};
+
+MDNS_OBJECT_SUBKIND_DEFINE_ABSTRACT(session);
+
+typedef union {
+       MDNS_UNION_MEMBER(session);
+       MDNS_UNION_MEMBER(connection_session);
+       MDNS_UNION_MEMBER(udp_socket_session);
+       MDNS_UNION_MEMBER(url_session);
+} mdns_any_session_t __attribute__((__transparent_union__));
+
+typedef OSStatus
+(*mdns_session_initialize_f)(mdns_any_session_t session, mdns_resolver_t resolver, bool need_bytestream,
+       const mdns_delegation_t *delegation, const uint8_t *qname);
+
+typedef OSStatus
+(*mdns_session_activate_f)(mdns_any_session_t session);
+
+typedef void
+(*mdns_session_invalidate_f)(mdns_any_session_t session);
+
+typedef bool
+(*mdns_session_is_ready_f)(mdns_any_session_t session);
+
+typedef void
+(*mdns_session_send_f)(mdns_any_session_t session, dispatch_data_t message, uint16_t qtype);
+
+typedef bool
+(*mdns_session_is_bytestream_f)(mdns_any_session_t session);
+
+typedef const struct mdns_session_kind_s *     mdns_session_kind_t;
+struct mdns_session_kind_s {
+       struct mdns_kind_s                              base;
+       const char *                                    name;
+       mdns_session_initialize_f               initialize;
+       mdns_session_activate_f                 activate;
+       mdns_session_invalidate_f               invalidate;
+       mdns_session_send_f                             send;
+       mdns_session_is_bytestream_f    is_bytestream_check;
+       mdns_session_is_ready_f                 is_ready_check;
+       bool                                                    is_bytestream;
+       bool                                                    is_always_ready;
+};
+
+#define MDNS_SESSION_SUBKIND_DECLARE(NAME)     MDNS_DECL_SUBKIND(NAME ## _session, session)
+#define MDNS_SESSION_SUBKIND_DEFINE(NAME, ...)                                                                                                                                         \
+       static void                                                                                                                                                                                                             \
+       _mdns_ ## NAME ## _session_finalize(mdns_ ## NAME ## _session_t session);                                                                               \
+                                                                                                                                                                                                                                       \
+       static OSStatus                                                                                                                                                                                                 \
+       _mdns_ ## NAME ## _session_initialize(mdns_ ## NAME ## _session_t session, mdns_resolver_t resolver,                    \
+               bool need_bytestream, const mdns_delegation_t *delegation, const uint8_t *qname);                                                       \
+                                                                                                                                                                                                                                       \
+       static OSStatus                                                                                                                                                                                                 \
+       _mdns_ ## NAME ## _session_activate(mdns_ ## NAME ## _session_t session);                                                                               \
+                                                                                                                                                                                                                                       \
+       static void                                                                                                                                                                                                             \
+       _mdns_ ## NAME ## _session_invalidate(mdns_ ## NAME ## _session_t session);                                                                             \
+                                                                                                                                                                                                                                       \
+       static void                                                                                                                                                                                                             \
+       _mdns_ ## NAME ## _session_send(mdns_ ## NAME ## _session_t session, dispatch_data_t message, uint16_t qtype);  \
+                                                                                                                                                                                                                                       \
+       static const struct mdns_session_kind_s _mdns_ ## NAME ## _session_kind = {                                                                             \
+               .base = {                                                                                                                                                                                                       \
+                       .superkind      = &_mdns_session_kind,                                                                                                                                          \
+                       .name           = "mdns_" # NAME "_session",                                                                                                                            \
+                       .finalize       = _mdns_ ## NAME ## _session_finalize,                                                                                                          \
+               },                                                                                                                                                                                                                      \
+               .name                   = # NAME "_session",                                                                                                                                            \
+               .initialize             = _mdns_ ## NAME ## _session_initialize,                                                                                                        \
+               .activate               = _mdns_ ## NAME ## _session_activate,                                                                                                          \
+               .invalidate             = _mdns_ ## NAME ## _session_invalidate,                                                                                                        \
+               .send                   = _mdns_ ## NAME ## _session_send,                                                                                                                      \
+               __VA_ARGS__                                                                                                                                                                                                     \
+       };                                                                                                                                                                                                                              \
+                                                                                                                                                                                                                                       \
+       static mdns_session_t                                                                                                                                                                                   \
+       _mdns_ ## NAME ## _session_alloc(void)                                                                                                                                                  \
+       {                                                                                                                                                                                                                               \
+               mdns_session_t obj = mdns_session_object_alloc(sizeof(struct mdns_ ## NAME ## _session_s));                                     \
+               require_quiet(obj, exit);                                                                                                                                                                       \
+                                                                                                                                                                                                                                       \
+               const mdns_object_t object = (mdns_object_t)obj;                                                                                                                        \
+               object->kind = &_mdns_ ## NAME ## _session_kind.base;                                                                                                           \
+                                                                                                                                                                                                                                       \
+       exit:                                                                                                                                                                                                                   \
+               return obj;                                                                                                                                                                                                     \
+       }                                                                                                                                                                                                                               \
+       MDNS_BASE_CHECK(NAME ## _session, session)
+
+OS_CLOSED_ENUM(mdns_session_type, int,
+       mdns_session_type_null                  = 0,
+       mdns_session_type_connection    = 1,
+       mdns_session_type_udp_socket    = 2,
+       mdns_session_type_url                   = 3
+);
+
+//======================================================================================================================
+// MARK: - Connection Session Kind Definition
+
+MDNS_SESSION_SUBKIND_DECLARE(connection);
+
+struct mdns_connection_session_s {
+       struct mdns_session_s   base;                   // Session object base.
+       nw_connection_t                 connection;             // Underlying connection.
+       bool                                    is_bytestream;  // True if the session is bytestream as opposed to datagram.
+};
+
+static bool
+_mdns_connection_session_is_bytestream(mdns_connection_session_t session);
+
+MDNS_SESSION_SUBKIND_DEFINE(connection,
+       .is_bytestream_check = _mdns_connection_session_is_bytestream
+);
+
+//======================================================================================================================
+// MARK: - UDP Socket Session Kind Definition
+
+// There are currently some performance problems with using connected UDP sockets and flow diverted traffic.
+// When connecting a UDP socket to a flow diverted destination, the connect() call will return EINPROGRESS.
+// The problem is that there's ambiguity as to when the socket is actually connected and ready for sending.
+#if !defined(MDNS_USE_CONNECTED_UDP_SOCKETS)
+       #define MDNS_USE_CONNECTED_UDP_SOCKETS  0
+#endif
+
+MDNS_SESSION_SUBKIND_DECLARE(udp_socket);
+
+struct mdns_udp_socket_session_s {
+       struct mdns_session_s   base;                                   // Session object base.
+       dispatch_source_t               read_source;                    // GCD read source for UDP socket.
+       int                                             sock;                                   // Underlying UDP socket.
+#if MDNS_USE_CONNECTED_UDP_SOCKETS
+       dispatch_source_t               write_source;                   // GCD write source for UDP socket. For delayed connections.
+       bool                                    connected;                              // True if the UDP socket is connected.
+       bool                                    read_source_suspended;  // True if the GCD read source is suspended;
+#else
+       sockaddr_ip                             server_addr;                    // sockaddr containing address of server.
+       socklen_t                               server_addr_len;                // Length of server sockaddr.
+#endif
+};
+
+#if MDNS_USE_CONNECTED_UDP_SOCKETS
+static bool
+_mdns_udp_socket_session_is_ready(mdns_udp_socket_session_t session);
+#endif
+
+#if MDNS_USE_CONNECTED_UDP_SOCKETS
+MDNS_SESSION_SUBKIND_DEFINE(udp_socket,
+       .is_bytestream          = false,
+       .is_ready_check         = _mdns_udp_socket_session_is_ready
+);
+#else
+MDNS_SESSION_SUBKIND_DEFINE(udp_socket,
+       .is_bytestream          = false,
+       .is_always_ready        = true
+);
+#endif
+
+//======================================================================================================================
+// MARK: - URL Session Kind Definition
+
+MDNS_SESSION_SUBKIND_DECLARE(url);
+
+struct mdns_url_session_s {
+       struct mdns_session_s   base;                   // Session object base.
+       nw_endpoint_t                   url_endpoint;   // Endpoint for URL session.
+       void *                                  http_task;              // HTTP task object.
+};
+
+MDNS_SESSION_SUBKIND_DEFINE(url,
+       .is_bytestream          = true,
+       .is_always_ready        = true
+);
+
+//======================================================================================================================
+// MARK: - Querier Kind Definition
+
+struct mdns_querier_s {
+       struct mdns_object_s                    base;                                   // Object base.
+       mdns_querier_t                                  next;                                   // Next querier in list.
+       mdns_resolver_t                                 resolver;                               // Resolver associated with querier.
+       mdns_server_t                                   current_server;                 // Current server being used to send datagram queries.
+       dispatch_queue_t                                user_queue;                             // User's queue for invoking response handler.
+       mdns_querier_result_handler_t   handler;                                // User's result handler.
+       mdns_query_message_t                    query;                                  // Query message.
+       dispatch_source_t                               rtx_timer;                              // Timer for scheduling datagram query retransmissions.
+       mdns_session_t                                  dgram_session_list;             // List of datagram sessions.
+       mdns_session_t                                  stream_session_list;    // List of bytestream sessions.
+       dispatch_source_t                               timeout_timer;                  // Time limit timer.
+       char *                                                  log_label;                              // User-specified UTF-8 prefix label to use for logging.
+       mdns_message_t                                  response;                               // Final DNS response.
+       mdns_message_t                                  bad_rcode_response;             // DNS response received with a bad RCODE.
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+       mdns_query_message_t                    test_query;                             // Test query to send while sending problematic QTYPE query.
+       uint32_t                                                test_send_count;                // Total number of test queries sent.
+       uint32_t                                                test_query_resp_bitmap; // Bitmap for keeping track of test query responses.
+#endif
+       unsigned int                                    unanswered_query_count; // Number of timed out dgram queries sent to current_server.
+       uint32_t                                                rtx_interval_ms;                // Current retransmit interval for datagram queries.
+       _Atomic(uint32_t)                               send_count;                             // Total number of queries sent.
+       mdns_query_over_tcp_reason_t    over_tcp_reason;                // Reason for using TCP instead of UDP. (For DNS53 only.)
+       uint32_t                                                will_send_bitmap;               // Bitmap for keeping track of servers that we will send to.
+       uint32_t                                                did_send_bitmap;                // Bitmap for keeping track of servers that we did sent to.
+       int32_t                                                 time_limit_ms;                  // Time limit in milliseconds.
+       uint32_t                                                bad_rcode_bitmap;               // Bitmap for keeping track of servers that sent bad RCODEs.
+       int                                                             bad_rcode;                              // RCODE of bad_rcode_response.
+       uint32_t                                                user_id;                                // User-defined identifier.
+       mdns_querier_result_type_t              result_type;                    // Type of result the querier concluded with.
+       OSStatus                                                error;                                  // The fatal error that caused the querier to conclude.
+       mdns_delegation_t                               delegation;                             // Querier's delegation information.
+       bool                                                    use_stream;                             // True if using bytestream (instead of datagram) session.
+       bool                                                    use_shared_stream;              // True if the querier is uses a shared bytestream.
+       bool                                                    activated;                              // True if the querier has been activated.
+       bool                                                    concluded;                              // True if the querier has concluded.
+       bool                                                    user_activated;                 // True if user called activate method.
+       bool                                                    response_is_fabricated; // True if the response is fabricated.
+};
+
+check_compile_time((sizeof_field(struct mdns_querier_s, will_send_bitmap) * 8) <= MDNS_RESOLVER_SERVER_COUNT_MAX);
+check_compile_time((sizeof_field(struct mdns_querier_s, did_send_bitmap)  * 8) <= MDNS_RESOLVER_SERVER_COUNT_MAX);
+check_compile_time((sizeof_field(struct mdns_querier_s, bad_rcode_bitmap) * 8) <= MDNS_RESOLVER_SERVER_COUNT_MAX);
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+check_compile_time((sizeof_field(struct mdns_querier_s, test_query_resp_bitmap) * 8) <= MDNS_RESOLVER_SERVER_COUNT_MAX);
+#endif
+
+MDNS_OBJECT_SUBKIND_DEFINE(querier);
+
+//======================================================================================================================
+// MARK: - Local Prototypes
+
+static dispatch_queue_t
+_mdns_resolver_queue(void);
+
+static bool
+_mdns_message_is_query_response_ignoring_id(const uint8_t *msg_ptr, size_t msg_len, mdns_query_message_t query,
+       uint16_t *out_id);
+
+#if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND
+static bool
+_mdns_message_is_query_response_ignoring_qtype(mdns_message_t msg, mdns_query_message_t query, uint16_t *out_qtype);
+#endif
+
+static bool
+_mdns_message_is_query_response_ex(const uint8_t *msg_ptr, size_t msg_len, mdns_query_message_t query,
+       uint16_t *out_id, uint16_t *out_qtype, bool ignore_qnames);
+
+static uint64_t
+_mdns_ticks_per_second(void);
+
+static bool
+_mdns_path_to_server_is_usable(nw_path_t path, bool encrypted_resolver);
+
+static uint32_t
+_mdns_rank_to_bitmask(const unsigned int rank);
+
+static const char *
+mdns_session_event_to_string(mdns_session_event_t event);
+
+static int64_t
+_mdns_ticks_diff(uint64_t t1, uint64_t t2);
+
+static uint64_t
+_mdns_ticks_to_whole_seconds(uint64_t ticks);
+
+static uint64_t
+_mdns_ticks_to_fractional_milliseconds(uint64_t ticks);
+
+static dispatch_source_t
+_mdns_resolver_create_oneshot_timer(uint32_t time_ms, unsigned int leeway_percent_numerator);
+
+static void
+_mdns_querier_activate_if_ready(mdns_querier_t querier);
+
+static void
+_mdns_querier_initiate_send(mdns_querier_t querier);
+
+static void
+_mdns_querier_start(mdns_querier_t querier);
+
+static void
+_mdns_querier_send_query(mdns_querier_t querier, mdns_session_t session);
+
+static const char *
+_mdns_querier_get_log_label(mdns_querier_t querier);
+
+static OSStatus
+_mdns_querier_reset_time_limit(mdns_querier_t querier);
+
+static void
+_mdns_querier_handle_no_response(mdns_querier_t querier);
+
+static void
+_mdns_querier_set_current_server(mdns_querier_t querier, mdns_server_t server);
+
+static mdns_server_t
+_mdns_querier_get_eligible_server(mdns_querier_t querier);
+
+static mdns_server_t
+_mdns_querier_get_unpenalized_eligible_server(mdns_querier_t querier);
+
+static void
+_mdns_querier_handle_stream_error(mdns_querier_t querier, mdns_server_t server);
+
+static void
+_mdns_querier_handle_bad_rcode(mdns_querier_t querier, mdns_message_t response, int rcode, mdns_server_t server);
+
+static const uint8_t *
+_mdns_querier_get_response_ptr_safe(mdns_querier_t querier);
+
+static size_t
+_mdns_querier_get_response_length_safe(mdns_querier_t querier);
+
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+static void
+_mdns_querier_set_test_query_got_response(mdns_querier_t querier, mdns_server_t server, bool got_response);
+
+static bool
+_mdns_querier_test_query_got_response(mdns_querier_t querier, mdns_server_t server);
+#endif
+
+static bool
+_mdns_server_has_stream_problems(mdns_server_t server);
+
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+static bool
+_mdns_server_supports_qtype(mdns_server_t server, int qtype);
+
+static void
+_mdns_server_handle_lack_of_response(mdns_server_t server, mdns_querier_t querier);
+
+static uint16_t
+_mdns_server_get_test_query_qtype(mdns_server_t server);
+#endif
+
+static bool
+_mdns_session_is_ready(mdns_session_t session);
+
+static void
+_mdns_session_send(mdns_session_t session, dispatch_data_t message, uint16_t qtype);
+
+static bool
+_mdns_session_is_bytestream(mdns_session_t session);
+
+static void
+_mdns_session_invalidate(mdns_session_t session);
+#define mdns_session_forget(X) ForgetCustomEx(X, _mdns_session_invalidate, mdns_release)
+
+static void
+_mdns_session_set_callbacks(mdns_session_t session, const mdns_session_callbacks_t *callbacks, void *context);
+
+static void
+_mdns_session_set_lateness_time(mdns_session_t session, uint32_t time_ms);
+
+static OSStatus
+_mdns_session_initialize(mdns_session_t session, mdns_resolver_t resolver, bool need_bytestream,
+       const mdns_delegation_t *delegation, const uint8_t *qname);
+
+static void
+_mdns_session_activate(mdns_session_t session);
+
+static nw_endpoint_t
+_mdns_common_session_get_server_endpoint(mdns_any_session_t session);
+
+static void
+_mdns_common_session_invoke_ready_event_handler(mdns_any_session_t session);
+
+static void
+_mdns_common_session_invoke_receive(mdns_any_session_t session, dispatch_data_t msg);
+
+static void
+_mdns_common_session_terminate(mdns_any_session_t session, OSStatus error);
+
+static void
+_mdns_common_session_terminate_async(mdns_any_session_t session, OSStatus error);
+
+static mdns_resolver_kind_t
+_mdns_resolver_get_kind(mdns_resolver_t resolver);
+
+static const char *
+_mdns_resolver_get_bytestream_protocol_string(mdns_resolver_t resolver);
+
+static const char *
+_mdns_resolver_get_datagram_protocol_string(mdns_resolver_t resolver);
+
+static uint16_t
+_mdns_resolver_get_default_port(mdns_any_resolver_t resolver);
+
+static bool
+_mdns_resolver_is_stream_only(mdns_resolver_t resolver);
+
+static bool
+_mdns_resolver_needs_edns0_padding(mdns_resolver_t resolver);
+
+static bool
+_mdns_resolver_needs_zero_ids(mdns_resolver_t resolver);
+
+static bool
+_mdns_resolver_needs_suspicious_reply_defense(mdns_resolver_t resolver);
+
+static bool
+_mdns_resolver_no_stream_session_sharing(mdns_resolver_t resolver);
+
+static OSStatus
+_mdns_resolver_add_server_by_endpoint(mdns_resolver_t resolver, nw_endpoint_t endpoint);
+
+static void
+_mdns_resolver_activate_internal(mdns_resolver_t resolver);
+
+static nw_parameters_t
+_mdns_resolver_get_stream_params(mdns_resolver_t resolver, OSStatus *out_error);
+
+static nw_endpoint_t
+_mdns_resolver_create_hostname_endpoint(mdns_resolver_t resolver);
+
+static nw_parameters_t
+_mdns_resolver_get_datagram_params(mdns_resolver_t resolver, OSStatus *out_error);
+
+static void
+_mdns_resolver_deregister_querier(mdns_resolver_t resolver, mdns_querier_t querier);
+
+static void
+_mdns_resolver_register_querier(mdns_resolver_t resolver, mdns_querier_t querier, bool force_stream_mode);
+
+static void
+_mdns_resolver_session_handle_event(mdns_session_t session, mdns_session_event_t event, OSStatus error, void *context);
+
+static void
+_mdns_resolver_session_receive(mdns_session_t session, dispatch_data_t response, void *context);
+
+static mdns_session_t
+_mdns_resolver_create_session(mdns_resolver_t resolver, mdns_server_t server, bool need_bytestream,
+       const mdns_delegation_t *delegation, const uint8_t *domain, OSStatus *out_error);
+
+static const char *
+_mdns_resolver_get_protocol_log_string(mdns_resolver_t resolver, bool for_bytestream);
+
+static const char *
+_mdns_resolver_get_interface_log_string(mdns_resolver_t resolver);
+
+static mdns_resolver_type_t
+_mdns_resolver_get_type(mdns_resolver_t resolver);
+
+static void
+_mdns_resolver_log_receive(mdns_resolver_t resolver, mdns_session_t session, mdns_message_t message, bool acceptable,
+       const char *log_prefix);
+
+static void
+_mdns_resolver_handle_stream_error(mdns_resolver_t resolver, mdns_server_t server, const char *label);
+
+static void
+_mdns_resolver_handle_stream_lateness(mdns_resolver_t resolver, mdns_server_t server, uint64_t session_start_ticks,
+       const char *label);
+
+static void
+_mdns_resolver_handle_stream_response(mdns_resolver_t resolver, mdns_server_t server);
+
+static void
+_mdns_resolver_check_for_problematic_servers(mdns_resolver_t resolver);
+
+static bool
+_mdns_resolver_has_usable_server_without_connection_problems(mdns_resolver_t resolver);
+
+static void
+_mdns_resolver_generate_event(mdns_any_resolver_t resolver, mdns_resolver_event_t event, xpc_object_t info);
+
+static void
+_mdns_resolver_generate_connection_event(mdns_resolver_t resolver);
+
+static void
+_mdns_resolver_log_server_problems(mdns_resolver_t resolver, mdns_server_t server, const char *label);
+
+static bool
+_mdns_resolver_uses_encryption(mdns_resolver_t resolver);
+
+static void
+_mdns_resolver_start_serverless_queries(mdns_resolver_t resolver);
+
+static void
+_mdns_resolver_start_serverless_queries_async(mdns_resolver_t resolver);
+
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+static bool
+_mdns_resolver_use_problematic_qtype_workaround(mdns_resolver_t resolver);
+#endif
+
+#if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND
+static bool
+_mdns_resolver_use_mixed_up_responses_workaround(mdns_resolver_t resolver);
+#endif
+
+static bool
+_mdns_querier_is_response_acceptable(mdns_querier_t querier, mdns_message_t response, bool *out_truncated,
+       bool *out_suspicious, int *out_rcode);
+
+static void
+_mdns_querier_conclude(mdns_querier_t querier, mdns_querier_result_type_t result_type);
+
+static void
+_mdns_querier_conclude_async(mdns_querier_t querier, mdns_querier_result_type_t result_type);
+
+static void
+_mdns_querier_conclude_with_error(mdns_querier_t querier, OSStatus error);
+
+static void
+_mdns_querier_conclude_with_error_async(mdns_querier_t querier, OSStatus error);
+
+static void
+_mdns_querier_conclude_with_response(mdns_querier_t querier, mdns_message_t response);
+
+static void
+_mdns_querier_conclude_with_response_async(mdns_querier_t querier, mdns_message_t response, bool fabricated);
+
+static void
+_mdns_querier_conclude_ex(mdns_querier_t querier, mdns_querier_result_type_t result_type, OSStatus status,
+       mdns_message_t response);
+
+static OSStatus
+_mdns_add_dns_over_bytestream_framer(nw_parameters_t params);
+
+static nw_parameters_t
+_mdns_create_udp_parameters(OSStatus *out_error);
+
+static nw_parameters_t
+_mdns_create_tcp_parameters(OSStatus *out_error);
+
+static bool
+_mdns_rcode_is_good(const int rcode);
+
+#if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND || MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+static bool
+_mdns_qtype_is_problematic(int qtype);
+
+static mdns_message_t
+_mdns_create_empty_response_for_query(mdns_query_message_t query, int rcode, OSStatus *out_error);
+#endif
+
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+static mdns_query_message_t
+_mdns_create_simple_test_query(mdns_query_message_t query, uint16_t qtype);
+
+static bool
+_mdns_message_is_adequate_test_query_response(mdns_message_t msg, mdns_query_message_t query);
+
+static pqw_info_t *
+_pqw_info_create(unsigned int threshold);
+
+static void
+_pqw_info_free(pqw_info_t *info);
+#define _pqw_info_forget(X)    ForgetCustom(X, _pqw_info_free)
+
+static bool
+_pqw_info_threshold_reached(const pqw_info_t *info);
+
+static bool
+_pqw_info_can_accept_qname(const pqw_info_t *info, const uint8_t *qname);
+
+static pqw_qname_item_t *
+_pqw_qname_item_create(const uint8_t *qname, OSStatus *out_error);
+
+static void
+_pqw_qname_item_free(pqw_qname_item_t *item);
+#define _pqw_qname_item_forget(X)      ForgetCustom(X, _pqw_qname_item_free)
+
+static void
+_pqw_qname_list_free(pqw_qname_item_t *list);
+#define _pqw_qname_list_forget(X)      ForgetCustom(X, _pqw_qname_list_free)
+#endif
+
+#if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND
+typedef bool
+(*qtype_test_f)(int qtype);
+
+static bool
+_mdns_qtype_is_address_type(int qtype);
+#endif
+
+//======================================================================================================================
+// MARK: - Internals
+
+MDNS_LOG_CATEGORY_DEFINE(resolver, "resolver");
+
+#define MDNS_RESOLVER_CONNECTION_TIMEOUT_MS    1500
+
+//======================================================================================================================
+// MARK: - Resolver Public Methods
+
+mdns_resolver_t
+mdns_resolver_create(mdns_resolver_type_t type, uint32_t interface_index, OSStatus *out_error)
+{
+       mdns_resolver_t resolver = NULL;
+       mdns_resolver_t obj;
+       OSStatus err;
+       switch (type) {
+               case mdns_resolver_type_normal:
+                       obj = _mdns_normal_resolver_alloc();
+                       require_action_quiet(obj, exit, err = kNoMemoryErr);
+                       break;
+
+               case mdns_resolver_type_tcp:
+                       obj = _mdns_tcp_resolver_alloc();
+                       require_action_quiet(obj, exit, err = kNoMemoryErr);
+                       break;
+
+               case mdns_resolver_type_tls:
+                       obj = _mdns_tls_resolver_alloc();
+                       require_action_quiet(obj, exit, err = kNoMemoryErr);
+                       break;
+
+               case mdns_resolver_type_https:
+                       obj = _mdns_https_resolver_alloc();
+                       require_action_quiet(obj, exit, err = kNoMemoryErr);
+                       break;
+
+               case mdns_resolver_type_null:
+               default:
+                       obj = NULL;
+                       err = kTypeErr;
+                       goto exit;
+       }
+       obj->server_array = CFArrayCreateMutable(NULL, 0, &mdns_cfarray_callbacks);
+       require_action_quiet(obj->server_array, exit, err = kNoResourcesErr);
+
+       if (interface_index != 0) {
+               obj->interface = nw_interface_create_with_index(interface_index);
+               require_action_quiet(obj->interface, exit, err = kUnknownErr);
+       }
+       resolver        = obj;
+       obj                     = NULL;
+       err                     = kNoErr;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       mdns_release_null_safe(obj);
+       return resolver;
+}
+
+//======================================================================================================================
+
+void
+mdns_resolver_set_queue(const mdns_resolver_t me, const dispatch_queue_t queue)
+{
+       if (!me->user_activated) {
+               dispatch_retain(queue);
+               dispatch_release_null_safe(me->user_queue);
+               me->user_queue = queue;
+       }
+}
+
+//======================================================================================================================
+
+void
+mdns_resolver_set_event_handler(const mdns_resolver_t me, const mdns_resolver_event_handler_t handler)
+{
+       if (!me->user_activated) {
+               const mdns_resolver_event_handler_t new_handler = handler ? Block_copy(handler) : NULL;
+               if (me->event_handler) {
+                       Block_release(me->event_handler);
+               }
+               me->event_handler = new_handler;
+       }
+}
+
+//======================================================================================================================
+
+OSStatus
+mdns_resolver_set_provider_name(mdns_resolver_t me, const char *provider_name)
+{
+       OSStatus err;
+       require_action_quiet(!me->user_activated, exit, err = kAlreadyInitializedErr);
+
+       const mdns_resolver_kind_t kind = _mdns_resolver_get_kind(me);
+       if (kind->set_provider_name) {
+               err = kind->set_provider_name(me, provider_name);
+       } else {
+               err = kNoErr;
+       }
+
+exit:
+       return err;
+}
+
+//======================================================================================================================
+
+void
+mdns_resolver_set_port(const mdns_resolver_t me, const uint16_t port)
+{
+       if (likely(!me->user_activated)) {
+               const mdns_resolver_kind_t kind = _mdns_resolver_get_kind(me);
+               if (kind->set_port) {
+                       kind->set_port(me, port);
+               }
+       }
+}
+
+//======================================================================================================================
+
+OSStatus
+mdns_resolver_set_url_path(mdns_resolver_t me, const char *url_path)
+{
+       OSStatus err;
+       require_action_quiet(!me->user_activated, exit, err = kAlreadyInitializedErr);
+
+       const mdns_resolver_kind_t kind = _mdns_resolver_get_kind(me);
+       if (kind->set_url_path) {
+               err = kind->set_url_path(me, url_path);
+       } else {
+               err = kNoErr;
+       }
+
+exit:
+       return err;
+}
+
+//======================================================================================================================
+
+void
+mdns_resolver_set_squash_cnames(const mdns_resolver_t me, const bool enable)
+{
+       if (!me->user_activated) {
+               me->squash_cnames = enable ? true : false;
+       }
+}
+
+//======================================================================================================================
+
+void
+mdns_resolver_enable_symptom_reporting(const mdns_resolver_t me, const bool enable)
+{
+       if (!me->user_activated) {
+               me->report_symptoms = enable ? true : false;
+       }
+}
+
+//======================================================================================================================
+
+OSStatus
+mdns_resolver_add_server_address(mdns_resolver_t me, mdns_address_t address)
+{
+       OSStatus err;
+       require_action_quiet(!me->user_activated, exit, err = kAlreadyInitializedErr);
+
+       sockaddr_ip sip;
+       memset(&sip, 0, sizeof(sip));
+       const sockaddr_ip * const addr_sip = (const sockaddr_ip *)mdns_address_get_sockaddr(address);
+       const int addr_family = addr_sip->sa.sa_family;
+       if (addr_family == AF_INET) {
+               sip.v4 = addr_sip->v4;
+               if (sip.v4.sin_port == 0) {
+                       sip.v4.sin_port = htons(_mdns_resolver_get_default_port(me));
+               }
+       } else if (addr_family == AF_INET6) {
+               sip.v6 = addr_sip->v6;
+               if (sip.v6.sin6_port == 0) {
+                       sip.v6.sin6_port = htons(_mdns_resolver_get_default_port(me));
+               }
+       } else {
+               err = kTypeErr;
+               goto exit;
+       }
+       nw_endpoint_t endpoint = nw_endpoint_create_address(&sip.sa);
+       require_action_quiet(endpoint, exit, err = kUnknownErr);
+
+       if (me->interface) {
+               nw_endpoint_set_interface(endpoint, me->interface);
+       }
+       err = _mdns_resolver_add_server_by_endpoint(me, endpoint);
+       nw_forget(&endpoint);
+
+exit:
+       return err;
+}
+
+//======================================================================================================================
+
+void
+mdns_resolver_set_initial_datagram_retransmission_interval(const mdns_resolver_t me, const uint32_t interval_secs)
+{
+       if (!me->user_activated) {
+               if (interval_secs < (UINT32_MAX / kMillisecondsPerSecond)) {
+                       me->initial_dgram_rtx_ms = interval_secs * kMillisecondsPerSecond;
+               } else {
+                       me->initial_dgram_rtx_ms = UINT32_MAX;
+               }
+       }
+}
+
+//======================================================================================================================
+
+void
+mdns_resolver_disable_connection_reuse(const mdns_resolver_t me, const bool disable)
+{
+       if (!me->user_activated) {
+               me->force_no_stream_sharing = disable ? true : false;
+       }
+}
+
+//======================================================================================================================
+
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+#define MDNS_RESOLVER_PQW_THRESHOLD_MAX                100
+void
+mdns_resolver_enable_problematic_qtype_workaround(const mdns_resolver_t me, const int threshold)
+{
+       require_return(!me->user_activated);
+       if (threshold > 0) {
+               me->pqw_threshold = Min((unsigned int)threshold, MDNS_RESOLVER_PQW_THRESHOLD_MAX);
+       } else {
+               me->pqw_threshold = 0;
+       }
+}
+#endif
+
+//======================================================================================================================
+
+void
+mdns_resolver_activate(mdns_resolver_t me)
+{
+       if (!me->user_activated) {
+               me->user_activated = true;
+               mdns_retain(me);
+               dispatch_async(_mdns_resolver_queue(),
+               ^{
+                       _mdns_resolver_activate_internal(me);
+                       mdns_release(me);
+               });
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_resolver_invalidate_internal(mdns_resolver_t resolver);
+
+void
+mdns_resolver_invalidate(mdns_resolver_t me)
+{
+       mdns_retain(me);
+       dispatch_async(_mdns_resolver_queue(),
+       ^{
+               _mdns_resolver_invalidate_internal(me);
+               mdns_release(me);
+       });
+}
+
+static void
+_mdns_resolver_invalidate_internal(mdns_resolver_t me)
+{
+       require_return(!me->invalidated);
+
+       me->invalidated = true;
+       dispatch_source_forget(&me->probe_timer);
+       mdns_querier_forget(&me->probe_querier);
+       mdns_server_t server;
+       while ((server = me->server_list) != NULL) {
+               me->server_list = server->next;
+               if (server->path_evaluator) {
+                       nw_path_evaluator_cancel(server->path_evaluator);
+                       nw_forget(&server->path_evaluator);
+               }
+               mdns_session_forget(&server->shared_stream_session);
+       }
+       mdns_querier_t querier;
+       while ((querier = me->querier_list) != NULL) {
+               me->querier_list = querier->next;
+               _mdns_querier_conclude(querier, mdns_querier_result_type_resolver_invalidation);
+               mdns_release(querier);
+       }
+       if (me->event_handler) {
+               mdns_retain(me);
+               dispatch_async(me->user_queue,
+               ^{
+                       me->event_handler(mdns_resolver_event_invalidated, NULL);
+                       mdns_release(me);
+               });
+       }
+}
+
+//======================================================================================================================
+
+bool
+mdns_resolver_type_uses_encryption(const mdns_resolver_type_t type)
+{
+       // Note: A default case isn't used so that the compiler can catch missing resolver types.
+       switch (type) {
+               case mdns_resolver_type_null:
+               case mdns_resolver_type_normal:
+               case mdns_resolver_type_tcp:
+                       return false;
+
+               case mdns_resolver_type_tls:
+               case mdns_resolver_type_https:
+                       return true;
+       }
+       return false;
+}
+
+//======================================================================================================================
+
+mdns_querier_t
+mdns_resolver_create_querier(mdns_resolver_t me, OSStatus *out_error)
+{
+       OSStatus err;
+       mdns_querier_t querier = NULL;
+       mdns_querier_t obj = _mdns_querier_alloc();
+       require_action_quiet(obj, exit, err = kNoMemoryErr);
+
+       atomic_init(&obj->send_count, 0);
+       obj->resolver = me;
+       mdns_retain(obj->resolver);
+
+       obj->query = mdns_query_message_create(mdns_message_init_option_disable_header_printing);
+       require_action_quiet(obj->query, exit, err = kNoResourcesErr);
+
+       querier = obj;
+       obj = NULL;
+       err = kNoErr;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       mdns_release_null_safe(obj);
+       return querier;
+}
+
+//======================================================================================================================
+// MARK: - Resolver Private Methods
+
+static void
+_mdns_resolver_finalize(mdns_resolver_t me)
+{
+       nw_forget(&me->interface);
+       ForgetMem(&me->interface_log_str);
+       ForgetCF(&me->server_array);
+       dispatch_forget(&me->user_queue);
+       BlockForget(&me->event_handler);
+}
+
+//======================================================================================================================
+
+static char *
+_mdns_resolver_copy_description(mdns_resolver_t me, const bool debug, const bool privacy)
+{
+       char *                          description = NULL;
+       char                            buffer[256];
+       char *                          dst = buffer;
+       const char * const      lim = &buffer[countof(buffer)];
+       int                                     n;
+
+       *dst = '\0';
+       if (debug) {
+               n = mdns_snprintf_add(&dst, lim, "<%s: %p>: ", me->base.kind->name, me);
+               require_quiet(n >= 0, exit);
+       }
+       n = mdns_snprintf_add(&dst, lim, "%s ", _mdns_resolver_get_kind(me)->name);
+       require_quiet(n >= 0, exit);
+
+       if (me->interface) {
+           const char *interface_name = nw_interface_get_name(me->interface);
+               n = mdns_snprintf_add(&dst, lim, "using interface %s (%u) ",
+                       interface_name ? interface_name : "???", nw_interface_get_index(me->interface));
+               require_quiet(n >= 0, exit);
+       }
+       n = mdns_snprintf_add(&dst, lim, "with servers [");
+       require_quiet(n >= 0, exit);
+
+       const char *separator = "";
+       const CFIndex count = CFArrayGetCount(me->server_array);
+       for (CFIndex i = 0; i < count; ++i) {
+               const mdns_server_t     server = (mdns_server_t)CFArrayGetValueAtIndex(me->server_array, i);
+
+               char * const server_desc = mdns_object_copy_description(&server->base, false, privacy);
+               n = mdns_snprintf_add(&dst, lim, "%s%s", separator, server_desc ? server_desc : "<NO SERVER DESC.>");
+               FreeNullSafe(server_desc);
+               require_quiet(n >= 0, exit);
+               separator = ", ";
+       }
+       n = mdns_snprintf_add(&dst, lim, "]");
+       require_quiet(n >= 0, exit);
+
+       description = strdup(buffer);
+
+exit:
+       return description;
+}
+
+//======================================================================================================================
+
+static mdns_resolver_kind_t
+_mdns_resolver_get_kind(const mdns_resolver_t me)
+{
+       return (mdns_resolver_kind_t)me->base.kind;
+}
+
+//======================================================================================================================
+
+static const char *
+_mdns_resolver_get_datagram_protocol_string(const mdns_resolver_t me)
+{
+       const mdns_resolver_kind_t kind = _mdns_resolver_get_kind(me);
+       const char * const string = kind->datagram_protocol_str;
+       return (string ? string : "???");
+}
+
+//======================================================================================================================
+
+static const char *
+_mdns_resolver_get_bytestream_protocol_string(const mdns_resolver_t me)
+{
+       const mdns_resolver_kind_t kind = _mdns_resolver_get_kind(me);
+       const char * const string = kind->bytestream_protocol_str;
+       return (string ? string : "???");
+}
+
+//======================================================================================================================
+
+static uint16_t
+_mdns_resolver_get_default_port(const mdns_any_resolver_t any)
+{
+       const mdns_resolver_kind_t kind = _mdns_resolver_get_kind(any.resolver);
+       return kind->default_port;
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_resolver_is_stream_only(const mdns_resolver_t me)
+{
+       const mdns_resolver_kind_t kind = _mdns_resolver_get_kind(me);
+       return kind->stream_only;
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_resolver_needs_edns0_padding(const mdns_resolver_t me)
+{
+       const mdns_resolver_kind_t kind = _mdns_resolver_get_kind(me);
+       return kind->needs_edns0_padding;
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_resolver_needs_zero_ids(const mdns_resolver_t me)
+{
+       const mdns_resolver_kind_t kind = _mdns_resolver_get_kind(me);
+       return kind->needs_zero_ids;
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_resolver_needs_suspicious_reply_defense(const mdns_resolver_t me)
+{
+       const mdns_resolver_kind_t kind = _mdns_resolver_get_kind(me);
+       return kind->suspicious_reply_defense;
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_resolver_no_stream_session_sharing(const mdns_resolver_t me)
+{
+       if (me->force_no_stream_sharing) {
+               return true;
+       }
+       const mdns_resolver_kind_t kind = _mdns_resolver_get_kind(me);
+       return kind->no_stream_session_sharing;
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mdns_resolver_add_server_by_endpoint(mdns_resolver_t me, nw_endpoint_t endpoint)
+{
+       OSStatus err;
+       mdns_server_t server = NULL;
+       const CFIndex server_count = CFArrayGetCount(me->server_array);
+       require_action_quiet(server_count < MDNS_RESOLVER_SERVER_COUNT_MAX, exit, err = kCountErr);
+
+       server = _mdns_server_alloc();
+       require_action_quiet(server, exit, err = kNoMemoryErr);
+
+       server->endpoint = endpoint;
+       nw_retain(server->endpoint);
+
+       const int default_port = _mdns_resolver_get_default_port(me);
+       if ((default_port != 0) && (nw_endpoint_get_port(server->endpoint) == default_port)) {
+               server->uses_default_port = true;
+       }
+       server->rank = ((unsigned int)server_count) + 1;
+       const uint64_t one_hour_ago_ticks       = mach_continuous_time() - (kSecondsPerHour * _mdns_ticks_per_second());
+       server->latest_session_start_ticks      = one_hour_ago_ticks;
+       server->last_stream_error_ticks         = one_hour_ago_ticks;
+
+       CFArrayAppendValue(me->server_array, server);
+       err = kNoErr;
+
+exit:
+       mdns_release_null_safe(server);
+       return err;
+}
+
+//======================================================================================================================
+
+#define MDNS_QUERIER_INITIAL_RTX_INTERVAL_DEFAULT_MS   1000
+
+static void
+_mdns_resolver_activate_servers(mdns_resolver_t resolver);
+
+static void
+_mdns_resolver_set_up_server_path_evaluator(mdns_resolver_t resolver, mdns_server_t server);
+
+static void
+_mdns_resolver_activate_internal(const mdns_resolver_t me)
+{
+       require_return(!me->invalidated && !me->activated);
+
+       me->activated = true;
+       if (unlikely(me->event_handler && !me->user_queue)) {
+               os_log_error(_mdns_resolver_log(), "API misuse: an event handler without a queue is useless!");
+               BlockForget(&me->event_handler);
+       }
+       if (me->initial_dgram_rtx_ms <= 0) {
+               me->initial_dgram_rtx_ms = MDNS_QUERIER_INITIAL_RTX_INTERVAL_DEFAULT_MS;
+       }
+       const CFIndex n = CFArrayGetCount(me->server_array);
+       if (n <= 0) {
+               const nw_endpoint_t endpoint = _mdns_resolver_create_hostname_endpoint(me);
+               if (endpoint) {
+                       _mdns_resolver_add_server_by_endpoint(me, endpoint);
+                       nw_release(endpoint);
+               }
+       }
+       _mdns_resolver_activate_servers(me);
+       if (_mdns_resolver_is_stream_only(me)) {
+               _mdns_resolver_check_for_problematic_servers(me);
+       }
+}
+
+static void
+_mdns_resolver_activate_servers(const mdns_resolver_t me)
+{
+       mdns_server_t *ptr = &me->server_list;
+       const CFIndex n = CFArrayGetCount(me->server_array);
+       for (CFIndex i = 0; i < n; ++i) {
+               const mdns_server_t server = (mdns_server_t)CFArrayGetValueAtIndex(me->server_array, i);
+
+               // Append server to list.
+               server->next = NULL;
+               *ptr = server;
+               ptr = &server->next;
+
+               // Set up a path evaluator if the server's endpoint is an IP address. The server's usability will be
+               // generally based on whether the path to the IP address is satisfied. Otherwise, the endpoint is a
+               // hostname that will be handled by libnetwork, i.e., it will request resolution, then deal with the
+               // paths of the resulting IP addresses, so mark the server as usable.
+               if (nw_endpoint_get_type(server->endpoint) == nw_endpoint_type_address) {
+                       _mdns_resolver_set_up_server_path_evaluator(me, server);
+               } else {
+                       server->usable = true;
+               }
+               os_log(_mdns_resolver_log(), "Server %@ is %{public}susable", server, server->usable ? "" : "un");
+       }
+}
+
+static void
+_mdns_resolver_set_up_server_path_evaluator(const mdns_resolver_t me, const mdns_server_t server)
+{
+       server->path_evaluator = nw_path_create_evaluator_for_endpoint(server->endpoint, NULL);
+       if (unlikely(!server->path_evaluator)) {
+               os_log_error(_mdns_resolver_log(), "Failed to create path evaluator for %@", server);
+               server->usable = true; // Assume that the server is usable.
+               return;
+       }
+       // Start the server's path evaluator.
+       nw_path_evaluator_set_queue(server->path_evaluator, _mdns_resolver_queue());
+       mdns_retain(me);
+       mdns_retain(server);
+       nw_path_evaluator_set_update_handler(server->path_evaluator, _mdns_resolver_queue(),
+       ^(nw_path_t updated_path)
+       {
+               if (_mdns_path_to_server_is_usable(updated_path, _mdns_resolver_uses_encryption(me))) {
+                       if (!server->usable) {
+                               server->usable = true;
+                               os_log(_mdns_resolver_log(), "Server %@ is now usable", server);
+                               _mdns_resolver_start_serverless_queries(me);
+                       }
+               } else {
+                       if (server->usable) {
+                               server->usable = false;
+                               os_log(_mdns_resolver_log(), "Server %@ is now unusable", server);
+                       }
+               }
+       });
+       nw_path_evaluator_set_cancel_handler(server->path_evaluator,
+       ^{
+               mdns_release(me);
+               mdns_release(server);
+       });
+       nw_path_evaluator_start(server->path_evaluator);
+
+       nw_path_t path = nw_path_evaluator_copy_path(server->path_evaluator);
+       if (path) {
+               if (_mdns_path_to_server_is_usable(path, _mdns_resolver_uses_encryption(me))) {
+                       server->usable = true;
+               }
+               nw_forget(&path);
+       }
+}
+
+//======================================================================================================================
+
+static nw_parameters_t
+_mdns_resolver_get_datagram_params(mdns_resolver_t me, OSStatus *out_error)
+{
+       const mdns_resolver_kind_t kind = _mdns_resolver_get_kind(me);
+       if (kind->get_datagram_params) {
+               return kind->get_datagram_params(me, out_error);
+       } else {
+               if (out_error) {
+                       *out_error = kUnsupportedErr;
+               }
+               return NULL;
+       }
+}
+
+//======================================================================================================================
+
+static nw_parameters_t
+_mdns_resolver_get_stream_params(mdns_resolver_t me, OSStatus *out_error)
+{
+       const mdns_resolver_kind_t kind = _mdns_resolver_get_kind(me);
+       if (kind->get_stream_params) {
+               return kind->get_stream_params(me, out_error);
+       } else {
+               if (out_error) {
+                       *out_error = kUnsupportedErr;
+               }
+               return NULL;
+       }
+}
+
+//======================================================================================================================
+
+static nw_endpoint_t
+_mdns_resolver_create_hostname_endpoint(mdns_resolver_t me)
+{
+       const mdns_resolver_kind_t kind = _mdns_resolver_get_kind(me);
+       if (kind->create_hostname_endpoint) {
+               return kind->create_hostname_endpoint(me);
+       } else {
+               return NULL;
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_resolver_insert_server(mdns_resolver_t me, mdns_server_t server)
+{
+       mdns_server_t *ptr;
+       for (ptr = &me->server_list; *ptr; ptr = &(*ptr)->next) {
+               if ((*ptr)->penalized || ((*ptr)->rank) > server->rank) {
+                       break;
+               }
+       }
+       server->next = *ptr;
+       *ptr = server;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_resolver_note_responsiveness(const mdns_resolver_t me, mdns_server_t server, const bool via_stream,
+       const uint64_t session_start_ticks, const int qtype)
+{
+       if (_mdns_ticks_diff(session_start_ticks, server->latest_session_start_ticks) > 0) {
+               server->latest_session_start_ticks = session_start_ticks;
+       }
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+       if (!server->responds_to_problematics && _mdns_qtype_is_problematic(qtype)) {
+               server->responds_to_problematics = true;
+       }
+#else
+       (void)qtype;
+#endif
+       if (server->penalized) {
+               mdns_server_t *ptr;
+               for (ptr = &me->server_list; *ptr; ptr = &(*ptr)->next) {
+                       if (*ptr == server) {
+                               break;
+                       }
+               }
+               if (*ptr) {
+                       *ptr = server->next;
+                       server->next = NULL;
+                       server->penalized = false;
+                       os_log_info(_mdns_resolver_log(), "Unpenalizing responsive server %@", server);
+                       _mdns_resolver_insert_server(me, server);
+               }
+       }
+       if (via_stream) {
+               _mdns_resolver_handle_stream_response(me, server);
+       }
+       if (me->report_symptoms && server->reported_unresponsiveness) {
+               const nw_endpoint_t endpoint = server->endpoint;
+               if (nw_endpoint_get_type(endpoint) == nw_endpoint_type_address) {
+                       mdns_symptoms_report_responsive_server(nw_endpoint_get_address(endpoint));
+                       server->reported_unresponsiveness = false;
+               }
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_resolver_penalize_server_ex(mdns_resolver_t resolver, mdns_server_t server, bool unresponsive,
+       mdns_querier_t querier, uint64_t last_send_ticks);
+
+static void
+_mdns_resolver_penalize_server(const mdns_resolver_t me, const mdns_server_t server)
+{
+       _mdns_resolver_penalize_server_ex(me, server, false, NULL, 0);
+}
+
+static void
+_mdns_resolver_penalize_unresponsive_server(const mdns_resolver_t me, const mdns_server_t server,
+       const mdns_querier_t querier, const uint64_t last_send_ticks)
+{
+       _mdns_resolver_penalize_server_ex(me, server, true, querier, last_send_ticks);
+}
+
+#define MDNS_SERVER_PENALTY_TIME_SECS  60
+
+static void
+_mdns_resolver_penalize_server_ex(const mdns_resolver_t me, const mdns_server_t server, const bool unresponsive,
+       const mdns_querier_t querier, const uint64_t last_send_ticks)
+{
+       if (unresponsive) {
+       #if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+               if (_mdns_resolver_use_problematic_qtype_workaround(me) && querier) {
+                       _mdns_server_handle_lack_of_response(server, querier);
+               }
+       #endif
+               if (_mdns_ticks_diff(last_send_ticks, server->latest_session_start_ticks) < 0) {
+                       return;
+               }
+       }
+       mdns_server_t *ptr;
+       for (ptr = &me->server_list; *ptr; ptr = &(*ptr)->next) {
+               if (*ptr == server) {
+                       break;
+               }
+       }
+       if (*ptr) {
+               *ptr = server->next;
+               server->next                    = NULL;
+               server->penalty_expiry  = mach_continuous_time() + (MDNS_SERVER_PENALTY_TIME_SECS * _mdns_ticks_per_second());
+               server->penalized               = true;
+               while (*ptr) {
+                       ptr = &(*ptr)->next;
+               }
+               *ptr = server;
+       }
+       os_log_info(_mdns_resolver_log(),
+               "%{public}sPenalizing server %@ for " StringifyExpansion( MDNS_SERVER_PENALTY_TIME_SECS ) " seconds",
+               querier ? _mdns_querier_get_log_label(querier) : "", server);
+       if (unresponsive && me->report_symptoms) {
+               const nw_endpoint_t endpoint = server->endpoint;
+               if (nw_endpoint_get_type(endpoint) == nw_endpoint_type_address) {
+                       mdns_symptoms_report_unresponsive_server(nw_endpoint_get_address(endpoint));
+                       server->reported_unresponsiveness = true;
+               }
+       }
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_server_is_excluded(mdns_server_t server, uint32_t exclude_bitmap);
+
+static mdns_server_t
+_mdns_resolver_get_server(const mdns_resolver_t me, const uint32_t exclude_bitmap)
+{
+       mdns_server_t server = me->server_list;
+       if (server && !((server->rank == 1) && !server->penalized && !_mdns_server_is_excluded(server, exclude_bitmap))) {
+               const uint64_t now = mach_continuous_time();
+               mdns_server_t *ptr = &me->server_list;
+               while ((server = *ptr) != NULL) {
+                       int64_t diff;
+                       if (server->penalized && ((diff = _mdns_ticks_diff(now, server->penalty_expiry)) >= 0)) {
+                               *ptr = server->next;
+                               server->next = NULL;
+                               server->penalized = false;
+                               _mdns_resolver_insert_server(me, server);
+
+                               os_log_info(_mdns_resolver_log(), "Unpenalizing server %@ (penalty expired %lld.%03lld seconds ago)",
+                                       server, (long long)_mdns_ticks_to_whole_seconds((uint64_t)diff),
+                                       (long long)_mdns_ticks_to_fractional_milliseconds((uint64_t)diff));
+                       } else {
+                               ptr = &server->next;
+                       }
+               }
+               for (server = me->server_list; server; server = server->next) {
+                       if (!_mdns_server_is_excluded(server, exclude_bitmap)) {
+                               break;
+                       }
+               }
+       }
+       return server;
+}
+
+static bool
+_mdns_server_is_excluded(const mdns_server_t me, const uint32_t exclude_bitmap)
+{
+       if (!me->usable || (exclude_bitmap & _mdns_rank_to_bitmask(me->rank))) {
+               return true;
+       } else {
+               return false;
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_resolver_session_handle_event(const mdns_session_t session, const mdns_session_event_t event,
+       const OSStatus error, void * const context)
+{
+       os_log_with_type(_mdns_resolver_log(),
+               ((event == mdns_session_event_terminated) && error) ? OS_LOG_TYPE_ERROR : OS_LOG_TYPE_INFO,
+               "Resolver session event -- type: %{public}s, error: %{mdns:err}ld",
+               mdns_session_event_to_string(event), (long)error);
+
+       const mdns_resolver_t me = (mdns_resolver_t)context;
+       switch (event) {
+               case mdns_session_event_ready: {
+                       const uint32_t bitmask = _mdns_rank_to_bitmask(session->server->rank);
+                       for (mdns_querier_t querier = me->querier_list; querier; querier = querier->next) {
+                               if (querier->use_stream && (querier->will_send_bitmap & bitmask)) {
+                                       _mdns_querier_send_query(querier, session);
+                               }
+                       }
+                       break;
+               }
+               case mdns_session_event_terminated: {
+                       mdns_server_t server;
+                       for (server = me->server_list; server; server = server->next) {
+                               if (server->shared_stream_session == session) {
+                                       break;
+                               }
+                       }
+                       require_quiet(server, exit);
+
+                       mdns_session_forget(&server->shared_stream_session);
+                       if (error || (session->receive_count == 0)) {
+                               _mdns_resolver_handle_stream_error(me, server, NULL);
+                               _mdns_resolver_penalize_server(me, server);
+                       }
+                       for (mdns_querier_t querier = me->querier_list; querier; querier = querier->next) {
+                               if (querier->use_stream) {
+                                       _mdns_querier_handle_stream_error(querier, server);
+                               }
+                       }
+                       break;
+               }
+               case mdns_session_event_lateness_warning:
+                       _mdns_resolver_handle_stream_lateness(me, session->server, session->start_ticks, NULL);
+                       break;
+
+               default:
+                       break;
+       }
+
+exit:
+       return;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_resolver_session_receive(mdns_session_t session, dispatch_data_t msg_data, void *context)
+{
+       const mdns_message_t msg = mdns_message_create_with_dispatch_data(msg_data,
+               mdns_message_init_option_disable_header_printing);
+       require_return(msg);
+
+       const mdns_resolver_t me = (mdns_resolver_t)context;
+       bool logged_msg = false;
+       // The current querier might conclude while traversing the querier list, so to be safe, its next pointer is
+       // saved at the beginning of the for-loop body.
+       for (mdns_querier_t next, querier = me->querier_list; querier; querier = next) {
+               next = querier->next;
+               int rcode = 0;
+               const bool acceptable = _mdns_querier_is_response_acceptable(querier, msg, NULL, NULL, &rcode);
+               if (acceptable) {
+                       if (!logged_msg) {
+                               _mdns_resolver_log_receive(me, session, msg, true, _mdns_querier_get_log_label(querier));
+                               logged_msg = true;
+                       }
+                       _mdns_resolver_note_responsiveness(me, session->server, _mdns_session_is_bytestream(session),
+                               session->start_ticks, mdns_querier_get_qtype(querier));
+                       if (_mdns_rcode_is_good(rcode)) {
+                               _mdns_querier_conclude_with_response(querier, msg);
+                       } else {
+                               // Note: _mdns_querier_handle_bad_rcode() may or may not conclude the querier.
+                               _mdns_querier_handle_bad_rcode(querier, msg, rcode, session->server);
+                       }
+               }
+       }
+       if (!logged_msg) {
+               _mdns_resolver_log_receive(me, session, msg, false, NULL);
+       }
+       mdns_release(msg);
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_resolver_is_in_suspicious_mode(mdns_resolver_t resolver);
+
+static void
+_mdns_resolver_register_querier(mdns_resolver_t me, mdns_querier_t querier, bool force_stream_mode)
+{
+       require_return_action(!me->invalidated,
+               _mdns_querier_conclude_async(querier, mdns_querier_result_type_resolver_invalidation));
+
+       if (_mdns_resolver_is_stream_only(me) || force_stream_mode) {
+               querier->use_stream = true;
+       } else if (_mdns_resolver_is_in_suspicious_mode(me)) {
+               querier->use_stream = true;
+               querier->over_tcp_reason = mdns_query_over_tcp_reason_in_suspicious_mode;
+       } else {
+               querier->use_stream = false;
+       }
+       querier->will_send_bitmap = 0;
+       querier->did_send_bitmap  = 0;
+       if (!querier->use_stream || _mdns_resolver_no_stream_session_sharing(me)) {
+               querier->use_shared_stream = false;
+       } else {
+               querier->use_shared_stream = true;
+       }
+       mdns_querier_t *ptr = &me->querier_list;
+       while (*ptr) {
+               ptr = &(*ptr)->next;
+       }
+       *ptr = querier;
+       mdns_retain(*ptr);
+       _mdns_querier_start(querier);
+}
+
+static bool
+_mdns_resolver_is_in_suspicious_mode(const mdns_resolver_t me)
+{
+       if (_mdns_resolver_needs_suspicious_reply_defense(me) && me->suspicious_mode) {
+               int64_t diff;
+               const uint64_t now = mach_continuous_time();
+               if ((diff = _mdns_ticks_diff(me->suspicious_mode_expiry, now)) >= 0) {
+                       os_log_info(_mdns_resolver_log(),
+                               "Suspicious mode (%lld.%03lld seconds left): forcing query over bytestream",
+                               (long long)_mdns_ticks_to_whole_seconds((uint64_t)diff),
+                               (long long)_mdns_ticks_to_fractional_milliseconds((uint64_t)diff));
+                       return true;
+               } else {
+                       me->suspicious_mode = false;
+               }
+       }
+       return false;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_forget_session_list(mdns_session_t *list_ptr);
+
+static void
+_mdns_resolver_deregister_querier(mdns_resolver_t me, mdns_querier_t querier)
+{
+       dispatch_source_forget(&querier->rtx_timer);
+
+       _mdns_forget_session_list(&querier->dgram_session_list);
+       _mdns_forget_session_list(&querier->stream_session_list);
+       mdns_querier_t *ptr = &me->querier_list;
+       while (*ptr && (*ptr != querier)) {
+               ptr = &(*ptr)->next;
+       }
+       if (*ptr) {
+               *ptr = querier->next;
+               querier->next = NULL;
+               mdns_release(querier);
+       }
+}
+
+static void
+_mdns_forget_session_list(mdns_session_t * const list_ptr)
+{
+       mdns_session_t list = *list_ptr;
+       if (list) {
+               *list_ptr = NULL;
+               mdns_session_t session;
+               while ((session = list) != NULL) {
+                       list = session->next;
+                       session->next = NULL;
+                       mdns_session_forget(&session);
+               }
+       }
+}
+
+//======================================================================================================================
+
+static mdns_session_t
+_mdns_resolver_create_session(const mdns_resolver_t me, const mdns_server_t server, const bool need_bytestream,
+       const mdns_delegation_t * const delegation, const uint8_t * const qname, OSStatus * const out_error)
+{
+       mdns_session_t session = NULL;
+
+       mdns_session_type_t session_type;
+       switch (_mdns_resolver_get_type(me)) {
+               case mdns_resolver_type_normal:
+                       if (!need_bytestream) {
+                       #if MDNS_USE_NW_CONNECTION_FOR_UDP_INSTEAD_OF_SOCKETS
+                               session_type = mdns_session_type_connection;
+                       #else
+                               session_type = mdns_session_type_udp_socket;
+                       #endif
+                       } else {
+                               session_type = mdns_session_type_connection;
+                       }
+                       break;
+
+               case mdns_resolver_type_tcp:
+               case mdns_resolver_type_tls:
+                       session_type = mdns_session_type_connection;
+                       break;
+
+               case mdns_resolver_type_https:
+                       session_type = mdns_session_type_url;
+                       break;
+
+               case mdns_resolver_type_null:
+               default:
+                       session_type = mdns_session_type_null;
+                       break;
+       }
+       OSStatus err;
+       mdns_session_t obj;
+       switch (session_type) {
+               case mdns_session_type_connection:
+                       obj = _mdns_connection_session_alloc();
+                       require_action_quiet(obj, exit, err = kNoMemoryErr);
+                       break;
+
+               case mdns_session_type_udp_socket:
+                       obj = _mdns_udp_socket_session_alloc();
+                       require_action_quiet(obj, exit, err = kNoMemoryErr);
+                       break;
+
+               case mdns_session_type_url:
+                       obj = _mdns_url_session_alloc();
+                       require_action_quiet(obj, exit, err = kNoMemoryErr);
+                       break;
+
+               case mdns_session_type_null:
+               default:
+                       obj = NULL;
+                       err = kTypeErr;
+                       goto exit;
+       }
+       obj->server = server;
+       mdns_retain(obj->server);
+
+       err = _mdns_session_initialize(obj, me, need_bytestream, delegation, qname);
+       require_noerr_quiet(err, exit);
+
+       const mdns_session_kind_t kind = (mdns_session_kind_t)obj->base.kind;
+       obj->is_stream  = kind->is_bytestream_check ? kind->is_bytestream_check(obj) : kind->is_bytestream;
+       obj->is_ready   = kind->is_ready_check      ? kind->is_ready_check(obj)      : kind->is_always_ready;
+       session = obj;
+       obj = NULL;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       mdns_release_null_safe(obj);
+       return session;
+}
+
+//======================================================================================================================
+
+#define MDNS_RESOLVER_SUSPICIOUS_MODE_DURATION_SECS    10
+
+static void
+_mdns_resolver_got_suspicious_reply(const mdns_resolver_t me)
+{
+       const uint64_t duration         = MDNS_RESOLVER_SUSPICIOUS_MODE_DURATION_SECS * _mdns_ticks_per_second();
+       me->suspicious_mode_expiry      = mach_continuous_time() + duration;
+       me->suspicious_mode                     = true;
+
+       os_log_info(_mdns_resolver_log(), "Got suspicious response, entering suspicious mode for %d seconds",
+               MDNS_RESOLVER_SUSPICIOUS_MODE_DURATION_SECS);
+}
+
+//======================================================================================================================
+
+static const char *
+_mdns_resolver_get_protocol_log_string(mdns_resolver_t me, bool for_bytestream)
+{
+       if (for_bytestream) {
+               return _mdns_resolver_get_bytestream_protocol_string(me);
+       } else {
+               return _mdns_resolver_get_datagram_protocol_string(me);
+       }
+}
+
+//======================================================================================================================
+
+static const char *
+_mdns_resolver_get_interface_log_string(mdns_resolver_t me)
+{
+       if (me->interface) {
+               if (!me->interface_log_str) {
+                       const char * const if_name = nw_interface_get_name(me->interface);
+                       asprintf(&me->interface_log_str, "%s/%u", if_name ? if_name : "", nw_interface_get_index(me->interface));
+               }
+               return (me->interface_log_str ? me->interface_log_str : "???");
+       } else {
+               return "any interface";
+       }
+}
+
+//======================================================================================================================
+
+static mdns_resolver_type_t
+_mdns_resolver_get_type(const mdns_resolver_t me)
+{
+       return _mdns_resolver_get_kind(me)->type;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_resolver_log_receive(const mdns_resolver_t me, const mdns_session_t session, const mdns_message_t msg,
+       const bool acceptable, const char *log_prefix)
+{
+       const size_t msg_len = mdns_message_get_length(msg);
+       os_log(_mdns_resolver_log(),
+               "%{public}sReceived %{public}sacceptable %zu-byte response from %@ over %{public}s via %{public}s -- "
+               "%{public,mdns:dnshdr}.*P, %@",
+               log_prefix ? log_prefix : "",
+               acceptable ? "" : "un",
+               msg_len,
+               session->server,
+               _mdns_resolver_get_protocol_log_string(me, _mdns_session_is_bytestream(session)),
+               _mdns_resolver_get_interface_log_string(me),
+               (int)Min(msg_len, kDNSHeaderLength), mdns_message_get_byte_ptr(msg),
+               msg);
+}
+
+//======================================================================================================================
+
+static void
+_mdns_resolver_handle_stream_error(const mdns_resolver_t me, const mdns_server_t server, const char * const label)
+{
+       require_return(_mdns_resolver_is_stream_only(me));
+
+       const uint64_t now_ticks = mach_continuous_time();
+       const uint64_t elapsed_ticks = now_ticks - server->last_stream_error_ticks;
+       // In case there's a burst of errors, ignore errors outside of the burst window.
+       // For example, a bunch of pending queriers may experience the same underlying error at once.
+       if (elapsed_ticks >= (MDNS_SERVER_STREAM_ERROR_BURST_WINDOW_SECS * _mdns_ticks_per_second())) {
+               const bool had_problems = _mdns_server_has_stream_problems(server);
+               server->last_stream_error_ticks = now_ticks;
+               increment_saturate(server->stream_error_count, UINT32_MAX);
+               if (!had_problems && _mdns_server_has_stream_problems(server)) {
+                       _mdns_resolver_log_server_problems(me, server, label);
+               }
+       }
+       _mdns_resolver_check_for_problematic_servers(me);
+}
+
+//======================================================================================================================
+
+static void
+_mdns_resolver_handle_stream_lateness(const mdns_resolver_t me, const mdns_server_t server,
+       const uint64_t session_start_ticks, const char * const label)
+{
+       require_return(_mdns_resolver_is_stream_only(me));
+
+       if (_mdns_ticks_diff(session_start_ticks, server->latest_session_start_ticks) > 0) {
+               const bool had_problems = _mdns_server_has_stream_problems(server);
+               server->stream_lateness = true;
+               if (!had_problems && _mdns_server_has_stream_problems(server)) {
+                       _mdns_resolver_log_server_problems(me, server, label);
+               }
+               _mdns_resolver_check_for_problematic_servers(me);
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_resolver_handle_stream_response(const mdns_resolver_t me, const mdns_server_t server)
+{
+       require_return(_mdns_resolver_is_stream_only(me));
+
+       const bool had_problems = _mdns_server_has_stream_problems(server);
+       server->stream_error_count      = 0;
+       server->stream_lateness         = false;
+       if (had_problems) {
+               os_log(_mdns_resolver_log(), "Cleared stream problems with %{public}s server %@",
+                       _mdns_resolver_get_bytestream_protocol_string(me), server);
+       }
+       if (me->cannot_connect && _mdns_resolver_has_usable_server_without_connection_problems(me)) {
+               me->cannot_connect = false;
+               dispatch_source_forget(&me->probe_timer);
+               mdns_querier_forget(&me->probe_querier);
+               _mdns_resolver_generate_connection_event(me);
+               // Some queriers may have become serverless if they backed off while the probe querier was active, so check
+               // for serverless queriers, but do it asynchronously in case we're in the middle of processing a response
+               // for a serverless querier.
+               _mdns_resolver_start_serverless_queries_async(me);
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_resolver_start_probe_querier(mdns_resolver_t resolver);
+
+#define MDNS_RESOLVER_PROBE_RETRY_INTERVAL_SECS        30
+
+static void
+_mdns_resolver_check_for_problematic_servers(const mdns_resolver_t me)
+{
+       if (!me->probe_timer && !_mdns_resolver_has_usable_server_without_connection_problems(me)) {
+               me->probe_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _mdns_resolver_queue());
+               require_action_quiet(me->probe_timer, exit,
+                       os_log_error(_mdns_resolver_log(), "Failed to create probe timer"));
+
+               dispatch_source_set_timer(me->probe_timer,
+                       _dispatch_monotonictime_after_sec(MDNS_RESOLVER_PROBE_RETRY_INTERVAL_SECS),
+                       MDNS_RESOLVER_PROBE_RETRY_INTERVAL_SECS * UINT64_C_safe(kNanosecondsPerSecond),
+                       MDNS_RESOLVER_PROBE_RETRY_INTERVAL_SECS * UINT64_C_safe(kNanosecondsPerSecond / 20));
+               dispatch_source_set_event_handler(me->probe_timer,
+               ^{
+                       _mdns_resolver_start_probe_querier(me);
+               });
+               dispatch_activate(me->probe_timer);
+               _mdns_resolver_start_probe_querier(me);
+               if (!me->cannot_connect) {
+                       me->cannot_connect = true;
+                       _mdns_resolver_generate_connection_event(me);
+               }
+       }
+
+exit:
+       return;
+}
+
+static void
+_mdns_resolver_start_probe_querier(const mdns_resolver_t me)
+{
+       mdns_querier_forget(&me->probe_querier);
+       me->probe_querier = mdns_resolver_create_querier(me, NULL);
+       require_action_quiet(me->probe_querier, exit, os_log_error(_mdns_resolver_log(), "Failed to create probe querier"));
+
+       mdns_querier_set_log_label(me->probe_querier, "PQ%u", ++me->probe_querier_id);
+       mdns_querier_set_queue(me->probe_querier, _mdns_resolver_queue());
+       const uint8_t * const probe_qname = (const uint8_t *)"\x5" "apple" "\x3" "com";
+       mdns_querier_set_query(me->probe_querier, probe_qname, kDNSRecordType_NS, kDNSClassType_IN);
+       mdns_querier_activate(me->probe_querier);
+
+exit:
+       return;
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_resolver_has_usable_server_without_connection_problems(const mdns_resolver_t me)
+{
+       for (mdns_server_t server = me->server_list; server; server = server->next) {
+               if (server->usable && !_mdns_server_has_stream_problems(server)) {
+                       return true;
+               }
+       }
+       return false;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_resolver_generate_event(const mdns_any_resolver_t any, const mdns_resolver_event_t event, const xpc_object_t info)
+{
+       const mdns_resolver_t me = any.resolver;
+       require_quiet(!me->invalidated, exit);
+
+       if (me->event_handler) {
+               mdns_retain(me);
+               xpc_retain(info);
+               dispatch_async(me->user_queue,
+               ^{
+                       me->event_handler(event, info);
+                       mdns_release(me);
+                       xpc_release(info);
+               });
+       }
+
+exit:
+       return;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_resolver_generate_connection_event(const mdns_resolver_t me)
+{
+       const xpc_object_t _Nonnull info = xpc_dictionary_create(NULL, NULL, 0);
+       xpc_dictionary_set_bool(info, MDNS_RESOLVER_EVENT_CONNECTION_INFO_KEY_CANNOT_CONNECT, me->cannot_connect);
+       _mdns_resolver_generate_event(me, mdns_resolver_event_connection, info);
+       xpc_release(info);
+}
+
+//======================================================================================================================
+
+static void
+_mdns_resolver_log_server_problems(const mdns_resolver_t me, const mdns_server_t server, const char * const label)
+{
+       os_log_error(_mdns_resolver_log(),
+               "%{public}sHaving stream problems with %{public}s server %@ -- lateness: %{bool}d, error count: %u",
+               label ? label : "", _mdns_resolver_get_bytestream_protocol_string(me), server, server->stream_lateness,
+               server->stream_error_count);
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_resolver_uses_encryption(const mdns_resolver_t me)
+{
+       return mdns_resolver_type_uses_encryption(_mdns_resolver_get_type(me));
+}
+
+//======================================================================================================================
+
+static void
+_mdns_resolver_start_serverless_queries(const mdns_resolver_t me)
+{
+       require_return(!me->invalidated);
+
+       bool have_usable_server = false;
+       for (mdns_server_t server = me->server_list; server; server = server->next) {
+               if (server->usable) {
+                       have_usable_server = true;
+                       break;
+               }
+       }
+       if (have_usable_server) {
+               for (mdns_querier_t querier = me->querier_list; querier; querier = querier->next) {
+                       if (!querier->current_server) {
+                               _mdns_querier_start(querier);
+                       }
+               }
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_resolver_start_serverless_queries_async(const mdns_resolver_t me)
+{
+       mdns_retain(me);
+       dispatch_async(_mdns_resolver_queue(),
+       ^{
+               _mdns_resolver_start_serverless_queries(me);
+               mdns_release(me);
+       });
+}
+
+//======================================================================================================================
+
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+static bool
+_mdns_resolver_use_problematic_qtype_workaround(const mdns_resolver_t me)
+{
+       return (me->pqw_threshold > 0);
+}
+#endif
+
+//======================================================================================================================
+
+#if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND
+static bool
+_mdns_resolver_use_mixed_up_responses_workaround(const mdns_resolver_t me)
+{
+       return (_mdns_resolver_get_type(me) == mdns_resolver_type_normal);
+}
+#endif
+
+//======================================================================================================================
+// MARK: - Normal Resolver Private Methods
+
+static void
+_mdns_normal_resolver_finalize(mdns_normal_resolver_t me)
+{
+       nw_forget(&me->udp_params);
+       nw_forget(&me->tcp_params);
+}
+
+//======================================================================================================================
+
+static nw_parameters_t
+_mdns_normal_resolver_get_datagram_params(mdns_normal_resolver_t me, OSStatus *out_error)
+{
+       OSStatus err;
+       nw_parameters_t params = NULL;
+
+       if (!me->udp_params) {
+               me->udp_params = _mdns_create_udp_parameters(&err);
+               require_noerr_quiet(err, exit);
+       }
+       params = me->udp_params;
+       err = kNoErr;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       return params;
+}
+
+//======================================================================================================================
+
+static nw_parameters_t
+_mdns_normal_resolver_get_stream_params(mdns_normal_resolver_t me, OSStatus *out_error)
+{
+       OSStatus err;
+       nw_parameters_t params = NULL;
+
+       if (!me->tcp_params) {
+               me->tcp_params = _mdns_create_tcp_parameters(&err);
+               require_noerr_quiet(err, exit);
+       }
+       params = me->tcp_params;
+       err = kNoErr;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       return params;
+}
+
+//======================================================================================================================
+// MARK: - TCP-Only Resolver Private Methods
+
+static void
+_mdns_tcp_resolver_finalize(mdns_tcp_resolver_t me)
+{
+       nw_forget(&me->params);
+}
+
+//======================================================================================================================
+
+static nw_parameters_t
+_mdns_tcp_resolver_get_stream_params(mdns_tcp_resolver_t me, OSStatus *out_error)
+{
+       OSStatus err;
+       nw_parameters_t params = NULL;
+       if (!me->params) {
+               me->params = _mdns_create_tcp_parameters(&err);
+               require_noerr_quiet(err, exit);
+       }
+       params = me->params;
+       err = kNoErr;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       return params;
+}
+
+//======================================================================================================================
+// MARK: - TLS Resolver Private Methods
+
+static void
+_mdns_tls_resolver_finalize(mdns_tls_resolver_t me)
+{
+       ForgetMem(&me->hostname);
+       nw_forget(&me->params);
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mdns_tls_resolver_set_provider_name(mdns_tls_resolver_t me, const char *provider_name)
+{
+       return mdns_replace_string(&me->hostname, provider_name);
+}
+
+//======================================================================================================================
+
+static void
+_mdns_tls_resolver_set_port(const mdns_tls_resolver_t me, const uint16_t port)
+{
+       me->port = port;
+}
+
+//======================================================================================================================
+
+static nw_parameters_t
+_mdns_tls_resolver_create_stream_params(mdns_tls_resolver_t me, OSStatus *out_error);
+
+static nw_parameters_t
+_mdns_tls_resolver_get_stream_params(mdns_tls_resolver_t me, OSStatus *out_error)
+{
+       OSStatus err;
+       nw_parameters_t params = NULL;
+       if (!me->params) {
+               me->params = _mdns_tls_resolver_create_stream_params(me, &err);
+               require_noerr_quiet(err, exit);
+       }
+       params = me->params;
+       err = kNoErr;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       return params;
+}
+
+static nw_parameters_t
+_mdns_tls_resolver_create_stream_params(mdns_tls_resolver_t me, OSStatus *out_error)
+{
+       OSStatus err;
+       nw_parameters_t params = NULL;
+       nw_parameters_t obj;
+       if (me->hostname) {
+               __block bool server_name_was_set = false;
+               nw_parameters_configure_protocol_block_t configure_tls = ^(nw_protocol_options_t tls_options)
+               {
+                       const sec_protocol_options_t sec_options = nw_tls_copy_sec_protocol_options(tls_options);
+                       if (sec_options) {
+                               sec_protocol_options_set_tls_server_name(sec_options, me->hostname);
+                               sec_protocol_options_set_peer_authentication_required(sec_options, true);
+                               sec_release(sec_options);
+                               server_name_was_set = true;
+                       }
+               };
+               obj = nw_parameters_create_secure_tcp(configure_tls, NW_PARAMETERS_DEFAULT_CONFIGURATION);
+               require_action_quiet(obj, exit, err = kNoResourcesErr);
+               require_action_quiet(server_name_was_set, exit, err = kUnknownErr);
+       } else {
+               obj = nw_parameters_create_secure_tcp(NW_PARAMETERS_DEFAULT_CONFIGURATION, NW_PARAMETERS_DEFAULT_CONFIGURATION);
+               require_action_quiet(obj, exit, err = kNoResourcesErr);
+       }
+       nw_parameters_set_indefinite(obj, false);
+
+       err = _mdns_add_dns_over_bytestream_framer(obj);
+       require_noerr_quiet(err, exit);
+
+       params  = obj;
+       obj             = NULL;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       nw_release_null_safe(obj);
+       return params;
+}
+
+//======================================================================================================================
+
+static nw_endpoint_t
+_mdns_tls_resolver_create_hostname_endpoint(mdns_tls_resolver_t me)
+{
+       if (me->hostname) {
+               const uint16_t port = (me->port == 0) ? _mdns_resolver_get_default_port(me) : me->port;
+               return nw_endpoint_create_host_with_numeric_port(me->hostname, port);
+       } else {
+               return NULL;
+       }
+}
+
+//======================================================================================================================
+// MARK: - HTTPS Resolver Private Methods
+
+static void
+_mdns_https_resolver_finalize(mdns_https_resolver_t me)
+{
+       ForgetMem(&me->provider_name);
+       ForgetMem(&me->url_path);
+       nw_forget(&me->params);
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mdns_https_resolver_set_provider_name(mdns_https_resolver_t me, const char *provider_name)
+{
+       return mdns_replace_string(&me->provider_name, provider_name);
+}
+
+//======================================================================================================================
+
+static void
+_mdns_https_resolver_set_port(const mdns_https_resolver_t me, const uint16_t port)
+{
+       me->port = port;
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mdns_https_resolver_set_url_path(mdns_https_resolver_t me, const char *url_path)
+{
+       return mdns_replace_string(&me->url_path, url_path);
+}
+
+//======================================================================================================================
+
+static nw_parameters_t
+_mdns_https_resolver_create_stream_params(mdns_https_resolver_t me, OSStatus *out_error)
+{
+       OSStatus err = 0;
+       nw_parameters_t params = NULL;
+       nw_parameters_t obj;
+       if (me->provider_name) {
+               __block bool server_name_was_set = false;
+               nw_parameters_configure_protocol_block_t configure_tls = ^(nw_protocol_options_t tls_options)
+               {
+                       const sec_protocol_options_t sec_options = nw_tls_copy_sec_protocol_options(tls_options);
+                       if (sec_options) {
+                               sec_protocol_options_set_tls_server_name(sec_options, me->provider_name);
+                               sec_protocol_options_set_peer_authentication_required(sec_options, true);
+                               sec_protocol_options_add_tls_application_protocol(sec_options, "h2");
+                               sec_release(sec_options);
+                               server_name_was_set = true;
+                       }
+               };
+               obj = nw_parameters_create_secure_tcp(configure_tls, NW_PARAMETERS_DEFAULT_CONFIGURATION);
+               require_action_quiet(obj, exit, err = kNoResourcesErr);
+               require_action_quiet(server_name_was_set, exit, err = kUnknownErr);
+       } else {
+               obj = nw_parameters_create_secure_tcp(NW_PARAMETERS_DEFAULT_CONFIGURATION, NW_PARAMETERS_DEFAULT_CONFIGURATION);
+               require_action_quiet(obj, exit, err = kNoResourcesErr);
+       }
+
+       char *url_string = NULL;
+       asprintf(&url_string, "https://%s%s", me->provider_name, me->url_path ? me->url_path : "");
+       nw_parameters_set_url(obj, url_string);
+       FreeNullSafe(url_string);
+
+       nw_parameters_set_indefinite(obj, false);
+
+       params  = obj;
+       obj             = NULL;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       nw_release_null_safe(obj);
+       return params;
+}
+
+static nw_parameters_t
+_mdns_https_resolver_get_stream_params(mdns_https_resolver_t me, OSStatus *out_error)
+{
+       OSStatus err;
+       nw_parameters_t params = NULL;
+       if (!me->params) {
+               me->params = _mdns_https_resolver_create_stream_params(me, &err);
+               require_noerr_quiet(err, exit);
+       }
+       params = me->params;
+       err = kNoErr;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       return params;
+}
+
+//======================================================================================================================
+
+static nw_endpoint_t
+_mdns_https_resolver_create_hostname_endpoint(mdns_https_resolver_t me)
+{
+       if (me->provider_name) {
+               const uint16_t port = (me->port == 0) ? _mdns_resolver_get_default_port(me) : me->port;
+               return nw_endpoint_create_host_with_numeric_port(me->provider_name, port);
+       } else {
+               return NULL;
+       }
+}
+
+//======================================================================================================================
+// MARK: - Server Private Methods
+
+static void
+_mdns_server_finalize(mdns_server_t me)
+{
+       nw_forget(&me->endpoint);
+       nw_forget(&me->path_evaluator);
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+       _pqw_info_forget(&me->pqw_info);
+#endif
+}
+
+//======================================================================================================================
+
+static char *
+_mdns_server_copy_description(mdns_server_t me, const bool debug, const bool privacy)
+{
+       char *                          description = NULL;
+       char                            buffer[128];
+       char *                          dst = buffer;
+       const char * const      lim = &buffer[countof(buffer)];
+       int                                     n;
+
+       *dst = '\0';
+       if (debug) {
+               n = mdns_snprintf_add(&dst, lim, "<%s: %p>: ", me->base.kind->name, me);
+               require_quiet(n >= 0, exit);
+       }
+       if (privacy) {
+               char strbuf[64];
+               if (nw_endpoint_get_type(me->endpoint) == nw_endpoint_type_address) {
+                       const char *str = NULL;
+                       const struct sockaddr * const sa = nw_endpoint_get_address(me->endpoint);
+                       const int family = sa->sa_family;
+                       if ((family == AF_INET) || (family == AF_INET6)) {
+                               n = mdns_print_obfuscated_ip_address(strbuf, sizeof(strbuf), sa);
+                               if (n >= 0) {
+                                       str = strbuf;
+                               }
+                       }
+                       if (str) {
+                               n = mdns_snprintf_add(&dst, lim, "%s", str);
+                               require_quiet(n >= 0, exit);
+                       } else {
+                               const char * const version = (family == AF_INET) ? "4" : ((family == AF_INET6) ? "6" : "?");
+                               n = mdns_snprintf_add(&dst, lim, "<IPv%s#%u>", version, me->rank);
+                               require_quiet(n >= 0, exit);
+                       }
+               } else {
+                       const char *str = NULL;
+                       const char *hostname = nw_endpoint_get_hostname(me->endpoint);
+                       if (hostname) {
+                               n = DNSMessagePrintObfuscatedString(strbuf, sizeof(strbuf), hostname);
+                               if (n >= 0) {
+                                       str = strbuf;
+                               }
+                       }
+                       if (str) {
+                               n = mdns_snprintf_add(&dst, lim, "%s", str);
+                               require_quiet(n >= 0, exit);
+                       } else {
+                               n = mdns_snprintf_add(&dst, lim, "<H#%u>", me->rank);
+                               require_quiet(n >= 0, exit);
+                       }
+               }
+               if (!me->uses_default_port) {
+                       n = mdns_snprintf_add(&dst, lim, ":%d", nw_endpoint_get_port(me->endpoint));
+                       require_quiet(n >= 0, exit);
+               }
+       } else {
+               const char *hostname = nw_endpoint_get_hostname(me->endpoint);
+               if (!hostname) {
+                       hostname = "<unknown>";
+               }
+               n = mdns_snprintf_add(&dst, lim, "%s", hostname);
+               require_quiet(n >= 0, exit);
+
+               if (!me->uses_default_port) {
+                       const char *sep = ":";
+                       if (nw_endpoint_get_type(me->endpoint) == nw_endpoint_type_address) {
+                               const struct sockaddr * const sa = nw_endpoint_get_address(me->endpoint);
+                               if (sa->sa_family == AF_INET6) {
+                                       sep = ".";
+                               }
+                       }
+                       n = mdns_snprintf_add(&dst, lim, "%s%d", sep, nw_endpoint_get_port(me->endpoint));
+                       require_quiet(n >= 0, exit);
+               }
+       }
+       description = strdup(buffer);
+
+exit:
+       return description;
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_server_has_stream_problems(const mdns_server_t me)
+{
+       return (me->stream_lateness || (me->stream_error_count >= MDNS_SERVER_STREAM_ERROR_COUNT_THRESHOLD));
+}
+
+//======================================================================================================================
+
+#if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND || MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+static bool
+_mdns_server_supports_qtype(const mdns_server_t me, const int qtype)
+{
+       if (_mdns_qtype_is_problematic(qtype)) {
+       #if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND
+               if (me->mixes_up_responses) {
+                       return false;
+               }
+       #endif
+       #if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+               if (!me->responds_to_problematics) {
+                       const pqw_info_t * const info = me->pqw_info;
+                       if (info && _pqw_info_threshold_reached(info)) {
+                               return false;
+                       }
+               }
+       #endif
+       }
+       return true;
+}
+#endif
+
+//======================================================================================================================
+
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+static void
+_mdns_server_handle_lack_of_response(const mdns_server_t me, const mdns_querier_t querier)
+{
+       require_return(!me->responds_to_problematics);
+       const int qtype = mdns_querier_get_qtype(querier);
+       if (!_mdns_qtype_is_problematic(qtype) || !_mdns_querier_test_query_got_response(querier, me)) {
+               return;
+       }
+       if (!me->pqw_info) {
+               me->pqw_info = _pqw_info_create(querier->resolver->pqw_threshold);
+               require_return_action(me->pqw_info, os_log_error(_mdns_resolver_log(),
+                       "%{public}sFailed to allocate memory for PQW info", _mdns_querier_get_log_label(querier)));
+       }
+       pqw_info_t * const info = me->pqw_info;
+       const uint8_t * const qname = mdns_querier_get_qname(querier);
+       if (_pqw_info_can_accept_qname(info, qname)) {
+               if (info->qname_count < (info->threshold - 1)) {
+                       OSStatus create_err;
+                       pqw_qname_item_t *item = _pqw_qname_item_create(qname, &create_err);
+                       require_return_action(item, os_log_error(_mdns_resolver_log(),
+                               "%{public}sFailed to create PQW qname item: %{mdns:err}ld",
+                               _mdns_querier_get_log_label(querier), (long)create_err));
+
+                       item->next = info->qname_list;
+                       info->qname_list = item;
+                       ++info->qname_count;
+               } else {
+                       _pqw_qname_list_forget(&info->qname_list);
+                       info->qname_count = info->threshold;
+               }
+               os_log(_mdns_resolver_log(),
+                       "%{public}sNo response (%u/%u) from server %@ for qtype %{mdns:rrtype}d",
+                       _mdns_querier_get_log_label(querier), info->qname_count, info->threshold, me, qtype);
+       }
+}
+
+//======================================================================================================================
+
+static uint16_t
+_mdns_server_get_test_query_qtype(const mdns_server_t me)
+{
+       if (me->test_query_qtype == 0) {
+               uint16_t qtype = kDNSRecordType_A;
+               if (nw_endpoint_get_type(me->endpoint) == nw_endpoint_type_address) {
+                       const struct sockaddr * const sa = nw_endpoint_get_address(me->endpoint);
+                       if (sa->sa_family == AF_INET6) {
+                               qtype = kDNSRecordType_AAAA;
+                       }
+               }
+               me->test_query_qtype = qtype;
+       }
+       return me->test_query_qtype;
+}
+#endif // MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+
+//======================================================================================================================
+// MARK: - Session Private Methods
+
+static void
+_mdns_session_finalize(mdns_session_t me)
+{
+       mdns_forget(&me->server);
+       me->context = NULL;
+}
+
+//======================================================================================================================
+
+static char *
+_mdns_session_copy_description(mdns_session_t me, const bool debug, __unused const bool privacy)
+{
+       char *                          description = NULL;
+       char                            buffer[128];
+       char *                          dst = buffer;
+       const char * const      lim = &buffer[countof(buffer)];
+       int                                     n;
+
+       *dst = '\0';
+       if (debug) {
+               n = mdns_snprintf_add(&dst, lim, "<%s: %p>: ", me->base.kind->name, me);
+               require_quiet(n >= 0, exit);
+       }
+       description = strdup(buffer);
+
+exit:
+       return description;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_session_invalidate_internal(mdns_session_t session);
+
+static void
+_mdns_session_invalidate(mdns_session_t me)
+{
+       // Set the state to done to prevent any further callback invocations.
+       me->state = mdns_session_state_done;
+       mdns_retain(me);
+       dispatch_async(_mdns_resolver_queue(),
+       ^{
+               _mdns_session_invalidate_internal(me);
+               if (me->callbacks->finalize_context) {
+                       me->callbacks->finalize_context(me->context);
+               }
+               me->context = NULL;
+               mdns_release(me);
+       });
+}
+
+static void
+_mdns_session_invalidate_internal(const mdns_session_t me)
+{
+       dispatch_source_forget(&me->lateness_timer);
+       const mdns_session_kind_t kind = (mdns_session_kind_t)me->base.kind;
+       if (kind->invalidate) {
+               kind->invalidate(me);
+       }
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_session_is_ready(const mdns_session_t me)
+{
+       return ((me->state == mdns_session_state_activated) && me->is_ready);
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mdns_session_initialize(const mdns_session_t me, const mdns_resolver_t resolver, const bool need_bytestream,
+       const mdns_delegation_t * const delegation, const uint8_t * const qname)
+{
+       const mdns_session_kind_t kind = (mdns_session_kind_t)me->base.kind;
+       if (kind->initialize) {
+               return kind->initialize(me, resolver, need_bytestream, delegation, qname);
+       } else {
+               return kNoErr;
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_session_activate(const mdns_session_t me)
+{
+       require_return(me->state == mdns_session_state_nascent);
+
+       OSStatus err;
+       if (me->lateness_time_ms > 0) {
+               me->lateness_timer = _mdns_resolver_create_oneshot_timer(me->lateness_time_ms, 5);
+               require_action_quiet(me->lateness_timer, exit, err = kNoResourcesErr);
+
+               dispatch_source_set_event_handler(me->lateness_timer,
+               ^{
+                       dispatch_source_forget(&me->lateness_timer);
+                       if (me->state == mdns_session_state_activated) {
+                               if (me->callbacks->handle_event) {
+                                       me->callbacks->handle_event(me, mdns_session_event_lateness_warning, kNoErr, me->context);
+                               }
+                       }
+               });
+               dispatch_activate(me->lateness_timer);
+       }
+       const uint64_t now_ticks = mach_continuous_time();
+       me->start_ticks         = now_ticks;
+       me->last_send_ticks     = now_ticks - (kSecondsPerHour * _mdns_ticks_per_second());
+       const mdns_session_kind_t kind = (mdns_session_kind_t)me->base.kind;
+       if (kind->activate) {
+               err = kind->activate(me);
+               require_noerr_quiet(err, exit);
+       }
+       me->state = mdns_session_state_activated;
+       err = kNoErr;
+
+exit:
+       if (err) {
+               me->state = mdns_session_state_failed;
+               _mdns_common_session_terminate_async(me, err);
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_session_send(const mdns_session_t me, const dispatch_data_t msg, const uint16_t qtype)
+{
+       if (me->state == mdns_session_state_activated) {
+               me->last_send_ticks = mach_continuous_time();
+               const mdns_session_kind_t kind = (mdns_session_kind_t)me->base.kind;
+               if (kind->send) {
+                       kind->send(me, msg, qtype);
+               }
+       }
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_session_is_bytestream(const mdns_session_t me)
+{
+       return me->is_stream;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_session_set_callbacks(mdns_session_t me, const mdns_session_callbacks_t * const callbacks, void * const context)
+{
+       if (me->state == mdns_session_state_nascent) {
+               me->context             = context;
+               me->callbacks   = callbacks;
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_session_set_lateness_time(const mdns_session_t me, uint32_t time_ms)
+{
+       require_return(me->state == mdns_session_state_nascent);
+       me->lateness_time_ms = time_ms;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_session_finalize_context_with_release(void * const context)
+{
+       mdns_release((mdns_object_t)context);
+}
+
+//======================================================================================================================
+// MARK: - Session Common Subkind Methods
+
+static nw_endpoint_t
+_mdns_common_session_get_server_endpoint(const mdns_any_session_t any)
+{
+       const mdns_session_t me = any.session;
+       return me->server->endpoint;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_common_session_invoke_ready_event_handler(const mdns_any_session_t any)
+{
+       const mdns_session_t me = any.session;
+       if ((me->state == mdns_session_state_activated) && !me->is_ready) {
+               me->is_ready = true;
+               if (me->callbacks->handle_event) {
+                       me->callbacks->handle_event(me, mdns_session_event_ready, kNoErr, me->context);
+               }
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_common_session_invoke_receive(const mdns_any_session_t any, const dispatch_data_t msg)
+{
+       const mdns_session_t me = any.session;
+       if (me->state == mdns_session_state_activated) {
+               dispatch_source_forget(&me->lateness_timer);
+               increment_saturate(me->receive_count, UINT32_MAX);
+               if (me->callbacks->receive) {
+                       me->callbacks->receive(me, msg, me->context);
+               }
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_common_session_terminate(const mdns_any_session_t any, const OSStatus error)
+{
+       const mdns_session_t me = any.session;
+       if (me->state != mdns_session_state_done) {
+               _mdns_session_invalidate_internal(me);
+               me->state = mdns_session_state_done;
+               if (me->callbacks->handle_event) {
+                       me->callbacks->handle_event(me, mdns_session_event_terminated, error, me->context);
+               }
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_common_session_terminate_async(const mdns_any_session_t any, const OSStatus error)
+{
+       const mdns_session_t me = any.session;
+       mdns_retain(me);
+       dispatch_async(_mdns_resolver_queue(),
+       ^{
+               _mdns_common_session_terminate(me, error);
+               mdns_release(me);
+       });
+}
+
+//======================================================================================================================
+// MARK: - Connection Session Private Methods
+
+static void
+_mdns_connection_session_finalize(__unused const mdns_connection_session_t me)
+{
+       // Nothing to free.
+}
+
+//======================================================================================================================
+
+static nw_endpoint_t
+_mdns_create_domain_attributed_endpoint(nw_endpoint_t original_endpoint, const uint8_t *hostname, OSStatus *out_error);
+
+static OSStatus
+_mdns_connection_session_initialize(const mdns_connection_session_t me, const mdns_resolver_t resolver,
+       const bool need_bytestream, const mdns_delegation_t * const delegation, const uint8_t * const qname)
+{
+       OSStatus err;
+       nw_parameters_t params_alt = NULL;
+       nw_endpoint_t server_endpoint_alt = NULL;
+       nw_parameters_t params;
+       if (_mdns_resolver_is_stream_only(resolver) || need_bytestream) {
+               params = _mdns_resolver_get_stream_params(resolver, &err);
+               require_noerr_quiet(err, exit);
+
+               me->is_bytestream = true;
+       } else {
+               params = _mdns_resolver_get_datagram_params(resolver, &err);
+               require_noerr_quiet(err, exit);
+
+               me->is_bytestream = false;
+       }
+       if (delegation && ((delegation->type == mdns_delegation_type_pid) ||
+               (delegation->type == mdns_delegation_type_uuid))) {
+               params_alt = nw_parameters_copy(params);
+               require_action_quiet(params_alt, exit, err = kNoResourcesErr);
+
+               if (delegation->type == mdns_delegation_type_pid) {
+                       nw_parameters_set_pid(params_alt, delegation->ident.pid);
+               } else {
+                       nw_parameters_set_e_proc_uuid(params_alt, delegation->ident.uuid);
+               }
+               params = params_alt;
+       }
+       nw_endpoint_t server_endpoint = _mdns_common_session_get_server_endpoint(me);
+       if (__builtin_available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *)) {
+               if (qname) {
+                       OSStatus create_err;
+                       server_endpoint_alt = _mdns_create_domain_attributed_endpoint(server_endpoint, qname, &create_err);
+                       if (likely(server_endpoint_alt)) {
+                               server_endpoint = server_endpoint_alt;
+                       } else {
+                               os_log_error(_mdns_resolver_log(),
+                                       "Failed to create domain-attributed endpoint for %@: %{mdns:err}ld",
+                                       server_endpoint, (long)create_err);
+                       }
+               }
+       }
+       me->connection = nw_connection_create(server_endpoint, params);
+       require_action_quiet(me->connection, exit, err = kNoResourcesErr);
+
+exit:
+       nw_forget(&params_alt);
+       nw_forget(&server_endpoint_alt);
+       return err;
+}
+
+static nw_endpoint_t
+_mdns_create_domain_attributed_endpoint(const nw_endpoint_t original_endpoint, const uint8_t * const domain,
+       OSStatus *out_error)
+{
+       OSStatus err;
+       nw_endpoint_t result = NULL;
+       const struct sockaddr * const sa = nw_endpoint_get_address(original_endpoint);
+       require_action_quiet(sa, exit, err = kTypeErr);
+
+       nw_endpoint_t endpoint = nw_endpoint_create_address(sa);
+       require_action_quiet(endpoint, exit, err = kNoResourcesErr);
+
+       char domain_str[kDNSServiceMaxDomainName];
+       err = DomainNameToString(domain, NULL, domain_str, NULL);
+       require_noerr_quiet(err, exit);
+
+       const uint16_t port = nw_endpoint_get_port(endpoint);
+       nw_endpoint_t parent = nw_endpoint_create_host_with_numeric_port(domain_str, port);
+       require_action_quiet(parent, exit, err = kNoResourcesErr);
+
+       if (__builtin_available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *)) {
+               nw_endpoint_set_parent_endpoint(endpoint, parent, false);
+       }
+       nw_forget(&parent);
+       result = endpoint;
+       endpoint = NULL;
+
+exit:
+       nw_forget(&endpoint);
+       if (out_error) {
+               *out_error = err;
+       }
+       return result;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_connection_session_schedule_receive(mdns_connection_session_t session);
+
+static OSStatus
+_mdns_connection_session_activate(const mdns_connection_session_t me)
+{
+       mdns_retain(me);
+       nw_connection_set_queue(me->connection, _mdns_resolver_queue());
+       nw_connection_set_state_changed_handler(me->connection,
+       ^(nw_connection_state_t state, __unused nw_error_t error)
+       {
+               if (likely(me->connection)) {
+                       os_log_debug(_mdns_resolver_log(), "Connection state changed to %s for connection %@",
+                               nw_connection_state_to_string(state), me->connection);
+                       if (state == nw_connection_state_ready) {
+                               _mdns_common_session_invoke_ready_event_handler(me);
+                       } else if (state == nw_connection_state_failed) {
+                               _mdns_common_session_terminate(me, kConnectionErr);
+                       }
+               }
+               if (state == nw_connection_state_cancelled) {
+                       mdns_release(me);
+               }
+       });
+       nw_connection_start(me->connection);
+       _mdns_connection_session_schedule_receive(me);
+       return kNoErr;
+}
+
+static void
+_mdns_connection_session_schedule_receive(const mdns_connection_session_t me)
+{
+       nw_connection_receive_message(me->connection,
+       ^(dispatch_data_t msg, nw_content_context_t context, __unused bool is_complete, nw_error_t error)
+       {
+               if (likely(me->connection)) {
+                       if (msg) {
+                               _mdns_common_session_invoke_receive(me, msg);
+                       }
+                       const bool final = (context && nw_content_context_get_is_final(context)) ? true : false;
+                       if (final || error) {
+                               _mdns_common_session_terminate(me, error ? kConnectionErr : kNoErr);
+                       } else {
+                               _mdns_connection_session_schedule_receive(me);
+                       }
+               }
+       });
+}
+
+//======================================================================================================================
+
+static void
+_mdns_connection_session_invalidate(const mdns_connection_session_t me)
+{
+       if (me->connection) {
+               nw_connection_cancel(me->connection);
+               nw_forget(&me->connection);
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_connection_session_send(const mdns_connection_session_t me, const dispatch_data_t msg, const uint16_t qtype)
+{
+       os_log_debug(_mdns_resolver_log(), "Sending message on connection %@", me->connection);
+
+       __block nw_activity_t activity = NULL;
+       if (qtype == kDNSRecordType_A) {
+               activity = nw_activity_create(kDNSActivityDomain, kDNSActivityLabelUnicastAQuery);
+       } else if (qtype == kDNSRecordType_AAAA) {
+               activity = nw_activity_create(kDNSActivityDomain, kDNSActivityLabelUnicastAAAAQuery);
+       }
+       if (activity) {
+               nw_connection_start_activity(me->connection, activity);
+       }
+       nw_connection_send(me->connection, msg, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true,
+       ^(const nw_error_t error)
+       {
+               if (activity) {
+                       if (me->connection) {
+                               nw_connection_end_activity(me->connection, activity);
+                       }
+                       nw_release(activity);
+               }
+               if (likely(me->connection) && error) {
+                       _mdns_common_session_terminate(me, kConnectionErr);
+               }
+       });
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_connection_session_is_bytestream(const mdns_connection_session_t me)
+{
+       return me->is_bytestream;
+}
+
+//======================================================================================================================
+// MARK: - UDP Socket Session Private Methods
+
+static void
+_mdns_udp_socket_session_finalize(const mdns_udp_socket_session_t me)
+{
+       _mdns_socket_forget(&me->sock);
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mdns_bind_ipv6_socket_to_random_port(const int sock);
+
+static OSStatus
+_mdns_udp_socket_session_initialize(const mdns_udp_socket_session_t me, const mdns_resolver_t resolver,
+       __unused const bool need_bytestream, const mdns_delegation_t * const delegation, const uint8_t * const qname)
+{
+       OSStatus err;
+       int sock = kInvalidSocketRef;
+       const struct sockaddr *dst = nw_endpoint_get_address(_mdns_common_session_get_server_endpoint(me));
+       require_action_quiet((dst->sa_family == AF_INET) || (dst->sa_family == AF_INET6), exit, err = kTypeErr);
+
+#if !MDNS_USE_CONNECTED_UDP_SOCKETS
+       struct sockaddr_in dst_ipv4;
+       if (dst->sa_family == AF_INET6) {
+               const struct sockaddr_in6 * const sin6 = (const struct sockaddr_in6 *)dst;
+               if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr) || IN6_IS_ADDR_V4COMPAT(&sin6->sin6_addr)) {
+                       memset(&dst_ipv4, 0, sizeof(dst_ipv4));
+                       dst_ipv4.sin_family     = AF_INET;
+                       SIN_LEN_SET(&dst_ipv4);
+                       dst_ipv4.sin_port       = sin6->sin6_port;
+                       memcpy(&dst_ipv4.sin_addr.s_addr, &sin6->sin6_addr.s6_addr[12], 4);
+                       dst = (const struct sockaddr *)&dst_ipv4;
+               }
+       }
+#endif
+       const bool ipv4 = (dst->sa_family == AF_INET);
+       sock = socket(ipv4 ? AF_INET : AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+       err = map_socket_creation_errno(sock);
+       require_noerr_quiet(err, exit);
+
+#define _do_setsockopt(SOCK, LEVEL, NAME, VALUE_PTR, VALUE_LEN)                                                                        \
+       do {                                                                                                                                                                            \
+               int opt_err = setsockopt(SOCK, LEVEL, NAME, VALUE_PTR, (socklen_t)(VALUE_LEN));                 \
+               opt_err = map_socket_noerr_errno(SOCK, opt_err);                                                                                \
+               if (unlikely(opt_err)) {                                                                                                                                \
+                       os_log_error(_mdns_resolver_log(),                                                                                                      \
+                               "setsockopt() for " # LEVEL "/" # NAME " failed %{darwin.errno}d", opt_err);    \
+               }                                                                                                                                                                               \
+       } while (0)
+
+       const int on = 1;
+       // Ensure that socket binds to a random port.
+       if (ipv4) {
+               _do_setsockopt(sock, SOL_SOCKET, SO_RANDOMPORT, &on, sizeof(on));
+       } else {
+               // Workaround for SO_RANDOMPORT not working for UDP/IPv6.
+               _mdns_bind_ipv6_socket_to_random_port(sock);
+       }
+       _do_setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on));                // Return EPIPE instead of raising SIGPIPE.
+       _do_setsockopt(sock, SOL_SOCKET, SO_NOWAKEFROMSLEEP, &on, sizeof(on));  // Don't wake from sleep on receive.
+       mdns_make_socket_nonblocking(sock);
+       // Restrict socket's network traffic to a particular interface.
+       if (resolver->interface) {
+               const uint32_t if_index = nw_interface_get_index(resolver->interface);
+               if (ipv4) {
+                       _do_setsockopt(sock, IPPROTO_IP, IP_BOUND_IF, &if_index, sizeof(if_index));
+               } else {
+                       _do_setsockopt(sock, IPPROTO_IPV6, IPV6_BOUND_IF, &if_index, sizeof(if_index));
+               }
+       }
+       // If delegation info is provided, attribute outbound data to the delegator.
+       if (delegation) {
+               switch (delegation->type) {
+                       case mdns_delegation_type_pid:
+                               _do_setsockopt(sock, SOL_SOCKET, SO_DELEGATED, &delegation->ident.pid, sizeof(delegation->ident.pid));
+                               break;
+
+                       case mdns_delegation_type_uuid:
+                               _do_setsockopt(sock, SOL_SOCKET, SO_DELEGATED_UUID, delegation->ident.uuid,
+                                       sizeof(delegation->ident.uuid));
+                               break;
+
+                       default:
+                       case mdns_delegation_type_none:
+                               break;
+               }
+       }
+#undef _do_setsockopt
+       if (qname) {
+               char qname_str[kDNSServiceMaxDomainName];
+               err = DomainNameToString(qname, NULL, qname_str, NULL);
+               require_noerr_quiet(err, exit);
+
+               const bool ok = ne_session_set_socket_attributes(sock, qname_str, NULL);
+               if (!ok) {
+                       os_log_error(_mdns_resolver_log(), "ne_session_set_socket_attributes() failed for '%s'", qname_str);
+               }
+       }
+       const socklen_t dst_len = (socklen_t)(ipv4 ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6));
+#if MDNS_USE_CONNECTED_UDP_SOCKETS
+       err = connect(sock, dst, dst_len);
+       err = map_socket_noerr_errno(sock, err);
+       if (!err) {
+               me->connected = true;
+               os_log_debug(_mdns_resolver_log(),
+                       "UDP socket connected to %{network:sockaddr}.*P", (int)dst_len, dst);
+       } else if (err == EINPROGRESS) {
+               os_log_info(_mdns_resolver_log(),
+                       "UDP socket connection to %{network:sockaddr}.*P is in progress", (int)dst_len, dst);
+               err = kNoErr;
+       } else {
+               os_log_error(_mdns_resolver_log(),
+                       "UDP socket connection to %{network:sockaddr}.*P failed: %{darwin.errno}d", (int)dst_len, dst, (int)err);
+               goto exit;
+       }
+#else
+       memcpy(&me->server_addr, dst, dst_len);
+       me->server_addr_len = dst_len;
+#endif
+       me->sock = sock;
+       sock = kInvalidSocketRef;
+
+exit:
+       _mdns_socket_forget(&sock);
+       return err;
+}
+
+static OSStatus
+_mdns_bind_ipv6_socket_to_random_port(const int sock)
+{
+       struct sockaddr_in6 sin6;
+       memset(&sin6, 0, sizeof(sin6));
+       SIN6_LEN_SET(&sin6);
+       sin6.sin6_family        = AF_INET6;
+       sin6.sin6_addr          = in6addr_any;
+       uint16_t port;
+       OSStatus err;
+       int tries = 0;
+       // Bind to a random port in the ephemeral port range (see <https://tools.ietf.org/html/rfc6335#section-6>).
+       // Note: This is the same algorithm used by mDNSResponder's MacOSX platform code for port randomization.
+       do {
+               port = RandomRange(0xC000U, 0xFFFFU);
+               sin6.sin6_port = htons(port);
+               err = bind(sock, (const struct sockaddr *)&sin6, sizeof(sin6));
+               err = map_socket_noerr_errno(sock, err);
+               ++tries;
+       } while ((err == EADDRINUSE) && (tries < 10000));
+       if (err) {
+               os_log_error(_mdns_resolver_log(),
+                       "Binding IPv6 socket to random port failed -- error: %{mdns:err}ld, tries: %d", (long)err, tries);
+       } else {
+               os_log_debug(_mdns_resolver_log(),
+                       "Binding IPv6 socket to random port succeeded -- port: %u, tries: %d", port, tries);
+       }
+       return err;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_udp_socket_session_read_handler(void *ctx);
+
+#if MDNS_USE_CONNECTED_UDP_SOCKETS
+static void
+_mdns_udp_socket_session_write_handler(void *ctx);
+#endif
+
+static void
+_mdns_udp_socket_session_cancel_handler(void *ctx);
+
+static OSStatus
+_mdns_udp_socket_session_activate(const mdns_udp_socket_session_t me)
+{
+       OSStatus err;
+       me->read_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, (uintptr_t)me->sock, 0, _mdns_resolver_queue());
+       require_action_quiet(me->read_source, exit, err = kNoResourcesErr);
+
+       mdns_retain(me);
+       dispatch_set_context(me->read_source, me);
+       dispatch_source_set_event_handler_f(me->read_source, _mdns_udp_socket_session_read_handler);
+       dispatch_source_set_cancel_handler_f(me->read_source, _mdns_udp_socket_session_cancel_handler);
+#if MDNS_USE_CONNECTED_UDP_SOCKETS
+       if (me->connected) {
+               dispatch_activate(me->read_source);
+               me->read_source_suspended = false;
+       } else {
+               me->read_source_suspended = true;
+
+               me->write_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, (uintptr_t)me->sock, 0,
+                       _mdns_resolver_queue());
+               require_action_quiet(me->write_source, exit, err = kNoResourcesErr);
+
+               mdns_retain(me);
+               dispatch_set_context(me->write_source, me);
+               dispatch_source_set_event_handler_f(me->write_source, _mdns_udp_socket_session_write_handler);
+               dispatch_source_set_cancel_handler_f(me->write_source, _mdns_udp_socket_session_cancel_handler);
+               dispatch_activate(me->write_source);
+       }
+#else
+       dispatch_activate(me->read_source);
+#endif
+       err = kNoErr;
+
+exit:
+       return err;
+}
+
+static void
+_mdns_udp_socket_session_read_handler(void * const ctx)
+{
+       const mdns_udp_socket_session_t me = (mdns_udp_socket_session_t)ctx;
+       uint8_t msg_buf[512];
+#if MDNS_USE_CONNECTED_UDP_SOCKETS
+       const ssize_t n = recv(me->sock, msg_buf, sizeof(msg_buf), 0);
+#else
+       sockaddr_ip     sender;
+       socklen_t       sender_len = (socklen_t)sizeof(sender);
+       const ssize_t n = recvfrom(me->sock, msg_buf, sizeof(msg_buf), 0, &sender.sa, &sender_len);
+#endif
+       const OSStatus err = map_socket_value_errno(me->sock, n >= 0, n);
+       require_noerr_quiet(err, exit);
+
+#if !MDNS_USE_CONNECTED_UDP_SOCKETS
+       if (me->server_addr.sa.sa_family == AF_INET) {
+               const struct sockaddr_in * const sa1 = &me->server_addr.v4;
+               const struct sockaddr_in * const sa2 = &sender.v4;
+               require_quiet(IN_ARE_ADDR_EQUAL(&sa1->sin_addr, &sa2->sin_addr), exit);
+               require_quiet(sa1->sin_port == sa2->sin_port, exit);
+       } else {
+               const struct sockaddr_in6 * const sa1 = &me->server_addr.v6;
+               const struct sockaddr_in6 * const sa2 = &sender.v6;
+               require_quiet(IN6_ARE_ADDR_EQUAL(&sa1->sin6_addr, &sa2->sin6_addr), exit);
+               require_quiet(sa1->sin6_port == sa2->sin6_port, exit);
+
+               if (IN6_IS_ADDR_LINKLOCAL(&sa1->sin6_addr) && (sa1->sin6_scope_id != 0) &&
+                       (sa1->sin6_scope_id != sa2->sin6_scope_id)) {
+                       goto exit;
+               }
+       }
+#endif
+       dispatch_data_t msg = dispatch_data_create(msg_buf, (size_t)n, _mdns_resolver_queue(),
+               DISPATCH_DATA_DESTRUCTOR_DEFAULT);
+       if (msg) {
+               _mdns_common_session_invoke_receive(me, msg);
+               dispatch_forget(&msg);
+       }
+
+exit:
+       if (err && (err != EWOULDBLOCK)) {
+               _mdns_common_session_terminate(me, err);
+       }
+}
+
+#if MDNS_USE_CONNECTED_UDP_SOCKETS
+static void
+_mdns_udp_socket_session_write_handler(void * const ctx)
+{
+       const mdns_udp_socket_session_t me = (mdns_udp_socket_session_t)ctx;
+       dispatch_source_forget(&me->write_source);
+       me->connected = true;
+       os_log_info(_mdns_resolver_log(), "UDP socket connection to %@ is complete", me->base.server);
+       dispatch_resume_if_suspended(me->read_source, &me->read_source_suspended);
+       _mdns_common_session_invoke_ready_event_handler(me);
+}
+#endif
+
+static void
+_mdns_udp_socket_session_cancel_handler(void * const ctx)
+{
+       const mdns_udp_socket_session_t me = (mdns_udp_socket_session_t)ctx;
+#if MDNS_USE_CONNECTED_UDP_SOCKETS
+       if (!me->read_source && !me->write_source) {
+               _mdns_socket_forget(&me->sock);
+       }
+#else
+       _mdns_socket_forget(&me->sock);
+#endif
+       mdns_release(me);
+}
+
+//======================================================================================================================
+
+static void
+_mdns_udp_socket_session_invalidate(const mdns_udp_socket_session_t me)
+{
+#if MDNS_USE_CONNECTED_UDP_SOCKETS
+       dispatch_source_forget(&me->write_source);
+       dispatch_source_forget_ex(&me->read_source, &me->read_source_suspended);
+#else
+       dispatch_source_forget(&me->read_source);
+#endif
+}
+
+//======================================================================================================================
+
+#if MDNS_USE_CONNECTED_UDP_SOCKETS
+static bool
+_mdns_udp_socket_session_is_ready(const mdns_udp_socket_session_t me)
+{
+       return me->connected;
+}
+#endif
+
+//======================================================================================================================
+
+static void
+_mdns_udp_socket_session_send(const mdns_udp_socket_session_t me, const dispatch_data_t msg,
+       __unused const uint16_t qtype)
+{
+       const void *    msg_ptr;
+       size_t                  msg_len;
+       dispatch_data_t msg_map = dispatch_data_create_map(msg, &msg_ptr, &msg_len);
+       require_quiet(msg_map, exit);
+
+#if MDNS_USE_CONNECTED_UDP_SOCKETS
+       const ssize_t n = send(me->sock, msg_ptr, msg_len, 0);
+#else
+       const ssize_t n = sendto(me->sock, msg_ptr, msg_len, 0, &me->server_addr.sa, me->server_addr_len);
+#endif
+       const OSStatus err = map_socket_value_errno(me->sock, n >= 0, n);
+       if (err) {
+               os_log_error(_mdns_resolver_log(), "sending to %@ failed: %{darwin.errno}d", me->base.server, (int)err);
+       }
+       require_noerr_quiet(err, exit);
+
+exit:
+       dispatch_release_null_safe(msg_map);
+}
+
+//======================================================================================================================
+// MARK: - URL Session Private Methods
+
+static void
+_mdns_url_session_finalize(__unused const mdns_url_session_t me)
+{
+       nw_forget(&me->url_endpoint);
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mdns_url_session_initialize(const mdns_url_session_t me, const mdns_resolver_t resolver,
+       __unused const bool need_bytestream, __unused const mdns_delegation_t * const delegation,
+       __unused const uint8_t * const qname)
+{
+       OSStatus err;
+       const nw_parameters_t params = _mdns_resolver_get_stream_params(resolver, &err);
+       require_noerr_quiet(err, exit);
+
+       me->url_endpoint = nw_parameters_copy_url_endpoint(params);
+       require_action_quiet(me->url_endpoint, exit, err = kNoResourcesErr);
+
+exit:
+       return err;
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mdns_url_session_activate(__unused const mdns_url_session_t me)
+{
+       // Nothing to do for now.
+       return kNoErr;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_url_session_invalidate(const mdns_url_session_t me)
+{
+       if (me->http_task) {
+               http_task_cancel(me->http_task);
+               me->http_task = NULL;
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_url_session_send(const mdns_url_session_t me, const dispatch_data_t msg, const uint16_t qtype)
+{
+       os_log_debug(_mdns_resolver_log(), "Sending message on URL %@", me->url_endpoint);
+
+       mdns_retain(me);
+       __block bool invoked = false;
+       me->http_task = http_task_create_dns_query(_mdns_common_session_get_server_endpoint(me),
+               nw_endpoint_get_url(me->url_endpoint), msg, qtype, false,
+       ^(const dispatch_data_t data, const CFErrorRef task_error) {
+               if (likely(!invoked)) {
+                       invoked = true;
+                       if (likely(me->http_task)) {
+                               if (data) {
+                                       _mdns_common_session_invoke_receive(me, data);
+                               }
+                               if (task_error) {
+                                       os_log_error(_mdns_resolver_log(), "Got error %@", task_error);
+                                       _mdns_common_session_terminate(me, (OSStatus)CFErrorGetCode(task_error));
+                               }
+                       }
+                       mdns_release(me);
+               }
+       });
+       if (me->http_task) {
+               http_task_start(me->http_task);
+       } else {
+               os_log_error(_mdns_resolver_log(), "Failed to create HTTP task");
+               _mdns_common_session_terminate_async(me, kUnknownErr);
+       }
+}
+
+//======================================================================================================================
+// MARK: - Querier Public Methods
+
+void
+mdns_querier_set_queue(mdns_querier_t me, dispatch_queue_t queue)
+{
+       if (!me->user_activated || !me->user_queue)
+       {
+               if (queue) {
+                       dispatch_retain(queue);
+               }
+               dispatch_release_null_safe(me->user_queue);
+               me->user_queue = queue;
+               _mdns_querier_activate_if_ready(me);
+       }
+}
+
+//======================================================================================================================
+
+OSStatus
+mdns_querier_set_query(mdns_querier_t me, const uint8_t *qname, uint16_t qtype, uint16_t qclass)
+{
+       OSStatus err;
+       require_action_quiet(!me->user_activated || !mdns_query_message_get_qname(me->query), exit,
+               err = kAlreadyInitializedErr);
+
+       err = mdns_query_message_set_qname(me->query, qname);
+       require_noerr_quiet(err, exit);
+
+       mdns_query_message_set_qtype(me->query, qtype);
+       mdns_query_message_set_qclass(me->query, qclass);
+
+       _mdns_querier_activate_if_ready(me);
+       err = kNoErr;
+
+exit:
+       return err;
+}
+
+//======================================================================================================================
+
+void
+mdns_querier_set_dnssec_ok(const mdns_querier_t me, const bool set)
+{
+       require_return(!me->user_activated);
+       mdns_query_message_set_do_bit(me->query, set);
+}
+
+//======================================================================================================================
+
+void
+mdns_querier_set_checking_disabled(const mdns_querier_t me, const bool checking_disabled)
+{
+       require_return(!me->user_activated);
+       mdns_query_message_set_cd_bit(me->query, checking_disabled);
+}
+
+//======================================================================================================================
+
+void
+mdns_querier_set_delegator_pid(mdns_querier_t me, pid_t pid)
+{
+       if (!me->user_activated) {
+               me->delegation.type = mdns_delegation_type_pid;
+               me->delegation.ident.pid = pid;
+       }
+}
+
+//======================================================================================================================
+
+void
+mdns_querier_set_delegator_uuid(mdns_querier_t me, uuid_t uuid)
+{
+       if (!me->user_activated) {
+               me->delegation.type = mdns_delegation_type_uuid;
+               uuid_copy(me->delegation.ident.uuid, uuid);
+       }
+}
+
+//======================================================================================================================
+
+void
+mdns_querier_set_user_id(const mdns_querier_t me, const uint32_t user_id)
+{
+       require_return(!me->user_activated);
+       me->user_id = user_id;
+}
+
+//======================================================================================================================
+
+OSStatus
+mdns_querier_set_log_label(const mdns_querier_t me, const char * const format, ...)
+{
+       require_return_value(!me->user_activated, kAlreadyInitializedErr);
+
+       va_list args;
+       va_start(args, format);
+       char *inner_str = NULL;
+       vasprintf(&inner_str, format, args);
+       va_end(args);
+       OSStatus err;
+       require_action_quiet(inner_str, exit, err = kNoMemoryErr);
+
+       char *log_label = NULL;
+       asprintf(&log_label, "[%s] ", inner_str);
+       require_action_quiet(log_label, exit, err = kNoMemoryErr);
+
+       FreeNullSafe(me->log_label);
+       me->log_label = log_label;
+       log_label = NULL;
+       err = kNoErr;
+
+exit:
+       ForgetMem(&inner_str);
+       return err;
+}
+
+//======================================================================================================================
+
+void
+mdns_querier_set_result_handler(mdns_querier_t me, mdns_querier_result_handler_t handler)
+{
+       if (!me->user_activated) {
+               const mdns_querier_result_handler_t new_handler = handler ? Block_copy(handler) : NULL;
+               if (me->handler) {
+                       Block_release(me->handler);
+               }
+               me->handler = new_handler;
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_querier_set_time_limit_ms(mdns_querier_t querier, int32_t time_limit_ms);
+
+void
+mdns_querier_set_time_limit_ms(const mdns_querier_t me, const int32_t time_limit_ms)
+{
+       mdns_retain(me);
+       dispatch_async(_mdns_resolver_queue(),
+       ^{
+               _mdns_querier_set_time_limit_ms(me, time_limit_ms);
+               mdns_release(me);
+       });
+}
+
+static void
+_mdns_querier_set_time_limit_ms(const mdns_querier_t me, const int32_t time_limit_ms)
+{
+       if (likely(!me->concluded)) {
+               me->time_limit_ms = time_limit_ms;
+               const OSStatus err = _mdns_querier_reset_time_limit(me);
+               if (err) {
+                       _mdns_querier_conclude(me, err);
+               }
+       }
+}
+
+//======================================================================================================================
+
+void
+mdns_querier_activate(mdns_querier_t me)
+{
+       if (!me->user_activated) {
+               me->user_activated = true;
+               _mdns_querier_activate_if_ready(me);
+       }
+}
+
+//======================================================================================================================
+
+void
+mdns_querier_invalidate(mdns_querier_t me)
+{
+       mdns_retain(me);
+       dispatch_async(_mdns_resolver_queue(),
+       ^{
+               _mdns_querier_conclude(me, mdns_querier_result_type_invalidation);
+               mdns_release(me);
+       });
+}
+
+//======================================================================================================================
+
+const uint8_t *
+mdns_querier_get_qname(const mdns_querier_t me)
+{
+       return mdns_query_message_get_qname(me->query);
+}
+
+//======================================================================================================================
+
+uint16_t
+mdns_querier_get_qtype(const mdns_querier_t me)
+{
+       return mdns_query_message_get_qtype(me->query);
+}
+
+//======================================================================================================================
+
+uint16_t
+mdns_querier_get_qclass(const mdns_querier_t me)
+{
+       return mdns_query_message_get_qclass(me->query);
+}
+
+//======================================================================================================================
+
+mdns_resolver_type_t
+mdns_querier_get_resolver_type(const mdns_querier_t me)
+{
+       return _mdns_resolver_get_type(me->resolver);
+}
+
+//======================================================================================================================
+
+mdns_querier_result_type_t
+mdns_querier_get_result_type(const mdns_querier_t me)
+{
+       return me->result_type;
+}
+
+//======================================================================================================================
+
+uint32_t
+mdns_querier_get_send_count(const mdns_querier_t me)
+{
+       return atomic_load(&me->send_count);
+}
+
+//======================================================================================================================
+
+uint32_t
+mdns_querier_get_query_length(const mdns_querier_t me)
+{
+       return (uint32_t)mdns_message_get_length(me->query);
+}
+
+//======================================================================================================================
+
+const uint8_t *
+mdns_querier_get_response_ptr(const mdns_querier_t me)
+{
+       return _mdns_querier_get_response_ptr_safe(me);
+}
+
+//======================================================================================================================
+
+uint32_t
+mdns_querier_get_response_length(const mdns_querier_t me)
+{
+       return (uint32_t)_mdns_querier_get_response_length_safe(me);
+}
+
+//======================================================================================================================
+
+bool
+mdns_querier_response_is_fabricated(const mdns_querier_t me)
+{
+       return me->response_is_fabricated;
+}
+
+//======================================================================================================================
+
+OSStatus
+mdns_querier_get_error(const mdns_querier_t me)
+{
+       return me->error;
+}
+
+//======================================================================================================================
+
+bool
+mdns_querier_get_dnssec_ok(const mdns_querier_t me)
+{
+       return mdns_query_message_do_bit_is_set(me->query);
+}
+
+//======================================================================================================================
+
+mdns_query_over_tcp_reason_t
+mdns_querier_get_over_tcp_reason(const mdns_querier_t me)
+{
+       return me->over_tcp_reason;
+}
+
+//======================================================================================================================
+
+bool
+mdns_querier_match(const mdns_querier_t me, const uint8_t * const qname, const int qtype, const int qclass)
+{
+       if ((mdns_query_message_get_qtype(me->query) == qtype) && (mdns_query_message_get_qclass(me->query) == qclass)) {
+               const uint8_t * const query_qname = mdns_query_message_get_qname(me->query);
+               if (query_qname && DomainNameEqual(query_qname, qname)) {
+                       return true;
+               }
+       }
+       return false;
+}
+
+//======================================================================================================================
+
+bool
+mdns_querier_has_concluded(const mdns_querier_t me)
+{
+       return (me->result_type != mdns_querier_result_type_null);
+}
+
+//======================================================================================================================
+
+uint32_t
+mdns_querier_get_user_id(const mdns_querier_t me)
+{
+       return me->user_id;
+}
+
+//======================================================================================================================
+// MARK: - Querier Private Methods
+
+static void
+_mdns_querier_finalize(mdns_querier_t me)
+{
+       me->current_server = NULL;
+       mdns_forget(&me->resolver);
+       dispatch_forget(&me->user_queue);
+       BlockForget(&me->handler);
+       mdns_forget(&me->query);
+       mdns_forget(&me->response);
+       ForgetMem(&me->log_label);
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+       mdns_forget(&me->test_query);
+#endif
+}
+
+//======================================================================================================================
+
+static char *
+_mdns_querier_copy_description(mdns_querier_t me, const bool debug, const bool privacy)
+{
+       char *                          description = NULL;
+       char                            buffer[128];
+       char *                          dst = buffer;
+       const char * const      lim = &buffer[countof(buffer)];
+       int                                     n;
+       char                            qname_str[kDNSServiceMaxDomainName];
+
+       *dst = '\0';
+       if (debug) {
+               n = mdns_snprintf_add(&dst, lim, "<%s: %p>: ", me->base.kind->name, me);
+               require_quiet(n >= 0, exit);
+       }
+       const char *qname;
+       char strbuf[64];
+       const uint8_t * const query_qname = mdns_query_message_get_qname(me->query);
+       if (query_qname) {
+               if (DomainNameToString(query_qname, NULL, qname_str, NULL) == kNoErr) {
+                       if (privacy) {
+                               n = DNSMessagePrintObfuscatedString(strbuf, sizeof(strbuf), qname_str);
+                               qname = (n >= 0) ? strbuf : "<REDACTED QNAME>";
+                       } else {
+                               qname = qname_str;
+                       }
+               } else {
+                       qname = "<INVALID QNAME>";
+               }
+       } else {
+               qname = "<NO QNAME>";
+       }
+       n = mdns_snprintf_add(&dst, lim, "%s", qname);
+       require_quiet(n >= 0, exit);
+
+       const char * const type_str = DNSRecordTypeValueToString(mdns_query_message_get_qtype(me->query));
+       if (type_str) {
+               n = mdns_snprintf_add(&dst, lim, " %s", type_str);
+               require_quiet(n >= 0, exit);
+       } else {
+               n = mdns_snprintf_add(&dst, lim, " TYPE%u", mdns_query_message_get_qtype(me->query));
+               require_quiet(n >= 0, exit);
+       }
+       if (mdns_query_message_get_qclass(me->query) == kDNSClassType_IN) {
+               n = mdns_snprintf_add(&dst, lim, " IN");
+               require_quiet(n >= 0, exit);
+       } else {
+               n = mdns_snprintf_add(&dst, lim, " CLASS%u", mdns_query_message_get_qclass(me->query));
+               require_quiet(n >= 0, exit);
+       }
+       description = strdup(buffer);
+
+exit:
+       return description;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_querier_activate(mdns_querier_t querier);
+
+static void
+_mdns_querier_activate_if_ready(mdns_querier_t me)
+{
+       if (me->user_activated && me->user_queue && mdns_query_message_get_qname(me->query)) {
+               mdns_retain(me);
+               dispatch_async(_mdns_resolver_queue(),
+               ^{
+                       _mdns_querier_activate(me);
+                       mdns_release(me);
+               });
+       }
+}
+
+static void
+_mdns_querier_activate(mdns_querier_t me)
+{
+       require_return(!me->activated && !me->concluded);
+
+       mdns_retain(me);
+       me->activated = true;
+
+       uint16_t msg_id;
+       if (_mdns_resolver_needs_zero_ids(me->resolver)) {
+               msg_id = 0;
+       } else {
+               msg_id = (uint16_t)RandomRange(1, UINT16_MAX);
+       }
+       mdns_query_message_set_message_id(me->query, msg_id);
+       mdns_query_message_use_edns0_padding(me->query, _mdns_resolver_needs_edns0_padding(me->resolver) ? true : false);
+       OSStatus err = mdns_query_message_construct(me->query);
+       require_noerr_quiet(err, exit);
+
+       if (me->time_limit_ms != 0) {
+               err = _mdns_querier_reset_time_limit(me);
+               require_noerr_quiet(err, exit);
+       }
+       _mdns_resolver_register_querier(me->resolver, me, false);
+
+exit:
+       if (err) {
+               _mdns_querier_conclude(me, err);
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_querier_session_handle_event(const mdns_session_t session, const mdns_session_event_t event,
+       const OSStatus error, void * const context)
+{
+       mdns_querier_t const me = (mdns_querier_t)context;
+       os_log_with_type(_mdns_resolver_log(),
+               ((event == mdns_session_event_terminated) && error) ? OS_LOG_TYPE_ERROR : OS_LOG_TYPE_INFO,
+               "%{public}sQuerier session event -- type: %{public}s, error: %{mdns:err}ld",
+               _mdns_querier_get_log_label(me), mdns_session_event_to_string(event), (long)error);
+
+       switch (event) {
+               case mdns_session_event_ready:
+                       _mdns_querier_send_query(me, session);
+                       break;
+
+               case mdns_session_event_terminated: {
+                       const bool is_stream = _mdns_session_is_bytestream(session);
+                       mdns_session_t *ptr = is_stream ? &me->stream_session_list : &me->dgram_session_list;
+                       while (*ptr && (*ptr != session)) {
+                               ptr = &(*ptr)->next;
+                       }
+                       require_quiet(*ptr, exit);
+
+                       *ptr = session->next;
+                       session->next = NULL;
+                       mdns_session_t tmp = session;
+                       mdns_session_forget(&tmp);
+
+                       if (is_stream) {
+                               const mdns_server_t server = session->server;
+                               if (error || (session->receive_count == 0)) {
+                                       _mdns_resolver_handle_stream_error(me->resolver, server, _mdns_querier_get_log_label(me));
+                                       _mdns_resolver_penalize_server(me->resolver, server);
+                               }
+                               _mdns_querier_handle_stream_error(me, server);
+                       }
+                       break;
+               }
+               case mdns_session_event_lateness_warning:
+                       if (_mdns_session_is_bytestream(session)) {
+                               _mdns_resolver_handle_stream_lateness(me->resolver, session->server, session->start_ticks,
+                                       _mdns_querier_get_log_label(me));
+                       }
+                       break;
+
+               default:
+                       break;
+       }
+
+exit:
+       return;
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_querier_is_response_acceptable(const mdns_querier_t me, const mdns_message_t msg, bool * const out_truncated,
+       bool * const out_suspicious, int * const out_rcode)
+{
+       bool id_match           = false;
+       bool question_match     = false;
+       bool acceptable         = false;
+       const size_t msg_len = mdns_message_get_length(msg);
+       require_quiet(msg_len >= kDNSHeaderLength, exit);
+
+       uint16_t msg_id = 0;
+       const uint8_t * const msg_ptr = mdns_message_get_byte_ptr(msg);
+       question_match = _mdns_message_is_query_response_ignoring_id(msg_ptr, msg_len, me->query, &msg_id);
+       require_quiet(question_match, exit);
+
+       id_match = (msg_id == mdns_query_message_get_message_id(me->query)) ? true : false;
+       if (id_match) {
+               acceptable = true;
+               const DNSHeader * const hdr = (const DNSHeader *)msg_ptr;
+               const unsigned int flags = DNSHeaderGetFlags(hdr);
+               if (out_truncated) {
+                       bool truncated = (flags & kDNSHeaderFlag_Truncation) ? true : false;
+                       if (truncated && !mdns_query_message_do_bit_is_set(me->query) && (DNSHeaderGetAnswerCount(hdr) > 0) &&
+                               ((DNSHeaderGetAuthorityCount(hdr) > 0) || (DNSHeaderGetAdditionalCount(hdr) > 0))) {
+                               truncated = false;
+                       }
+                       *out_truncated = truncated;
+               }
+               if (out_rcode) {
+                       *out_rcode = DNSFlagsGetRCode(flags);
+               }
+       }
+
+exit:
+       if (out_suspicious) {
+               *out_suspicious = (!id_match && question_match) ? true : false;
+       }
+       return acceptable;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_querier_conclude(const mdns_querier_t me, const mdns_querier_result_type_t result_type)
+{
+       _mdns_querier_conclude_ex(me, result_type, 0, NULL);
+}
+
+//======================================================================================================================
+
+static void
+_mdns_querier_conclude_async(const mdns_querier_t me, const mdns_querier_result_type_t result_type)
+{
+       mdns_retain(me);
+       dispatch_async(_mdns_resolver_queue(),
+       ^{
+               _mdns_querier_conclude(me, result_type);
+               mdns_release(me);
+       });
+}
+
+//======================================================================================================================
+
+static void
+_mdns_querier_conclude_with_error(const mdns_querier_t me, const OSStatus error)
+{
+       _mdns_querier_conclude_ex(me, mdns_querier_result_type_error, error, NULL);
+}
+
+//======================================================================================================================
+
+static void
+_mdns_querier_conclude_with_error_async(const mdns_querier_t me, const OSStatus error)
+{
+       mdns_retain(me);
+       dispatch_async(_mdns_resolver_queue(),
+       ^{
+               _mdns_querier_conclude_with_error(me, error);
+               mdns_release(me);
+       });
+}
+
+//======================================================================================================================
+
+static void
+_mdns_querier_conclude_with_response(const mdns_querier_t me, const mdns_message_t response)
+{
+       _mdns_querier_conclude_ex(me, mdns_querier_result_type_response, 0, response);
+}
+
+//======================================================================================================================
+
+#define MDNS_QUERIER_RESPONSE_STATUS_FABRICATED        1
+
+static void
+_mdns_querier_conclude_with_response_async(const mdns_querier_t me, const mdns_message_t response,
+       const bool fabricated)
+{
+       mdns_retain(me);
+       mdns_retain(response);
+       dispatch_async(_mdns_resolver_queue(),
+       ^{
+               const OSStatus status = fabricated ? MDNS_QUERIER_RESPONSE_STATUS_FABRICATED : 0;
+               _mdns_querier_conclude_ex(me, mdns_querier_result_type_response, status, response);
+               mdns_release(me);
+               mdns_release(response);
+       });
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_querier_postprocess_response(mdns_querier_t querier);
+
+static void
+_mdns_querier_conclude_ex(const mdns_querier_t me, const mdns_querier_result_type_t result_type, const OSStatus status,
+       const mdns_message_t response)
+{
+       dispatch_source_forget(&me->rtx_timer);
+       dispatch_source_forget(&me->timeout_timer);
+       mdns_forget(&me->bad_rcode_response);
+
+       _mdns_resolver_deregister_querier(me->resolver, me);
+       require_return(!me->concluded);
+
+       me->concluded = true;
+       switch (result_type) {
+               case mdns_querier_result_type_response: {
+                       mdns_replace(&me->response, response);
+                       const bool fabricated = _mdns_querier_postprocess_response(me);
+                       me->response_is_fabricated = fabricated || (status == MDNS_QUERIER_RESPONSE_STATUS_FABRICATED);
+                       if (me->response_is_fabricated) {
+                               os_log_info(_mdns_resolver_log(),
+                                       "%{public}sQuerier concluded -- reason: response (fabricated)", _mdns_querier_get_log_label(me));
+                       } else {
+                               os_log_info(_mdns_resolver_log(),
+                                       "%{public}sQuerier concluded -- reason: response", _mdns_querier_get_log_label(me));
+                       }
+                       break;
+               }
+               case mdns_querier_result_type_timeout:
+                       os_log_info(_mdns_resolver_log(),
+                               "%{public}sQuerier concluded -- reason: timeout", _mdns_querier_get_log_label(me));
+                       break;
+
+               case mdns_querier_result_type_invalidation:
+                       os_log_info(_mdns_resolver_log(),
+                               "%{public}sQuerier concluded -- reason: invalidation", _mdns_querier_get_log_label(me));
+                       break;
+
+               case mdns_querier_result_type_resolver_invalidation:
+                       os_log_info(_mdns_resolver_log(),
+                               "%{public}sQuerier concluded -- reason: resolver invalidation", _mdns_querier_get_log_label(me));
+                       break;
+
+               case mdns_querier_result_type_error:
+                       me->error = status;
+                       os_log_error(_mdns_resolver_log(),
+                               "%{public}sQuerier concluded -- error: %{mdns:err}ld",
+                               _mdns_querier_get_log_label(me), (long)me->error);
+                       break;
+
+               case mdns_querier_result_type_null:
+                       break;
+       }
+       if (me->user_queue) {
+               const mdns_querier_result_handler_t handler = me->handler;
+               me->handler = NULL;
+               mdns_retain(me);
+               dispatch_async(me->user_queue,
+               ^{
+                       me->result_type = result_type;
+                       if (handler) {
+                               handler();
+                               Block_release(handler);
+                       }
+                       mdns_release(me);
+               });
+       }
+       if (me->activated) {
+               mdns_release(me);
+       }
+}
+
+static bool
+_mdns_querier_postprocess_response(const mdns_querier_t me)
+{
+       if (!me->resolver->squash_cnames) {
+               return false;
+       }
+       const uint8_t *resp_ptr = _mdns_querier_get_response_ptr_safe(me);
+       require_return_value(resp_ptr, false);
+
+       mdns_message_t new_msg = NULL;
+       OSStatus err;
+       size_t new_len;
+       uint8_t *new_ptr = DNSMessageCollapse(resp_ptr, _mdns_querier_get_response_length_safe(me), &new_len, &err);
+       if (new_ptr) {
+               dispatch_data_t data = dispatch_data_create(new_ptr, new_len, NULL, DISPATCH_DATA_DESTRUCTOR_FREE);
+               if (data) {
+                       new_ptr = NULL;
+                       new_msg = mdns_message_create_with_dispatch_data(data, mdns_message_init_option_disable_header_printing);
+                       dispatch_forget(&data);
+                       if (!new_msg) {
+                               err = kNoResourcesErr;
+                       }
+               } else {
+                       ForgetMem(&new_ptr);
+                       err = kNoResourcesErr;
+               }
+       }
+       bool fabricated;
+       if (new_msg) {
+               mdns_replace(&me->response, new_msg);
+               mdns_forget(&new_msg);
+               resp_ptr = _mdns_querier_get_response_ptr_safe(me);
+               size_t resp_len = _mdns_querier_get_response_length_safe(me);
+               os_log(_mdns_resolver_log(),
+                       "%{public}sUsing squashed response -- %{public,mdns:dnshdr}.*P, %@",
+                       _mdns_querier_get_log_label(me), (int)Min(resp_len, kDNSHeaderLength), resp_ptr, me->response);
+               fabricated = true;
+       } else {
+               os_log_error(_mdns_resolver_log(),
+                       "%{public}sFailed to squash response -- error:%{mdns:err}ld",
+                       _mdns_querier_get_log_label(me), (long)err);
+               fabricated = false;
+       }
+       return fabricated;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_querier_reregister_in_stream_mode(mdns_querier_t querier);
+
+static void
+_mdns_querier_session_receive(const mdns_session_t session, const dispatch_data_t msg_data, void * const context)
+{
+       const mdns_querier_t me = (mdns_querier_t)context;
+       const mdns_message_t msg = mdns_message_create_with_dispatch_data(msg_data,
+               mdns_message_init_option_disable_header_printing);
+       require_return_action(msg, _mdns_querier_conclude_with_error(me, kNoResourcesErr));
+
+       bool truncated  = false;
+       bool suspicious = false;
+       const bool is_dgram = !_mdns_session_is_bytestream(session);
+       bool * const truncated_ptr = is_dgram ? &truncated : NULL;
+       const bool need_suspicious_reply_defense = _mdns_resolver_needs_suspicious_reply_defense(me->resolver);
+       bool * const suspicious_ptr = (need_suspicious_reply_defense && is_dgram) ? &suspicious : NULL;
+       int rcode = 0;
+       const bool acceptable = _mdns_querier_is_response_acceptable(me, msg, truncated_ptr, suspicious_ptr, &rcode);
+       _mdns_resolver_log_receive(me->resolver, session, msg, acceptable, _mdns_querier_get_log_label(me));
+       const mdns_server_t server = session->server;
+       if (acceptable) {
+               _mdns_resolver_note_responsiveness(me->resolver, server, !is_dgram, session->start_ticks,
+                       mdns_querier_get_qtype(me));
+               if (_mdns_rcode_is_good(rcode)) {
+                       if (is_dgram && truncated) {
+                               me->over_tcp_reason = mdns_query_over_tcp_reason_truncation;
+                               _mdns_querier_reregister_in_stream_mode(me);
+                       } else {
+                               _mdns_querier_conclude_with_response(me, msg);
+                       }
+               } else {
+                       // Note: _mdns_querier_handle_bad_rcode() may or may not conclude the querier.
+                       _mdns_querier_handle_bad_rcode(me, msg, rcode, server);
+               }
+       } else if (is_dgram && need_suspicious_reply_defense && suspicious) {
+               me->over_tcp_reason = mdns_query_over_tcp_reason_got_suspicious_reply;
+               _mdns_resolver_got_suspicious_reply(me->resolver);
+               _mdns_querier_reregister_in_stream_mode(me);
+#if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND || MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+       } else {
+               bool handled_response = false;
+       #if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+               if (_mdns_resolver_use_problematic_qtype_workaround(me->resolver)) {
+                       if (me->test_query && _mdns_message_is_adequate_test_query_response(msg, me->test_query)) {
+                               _mdns_querier_set_test_query_got_response(me, server, true);
+                               _mdns_resolver_note_responsiveness(me->resolver, server, !is_dgram, session->start_ticks,
+                                       mdns_query_message_get_qtype(me->test_query));
+                               handled_response = true;
+                       }
+               }
+       #endif
+       #if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND
+               if (!handled_response && _mdns_resolver_use_mixed_up_responses_workaround(me->resolver)) {
+                       if (!server->mixes_up_responses) {
+                               qtype_test_f qtype_test = NULL;
+                               const int qtype = mdns_querier_get_qtype(me);
+                               if (_mdns_qtype_is_address_type(qtype)) {
+                                       qtype_test = _mdns_qtype_is_problematic;
+                               } else if (_mdns_qtype_is_problematic(qtype)) {
+                                       qtype_test = _mdns_qtype_is_address_type;
+                               }
+                               if (qtype_test) {
+                                       uint16_t msg_qtype = 0;
+                                       if (_mdns_message_is_query_response_ignoring_qtype(msg, me->query, &msg_qtype)) {
+                                               if (qtype_test(msg_qtype)) {
+                                                       server->mixes_up_responses = true;
+                                               }
+                                       }
+                               }
+                       }
+               }
+       #else
+               (void)handled_response;
+       #endif
+#endif
+       }
+       mdns_release(msg);
+}
+
+static void
+_mdns_querier_reregister_in_stream_mode(mdns_querier_t me)
+{
+       _mdns_resolver_deregister_querier(me->resolver, me);
+       _mdns_resolver_register_querier(me->resolver, me, true);
+}
+
+//======================================================================================================================
+
+static mdns_session_t
+_mdns_querier_get_shared_session(mdns_querier_t querier);
+
+static mdns_session_t
+_mdns_querier_get_unshared_session(mdns_querier_t querier);
+
+static void
+_mdns_querier_initiate_send(const mdns_querier_t me)
+{
+       dispatch_source_forget(&me->rtx_timer);
+       do {
+               if (!me->current_server) {
+                       if (me->bad_rcode_response && !_mdns_resolver_get_server(me->resolver, me->bad_rcode_bitmap)) {
+                               const bool fabricated = (me->bad_rcode < 0) ? true : false;
+                               _mdns_querier_conclude_with_response_async(me, me->bad_rcode_response, fabricated);
+                               return;
+                       }
+                       _mdns_querier_set_current_server(me, _mdns_querier_get_eligible_server(me));
+                       if (!me->current_server) {
+                               os_log_debug(_mdns_resolver_log(),
+                                       "%{public}sNo more eligible servers", _mdns_querier_get_log_label(me));
+                               return;
+                       }
+               }
+       #if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND || MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+               bool check_qtype_support = false;
+               if (0) {
+       #if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND
+               } else if (_mdns_resolver_use_mixed_up_responses_workaround(me->resolver)) {
+                       check_qtype_support = true;
+       #endif
+       #if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+               } else if (_mdns_resolver_use_problematic_qtype_workaround(me->resolver)) {
+                       check_qtype_support = true;
+       #endif
+               }
+               if (check_qtype_support) {
+                       const mdns_server_t server = me->current_server;
+                       const int qtype = mdns_querier_get_qtype(me);
+                       if (!_mdns_server_supports_qtype(server, qtype)) {
+                               if (0) {
+                       #if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND
+                               } else if (server->mixes_up_responses) {
+                                       os_log_info(_mdns_resolver_log(),
+                                               "%{public}sNot sending query to server %@, which mixes up responses of type %{mdns:rrtype}d",
+                                               _mdns_querier_get_log_label(me), server, qtype);
+                       #endif
+                               } else {
+                                       os_log_info(_mdns_resolver_log(),
+                                               "%{public}sNot sending query to server %@, which ignores queries of type %{mdns:rrtype}d",
+                                               _mdns_querier_get_log_label(me), server, qtype);
+                               }
+                               if (!me->bad_rcode_response) {
+                                       OSStatus err;
+                                       const int rcode = kDNSRCode_NotImp;
+                                       me->bad_rcode_response = _mdns_create_empty_response_for_query(me->query, rcode, &err);
+                                       require_return_action(me->bad_rcode_response, _mdns_querier_conclude_with_error_async(me, err));
+                                       me->bad_rcode = -rcode;
+                               }
+                               me->bad_rcode_bitmap |= _mdns_rank_to_bitmask(server->rank);
+                               _mdns_querier_set_current_server(me, NULL);
+                       }
+               }
+       #endif // MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND || MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+       } while (!me->current_server);
+
+       me->rtx_timer = _mdns_resolver_create_oneshot_timer(me->rtx_interval_ms, 5);
+       require_return_action(me->rtx_timer, _mdns_querier_conclude_with_error_async(me, kNoResourcesErr));
+
+       dispatch_source_set_event_handler(me->rtx_timer,
+       ^{
+               dispatch_source_forget(&me->rtx_timer);
+               _mdns_querier_handle_no_response(me);
+       });
+       dispatch_activate(me->rtx_timer);
+
+       mdns_session_t session;
+       if (me->use_shared_stream) {
+               session = _mdns_querier_get_shared_session(me);
+       } else {
+               session = _mdns_querier_get_unshared_session(me);
+       }
+       if (session) {
+               _mdns_querier_send_query(me, session);
+       }
+}
+
+#define MDNS_RESOLVER_STREAM_LATENESS_TIME_MS  (10 * kMillisecondsPerSecond)
+
+static mdns_session_t
+_mdns_querier_get_shared_session(const mdns_querier_t me)
+{
+       require_return_value(me->current_server, NULL);
+
+       const mdns_server_t server = me->current_server;
+       mdns_session_t session = server->shared_stream_session;
+       if (!session) {
+               os_log_debug(_mdns_resolver_log(),
+                       "%{public}sCreating shared session to %@", _mdns_querier_get_log_label(me), server);
+               OSStatus err;
+               session = _mdns_resolver_create_session(me->resolver, server, true, NULL, NULL, &err);
+               if (likely(session)) {
+                       static const mdns_session_callbacks_t s_resolver_callbacks = {
+                               .handle_event           = _mdns_resolver_session_handle_event,
+                               .receive                        = _mdns_resolver_session_receive,
+                               .finalize_context       = _mdns_session_finalize_context_with_release
+                       };
+                       mdns_retain(me->resolver);
+                       _mdns_session_set_callbacks(session, &s_resolver_callbacks, me->resolver);
+                       _mdns_session_set_lateness_time(session, MDNS_RESOLVER_STREAM_LATENESS_TIME_MS);
+                       _mdns_session_activate(session);
+                       server->shared_stream_session = session;
+               } else {
+                       os_log_error(_mdns_resolver_log(),
+                               "Failed to create session to %@ for resolver: %{mdns:err}ld", server, (long)err);
+                       _mdns_resolver_penalize_server(me->resolver, server);
+                       _mdns_querier_set_current_server(me, NULL);
+               }
+       }
+       return session;
+}
+
+static mdns_session_t
+_mdns_querier_get_unshared_session(const mdns_querier_t me)
+{
+       require_return_value(me->current_server, NULL);
+
+       const mdns_server_t server = me->current_server;
+       mdns_session_t session;
+       mdns_session_t *ptr = me->use_stream ? &me->stream_session_list : &me->dgram_session_list;
+       while ((session = *ptr) != NULL) {
+               if (session->server == server) {
+                       break;
+               }
+               ptr = &session->next;
+       }
+       if (!session) {
+               os_log_debug(_mdns_resolver_log(),
+                       "%{public}sCreating session to %@", _mdns_querier_get_log_label(me), server);
+               OSStatus err;
+               session = _mdns_resolver_create_session(me->resolver, server, me->use_stream, &me->delegation,
+                       mdns_query_message_get_qname(me->query), &err);
+               if (likely(session)) {
+                       static const mdns_session_callbacks_t s_querier_callbacks = {
+                               .handle_event           = _mdns_querier_session_handle_event,
+                               .receive                        = _mdns_querier_session_receive,
+                               .finalize_context       = _mdns_session_finalize_context_with_release
+                       };
+                       mdns_retain(me);
+                       _mdns_session_set_callbacks(session, &s_querier_callbacks, me);
+                       if (me->use_stream) {
+                               _mdns_session_set_lateness_time(session, MDNS_RESOLVER_STREAM_LATENESS_TIME_MS);
+                       }
+                       _mdns_session_activate(session);
+                       *ptr = session;
+               } else {
+                       os_log_error(_mdns_resolver_log(),
+                               "Failed to create session to %@ for querier: %{mdns:err}ld", server, (long)err);
+                       _mdns_resolver_penalize_server(me->resolver, server);
+                       _mdns_querier_set_current_server(me, NULL);
+               }
+       }
+       return session;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_querier_start(const mdns_querier_t me)
+{
+       _mdns_querier_set_current_server(me, NULL);
+       if (me->use_stream) {
+               me->rtx_interval_ms = MDNS_RESOLVER_CONNECTION_TIMEOUT_MS;
+       } else {
+               me->rtx_interval_ms = me->resolver->initial_dgram_rtx_ms;
+       }
+       _mdns_querier_initiate_send(me);
+}
+
+//======================================================================================================================
+
+static void
+_mdns_querier_send_query_immediate(mdns_querier_t querier, mdns_session_t session);
+
+static void
+_mdns_querier_log_query_send(mdns_querier_t querier, mdns_session_t session);
+
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+static void
+_mdns_querier_log_test_query_send(mdns_querier_t querier, mdns_session_t session);
+
+static bool
+_mdns_querier_needs_test_query(mdns_querier_t querier, mdns_server_t server);
+#endif
+
+static void
+_mdns_querier_send_query(const mdns_querier_t me, const mdns_session_t session)
+{
+       const uint32_t bitmask = _mdns_rank_to_bitmask(session->server->rank);
+       if (_mdns_session_is_ready(session)) {
+               me->will_send_bitmap &= ~bitmask;
+               if (_mdns_session_is_bytestream(session)) {
+                       if ((me->did_send_bitmap & bitmask) == 0) {
+                               _mdns_querier_send_query_immediate(me, session);
+                               me->did_send_bitmap |= bitmask;
+                       }
+               } else {
+                       _mdns_querier_send_query_immediate(me, session);
+                       me->did_send_bitmap |= bitmask;
+               }
+       } else {
+               me->will_send_bitmap |= bitmask;
+       }
+}
+
+static void
+_mdns_querier_send_query_immediate(const mdns_querier_t me, const mdns_session_t session)
+{
+       const uint16_t qtype = mdns_query_message_get_qtype(me->query);
+       _mdns_session_send(session, mdns_message_get_dispatch_data(me->query), qtype);
+       atomic_fetch_add(&me->send_count, 1);
+       _mdns_querier_log_query_send(me, session);
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+       if (_mdns_resolver_use_problematic_qtype_workaround(me->resolver)) {
+               const mdns_server_t server = session->server;
+               if (_mdns_querier_needs_test_query(me, server)) {
+                       if (!me->test_query) {
+                               me->test_query = _mdns_create_simple_test_query(me->query, _mdns_server_get_test_query_qtype(server));
+                       }
+                       if (me->test_query) {
+                               _mdns_session_send(session, mdns_message_get_dispatch_data(me->test_query),
+                                       mdns_query_message_get_qtype(me->test_query));
+                               ++me->test_send_count;
+                               _mdns_querier_log_test_query_send(me, session);
+                       } else {
+                               os_log_error(_mdns_resolver_log(),
+                                       "%{public}sFailed to create test query", _mdns_querier_get_log_label(me));
+                       }
+               }
+       }
+#endif
+}
+
+static void
+_mdns_querier_log_query_send(const mdns_querier_t me, const mdns_session_t session)
+{
+       const size_t query_len = mdns_message_get_length(me->query);
+       os_log(_mdns_resolver_log(),
+               "%{public}sSent %zu-byte query #%u to %@ over %{public}s via %{public}s -- %{public,mdns:dnshdr}.*P, %@",
+               _mdns_querier_get_log_label(me),
+               query_len,
+               me->send_count,
+               session->server,
+               _mdns_resolver_get_protocol_log_string(me->resolver, _mdns_session_is_bytestream(session)),
+               _mdns_resolver_get_interface_log_string(me->resolver),
+               (int)Min(query_len, kDNSHeaderLength), mdns_message_get_byte_ptr(me->query),
+               me->query);
+}
+
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+static void
+_mdns_querier_log_test_query_send(const mdns_querier_t me, const mdns_session_t session)
+{
+       require_return(me->test_query);
+       const size_t query_len = mdns_message_get_length(me->test_query);
+       os_log(_mdns_resolver_log(),
+               "%{public}sSent %zu-byte test query #%u to %@ over %{public}s via %{public}s -- %{public,mdns:dnshdr}.*P, %@",
+               _mdns_querier_get_log_label(me),
+               query_len,
+               me->test_send_count,
+               session->server,
+               _mdns_resolver_get_protocol_log_string(me->resolver, _mdns_session_is_bytestream(session)),
+               _mdns_resolver_get_interface_log_string(me->resolver),
+               (int)Min(query_len, kDNSHeaderLength), mdns_message_get_byte_ptr(me->test_query),
+               me->test_query);
+}
+
+static bool
+_mdns_querier_needs_test_query(const mdns_querier_t me, const mdns_server_t server)
+{
+       if (server->responds_to_problematics) {
+               return false;
+       }
+       const int qtype = mdns_query_message_get_qtype(me->query);
+       if (!_mdns_qtype_is_problematic(qtype)) {
+               return false;
+       }
+       if (_mdns_querier_test_query_got_response(me, server)) {
+               return false;
+       }
+       if (server->pqw_info && !_pqw_info_can_accept_qname(server->pqw_info, mdns_query_message_get_qname(me->query))) {
+               return false;
+       }
+       return true;
+}
+#endif
+
+//======================================================================================================================
+
+static const char *
+_mdns_querier_get_log_label(const mdns_querier_t me)
+{
+       return (me->log_label ? me->log_label : "");
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mdns_querier_reset_time_limit(const mdns_querier_t me)
+{
+       OSStatus err;
+       require_action_quiet(!me->concluded && me->activated, exit, err = kNoErr);
+
+       os_log_info(_mdns_resolver_log(),
+               "%{public}sResetting time limit to %ld ms", _mdns_querier_get_log_label(me), (long)me->time_limit_ms);
+       dispatch_source_forget(&me->timeout_timer);
+       require_action_quiet(me->time_limit_ms >= 0, exit, err = kTimeoutErr);
+
+       if (me->time_limit_ms > 0) {
+               me->timeout_timer = _mdns_resolver_create_oneshot_timer((uint32_t)me->time_limit_ms, 5);
+               require_action_quiet(me->timeout_timer, exit, err = kNoResourcesErr);
+
+               dispatch_source_set_event_handler(me->timeout_timer,
+               ^{
+                       _mdns_querier_conclude(me, mdns_querier_result_type_timeout);
+               });
+               dispatch_activate(me->timeout_timer);
+       }
+       err = kNoErr;
+
+exit:
+       return err;
+}
+
+//======================================================================================================================
+
+#define MDNS_QUERIER_UNANSWERED_QUERY_COUNT_MAX        2
+#define MDNS_QUERIER_RTX_INTERVAL_MAX_MS               (120 * kMillisecondsPerSecond)
+
+static void
+_mdns_querier_handle_no_response(const mdns_querier_t me)
+{
+       if (me->current_server) {
+               if (me->use_stream) {
+                       _mdns_resolver_penalize_server(me->resolver, me->current_server);
+                       _mdns_querier_set_current_server(me, NULL);
+               } else {
+                       ++me->unanswered_query_count;
+                       if (me->rtx_interval_ms <= (MDNS_QUERIER_RTX_INTERVAL_MAX_MS / 2)) {
+                               me->rtx_interval_ms *= 2;
+                       } else {
+                               me->rtx_interval_ms = MDNS_QUERIER_RTX_INTERVAL_MAX_MS;
+                       }
+                       if (me->unanswered_query_count >= MDNS_QUERIER_UNANSWERED_QUERY_COUNT_MAX) {
+                               mdns_session_t session = me->dgram_session_list;
+                               while (session && (session->server != me->current_server)) {
+                                       session = session->next;
+                               }
+                               if (session) {
+                                       const mdns_server_t server = me->current_server;
+                                       _mdns_resolver_penalize_unresponsive_server(me->resolver, server, me, session->last_send_ticks);
+                               #if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+                                       _mdns_querier_set_test_query_got_response(me, server, false);
+                               #endif
+                               }
+                               _mdns_querier_set_current_server(me, NULL);
+                       }
+               }
+       }
+       _mdns_querier_initiate_send(me);
+}
+
+//======================================================================================================================
+
+static void
+_mdns_querier_set_current_server(const mdns_querier_t me, const mdns_server_t server)
+{
+       me->current_server = server;
+       me->unanswered_query_count = 0;
+       if (!me->use_stream && me->current_server) {
+               const uint32_t bitmask = _mdns_rank_to_bitmask(me->current_server->rank);
+               if ((me->did_send_bitmap & bitmask) == 0) {
+                       me->rtx_interval_ms = me->resolver->initial_dgram_rtx_ms;
+               }
+       }
+}
+
+//======================================================================================================================
+
+static mdns_server_t
+_mdns_querier_get_eligible_server(const mdns_querier_t me)
+{
+       uint32_t exclude_bitmap = me->bad_rcode_bitmap;
+       if (me->use_stream) {
+               exclude_bitmap |= me->will_send_bitmap;
+               exclude_bitmap |= me->did_send_bitmap;
+       }
+       mdns_server_t server = _mdns_resolver_get_server(me->resolver, exclude_bitmap);
+       if (server && me->resolver->probe_querier && (me != me->resolver->probe_querier)) {
+               os_log(_mdns_resolver_log(),
+                       "%{public}sBacking off while probe querier is active", _mdns_querier_get_log_label(me));
+               server = NULL;
+       }
+       return server;
+}
+
+//======================================================================================================================
+
+static mdns_server_t
+_mdns_querier_get_unpenalized_eligible_server(const mdns_querier_t me)
+{
+       const mdns_server_t server = _mdns_querier_get_eligible_server(me);
+       return ((server && !server->penalized) ? server : NULL);
+}
+
+//======================================================================================================================
+
+static void
+_mdns_querier_handle_stream_error(const mdns_querier_t me, const mdns_server_t server)
+{
+       const uint32_t bitmask = _mdns_rank_to_bitmask(server->rank);
+       me->will_send_bitmap &= ~bitmask;
+       me->did_send_bitmap      &= ~bitmask;
+       if (me->current_server == server) {
+               _mdns_querier_set_current_server(me, _mdns_querier_get_unpenalized_eligible_server(me));
+               if (me->current_server) {
+                       _mdns_querier_initiate_send(me);
+               }
+       } else if (!me->current_server && !me->rtx_timer) {
+               _mdns_querier_initiate_send(me);
+       }
+}
+
+//======================================================================================================================
+
+static void
+_mdns_querier_handle_bad_rcode(const mdns_querier_t me, const mdns_message_t response, const int rcode,
+       const mdns_server_t server)
+{
+       me->bad_rcode_bitmap |= _mdns_rank_to_bitmask(server->rank);
+#if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND || MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+       if (me->bad_rcode < 0) {
+               mdns_forget(&me->bad_rcode_response);
+               me->bad_rcode = 0;
+       }
+#endif
+       // Save the response if it's the first bad RCODE response that's been received.
+       // Only replace the saved bad RCODE response if its RCODE is Refused and the newer response's RCODE is not
+       // Refused. This way, if there's a server that consistently returns Refused, its responses won't mask a bad
+       // RCODE response with a potentially more informative RCODE.
+       if (!me->bad_rcode_response || ((me->bad_rcode == kDNSRCode_Refused) && (rcode != kDNSRCode_Refused))) {
+               mdns_replace(&me->bad_rcode_response, response);
+               me->bad_rcode = rcode;
+       }
+       if (rcode == kDNSRCode_Refused) {
+               _mdns_resolver_penalize_server(me->resolver, server);
+       }
+       // If there are any servers that haven't returned a bad RCODE, then move on to the next server.
+       // Otherwise, conclude with the saved bad RCODE response.
+       if (_mdns_resolver_get_server(me->resolver, me->bad_rcode_bitmap)) {
+               if (me->current_server == server) {
+                       _mdns_querier_set_current_server(me, NULL);
+                       _mdns_querier_initiate_send(me);
+               }
+       } else {
+               const mdns_message_t bad_rcode_response = me->bad_rcode_response;
+               me->bad_rcode_response = NULL;
+               _mdns_querier_conclude_with_response(me, bad_rcode_response);
+               mdns_release(bad_rcode_response);
+       }
+}
+
+//======================================================================================================================
+
+static const uint8_t *
+_mdns_querier_get_response_ptr_safe(const mdns_querier_t me)
+{
+       return (me->response ? mdns_message_get_byte_ptr(me->response) : NULL);
+}
+
+//======================================================================================================================
+
+static size_t
+_mdns_querier_get_response_length_safe(const mdns_querier_t me)
+{
+       return (me->response ? mdns_message_get_length(me->response) : 0);
+}
+
+//======================================================================================================================
+
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+static void
+_mdns_querier_set_test_query_got_response(const mdns_querier_t me, const mdns_server_t server, const bool got_response)
+{
+       const uint32_t bitmask = _mdns_rank_to_bitmask(server->rank);
+       if (got_response) {
+               me->test_query_resp_bitmap |= bitmask;
+       } else {
+               me->test_query_resp_bitmap &= ~bitmask;
+       }
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_querier_test_query_got_response(const mdns_querier_t me, const mdns_server_t server)
+{
+       return ((me->test_query_resp_bitmap & _mdns_rank_to_bitmask(server->rank)) ? true : false);
+}
+#endif
+
+//======================================================================================================================
+// MARK: - Helper Functions
+
+static dispatch_queue_t
+_mdns_resolver_queue(void)
+{
+       static dispatch_once_t  s_once  = 0;
+       static dispatch_queue_t s_queue = NULL;
+
+       dispatch_once(&s_once,
+       ^{
+               s_queue = dispatch_queue_create("com.apple.mdns.resolver-queue", DISPATCH_QUEUE_SERIAL);
+               http_set_resolver_queue(s_queue);
+       });
+       return s_queue;
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_message_is_query_response_ignoring_id(const uint8_t * const msg_ptr, const size_t msg_len,
+       const mdns_query_message_t query, uint16_t *out_id)
+{
+       uint16_t tmp_id = 0;
+       return _mdns_message_is_query_response_ex(msg_ptr, msg_len, query, out_id ? out_id : &tmp_id, NULL, false);
+}
+
+//======================================================================================================================
+
+#if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND
+static bool
+_mdns_message_is_query_response_ignoring_qtype(const mdns_message_t msg, const mdns_query_message_t query,
+       uint16_t * const out_qtype)
+{
+       uint16_t tmp_qtype = 0;
+       return _mdns_message_is_query_response_ex(mdns_message_get_byte_ptr(msg), mdns_message_get_length(msg), query,
+               NULL, out_qtype ? out_qtype : &tmp_qtype, false);
+}
+#endif
+
+//======================================================================================================================
+
+static bool
+_mdns_message_is_query_response_ex(const uint8_t * const msg_ptr, const size_t msg_len,
+       const mdns_query_message_t query, uint16_t * const out_id, uint16_t * const out_qtype, const bool ignore_qnames)
+{
+       require_return_value(msg_len >= kDNSHeaderLength, false);
+
+       const DNSHeader * const hdr = (const DNSHeader *)msg_ptr;
+       const uint16_t msg_id = DNSHeaderGetID(hdr);
+       require_return_value(out_id || (msg_id == mdns_query_message_get_message_id(query)), false);
+
+       const unsigned int flags = DNSHeaderGetFlags(hdr);
+       require_return_value(flags & kDNSHeaderFlag_Response, false);
+       require_return_value(DNSFlagsGetOpCode(flags) == kDNSOpCode_Query, false);
+       require_return_value(DNSHeaderGetQuestionCount(hdr) == 1, false);
+
+       uint16_t qtype, qclass;
+       uint8_t qname[kDomainNameLengthMax];
+       const uint8_t * const qptr = (const uint8_t *)&hdr[1];
+       const OSStatus err = DNSMessageExtractQuestion(msg_ptr, msg_len, qptr, qname, &qtype, &qclass, NULL);
+       require_return_value(!err, false);
+       require_return_value(ignore_qnames || DomainNameEqual(qname, mdns_query_message_get_qname(query)), false);
+       require_return_value(out_qtype || (qtype == mdns_query_message_get_qtype(query)), false);
+       require_return_value(qclass == mdns_query_message_get_qclass(query), false);
+       if (out_id) {
+               *out_id = msg_id;
+       }
+       if (out_qtype) {
+               *out_qtype = qtype;
+       }
+       return true;
+}
+
+//======================================================================================================================
+
+static nw_parameters_t
+_mdns_create_udp_parameters(OSStatus *out_error)
+{
+       OSStatus err;
+       nw_parameters_t params = nw_parameters_create_secure_udp(NW_PARAMETERS_DISABLE_PROTOCOL,
+               NW_PARAMETERS_DEFAULT_CONFIGURATION);
+       require_action_quiet(params, exit, err = kNoResourcesErr);
+
+       err = kNoErr;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       return params;
+}
+
+//======================================================================================================================
+
+static nw_parameters_t
+_mdns_create_tcp_parameters(OSStatus *out_error)
+{
+       OSStatus err;
+       nw_parameters_t params = nw_parameters_create_secure_tcp(NW_PARAMETERS_DISABLE_PROTOCOL,
+               NW_PARAMETERS_DEFAULT_CONFIGURATION);
+       require_action_quiet(params, exit, err = kNoResourcesErr);
+
+       nw_parameters_set_indefinite(params, false);
+
+       err = _mdns_add_dns_over_bytestream_framer(params);
+       require_noerr_quiet(err, exit);
+
+exit:
+       if (err) {
+               nw_forget(&params);
+       }
+       if (out_error) {
+               *out_error = err;
+       }
+       return params;
+}
+
+//======================================================================================================================
+
+static nw_protocol_definition_t
+_mdns_copy_dns_over_bytestream_framer(void);
+
+static OSStatus
+_mdns_add_dns_over_bytestream_framer(nw_parameters_t params)
+{
+       OSStatus                                        err;
+       nw_protocol_definition_t        framer_def      = NULL;
+       nw_protocol_options_t           framer_opts     = NULL;
+
+       nw_protocol_stack_t stack = nw_parameters_copy_default_protocol_stack(params);
+       require_action_quiet(stack, exit, err = kNoResourcesErr);
+
+       framer_def = _mdns_copy_dns_over_bytestream_framer();
+       require_action_quiet(framer_def, exit, err = kNoResourcesErr);
+
+       framer_opts = nw_framer_create_options(framer_def);
+       require_action_quiet(framer_opts, exit, err = kNoResourcesErr);
+
+       nw_protocol_stack_prepend_application_protocol(stack, framer_opts);
+       err = kNoErr;
+
+exit:
+       nw_release_null_safe(stack);
+       nw_release_null_safe(framer_def);
+       nw_release_null_safe(framer_opts);
+       return err;
+}
+
+static nw_protocol_definition_t
+_mdns_create_dns_over_bytestream_framer(void);
+
+static nw_protocol_definition_t
+_mdns_copy_dns_over_bytestream_framer(void)
+{
+       static dispatch_once_t                  s_once = 0;
+       static nw_protocol_definition_t s_framer_def = NULL;
+
+       dispatch_once(&s_once,
+       ^{
+               s_framer_def = _mdns_create_dns_over_bytestream_framer();
+       });
+       if (likely(s_framer_def)) {
+               nw_retain(s_framer_def);
+       }
+       return s_framer_def;
+}
+
+static nw_protocol_definition_t
+_mdns_create_dns_over_bytestream_framer(void)
+{
+       static const nw_framer_input_handler_t input_handler =
+       ^ size_t (nw_framer_t framer)
+       {
+               for (;;) {
+                       uint8_t length_buf[2];
+                       bool ok = nw_framer_parse_input(framer, sizeof(length_buf), sizeof(length_buf), length_buf,
+                       ^ size_t (__unused uint8_t *buf_ptr, size_t buf_len, __unused bool is_complete)
+                       {
+                               return ((buf_len >= sizeof(length_buf)) ? sizeof(length_buf) : 0);
+                       });
+                       if (!ok) {
+                               return sizeof(length_buf);
+                       }
+                       const size_t msg_len = ReadBig16(length_buf);
+                       nw_framer_message_t msg = nw_framer_message_create(framer);
+                       ok = nw_framer_deliver_input_no_copy(framer, msg_len, msg, true);
+                       nw_release(msg);
+                       if (!ok) {
+                               return sizeof(length_buf);
+                       }
+               }
+       };
+       static const nw_framer_output_handler_t output_handler =
+       ^(nw_framer_t framer, __unused nw_framer_message_t msg, size_t msg_len, __unused bool is_complete)
+       {
+               if (msg_len > UINT16_MAX) {
+                       nw_framer_mark_failed_with_error(framer, EMSGSIZE);
+                       return;
+               }
+               uint8_t length_buf[2];
+               WriteBig16(length_buf, msg_len);
+               nw_framer_write_output(framer, length_buf, sizeof(length_buf));
+               nw_framer_write_output_no_copy(framer, msg_len);
+       };
+       nw_protocol_definition_t framer_def = nw_framer_create_definition("DNS over byte-stream",
+               NW_FRAMER_CREATE_FLAGS_DEFAULT,
+       ^ nw_framer_start_result_t (nw_framer_t framer)
+       {
+               nw_framer_set_input_handler(framer, input_handler);
+               nw_framer_set_output_handler(framer, output_handler);
+               return nw_framer_start_result_ready;
+       });
+       return framer_def;
+}
+
+//======================================================================================================================
+
+static uint64_t
+_mdns_ticks_per_second(void)
+{
+       return mdns_mach_ticks_per_second();
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_path_to_server_is_usable(const nw_path_t path, bool encrypted_resolver)
+{
+       const nw_path_status_t status = nw_path_get_status(path);
+       if ((status == nw_path_status_satisfied) || (status == nw_path_status_satisfiable)) {
+               return true;
+       } else if (nw_path_is_per_app_vpn(path)) {
+               // For Per-App VPN, assume that the path to the server is usable since such paths will only have a
+               // satisfied status if the right per-app parameters are provided to the path evaluator. For VPNs, it's
+               // very likely that the DNS server addresses provided by the VPN configuration are reachable, if not, then
+               // the server penalization logic will kick in to favor reachable server addresses over unreachable ones.
+               return true;
+       } else {
+               // For encrypted resolvers, it is possible to use unencrypted resolvers to synthesize IPv4 addresses
+               // on NAT64 networks.
+               bool nat64_eligible = false;
+               if (encrypted_resolver && nw_path_has_dns(path)) {
+                       nw_endpoint_t endpoint = nw_path_copy_endpoint(path);
+                       if (endpoint != NULL && nw_endpoint_get_type(endpoint) == nw_endpoint_type_address) {
+                               const struct sockaddr *address = nw_endpoint_get_address(endpoint);
+                               if (address != NULL && address->sa_family == AF_INET) {
+                                       nat64_eligible = true;
+                               }
+                       }
+                       nw_forget(&endpoint);
+               }
+
+               return nat64_eligible;
+       }
+}
+
+//======================================================================================================================
+
+static uint32_t
+_mdns_rank_to_bitmask(const unsigned int rank)
+{
+       if ((rank >= 1) && (rank <= 32)) {
+               return (UINT32_C(1) << (rank - 1));
+       } else {
+               return 0;
+       }
+}
+
+//======================================================================================================================
+
+static const char *
+mdns_session_event_to_string(const mdns_session_event_t event)
+{
+       switch (event) {
+               case mdns_session_event_null:                           return "null";
+               case mdns_session_event_ready:                          return "ready";
+               case mdns_session_event_lateness_warning:       return "lateness-warning";
+               case mdns_session_event_terminated:                     return "terminated";
+               default:                                                                        return "<UNKNOWN SESSION EVENT>";
+       }
+}
+
+//======================================================================================================================
+
+static int64_t
+_mdns_ticks_diff(const uint64_t t1, const uint64_t t2)
+{
+       return ((int64_t)(t1 - t2));
+}
+
+//======================================================================================================================
+
+static uint64_t
+_mdns_ticks_to_whole_seconds(const uint64_t ticks)
+{
+       return (ticks / _mdns_ticks_per_second());
+}
+
+//======================================================================================================================
+
+static uint64_t
+_mdns_ticks_to_fractional_milliseconds(const uint64_t ticks)
+{
+       const uint64_t remainder = ticks % _mdns_ticks_per_second();
+       return ((remainder * kMillisecondsPerSecond) / _mdns_ticks_per_second());
+}
+
+//======================================================================================================================
+
+static dispatch_source_t
+_mdns_resolver_create_oneshot_timer(const uint32_t time_ms, const unsigned int leeway_percent_numerator)
+{
+       const dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _mdns_resolver_queue());
+       require_quiet(timer, exit);
+
+       const unsigned int      numerator = Min(leeway_percent_numerator, 100);
+       const uint64_t          leeway_ns = time_ms * (numerator * (UINT64_C_safe(kNanosecondsPerMillisecond) / 100));
+       dispatch_source_set_timer(timer, _dispatch_monotonictime_after_msec(time_ms), DISPATCH_TIME_FOREVER, leeway_ns);
+
+exit:
+       return timer;
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_rcode_is_good(const int rcode)
+{
+       return ((rcode == kDNSRCode_NoError) || (rcode == kDNSRCode_NXDomain) || (rcode == kDNSRCode_NotAuth));
+}
+
+//======================================================================================================================
+
+#if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND || MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+static bool
+_mdns_qtype_is_problematic(const int qtype)
+{
+       return ((qtype == kDNSRecordType_HTTPS) || (qtype == kDNSRecordType_SVCB));
+}
+
+//======================================================================================================================
+
+static mdns_message_t
+_mdns_create_empty_response_for_query(const mdns_query_message_t query, const int rcode, OSStatus * const out_error)
+{
+       mdns_message_t response_msg = NULL;
+       OSStatus err;
+       const size_t len = mdns_message_get_length(query);
+       require_action_quiet(len > kDNSHeaderLength, exit, err = kInternalErr);
+
+       uint8_t *response_ptr = malloc(len);
+       require_action_quiet(response_ptr, exit, err = kNoMemoryErr);
+
+       memcpy(response_ptr, mdns_message_get_byte_ptr(query), len);
+       unsigned int flags = 0;
+       flags |= kDNSHeaderFlag_Response;
+       DNSFlagsSetOpCode(flags, kDNSOpCode_Query);
+       flags |= kDNSHeaderFlag_RecursionDesired;
+       flags |= kDNSHeaderFlag_RecursionAvailable;
+       DNSFlagsSetRCode(flags, rcode);
+       DNSHeaderSetFlags((DNSHeader *)response_ptr, flags);
+
+       dispatch_data_t response_data = dispatch_data_create(response_ptr, len, NULL, DISPATCH_DATA_DESTRUCTOR_FREE);
+       require_action_quiet(response_data, exit, ForgetMem(&response_ptr); err = kNoResourcesErr);
+       response_ptr = NULL;
+
+       response_msg = mdns_message_create_with_dispatch_data(response_data,
+               mdns_message_init_option_disable_header_printing);
+       dispatch_forget(&response_data);
+       require_action_quiet(response_msg, exit, err = kNoResourcesErr);
+       err = kNoErr;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       return response_msg;
+}
+#endif // MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND || MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+
+//======================================================================================================================
+
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+static mdns_query_message_t
+_mdns_create_simple_test_query(const mdns_query_message_t query, const uint16_t qtype)
+{
+       mdns_query_message_t result = NULL;
+       mdns_query_message_t test_query = mdns_query_message_create(mdns_message_init_option_disable_header_printing);
+       require_quiet(test_query, exit);
+
+       OSStatus err = mdns_query_message_set_qname(test_query, mdns_query_message_get_qname(query));
+       require_noerr_quiet(err, exit);
+
+       mdns_query_message_set_qtype(test_query, qtype);
+       mdns_query_message_set_qclass(test_query, mdns_query_message_get_qclass(query));
+       uint16_t msg_id = (uint16_t)RandomRange(1, UINT16_MAX);
+       // Make sure that the test query's ID is different from the original query's ID.
+       if (msg_id == mdns_query_message_get_message_id(query)) {
+               msg_id = ~msg_id;
+               if (msg_id == 0) {
+                       msg_id = 1;
+               }
+       }
+       mdns_query_message_set_message_id(test_query, msg_id);
+       err = mdns_query_message_construct(test_query);
+       require_noerr_quiet(err, exit);
+
+       result = test_query;
+       test_query = NULL;
+
+exit:
+       mdns_forget(&test_query);
+       return result;
+}
+
+//======================================================================================================================
+
+static bool
+_mdns_message_is_adequate_test_query_response(const mdns_message_t msg, const mdns_query_message_t query)
+{
+       return _mdns_message_is_query_response_ex(mdns_message_get_byte_ptr(msg), mdns_message_get_length(msg), query,
+               NULL, NULL, true);
+}
+
+//======================================================================================================================
+
+static pqw_info_t *
+_pqw_info_create(const unsigned int threshold)
+{
+       pqw_info_t *info = (pqw_info_t *)calloc(1, sizeof(*info));
+       require_return_value(info, NULL);
+       info->threshold = threshold;
+       return info;
+}
+
+//======================================================================================================================
+
+static void
+_pqw_info_free(pqw_info_t * const info)
+{
+       _pqw_qname_list_forget(&info->qname_list);
+       free(info);
+}
+
+//======================================================================================================================
+
+static bool
+_pqw_info_threshold_reached(const pqw_info_t * const info)
+{
+       return ((info->qname_count < info->threshold) ? false : true);
+}
+
+//======================================================================================================================
+
+static bool
+_pqw_info_can_accept_qname(const pqw_info_t * const info, const uint8_t * const qname)
+{
+       if (_pqw_info_threshold_reached(info)) {
+               return false;
+       }
+       for (const pqw_qname_item_t *item = info->qname_list; item; item = item->next) {
+               if (DomainNameEqual(item->qname, qname)) {
+                       return false;
+               }
+       }
+       return true;
+}
+
+//======================================================================================================================
+
+static pqw_qname_item_t *
+_pqw_qname_item_create(const uint8_t * const qname, OSStatus * const out_error)
+{
+       pqw_qname_item_t *result = NULL;
+       OSStatus err;
+       pqw_qname_item_t *item = (pqw_qname_item_t *)calloc(1, sizeof(*item));
+       require_action_quiet(item, exit, err = kNoMemoryErr);
+
+       err = DomainNameDup(qname, &item->qname, NULL);
+       require_noerr_quiet(err, exit);
+
+       result = item;
+       item = NULL;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       _pqw_qname_item_forget(&item);
+       return result;
+}
+
+//======================================================================================================================
+
+static void
+_pqw_qname_item_free(pqw_qname_item_t * const item)
+{
+       ForgetMem(&item->qname);
+       free(item);
+}
+
+//======================================================================================================================
+
+static void
+_pqw_qname_list_free(pqw_qname_item_t *list)
+{
+       pqw_qname_item_t *item;
+       while ((item = list) != NULL) {
+               list = item->next;
+               _pqw_qname_item_free(item);
+       }
+}
+#endif // MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+
+//======================================================================================================================
+
+#if MDNS_RESOLVER_MIXED_UP_RESPONSES_WORKAROUND
+static bool
+_mdns_qtype_is_address_type(const int qtype)
+{
+       return ((qtype == kDNSRecordType_A) || (qtype == kDNSRecordType_AAAA));
+}
+#endif
diff --git a/mDNSMacOSX/mdns_objects/mdns_resolver.h b/mDNSMacOSX/mdns_objects/mdns_resolver.h
new file mode 100644 (file)
index 0000000..59dac3a
--- /dev/null
@@ -0,0 +1,979 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_RESOLVER_H__
+#define __MDNS_RESOLVER_H__
+
+#include "mdns_base.h"
+#include "mdns_address.h"
+#include "mdns_object.h"
+
+#include <dispatch/dispatch.h>
+#include <MacTypes.h>
+#include <xpc/xpc.h>
+
+MDNS_DECL(querier);
+MDNS_DECL(resolver);
+
+// Workaround for the new SVCB and HTTPS resource record types, to which some DNS servers react negatively.
+#define MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND     1
+
+OS_CLOSED_ENUM(mdns_resolver_type, int,
+       mdns_resolver_type_null         = 0,
+       /*! @const mdns_resolver_type_normal A resolver that uses normal DNS, i.e., DNS over UDP and TCP. */
+       mdns_resolver_type_normal       = 1,
+       /*! @const mdns_resolver_type_tcp A resolver that uses DNS over TCP. */
+       mdns_resolver_type_tcp          = 2,
+       /*! @const mdns_resolver_type_tls A resolver that uses DNS over TLS. */
+       mdns_resolver_type_tls          = 3,
+       /*! @const mdns_resolver_type_https A resolver that uses DNS over HTTPS. */
+       mdns_resolver_type_https        = 4
+);
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+static inline const char *
+mdns_resolver_type_to_string(mdns_resolver_type_t type)
+{
+       switch (type) {
+               case mdns_resolver_type_null:           return "null";
+               case mdns_resolver_type_normal:         return "normal";
+               case mdns_resolver_type_tcp:            return "tcp";
+               case mdns_resolver_type_tls:            return "tls";
+               case mdns_resolver_type_https:          return "https";
+               default:                                                        return "<INVALID RESOLVER TYPE>";
+       }
+}
+
+__BEGIN_DECLS
+
+/*!
+ *     @brief
+ *             Creates a resolver object, which represents a DNS service.
+ *
+ *     @param type
+ *             The type of resolver to create.
+ *
+ *     @param interface_index
+ *             Index of the interface to use for network traffic to the DNS service.
+ *
+ *     @result
+ *             A new resolver object or NULL if there was a lack of resources.
+ *
+ *     @discussion
+ *             Generally, the servers that implement the DNS service are specified with
+ *             mdns_resolver_add_server_address().
+ *
+ *             However, for resolvers of type mdns_resolver_type_tls and mdns_resolver_type_https, no server addresses
+ *             need to be specified at all so long as a hostname is specified with mdns_resolver_set_provider_name(),
+ *             and, optionally, a port number with mdns_resolver_set_port(). In this case, the system's stub resolver
+ *             will be used to resolve the hostname to IP addresses.
+ */
+MDNS_RETURNS_RETAINED mdns_resolver_t _Nullable
+mdns_resolver_create(mdns_resolver_type_t type, uint32_t interface_index, OSStatus * _Nullable out_error);
+
+/*!
+ *     @brief
+ *             Specifies the queue on which to invoke the resolver's asynchronous handlers.
+ *
+ *     @param resolver
+ *             The resolver.
+ *
+ *     @param queue
+ *             A dispatch queue.
+ *
+ *     @discussion
+ *             Currently, a resolver's only asynchronous handler is its event handler.
+ *
+ *             This function has no effect on a querier that has been activated or invalidated.
+ */
+void
+mdns_resolver_set_queue(mdns_resolver_t resolver, dispatch_queue_t queue);
+
+OS_CLOSED_ENUM(mdns_resolver_event, int,
+       /*! @const mdns_resolver_event_null This value represents the absence of an event (will never be delivered). */
+       mdns_resolver_event_null                = 0,
+       /*! @const mdns_resolver_event_invalidated Indicates that the resolver has been invalidated. */
+       mdns_resolver_event_invalidated = 1,
+       /*! @const mdns_resolver_event_connection Used to report the status of a resolver's connection. */
+       mdns_resolver_event_connection  = 2
+);
+
+static inline const char *
+mdns_resolver_event_to_string(const mdns_resolver_event_t event)
+{
+       switch (event) {
+               case mdns_resolver_event_null:                  return "null";
+               case mdns_resolver_event_invalidated:   return "invalidated";
+               case mdns_resolver_event_connection:    return "connection";
+               default:                                                                return "<invalid event value>";
+       }
+}
+
+#define MDNS_RESOLVER_EVENT_CONNECTION_INFO_KEY_CANNOT_CONNECT "cannot_connect"
+
+/*!
+ *     @brief
+ *             A block for handling an asynchronous resolver event.
+ *
+ *     @param event
+ *             Indicates the event's type.
+ *
+ *     @param info
+ *             A dictionary whose format is specific to the event's type. The information contained in the dictionary
+ *             is relevant to the event currently being handled. If the dictionary is required after the handler has
+ *             finished executing, it should be retained with xpc_retain().
+ */
+typedef void (^mdns_resolver_event_handler_t)(mdns_resolver_event_t event, xpc_object_t _Nullable info);
+
+/*!
+ *     @brief
+ *             Sets a resolver's event handler.
+ *
+ *     @param resolver
+ *             The resolver.
+ *
+ *     @param handler
+ *             The event handler.
+ *
+ *     @discussion
+ *             If invoked, the event handler will be submitted to the dispatch queue specified by
+ *             mdns_resolver_set_queue() for any of the following events:
+ *
+ *             - mdns_resolver_event_invalidated
+ *                     Indicates that the resolver has been completely invalidated. After this event, the event handler
+ *                     will never be invoked again. This event doesn't provide an info dictionary.
+ *
+ *             The event handler will never be invoked prior to a call to either mdns_resolver_activate() or
+ *             mdns_resolver_invalidate().
+ *
+ *             This function has no effect on a resolver that has been activated or invalidated.
+ */
+void
+mdns_resolver_set_event_handler(mdns_resolver_t resolver, mdns_resolver_event_handler_t handler);
+
+/*!
+ *     @brief
+ *             Specifies the "provider name" of the DNS service represented by a resolver.
+ *
+ *     @param resolver
+ *             The resolver.
+ *
+ *     @param provider_name
+ *             The provider name.
+ *
+ *     @result
+ *             kNoErr if the provider name was successfully set. Otherwise, a non-zero error code.
+ *
+ *     @discussion
+ *             The meaning of a provider name depends on the type of resolver.
+ *
+ *             This function is currently only meaningful for resolvers that use DNS over TLS or HTTPS, i.e., resolvers
+ *             of type mdns_resolver_type_tls or mdns_resolver_type_https. For these resolvers, the provider name is
+ *             the hostname used for TLS certificate authentication.
+ *
+ *             If no server addresses are specified with mdns_resolver_add_server_address() and the DNS service uses a
+ *             port number other than the default for its type of service, then use mdns_resolver_set_port() to specify
+ *             that port.
+ *
+ *             This function has no effect on a resolver that has been activated or invalidated.
+ */
+OSStatus
+mdns_resolver_set_provider_name(mdns_resolver_t resolver, const char * _Nullable provider_name);
+
+/*!
+ *     @brief
+ *             Specifies the port number of the DNS service represented by a resolver.
+ *
+ *     @param resolver
+ *             The resolver.
+ *
+ *     @param port
+ *             The port number. A value of 0 means to use the DNS service's default port number.
+ *
+ *     @discussion
+ *             This function is currently only meaningful for resolvers that use DNS over TLS or HTTPS, i.e., resolvers
+ *             of type mdns_resolver_type_tls or mdns_resolver_type_https, and only when a hostname has been specified
+ *             with mdns_resolver_set_provider_name() and no server addresses have been specified with
+ *             mdns_resolver_add_server_address().
+ *
+ *             This function has no effect on a resolver that has been activated or invalidated.
+ */
+void
+mdns_resolver_set_port(mdns_resolver_t resolver, uint16_t port);
+
+/*!
+ *     @brief
+ *             For resolvers that use HTTP, specifies the path part of the DNS service's URL.
+ *
+ *     @param resolver
+ *             The resolver.
+ *
+ *     @param url_path
+ *             The path part of the DNS service's URL.
+ *
+ *     @result
+ *             kNoErr if the URL path was successfully set. Otherwise, a non-zero error code.
+ *
+ *     @discussion
+ *             This function has no effect on a resolver that has been activated or invalidated.
+ *
+ *             This function is currently only meaningful for resolvers that use DNS over HTTPS, i.e., resolvers of type
+ *             mdns_resolver_type_https.
+ */
+OSStatus
+mdns_resolver_set_url_path(mdns_resolver_t resolver, const char * _Nullable url_path);
+
+/*!
+ *     @brief
+ *             Squash CNAME chains for responses.
+ *
+ *     @param resolver
+ *             The resolver.
+ *
+ *     @param squash_cnames
+ *             A boolean to indicate that CNAME chains should be squashed.
+ *
+ *     @discussion
+ *             This function has no effect on a resolver that has been activated or invalidated.
+ */
+void
+mdns_resolver_set_squash_cnames(mdns_resolver_t resolver, bool squash_cnames);
+
+/*!
+ *     @brief
+ *             Determines whether a resolver reports DNS server responsiveness symptoms.
+ *
+ *     @param resolver
+ *             The resolver.
+ *
+ *     @param enable
+ *             If true, the resolver will report DNS server responsiveness symptoms. If false, it will not.
+ *
+ *     @discussion
+ *             Symptom reporting is disabled by default.
+ *
+ *             This function has no effect on a resolver that has been activated or invalidated.
+ */
+void
+mdns_resolver_enable_symptom_reporting(mdns_resolver_t resolver, bool enable);
+
+/*!
+ *     @brief
+ *             Specifies the IP address and port pair of one of the servers that implement the DNS service represented
+ *             by a resolver.
+ *
+ *     @param resolver
+ *             The resolver.
+ *
+ *     @param address
+ *             The server's IP address and port pair. If the port number is 0, then the DNS service's default port
+ *             number will be used.
+ *
+ *     @result
+ *             kNoErr on success. Otherwise, a non-zero error code.
+ *
+ *     @discussion
+ *             This function has no effect on a resolver that has been activated or invalidated.
+ */
+OSStatus
+mdns_resolver_add_server_address(mdns_resolver_t resolver, mdns_address_t address);
+
+/*!
+ *     @brief
+ *             Specifies the initial datagram retransmission interval in seconds.
+ *
+ *     @param resolver
+ *             The resolver.
+ *
+ *     @param interval_secs
+ *             The interval in seconds.
+ *
+ *     @discussion
+ *             This function has no effect on a resolver that has been activated or invalidated.
+ */
+void
+mdns_resolver_set_initial_datagram_retransmission_interval(mdns_resolver_t resolver, uint32_t interval_secs);
+
+/*!
+ *     @brief
+ *             Determines whether a resolver makes an effort to reuse existing connections for queries that need to be
+ *             sent over a connection.
+ *
+ *     @param resolver
+ *             The resolver.
+ *
+ *     @param disable
+ *             If true, disables connection reuse. If false, enables connection reuse.
+ *
+ *     @discussion
+ *             For efficiency, connection reuse is enabled by default. If a query needs to be sent to a server via a
+ *             connection (as opposed to via a datagram) and a connection to the server already exists, then that
+ *             connection will be reused instead of establishing a new connection.
+ *
+ *             If connection reuse is disabled, then each query that needs to be sent over a connection will use its
+ *             own connection.
+ *
+ *             This function has no effect on a resolver that has been activated or invalidated.
+ */
+void
+mdns_resolver_disable_connection_reuse(mdns_resolver_t resolver, bool disable);
+
+#if MDNS_RESOLVER_PROBLEMATIC_QTYPE_WORKAROUND
+/*!
+ *     @brief
+ *             Enables or disables a workaround where a resolver's queriers will refrain from sending queries of type
+ *             SVCB and HTTPS to a server if the server has been determined to not respond to queries of those types.
+ *
+ *     @param resolver
+ *             The resolver.
+ *
+ *     @param threshold
+ *             If greater than zero, the workaround is enabled. Otherwise, the workaround is disabled.
+ *
+ *     @discussion
+ *             This is a workaround for DNS servers that don't respond to SVCB and HTTPS queries and then become less
+ *             responsive to queries of other types as more SVCB and HTTPS retry queries are sent.
+ *
+ *             The workaround is disabled by default.
+ *
+ *             This function has no effect on a resolver after it has been activated or invalidated.
+ */
+void
+mdns_resolver_enable_problematic_qtype_workaround(mdns_resolver_t resolver, int threshold);
+#endif
+
+/*!
+ *     @brief
+ *             Activates a resolver.
+ *
+ *     @param resolver
+ *             The resolver.
+ *
+ *     @discussion
+ *             Activation makes the resolver usable to its queriers.
+ *
+ *             This function has no effect on a resolver that has already been activated or one that has been invalidated.
+ */
+void
+mdns_resolver_activate(mdns_resolver_t resolver);
+
+/*!
+ *     @brief
+ *             Invalidates a resolver.
+ *
+ *     @param resolver
+ *             The resolver.
+ *
+ *     @discussion
+ *             This function should be called when the resolver is no longer needed. When called, all outstanding
+ *             queriers that were created by this resolver will asynchronously conclude with a kEndingErr error, unless
+ *             a response or some other error is pending.
+ *
+ *             If a queue was specified with mdns_resolver_set_queue() and an event handler was specified with
+ *             mdns_resolver_set_event_handler(), then an mdns_resolver_event_invalidated event will be asynchronously
+ *             delivered to the event handler to indicate when the invalidation is complete.
+ *
+ *             This function has no effect on a resolver that has already been invalidated.
+ */
+void
+mdns_resolver_invalidate(mdns_resolver_t resolver);
+
+/*!
+ *     @brief
+ *             Determines if resolvers of a given type use encryption.
+ *
+ *     @param type
+ *             The resolver type.
+ *
+ *     @result
+ *             Returns true if the resolvers of the specified type use encryption. Otherwise, returns false.
+ */
+bool
+mdns_resolver_type_uses_encryption(mdns_resolver_type_t type);
+
+/*!
+ *     @brief
+ *             Creates a querier to issue queries to the DNS service represented by the resolver.
+ *
+ *     @param resolver
+ *             The resolver.
+ *
+ *     @discussion
+ *             A querier issues one or more queries to the DNS service represented by a resolver for a particular QNAME,
+ *             QTYPE, and QCLASS triple until is gets a response.
+ */
+MDNS_RETURNS_RETAINED mdns_querier_t _Nullable
+mdns_resolver_create_querier(mdns_resolver_t resolver, OSStatus * _Nullable out_error);
+
+/*!
+ *     @brief
+ *             Specifies the queue on which to invoke the querier's result handler.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @param queue
+ *             The queue.
+ *
+ *     @discussion
+ *             This function must be called before activating the querier.
+ *
+ *             This function has no effect on a querier that has been activated or invalidated.
+ */
+void
+mdns_querier_set_queue(mdns_querier_t querier, dispatch_queue_t queue);
+
+/*!
+ *     @brief
+ *             Defines the query, i.e., the name, type, and class of the resource record(s) to query for.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @param qname
+ *             The name of the resource record(s) to query for as a sequence of domain name labels, i.e., the querier's
+ *             DNS query's QNAME value.
+ *
+ *     @param qtype
+ *             The type of the resource record(s) to query for, i.e., the querier's DNS query's QTYPE value.
+ *
+ *     @param qclass
+ *             The class of the resource record(s) to query for, i.e., the querier's DNS query's QCLASS value.
+ *
+ *     @discussion
+ *             This function must be called before activating the querier.
+ *
+ *             This function has no effect on a querier that has been activated or invalidated.
+ */
+OSStatus
+mdns_querier_set_query(mdns_querier_t querier, const uint8_t *qname, uint16_t qtype, uint16_t qclass);
+
+/*!
+ *     @brief
+ *             Determines whether a querier's queries will include an OPT record in the additional section with the
+ *             "DNSSEC OK" (DO) bit set.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @param dnssec_ok
+ *             If true, a querier's queries will include an OPT record in the additional section with the DO bit set.
+ *
+ *             If false and the querier's queries do include an OPT record in the additional section, then the DO bit
+ *             will be cleared.
+ *
+ *     @discussion
+ *             From section 3 of RFC3225 <https://tools.ietf.org/html/rfc3225#section-3>:
+ *
+ *                     Setting the DO bit to one in a query indicates to the server that the
+ *                     resolver is able to accept DNSSEC security RRs.  The DO bit cleared
+ *                     (set to zero) indicates the resolver is unprepared to handle DNSSEC
+ *                     security RRs and those RRs MUST NOT be returned in the response
+ *                     (unless DNSSEC security RRs are explicitly queried for).
+ *
+ *             If an OPT record is included in a query, the default behavior is to clear the DO bit.
+ *
+ *             This function has no effect on a querier that has been activated or invalidated.
+ */
+void
+mdns_querier_set_dnssec_ok(mdns_querier_t querier, bool dnssec_ok);
+
+/*!
+ *     @brief
+ *             Determines whether a querier's queries will have the Checking Disabled (CD) bit set.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @param checking_disabled
+ *             If true, a querier's queries will have the CD bit set.
+ *
+ *             If false, a querier's queries will have the CD bit cleared.
+ *
+ *     @discussion
+ *             From section 3.2.2 of RFC4035 <https://tools.ietf.org/html/rfc4035#section-3.2.2>:
+ *
+ *                     The CD bit exists in order to allow a security-aware resolver to
+ *                     disable signature validation in a security-aware name server's
+ *                     processing of a particular query.
+ *
+ *             The default behavior is to clear the CD bit.
+ *
+ *             This function has no effect on a querier that has been activated or invalidated.
+ */
+void
+mdns_querier_set_checking_disabled(mdns_querier_t querier, bool checking_disabled);
+
+/*!
+ *     @brief
+ *             Sets the querier's delegator by its process identifier (PID).
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @param pid
+ *             The delegator's PID.
+ *
+ *     @discussion
+ *             This function marks the querier's datagram network traffic as belonging to the delegator.
+ *
+ *             If this functionality is needed, this function must be called before activating the querier.
+ *
+ *             This function has no effect on a querier that has been activated or invalidated.
+ */
+void
+mdns_querier_set_delegator_pid(mdns_querier_t querier, pid_t pid);
+
+/*!
+ *     @brief
+ *             Sets the querier's delegator by its universally unique identifier (UUID).
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @param uuid
+ *             The delegator's UUID.
+ *
+ *     @discussion
+ *             This function marks the querier's datagram network traffic as belonging to the delegator.
+ *
+ *             If this functionality is needed, this function must be called before activating the querier.
+ *
+ *             This function has no effect on a querier that has been activated or invalidated.
+ */
+void
+mdns_querier_set_delegator_uuid(mdns_querier_t querier, uuid_t _Nonnull uuid);
+
+/*!
+ *     @brief
+ *             Sets the querier's logging label.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @param format
+ *             The printf-style format string for the label.
+ *
+ *     @discussion
+ *             Log messages associated with this querier will be prefixed with the specified label.
+ *
+ *             If this functionality is needed, this function must be called before activating the querier.
+ *
+ *             This function has no effect on a querier that has been activated or invalidated.
+ */
+OSStatus
+mdns_querier_set_log_label(mdns_querier_t querier, const char *format, ...) MDNS_PRINTF_FORMAT(2, 3);
+
+/*!
+ *     @typedef mdns_querier_result_type_t
+ *
+ *     @brief 
+ *             Inidicates the type of result with which a querier concluded.
+ *
+ *     @const mdns_querier_result_type_null
+ *             Used as a placeholder value to indicate no result. A querier will never conclude with this result type.
+ *
+ *     @const mdns_querier_result_type_response
+ *             Indicates that a querier concluded with a response to its query.
+ *
+ *     @const mdns_querier_result_type_timeout
+ *             Indicates that a querier concluded because it reached its current time limit before getting a response.
+ *
+ *     @const mdns_querier_result_type_invalidation
+ *             Indicates that a querier concluded because it was invalidated before getting a response.
+ *
+ *     @const mdns_querier_result_type_resolver_invalidation
+ *             Indicates that a querier concluded because its resolver was invalidated before getting a response.
+ *
+ *     @const mdns_querier_result_type_error
+ *             Indicates that a querier concluded because it ran into a fatal error before getting a response.
+ */
+OS_CLOSED_ENUM(mdns_querier_result_type, int,
+       mdns_querier_result_type_null                                   = 0,
+       mdns_querier_result_type_response                               = 1,
+       mdns_querier_result_type_timeout                                = 2,
+       mdns_querier_result_type_invalidation                   = 3,
+       mdns_querier_result_type_resolver_invalidation  = 4,
+       mdns_querier_result_type_error                                  = 5
+);
+
+static inline const char *
+mdns_querier_result_type_to_string(const mdns_querier_result_type_t type)
+{
+       switch (type) {
+               case mdns_querier_result_type_null:                                             return "null";
+               case mdns_querier_result_type_response:                                 return "response";
+               case mdns_querier_result_type_timeout:                                  return "timeout";
+               case mdns_querier_result_type_invalidation:                             return "invalidation";
+               case mdns_querier_result_type_resolver_invalidation:    return "resolver-invalidation";
+               case mdns_querier_result_type_error:                                    return "error";
+       }
+       return "<INVALID RESOLVER TYPE>";
+}
+typedef void
+(^mdns_querier_result_handler_t)(void);
+
+/*!
+ *     @brief
+ *             Sets a querier's result handler.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @param handler
+ *             The result handler.
+ *
+ *     @discussion
+ *             The result handler will be invoked on the dispatch queue specified by
+ *             mdns_querier_set_queue() and will never be invoked more than once.
+ *
+ *             A querier's result type can be determined with mdns_querier_get_result_type().
+ *
+ *             This function has no effect on a querier that has been activated or invalidated.
+ */
+void
+mdns_querier_set_result_handler(mdns_querier_t querier, mdns_querier_result_handler_t handler);
+
+/*!
+ *     @brief
+ *             Imposes a time limit on the time that a querier spends waiting for a response to its query.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @param time_limit_ms
+ *             The time limit in milliseconds.
+ *
+ *     @discussion
+ *             This function can be called more than once to reset the current time limit.
+ *
+ *             If this function is called before activation, then the time limit doesn't apply until activation.
+ *
+ *             This function has no effect on a querier that has concluded.
+ */
+void
+mdns_querier_set_time_limit_ms(mdns_querier_t querier, int32_t time_limit_ms);
+
+/*!
+ *     @brief
+ *             Sets a user-defined identifier on a querier.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @param user_id
+ *             The identifier.
+ *
+ *     @discussion
+ *             This function is a convenience function meant to help users in situations where a querier is associated
+ *             with a numerical value. This function has no effect on a querier's operational behavior.
+ *
+ *             The default identifier is 0.
+ *
+ *             This function has no effect on a querier that has been activated.
+ */
+void
+mdns_querier_set_user_id(mdns_querier_t querier, uint32_t user_id);
+
+/*!
+ *     @brief
+ *             Activates a querier.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @discussion
+ *             This function has no effect on a querier that has already been activated or one that has been invalidated.
+ */
+void
+mdns_querier_activate(mdns_querier_t querier);
+
+/*!
+ *     @brief
+ *             Invalidates a querier.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @discussion
+ *             This function should be called when the querier is no longer needed.
+ *
+ *             This function has no effect on a querier that has already been invalidated.
+ */
+void
+mdns_querier_invalidate(mdns_querier_t querier);
+
+/*!
+ *     @brief
+ *             Gets the QNAME value that a querier uses for its DNS queries.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @result
+ *             If at least one successful call was made to mdns_querier_set_query(), then this function returns a
+ *             non-NULL pointer to a domain name as a sequence of labels that's equal in value to the qname argument
+ *             used for the last successful call to mdns_querier_set_query(). Otherwise, NULL.
+ *
+ *             If the result is a non-NULL pointer, then the pointer is guaranteed to be valid until the next
+ *             successful call to mdns_querier_set_query() or until all references to the querier have been released,
+ *             whichever comes first.
+ */
+const uint8_t * _Nullable
+mdns_querier_get_qname(mdns_querier_t querier);
+
+/*!
+ *     @brief
+ *             Gets the QTYPE value that a querier uses for its DNS queries.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @result
+ *             If at least one successful call was made to mdns_querier_set_query(), then this function returns a value
+ *             equal to the qtype argument used for the last successful call to mdns_querier_set_query(). Otherwise, 0.
+ */
+uint16_t
+mdns_querier_get_qtype(mdns_querier_t querier);
+
+/*!
+ *     @brief
+ *             Gets the QCLASS value that a querier uses for its DNS queries.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @result
+ *             If at least one successful call was made to mdns_querier_set_query(), then this function returns a value
+ *             equal to the qclass argument used for the last successful call to mdns_querier_set_query(). Otherwise,
+ *             0.
+ */
+uint16_t
+mdns_querier_get_qclass(mdns_querier_t querier);
+
+/*!
+ *     @brief
+ *             Gets a querier's resolver type.
+ *
+ *     @param querier
+ *             The querier.
+ */
+mdns_resolver_type_t
+mdns_querier_get_resolver_type(mdns_querier_t querier);
+
+/*!
+ *     @brief
+ *             Returns the type of result with which a querier concluded.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @discussion
+ *             This function should only be called from the result handler or after the result handler has been
+ *             invoked.
+ */
+mdns_querier_result_type_t
+mdns_querier_get_result_type(mdns_querier_t querier);
+
+/*!
+ *     @brief
+ *             Gets the current number of queries that a querier has sent.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @discussion
+ *             It's safe to call this function before a querier has concluded.
+ */
+uint32_t
+mdns_querier_get_send_count(mdns_querier_t querier);
+
+/*!
+ *     @brief
+ *             Gets the byte-length of a querier's DNS query message.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @discussion
+ *             This function should only be called from the result handler or after the result handler has been
+ *             invoked.
+ */
+uint32_t
+mdns_querier_get_query_length(mdns_querier_t querier);
+
+/*!
+ *     @brief
+ *             Returns a pointer to the first bye of a querier's DNS response message in wire format.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @discussion
+ *             This function should only be called from the result handler or after the result handler has been
+ *             invoked. The pointer returned is only meaningful if the querier's result type is
+ *             mdns_querier_result_type_response.
+ */
+const uint8_t * _Nullable
+mdns_querier_get_response_ptr(mdns_querier_t querier);
+
+/*!
+ *     @brief
+ *             Gets the byte-length of a querier's DNS response message.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @discussion
+ *             This function should only be called from the result handler or after the result handler has been
+ *             invoked. The length returned is only meaningful if the querier's result type is
+ *             mdns_querier_result_type_response.
+ */
+uint32_t
+mdns_querier_get_response_length(mdns_querier_t querier);
+
+/*!
+ *     @brief
+ *             Returns whether or not a querier's response is fabricated.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @result
+ *             Returns true if the response is fabricated. Otherwise, false.
+ *
+ *     @discussion
+ *             This function should only be called from the result handler or after the result handler has been
+ *             invoked. The value returned is only meaningful if the querier's result type is
+ *             mdns_querier_result_type_response.
+ */
+bool
+mdns_querier_response_is_fabricated(mdns_querier_t querier);
+
+/*!
+ *     @brief
+ *             Returns the fatal error encountered by a querier.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @discussion
+ *             This function should only be called from the result handler or after the result handler has been
+ *             invoked. The error returned is only meaningful if the querier's result type is
+ *             mdns_querier_result_type_error.
+ */
+OSStatus
+mdns_querier_get_error(mdns_querier_t querier);
+
+/*!
+ *     @brief
+ *             Gets the DNSSEC OK bit of the querier.
+ *
+ *     @param querier
+ *             The querier.
+ */
+bool
+mdns_querier_get_dnssec_ok(mdns_querier_t querier);
+
+OS_CLOSED_ENUM(mdns_query_over_tcp_reason, int,
+       mdns_query_over_tcp_reason_null                                 = 0,
+       mdns_query_over_tcp_reason_truncation                   = 1,
+       mdns_query_over_tcp_reason_got_suspicious_reply = 2,
+       mdns_query_over_tcp_reason_in_suspicious_mode   = 3
+);
+
+/*!
+ *     @brief
+ *             If a querier uses a DNS53 resolver and at some point switched over to TCP to send its query, then this
+ *             function returns the reason for doing so.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @result
+ *             If the querier didn't use a DNS53 resolver or used UDP exclusively, then the return value is
+ *             mdns_query_over_tcp_reason_null. Otherwise, one of the other mdns_query_over_tcp_reason_t enum values.
+ *
+ *     @discussion
+ *             This function should only be called from the result handler or after the result handler has been
+ *             invoked.
+ */
+mdns_query_over_tcp_reason_t
+mdns_querier_get_over_tcp_reason(mdns_querier_t querier);
+
+/*!
+ *     @brief
+ *             Determines whether a querier has been configured to query for a DNS resource record of a given name,
+ *             type, and class.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @param qname
+ *             A domain name as a sequence of domain labels.
+ *
+ *     @param qtype
+ *             A DNS resource record type.
+ *
+ *     @param qclass
+ *             A DNS resource record class.
+ *
+ *     @result
+ *             Returns true if the querier was configured to query for a DNS resource record of the given name, type,
+ *             and class. Otherwise, false.
+ */
+bool
+mdns_querier_match(mdns_querier_t querier, const uint8_t *qname, int qtype, int qclass);
+
+/*!
+ *     @brief
+ *             Determines whether or not a querier has concluded.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @result
+ *             Returns true if the querier has concluded. Otherwise, false.
+ *
+ *     @discussion
+ *             Before the result handler specified by mdns_querier_set_result_handler() has been invoked, this
+ *             function should only be called from the dispatch queue specified by mdns_querier_set_queue().
+ */
+bool
+mdns_querier_has_concluded(mdns_querier_t querier);
+
+/*!
+ *     @brief
+ *             Returns a querier's user-defined ID.
+ *
+ *     @param querier
+ *             The querier.
+ *
+ *     @discussion
+ *             The value returned is the last value set with mdns_querier_set_user_id() before activation.
+ */
+uint32_t
+mdns_querier_get_user_id(mdns_querier_t querier);
+
+__END_DECLS
+
+#define mdns_querier_forget(X) mdns_forget_with_invalidation(X, querier)
+#define mdns_resolver_forget(X)        mdns_forget_with_invalidation(X, resolver)
+
+MDNS_ASSUME_NONNULL_END
+
+#endif // __MDNS_RESOLVER_H__
diff --git a/mDNSMacOSX/mdns_objects/mdns_set.c b/mDNSMacOSX/mdns_objects/mdns_set.c
new file mode 100644 (file)
index 0000000..bd427a1
--- /dev/null
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mdns_set.h"
+#include "mdns_helpers.h"
+#include "mdns_objects.h"
+
+#include <CoreUtils/CoreUtils.h>
+
+//======================================================================================================================
+// MARK: - Set Kind Definition
+
+typedef struct _subset_s * _subset_t;
+
+struct mdns_set_s {
+       struct mdns_object_s    base;   // Object base.
+       _subset_t                               list;   // Subset list.
+};
+
+MDNS_OBJECT_SUBKIND_DEFINE(set);
+
+//======================================================================================================================
+// MARK: - Internal Data Structures
+
+typedef struct _item_s * _item_t;
+struct _item_s {
+       _item_t                 next;   // Next item in list.
+       mdns_object_t   object; // Object.
+};
+
+struct _subset_s {
+       _subset_t       next;   // Next subset in list.
+       uintptr_t       ident;  // Subset ID.
+       _item_t         list;   // Querier list.
+       size_t          count;  // Item count.
+};
+
+//======================================================================================================================
+// MARK: - Internal Helper Function Prototypes
+
+static _subset_t
+_subset_create(uintptr_t subset_id);
+
+static void
+_subset_free(_subset_t subset);
+#define _subset_forget(X) ForgetCustom(X, _subset_free)
+
+static _item_t
+_item_create(mdns_object_t object);
+
+static void
+_item_free(_item_t item);
+#define _item_forget(X) ForgetCustom(X, _item_free)
+
+//======================================================================================================================
+// MARK: - Set Public Methods
+
+mdns_set_t
+mdns_set_create(void)
+{
+       return _mdns_set_alloc();
+}
+
+//======================================================================================================================
+
+OSStatus
+mdns_set_add(const mdns_set_t me, const uintptr_t subset_id, const mdns_object_t object)
+{
+       _subset_t *subset_ptr;
+       _subset_t subset;
+       for (subset_ptr = &me->list; (subset = *subset_ptr) != NULL; subset_ptr = &subset->next) {
+               if (subset->ident == subset_id) {
+                       break;
+               }
+       }
+       OSStatus err;
+       _subset_t new_subset = NULL;
+       if (!subset) {
+               new_subset = _subset_create(subset_id);
+               require_action_quiet(new_subset, exit, err = kNoMemoryErr);
+               subset = new_subset;
+       }
+       _item_t *item_ptr;
+       _item_t item;
+       for (item_ptr = &subset->list; (item = *item_ptr) != NULL; item_ptr = &item->next) {
+               if (item->object == object) {
+                       break;
+               }
+       }
+       require_action_quiet(!item, exit, err = kNoErr);
+
+       item = _item_create(object);
+       require_action_quiet(item, exit, err = kNoMemoryErr);
+
+       *item_ptr = item;
+       item = NULL;
+       ++subset->count;
+       if (new_subset) {
+               *subset_ptr = new_subset;
+               new_subset = NULL;
+       }
+       err = kNoErr;
+
+exit:
+       _subset_forget(&new_subset);
+       return err;
+}
+
+//======================================================================================================================
+
+OSStatus
+mdns_set_remove(const mdns_set_t me, const uintptr_t subset_id, const mdns_object_t object)
+{
+       _subset_t *subset_ptr;
+       _subset_t subset;
+       for (subset_ptr = &me->list; (subset = *subset_ptr) != NULL; subset_ptr = &subset->next) {
+               if (subset->ident == subset_id) {
+                       break;
+               }
+       }
+       OSStatus err;
+       require_action_quiet(subset, exit, err = kNotFoundErr);
+
+       _item_t *item_ptr;
+       _item_t item;
+       for (item_ptr = &subset->list; (item = *item_ptr) != NULL; item_ptr = &item->next) {
+               if (item->object == object) {
+                       break;
+               }
+       }
+       require_action_quiet(item, exit, err = kNotFoundErr);
+
+       *item_ptr = item->next;
+       --subset->count;
+       _item_forget(&item);
+       if (!subset->list) {
+               *subset_ptr = subset->next;
+               _subset_forget(&subset);
+       }
+       err = kNoErr;
+
+exit:
+       return err;
+}
+
+//======================================================================================================================
+
+size_t
+mdns_set_get_count(const mdns_set_t me, const uintptr_t subset_id)
+{
+       _subset_t subset;
+       for (subset = me->list; subset; subset = subset->next) {
+               if (subset->ident == subset_id) {
+                       return subset->count;
+               }
+       }
+       return 0;
+}
+
+//======================================================================================================================
+
+void
+mdns_set_iterate(const mdns_set_t me, const uintptr_t subset_id, mdns_set_applier_t applier)
+{
+       _subset_t subset = me->list;
+       while (subset && (subset->ident != subset_id)) {
+               subset = subset->next;
+       }
+       require_quiet(subset, exit);
+
+       for (_item_t item = subset->list; item; item = item->next) {
+               const bool stop = applier(item->object);
+               if (stop) {
+                       break;
+               }
+       }
+
+exit:
+       return;
+}
+
+//======================================================================================================================
+// MARK: - Set Private Methods
+
+static char *
+_mdns_set_copy_description(const mdns_set_t me, __unused const bool debug, __unused const bool privacy)
+{
+       char *                          description     = NULL;
+       char                            buffer[128];
+       char *                          dst                     = buffer;
+       const char * const      lim                     = &buffer[countof(buffer)];
+       int                                     n;
+
+       *dst = '\0';
+       n = mdns_snprintf_add(&dst, lim, "<%s: %p>: ", me->base.kind->name, me);
+       require_quiet(n >= 0, exit);
+
+       description = strdup(buffer);
+
+exit:
+       return description;
+}
+//======================================================================================================================
+
+static void
+_mdns_set_finalize(const mdns_set_t me)
+{
+       _subset_t subset;
+       while ((subset = me->list) != NULL) {
+               me->list = subset->next;
+               _subset_free(subset);
+       }
+}
+
+//======================================================================================================================
+// MARK: - Internal Helper Functions
+
+static _subset_t
+_subset_create(const uintptr_t subset_id)
+{
+       const _subset_t obj = (_subset_t)calloc(1, sizeof(*obj));
+       require_quiet(obj, exit);
+
+       obj->ident = subset_id;
+
+exit:
+       return obj;
+}
+
+//======================================================================================================================
+
+static void
+_subset_free(const _subset_t me)
+{
+       me->next = NULL;
+       _item_t item;
+       while ((item = me->list) != NULL) {
+               me->list = item->next;
+               _item_free(item);
+       }
+       free(me);
+}
+
+//======================================================================================================================
+
+static _item_t
+_item_create(mdns_object_t object)
+{
+       const _item_t obj = (_item_t)calloc(1, sizeof(*obj));
+       require_quiet(obj, exit);
+
+       obj->object = object;
+       mdns_retain(obj->object);
+
+exit:
+       return obj;
+}
+
+//======================================================================================================================
+
+static void
+_item_free(const _item_t me)
+{
+       me->next = NULL;
+       mdns_forget(&me->object);
+       free(me);
+}
diff --git a/mDNSMacOSX/mdns_objects/mdns_set.h b/mDNSMacOSX/mdns_objects/mdns_set.h
new file mode 100644 (file)
index 0000000..d5f97a2
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_SET_H__
+#define __MDNS_SET_H__
+
+#include "mdns_base.h"
+#include "mdns_object.h"
+
+#include <MacTypes.h>
+
+MDNS_DECL(set);
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
+
+MDNS_RETURNS_RETAINED MDNS_WARN_RESULT _Nullable
+mdns_set_t
+mdns_set_create(void);
+
+OSStatus
+mdns_set_add(mdns_set_t set, uintptr_t subset_id, mdns_any_t any);
+
+OSStatus
+mdns_set_remove(mdns_set_t set, uintptr_t subset_id, mdns_any_t any);
+
+size_t
+mdns_set_get_count(mdns_set_t set, uintptr_t subset_id);
+
+typedef bool (^mdns_set_applier_t)(mdns_object_t object);
+
+void
+mdns_set_iterate(mdns_set_t set, uintptr_t subset_id, mdns_set_applier_t applier);
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#endif // __MDNS_SET_H__
diff --git a/mDNSMacOSX/mdns_objects/mdns_symptoms.c b/mDNSMacOSX/mdns_objects/mdns_symptoms.c
new file mode 100644 (file)
index 0000000..08e9730
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2019 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mdns_symptoms.h"
+
+#include <CoreUtils/CoreUtils.h>
+#include <SymptomReporter/SymptomReporter.h>
+
+#define MAX_DOMAIN_NAME 256
+
+#define SYMPTOM_REPORTER_mDNSResponder_NUMERIC_ID      101
+#define SYMPTOM_REPORTER_mDNSResponder_TEXT_ID         "com.apple.mDNSResponder"
+
+#define SYMPTOM_DNS_NO_REPLIES                                         0x00065001
+#define SYMPTOM_DNS_RESUMED_RESPONDING                         0x00065002
+#define SYMPTOM_DNS_ENCRYPTED_CONNECTION_FAILURE       0x00065004
+
+//======================================================================================================================
+// MARK: - Soft Linking
+
+SOFT_LINK_FRAMEWORK_EX(PrivateFrameworks, SymptomReporter);
+SOFT_LINK_FUNCTION_EX(SymptomReporter, symptom_framework_init,
+       symptom_framework_t,
+       (symptom_ident_t id, const char *originator_string),
+       (id, originator_string));
+SOFT_LINK_FUNCTION_EX(SymptomReporter, symptom_new,
+       symptom_t,
+       (symptom_framework_t framework, symptom_ident_t id),
+       (framework, id));
+SOFT_LINK_FUNCTION_EX(SymptomReporter, symptom_set_additional_qualifier,
+       int,
+       (symptom_t symptom, uint32_t qualifier_type, size_t qualifier_len, const void *qualifier_data),
+       (symptom, qualifier_type, qualifier_len, qualifier_data));
+SOFT_LINK_FUNCTION_EX(SymptomReporter, symptom_send,
+       int,
+       (symptom_t symptom),
+       (symptom));
+
+//======================================================================================================================
+// MARK: - Local Helper Prototypes
+
+static symptom_framework_t
+_mdns_symptoms_get_reporter(void);
+
+static void
+_mdns_symptoms_report_dns_server_symptom(symptom_ident_t id, const struct sockaddr *address);
+
+static void
+_mdns_symptoms_report_dns_host_symptom(symptom_ident_t id, const char *host);
+
+//======================================================================================================================
+// MARK - External Functions
+
+void
+mdns_symptoms_report_unresponsive_server(const struct sockaddr *address)
+{
+       _mdns_symptoms_report_dns_server_symptom(SYMPTOM_DNS_NO_REPLIES, address);
+}
+
+//======================================================================================================================
+
+void
+mdns_symptoms_report_encrypted_dns_connection_failure(const char *host)
+{
+       _mdns_symptoms_report_dns_host_symptom(SYMPTOM_DNS_ENCRYPTED_CONNECTION_FAILURE, host);
+}
+
+//======================================================================================================================
+
+void
+mdns_symptoms_report_responsive_server(const struct sockaddr *address)
+{
+       _mdns_symptoms_report_dns_server_symptom(SYMPTOM_DNS_RESUMED_RESPONDING, address);
+}
+
+//======================================================================================================================
+// MARK - Local Helpers
+
+static symptom_framework_t
+_mdns_symptoms_get_reporter(void)
+{
+       static dispatch_once_t          s_once          = 0;
+       static symptom_framework_t      s_reporter      = NULL;
+
+       dispatch_once(&s_once,
+       ^{
+               if (SOFT_LINK_HAS_FUNCTION(SymptomReporter, symptom_framework_init)) {
+                       s_reporter = soft_symptom_framework_init(SYMPTOM_REPORTER_mDNSResponder_NUMERIC_ID,
+                               SYMPTOM_REPORTER_mDNSResponder_TEXT_ID);
+               }
+       });
+       return s_reporter;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_symptoms_report_dns_server_symptom(symptom_ident_t id, const struct sockaddr *address)
+{
+       const symptom_framework_t reporter = _mdns_symptoms_get_reporter();
+       require_quiet(reporter, exit);
+
+       size_t address_len;
+       if (address->sa_family == AF_INET) {
+               address_len = sizeof(struct sockaddr_in);
+       } else if (address->sa_family == AF_INET6) {
+               address_len = sizeof(struct sockaddr_in6);
+       } else {
+               goto exit;
+       }
+       const symptom_t symptom = soft_symptom_new(reporter, id);
+       soft_symptom_set_additional_qualifier(symptom, 1, address_len, address);
+       soft_symptom_send(symptom);
+
+exit:
+       return;
+}
+
+//======================================================================================================================
+
+static void
+_mdns_symptoms_report_dns_host_symptom(symptom_ident_t id, const char *host)
+{
+       const symptom_framework_t reporter = _mdns_symptoms_get_reporter();
+       require_quiet(reporter, exit);
+
+       size_t hostname_len = strnlen(host, MAX_DOMAIN_NAME);
+       const symptom_t symptom = soft_symptom_new(reporter, id);
+       soft_symptom_set_additional_qualifier(symptom, 2, hostname_len, host);
+       soft_symptom_send(symptom);
+
+exit:
+       return;
+}
diff --git a/mDNSMacOSX/mdns_objects/mdns_symptoms.h b/mDNSMacOSX/mdns_objects/mdns_symptoms.h
new file mode 100644 (file)
index 0000000..562a062
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2019 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_SYMPTOMS__
+#define __MDNS_SYMPTOMS__
+
+#include "mdns_base.h"
+
+#include <MacTypes.h>
+#include <mach/mach.h> // audit_token_t
+#include <sys/socket.h>
+
+// Activity domains and labels for metrics collection.
+// These are defined in "activity_registry.h" from libnetcore.
+#define kDNSActivityDomain 33
+#define kDNSActivityLabelUnicastAQuery 1
+#define kDNSActivityLabelUnicastAAAAQuery 2
+#define kDNSActivityLabelProvisioningRequest 3
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+void
+mdns_symptoms_report_unresponsive_server(const struct sockaddr *address);
+
+void
+mdns_symptoms_report_responsive_server(const struct sockaddr *address);
+
+void
+mdns_symptoms_report_encrypted_dns_connection_failure(const char *host);
+
+MDNS_ASSUME_NONNULL_END
+
+#endif // __MDNS_SYMPTOMS__
diff --git a/mDNSMacOSX/mdns_objects/mdns_tlv.c b/mDNSMacOSX/mdns_objects/mdns_tlv.c
new file mode 100644 (file)
index 0000000..49c1aba
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mdns_tlv.h"
+
+#include <CoreUtils/CoreUtils.h>
+
+//======================================================================================================================
+// MARK: - Type-Length Headers
+
+typedef struct
+{
+       uint8_t type[2];
+       uint8_t length[2];
+} mdns_tl16_t;
+
+check_compile_time(sizeof(mdns_tl16_t) == 4);
+
+//======================================================================================================================
+// MARK: - Local Prototypes
+
+static uint16_t
+_mdns_tl16_get_type(const mdns_tl16_t *tl);
+
+static uint16_t
+_mdns_tl16_get_length(const mdns_tl16_t *tl);
+
+static void
+_mdns_tl16_init(mdns_tl16_t *tl, uint16_t type, uint16_t length);
+
+//======================================================================================================================
+// MARK: - Public Functions
+
+OSStatus
+mdns_tlv16_get_value(const uint8_t * const start, const uint8_t * const end, const uint16_t type,
+       size_t * const out_length, const uint8_t ** const out_value, const uint8_t ** const out_ptr)
+{
+       OSStatus err;
+       require_action_quiet(start <= end, exit, err = kRangeErr);
+
+       const uint8_t *ptr = start;
+       while ((end - ptr) > 0) {
+               const mdns_tl16_t *tl;
+               require_action_quiet((end - ptr) >= (ptrdiff_t)sizeof(*tl), exit, err = kUnderrunErr);
+
+               tl = (const mdns_tl16_t *)ptr;
+               const uint16_t                  tlv_type        = _mdns_tl16_get_type(tl);
+               const uint16_t                  tlv_length      = _mdns_tl16_get_length(tl);
+               const uint8_t * const   tlv_value       = (const uint8_t *)&tl[1];
+               require_action_quiet((end - tlv_value) >= tlv_length, exit, err = kUnderrunErr);
+
+               ptr = &tlv_value[tlv_length];
+               if (tlv_type == type) {
+                       if (out_length) {
+                               *out_length = tlv_length;
+                       }
+                       if (out_value) {
+                               *out_value = tlv_value;
+                       }
+                       if (out_ptr) {
+                               *out_ptr = ptr;
+                       }
+                       return kNoErr;
+               }
+       }
+       err = kNotFoundErr;
+
+exit:
+       return err;
+}
+
+//======================================================================================================================
+
+OSStatus
+mdns_tlv16_set(uint8_t * const dst, const uint8_t * const limit, const uint16_t type, const uint16_t length,
+       const uint8_t * const value, uint8_t ** const out_end)
+{
+       OSStatus err;
+       mdns_tl16_t *tl;
+       require_action_quiet(!limit || ((limit - dst) >= (ptrdiff_t)(sizeof(tl) + length)), exit, err = kUnderrunErr);
+
+       tl = (mdns_tl16_t *)dst;
+       _mdns_tl16_init(tl, type, length);
+       uint8_t * const dst_value = (uint8_t *)&tl[1];
+       if (length > 0) {
+               memcpy(dst_value, value, length);
+       }
+       if (out_end) {
+               *out_end = &dst_value[length];
+       }
+       err = kNoErr;
+
+exit:
+       return err;
+}
+
+//======================================================================================================================
+
+size_t
+mdns_tlv16_get_required_length(const uint16_t value_length)
+{
+       return (sizeof(mdns_tl16_t) + value_length);
+}
+
+//======================================================================================================================
+// MARK: - Private Functions
+
+static uint16_t
+_mdns_tl16_get_type(const mdns_tl16_t * const tl)
+{
+       return ReadBig16(tl->type);
+}
+
+//======================================================================================================================
+
+static uint16_t
+_mdns_tl16_get_length(const mdns_tl16_t * const tl)
+{
+       return ReadBig16(tl->length);
+}
+
+//======================================================================================================================
+
+static void
+_mdns_tl16_init(mdns_tl16_t * const tl, const uint16_t type, const uint16_t length)
+{
+       WriteBig16(tl->type, type);
+       WriteBig16(tl->length, length);
+}
diff --git a/mDNSMacOSX/mdns_objects/mdns_tlv.h b/mDNSMacOSX/mdns_objects/mdns_tlv.h
new file mode 100644 (file)
index 0000000..802a0c2
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_TLV_H__
+#define __MDNS_TLV_H__
+
+#include "mdns_base.h"
+
+#include <MacTypes.h>
+#include <stdint.h>
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
+
+OSStatus
+mdns_tlv16_get_value(const uint8_t *start, const uint8_t *end, uint16_t type, size_t *out_length,
+       const uint8_t * _Nonnull * _Nullable out_value, const uint8_t * _Nonnull * _Nullable out_ptr);
+
+OSStatus
+mdns_tlv16_set(uint8_t *dst, const uint8_t *limit, uint16_t type, uint16_t length, const uint8_t *value,
+       uint8_t * _Nonnull * _Nullable out_end);
+
+size_t
+mdns_tlv16_get_required_length(const uint16_t value_length);
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#endif // __MDNS_TLV_H__
diff --git a/mDNSMacOSX/mdns_objects/mdns_trust.c b/mDNSMacOSX/mdns_objects/mdns_trust.c
new file mode 100644 (file)
index 0000000..313bb7a
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mDNSFeatures.h"
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+
+#include "mdns_trust.h"
+#include "mdns_trust_checks.h"
+#include "mdns_objects.h"
+#include "mdns_helpers.h"
+#include "dns_sd.h"
+
+#include <bsm/libbsm.h>
+#include <CoreUtils/DebugServices.h>
+
+//======================================================================================================================
+// MARK: - mdns_trust Kind Definition
+
+struct mdns_trust_s {
+       struct mdns_object_s                            base;                                   // Object base.
+       bool                                                            activated;                              // True if the trust has been activated.
+       bool                                                            invalidated;                    // True if the trust has bee invalidated.
+       bool                                                            user_activated;                 // True if user called activate method.
+       dispatch_queue_t                                        queue;                                  // Internal serial queue.
+       dispatch_queue_t                                        user_queue;                             // Users serial queue.
+       mdns_trust_event_handler_t                      handler;                                // User's event handler.
+       void *                                                          context;
+       audit_token_t                                           audit_token;
+       char *                                                          query;
+       mdns_trust_flags_t                                      flags;
+};
+
+MDNS_OBJECT_SUBKIND_DEFINE(trust);
+
+//======================================================================================================================
+// MARK: - mdns_trust check Public Functions
+
+void
+mdns_trust_init(void)
+{
+       mdns_trust_checks_init();
+}
+
+mdns_trust_status_t
+mdns_trust_check_register_service(audit_token_t audit_token, const char * _Nullable service, mdns_trust_flags_t * _Nullable flags)
+{
+       return mdns_trust_checks_check(&audit_token, trust_request_reg_service, NULL, service, 0, false, flags);
+}
+
+mdns_trust_status_t
+mdns_trust_check_bonjour(audit_token_t audit_token, const char * _Nullable service, mdns_trust_flags_t * _Nullable flags)
+{
+       return mdns_trust_checks_check(&audit_token, trust_request_bonjour, NULL, service, 0, true, flags);
+}
+
+mdns_trust_status_t
+mdns_trust_check_query(audit_token_t audit_token, const char * qname, const char * _Nullable service, uint16_t qtype,
+       bool force_multicast, mdns_trust_flags_t * _Nullable flags)
+{
+       return mdns_trust_checks_check(&audit_token, trust_request_query, qname, service, qtype, force_multicast, flags);
+}
+
+mdns_trust_status_t
+mdns_trust_check_getaddrinfo(audit_token_t audit_token, const char * hostname, mdns_trust_flags_t * _Nullable flags)
+{
+       return mdns_trust_checks_check(&audit_token, trust_request_query, hostname, NULL, 0, false, flags);
+}
+
+//======================================================================================================================
+// MARK: - mdns_trust Private Methods
+
+static void
+_mdns_trust_finalize(mdns_trust_t me)
+{
+       dispatch_forget(&me->queue);
+       dispatch_release_null_safe(me->user_queue);
+       ForgetMem(&me->query);
+       BlockForget(&me->handler);
+}
+
+//======================================================================================================================
+
+static char *
+_mdns_trust_copy_description(mdns_trust_t me, const bool debug, const bool __unused privacy)
+{
+       char *                          description = NULL;
+       char                            buffer[256];
+       char *                          dst = buffer;
+       const char * const      lim = &buffer[countof(buffer)];
+       int                                     n;
+
+       *dst = '\0';
+       if (debug) {
+               n = mdns_snprintf_add(&dst, lim, "<%s: %p>: ", me->base.kind->name, me);
+               require_quiet(n >= 0, exit);
+       }
+       n = mdns_snprintf_add(&dst, lim, "%s ", me->base.kind->name);
+       require_quiet(n >= 0, exit);
+
+       n = mdns_snprintf_add(&dst, lim, "for pid %d", audit_token_to_pid(me->audit_token));
+       require_quiet(n >= 0, exit);
+
+       description = strdup(buffer);
+
+exit:
+       return description;
+}
+
+static void
+_mdns_trust_activate_internal(mdns_trust_t me)
+{
+       mdns_retain(me);
+       mdns_trust_checks_local_network_access_policy_update(&me->audit_token, me->queue, me->query, me->flags,
+               ^(trust_policy_state_t state) {
+               if (!me->invalidated) {
+                       me->invalidated = true;
+                       mdns_retain(me);
+                       dispatch_async(me->user_queue,
+                       ^{
+                               if (me->handler) {
+                                       mdns_trust_status_t status = (state != trust_policy_state_granted) ?
+                                                                                                       mdns_trust_status_denied : mdns_trust_status_granted;
+                                       me->handler(mdns_trust_event_result, status);
+                               }
+                               mdns_release(me);
+                       });
+               }
+               mdns_release(me);
+       });
+}
+
+static void
+_mdns_trust_invalidate_internal(mdns_trust_t me)
+{
+       require_quiet(!me->invalidated, exit);
+       me->invalidated = true;
+       if (me->handler) {
+               me->handler(mdns_trust_event_invalidated, 0);
+       }
+exit:
+       return;
+}
+
+static void
+_mdns_trust_activate_if_ready(mdns_trust_t me)
+{
+       if (me->user_activated && me->user_queue && !me->activated) {
+               mdns_retain(me);
+               dispatch_async(me->queue,
+               ^{
+                       me->activated = true;
+                       _mdns_trust_activate_internal(me);
+                       mdns_release(me);
+               });
+       }
+}
+
+//======================================================================================================================
+// MARK: - mdns_trust Public instance specific
+
+mdns_trust_t
+mdns_trust_create(audit_token_t audit_token, const char *_Nullable query, mdns_trust_flags_t flags)
+{
+       mdns_trust_t op = NULL;
+       mdns_trust_t obj        = _mdns_trust_alloc();
+       obj->queue                      = dispatch_queue_create("trust-internal", DISPATCH_QUEUE_SERIAL);
+       obj->audit_token        = audit_token;
+       obj->flags                      = flags;
+       if (query != NULL) {
+               obj->query              = strdup(query);
+       }
+       op                                      = obj;
+       return op;
+}
+
+void
+mdns_trust_set_context(mdns_trust_t me, void *_Nullable context)
+{
+       require_quiet(me, exit);
+       me->context = context;
+exit:
+       ;
+}
+
+void *_Nullable
+mdns_trust_get_context(mdns_trust_t me)
+{
+       require_quiet(me, exit);
+       return me->context;
+exit:
+       return NULL;
+}
+
+void
+mdns_trust_activate(mdns_trust_t me)
+{
+       if (!me->user_activated) {
+               me->user_activated = true;
+               _mdns_trust_activate_if_ready(me);
+       }
+}
+
+void
+mdns_trust_invalidate(mdns_trust_t me)
+{
+       mdns_retain(me);
+       dispatch_async(me->queue,
+       ^{
+               _mdns_trust_invalidate_internal(me);
+               mdns_release(me);
+       });
+}
+
+void
+mdns_trust_set_queue(mdns_trust_t me, dispatch_queue_t queue)
+{
+       if (!me->user_activated || !me->user_queue)
+       {
+               if (queue) {
+                       dispatch_retain(queue);
+               }
+               dispatch_release_null_safe(me->user_queue);
+               me->user_queue = queue;
+               _mdns_trust_activate_if_ready(me);
+       }
+}
+
+void
+mdns_trust_set_event_handler(mdns_trust_t me, mdns_trust_event_handler_t handler)
+{
+       const mdns_trust_event_handler_t new_handler = handler ? Block_copy(handler) : NULL;
+       if (me->handler) {
+               Block_release(me->handler);
+       }
+       me->handler = new_handler;
+}
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
diff --git a/mDNSMacOSX/mdns_objects/mdns_trust.h b/mDNSMacOSX/mdns_objects/mdns_trust.h
new file mode 100644 (file)
index 0000000..452b7fe
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_TRUST_H__
+#define __MDNS_TRUST_H__
+
+#include "mdns_base.h"
+#include <dispatch/dispatch.h>
+
+MDNS_DECL(trust);
+
+OS_CLOSED_ENUM(mdns_trust_status, int,
+       mdns_trust_status_denied                                = 0,
+       /*! @const mdns_trust_status_deny The requested trust is denied. */
+       mdns_trust_status_granted                               = 1,
+       /*! @const mdns_trust_status_granted The requested trust is granted. */
+       mdns_trust_status_pending                               = 2,
+       /*! @const mdns_trust_status_pending The requested trust is pending user interaction. */
+       mdns_trust_status_no_entitlement                = 3
+       /*! @const mdns_trust_status_no_entitlement The requested trust did not have a proper entitlement. */
+);
+
+OS_CLOSED_OPTIONS(mdns_trust_flags, uint32_t,
+       mdns_trust_flags_none                                   = 0,
+       mdns_trust_flags_entitlement                    = (1U << 0),
+       /*! @const mdns_trust_flags_entitlement The app has a valid catch-all entitlement. */
+);
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+static inline const char *
+mdns_trust_status_to_string(mdns_trust_status_t result)
+{
+       switch (result) {
+               case mdns_trust_status_denied:                          return "denied";
+               case mdns_trust_status_granted:                         return "granted";
+               case mdns_trust_status_pending:                         return "pending";
+               case mdns_trust_status_no_entitlement:          return "no_entitlement";
+               default:                                                                        return "<INVALID RESULT>";
+       }
+}
+
+//======================================================================================================================
+// MARK: - mdns_trust Initialization
+
+void
+mdns_trust_init(void);
+
+//======================================================================================================================
+// MARK: - mdns_trust Direct checks
+
+mdns_trust_status_t
+mdns_trust_check_bonjour(audit_token_t audit_token, const char * _Nullable service, mdns_trust_flags_t * _Nullable flags);
+
+mdns_trust_status_t
+mdns_trust_check_register_service(audit_token_t audit_token, const char * _Nullable service, mdns_trust_flags_t * _Nullable flags);
+
+mdns_trust_status_t
+mdns_trust_check_query(audit_token_t audit_token, const char * qname, const char * _Nullable service, uint16_t qtype, bool force_multicast, mdns_trust_flags_t * _Nullable flags);
+
+mdns_trust_status_t
+mdns_trust_check_getaddrinfo(audit_token_t audit_token, const char * hostname, mdns_trust_flags_t * _Nullable flags);
+
+//======================================================================================================================
+// MARK: - mdns_trust Object to receive status updates
+
+MDNS_RETURNS_RETAINED mdns_trust_t _Nullable
+mdns_trust_create(audit_token_t audit_token, const char *_Nullable query, mdns_trust_flags_t flags);
+
+void
+mdns_trust_set_context(mdns_trust_t trust, void *_Nullable context);
+
+void *_Nullable
+mdns_trust_get_context(mdns_trust_t trust);
+
+void
+mdns_trust_activate(mdns_trust_t trust);
+
+void
+mdns_trust_invalidate(mdns_trust_t trust);
+
+void
+mdns_trust_set_queue(mdns_trust_t trust, dispatch_queue_t queue);
+
+OS_CLOSED_ENUM(mdns_trust_event, int,
+       mdns_trust_event_result                 = 0,
+       mdns_trust_event_invalidated    = 1
+);
+
+static inline const char *
+mdns_trust_event_to_string(const mdns_trust_event_t event)
+{
+       switch (event) {
+               case mdns_trust_event_result:           return "result";
+               case mdns_trust_event_invalidated:      return "invalidated";
+               default:                                                        return "<invalid event value>";
+       }
+}
+
+typedef void
+(^mdns_trust_event_handler_t)(mdns_trust_event_t event, mdns_trust_status_t status);
+
+void
+mdns_trust_set_event_handler(mdns_trust_t trust, mdns_trust_event_handler_t handler);
+
+#define mdns_trust_forget(X)   mdns_forget_with_invalidation(X, trust)
+
+MDNS_ASSUME_NONNULL_END
+
+#endif // __MDNS_TRUST_H__
diff --git a/mDNSMacOSX/mdns_objects/mdns_trust_checks.h b/mDNSMacOSX/mdns_objects/mdns_trust_checks.h
new file mode 100644 (file)
index 0000000..69fdccb
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_TRUST_CHECKS_H__
+#define __MDNS_TRUST_CHECKS_H__
+
+#include "mdns_base.h"
+#include <dispatch/dispatch.h>
+
+OS_CLOSED_ENUM(trust_policy_state, int,
+       trust_policy_state_denied                               = 0,
+       trust_policy_state_granted                              = 1,
+       trust_policy_state_pending                              = 2
+);
+
+OS_CLOSED_ENUM(trust_request, int,
+       trust_request_bonjour                                   = 0,
+       trust_request_reg_service                               = 1,
+       trust_request_query                                             = 2
+);
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+static inline const char *
+_mdns_trust_checks_policy_to_string(trust_policy_state_t state)
+{
+       switch (state) {
+               case trust_policy_state_denied:         return "denied";
+               case trust_policy_state_granted:        return "granted";
+               case trust_policy_state_pending:        return "pending";
+               default:                                                        return "<INVALID STATE>";
+       }
+}
+
+static inline const char *
+_mdns_trust_checks_request_to_string(trust_request_t request)
+{
+       switch (request) {
+               case trust_request_bonjour:                     return "bonjour";
+               case trust_request_reg_service:         return "reg_service";
+               case trust_request_query:                       return "query";
+               default:                                                        return "<INVALID REQUEST>";
+       }
+}
+
+void
+mdns_trust_checks_init(void);
+
+typedef void
+(^_mdns_trust_checks_update_handler_t)(trust_policy_state_t status);
+
+void
+mdns_trust_checks_local_network_access_policy_update(audit_token_t *auditToken, dispatch_queue_t queue,
+       const char * _Nullable query, mdns_trust_flags_t flags, _mdns_trust_checks_update_handler_t handler);
+
+mdns_trust_status_t
+mdns_trust_checks_check(audit_token_t *audit_token, trust_request_t request, const char * _Nullable query_name,
+       const char * _Nullable service_name, uint16_t qtype, bool force_multicast, mdns_trust_flags_t * _Nullable flags);
+
+MDNS_ASSUME_NONNULL_END
+
+#endif // __MDNS_TRUST_CHECKS_H__
diff --git a/mDNSMacOSX/mdns_objects/mdns_trust_checks.m b/mDNSMacOSX/mdns_objects/mdns_trust_checks.m
new file mode 100644 (file)
index 0000000..ec830a9
--- /dev/null
@@ -0,0 +1,598 @@
+/*
+* Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     https://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#import "mDNSEmbeddedAPI.h"
+#import "mdns_trust.h"
+#import "mdns_trust_checks.h"
+#import "mdns_helpers.h"
+
+#if TARGET_OS_IOS && (TARGET_OS_EMBEDDED || TARGET_OS_SIMULATOR || !TARGET_OS_IOSMAC)
+#define USE_IOS_LIBS   1
+#else
+#define USE_IOS_LIBS   0
+#endif
+
+#if USE_IOS_LIBS
+#import <CoreServices/LSBundleProxy.h>
+#import <CoreServices/LSDiskUsagePriv.h>
+#import <CoreServices/LSApplicationRecordPriv.h>
+#else
+#import <CoreServices/CoreServicesPriv.h>
+#import <Security/CodeSigning.h>
+#endif
+
+#import <bsm/libbsm.h>
+#import <CoreAnalytics/CoreAnalytics.h>
+#import <CoreUtils/DebugServices.h>
+#import <NetworkExtension/NetworkExtensionPrivate.h>
+#import <nw/path_evaluation.h>
+#import <os/feature_private.h>
+#import <xpc/private.h>
+#import <stdatomic.h>
+
+#define BROWSE_ALL_SERVICES_PRIVATE_ENTITLEMENT                "com.apple.developer.networking.multicast"
+#define ON_DEMAND_ENTITLEMENT                                          "com.apple.developer.on-demand-install-capable"
+#define TRUST_BYPASS_GAME_CENTER_SERVICE                       "_gamecenter._tcp"
+
+#define kLocalDomain           ((const domainname *)   "\x5" "local"                           )
+#define kReverseIPv6Domain  ((const domainname *)      "\x3" "ip6"     "\x4" "arpa")
+#define kReverseIPv4Domain  ((const domainname *)      "\x7" "in-addr" "\x4" "arpa")
+
+MDNS_LOG_CATEGORY_DEFINE(trust, "trust");
+
+//======================================================================================================================
+// MARK: - mdns_trust_check struct
+
+typedef struct mdns_trust_check_s {
+       trust_request_t                                         request;
+       const char *                                            query_name;
+       const char *                                            service_name;
+       audit_token_t *                                         audit_token;
+       trust_policy_state_t                            policy_state;
+       uint16_t                                                        request_type;
+       mdns_trust_flags_t                                      flags;
+       bool                                                            entitlement_allowed;
+       bool                                                            force_multicast;
+} *mdns_trust_check_t;
+
+//======================================================================================================================
+// MARK: - Private helpers
+
+static void
+_mdns_trust_post_analytic(const mdns_trust_check_t me, const LSBundleProxy * bundle_proxy,
+       const NSArray * _Nullable services)
+{
+       AnalyticsSendEvent(@"com.apple.network.localnetwork.check",
+                                          @{@"bundleID"        : bundle_proxy.bundleIdentifier,
+                                                @"entitlement" : (me->flags & mdns_trust_flags_entitlement) ? @YES : @NO,
+                                                @"allowed"             : (me->entitlement_allowed) ? @YES : @NO,
+                                                @"services"    : services ?: @[],
+                                                @"service"             : [NSString stringWithUTF8String:me->service_name]});
+}
+
+static bool
+_mdns_trust_checks_is_same_domain_name(const char *domain1, const char *domain2)
+{
+       domainname d1, d2;
+       bool good = (MakeDomainNameFromDNSNameString(&d1, domain1) != NULL);
+       require_quiet(good, exit);
+
+       good = (MakeDomainNameFromDNSNameString(&d2, domain2) != NULL);
+       require_quiet(good, exit);
+
+       good = (SameDomainName(&d1, &d2) != mDNSfalse);
+
+exit:
+       return good;
+}
+
+static LSBundleProxy*
+_mdns_trust_checks_bundle_proxy_for_app(const audit_token_t *audit_token)
+{
+    NSError *error;
+       LSBundleProxy *bundle_proxy = nil;
+
+       if (__builtin_available(macOS 10.15, ios 13.0, watchos 6.0, tvos 13.0, *)) {
+               bundle_proxy = [LSBundleProxy bundleProxyWithAuditToken:*audit_token error:&error];
+       }
+
+       return bundle_proxy;
+}
+
+static bool
+_mdns_trust_checks_is_local_address(const char * _Nonnull address)
+{
+       nw_endpoint_t endpoint = nw_endpoint_create_host(address, "0");
+       nw_path_evaluator_t evaluator = nw_path_create_evaluator_for_endpoint(endpoint, nil);
+       nw_path_t path = nw_path_evaluator_copy_path(evaluator);
+       bool local_network = nw_path_is_direct(path);
+       return (local_network != false);
+}
+
+static bool
+_mdns_trust_checks_reverse_ipv6_to_ipv6_string(const domainname *name, char * _Nonnull addr_buffer,
+       socklen_t addr_buffer_len)
+{
+       bool                            result = false;
+       const uint8_t *     ptr;
+       int                 i;
+       uint8_t             ipv6[16];
+
+       // If the name is of the form "x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.ip6.arpa.", where
+       // each x is a hex digit, then the sequence of 32 hex digit labels represents the nibbles of an IPv6 address in
+       // reverse order. See <https://tools.ietf.org/html/rfc3596#section-2.5>.
+
+       ptr = name->c;
+       for (i = 0; i < 32; i++)
+       {
+               unsigned int c, nibble;
+               const int j = 15 - (i / 2);
+               require_quiet(*ptr++ == 1, exit);                                               // If this label's length is not 1, then fail.
+               c = *ptr++;                                             // Get label byte.
+               if ( (c >= '0') && (c <= '9')) {
+                       nibble =  c - '0';   // If it's a hex digit, get its numeric value.
+               } else if ((c >= 'a') && (c <= 'f')) {
+                       nibble = (c - 'a') + 10;
+               } else if ((c >= 'A') && (c <= 'F')) {
+                       nibble = (c - 'A') + 10;
+               } else {
+                       goto exit;
+               }
+               if ((i % 2) == 0) {
+                       ipv6[j] = (uint8_t)nibble;
+               } else {
+                       ipv6[j] |= (uint8_t)(nibble << 4);
+               }
+       }
+
+       // The rest of the name needs to be "ip6.arpa.". If it isn't, fail.
+       require_quiet(SameDomainName((const domainname *)ptr, kReverseIPv6Domain), exit);
+       (void)inet_ntop(AF_INET6, &ipv6, addr_buffer, addr_buffer_len);
+       result = true;
+exit:
+       return result;
+}
+
+static bool
+_mdns_trust_checks_reverse_ipv4_to_ipv4_string(const domainname *name, char * _Nonnull addr_buffer,
+       socklen_t addr_buffer_len)
+{
+       bool                    result = false;
+       const mDNSu8 *  ptr;
+       uint8_t                 ipv4[4];
+       uint32_t                ipv4_addr;
+
+       // If the name is of the form "x.x.x.x.in-addr.arpa.", where each x is a uint8
+
+       uint8_t *               dst;
+       int                             segments = 0;
+       bool                    sawDigit = 0;
+       int                             c, v, label_length;
+
+       ptr                     = name->c;
+       for (int i = 1; i <= 4; i++)
+       {
+               dst = &ipv4[4 - i];
+               *dst = 0;
+               sawDigit = false;
+               label_length = *ptr++;
+               require_quiet((label_length >= 1) && (label_length <= 3), exit);
+               for (int j = 0; j < label_length; j++) {
+                       c = *ptr++;                                             // Get label byte.
+                       require_quiet((c >= '0') && (c <= '9'), exit);
+                       v = (*dst * 10) + (c - '0');
+                       require_quiet(v <= 255, exit);
+                       *dst = (uint8_t) v;
+                       if (!sawDigit) {
+                               ++segments;
+                               require_quiet(segments <= 4, exit);
+                               sawDigit = true;
+                       }
+               }
+               require_quiet(segments == i, exit);
+       }
+       require_quiet(segments == 4, exit);
+       ipv4_addr = *(uint32_t*)&ipv4;                                                          // Already in network byte order
+
+       // The rest of the name needs to be "ip-addr.arpa.". If it isn't, fail.
+       require_quiet(SameDomainName((const domainname *)ptr, kReverseIPv4Domain), exit);
+       (void)inet_ntop(AF_INET, &ipv4_addr, addr_buffer, addr_buffer_len);
+       result = true;
+exit:
+       return result;
+}
+
+static bool
+_mdns_trust_checks_should_check_query_type(const char *qname, uint16_t qtype)
+{
+       domainname domain;
+       bool good = (MakeDomainNameFromDNSNameString(&domain, qname) != NULL);
+       require_quiet(good, exit);
+
+       bool qtype_valid = (qtype == kDNSType_PTR || qtype == kDNSQType_ANY);
+       const domainname *d = &domain;
+
+       const domainname *d1, *d2;                                                                                              // Top-level domain, second-level domain
+       d1 = d2 = nil;
+       while (d->c[0]) {
+               d2 = d1; d1 = d;
+               d = (const domainname*)(d->c + 1 + d->c[0]);
+       }
+
+       if (d1 && SameDomainName(d1, kLocalDomain)) {
+               return true;
+       } else if (qtype_valid && IsLocalDomain(&domain)) {
+               return true;
+       } else if (d2 && qtype_valid && SameDomainName(d2, kReverseIPv4Domain)) {
+               char str_buffer[INET_ADDRSTRLEN];
+               if (_mdns_trust_checks_reverse_ipv4_to_ipv4_string(&domain, str_buffer, sizeof(str_buffer))) {
+                       if (_mdns_trust_checks_is_local_address(str_buffer)) {
+                               return true;
+                       }
+               }
+       } else if (d2 && qtype_valid && SameDomainName(d2, kReverseIPv6Domain)) {
+               char str_buffer[INET6_ADDRSTRLEN];
+               if (_mdns_trust_checks_reverse_ipv6_to_ipv6_string(&domain, str_buffer, sizeof(str_buffer))) {
+                       if (_mdns_trust_checks_is_local_address(str_buffer)) {
+                               return true;
+                       }
+               }
+       }
+exit:
+       return false;
+}
+
+//======================================================================================================================
+// MARK: - mdns_trust_checks System checks
+
+#if USE_IOS_LIBS
+
+static bool
+_mdns_trust_checks_system_trusted_app(const LSBundleProxy *bundle_proxy)
+{
+       NSString * bundleid = bundle_proxy.bundleIdentifier;
+       return ([bundleid hasPrefix: @"com.apple."] != NO);
+}
+
+#else // !USE_IOS_LIBS
+
+static bool
+_mdns_trust_checks_codesigned_by_apple(const LSBundleProxy *bundle_proxy)
+{
+    OSStatus err;
+    SecRequirementRef requirement = NULL;
+    SecStaticCodeRef code = NULL;
+
+    err = SecRequirementCreateWithString(CFSTR("anchor apple"), kSecCSDefaultFlags, &requirement);
+       require_noerr_quiet(err, exit);
+
+       err = SecStaticCodeCreateWithPath((__bridge CFURLRef)bundle_proxy.bundleURL, kSecCSDefaultFlags, &code);
+       require_noerr_quiet(err, exit);
+
+    err = SecStaticCodeCheckValidity(code, kSecCSDefaultFlags, requirement);
+
+exit:
+       if (code) {
+               CFRelease(code);
+       }
+       if (requirement) {
+               CFRelease(requirement);
+       }
+
+    return (err == noErr);
+}
+
+#endif // USE_IOS_LIBS
+
+static bool
+_mdns_trust_checks_app_is_apple_internal(const LSBundleProxy *bundle_proxy)
+{
+#if USE_IOS_LIBS
+       NSString * bundleType = bundle_proxy.bundleType;
+       if ([bundleType isEqualToString:LSInternalApplicationType]      ||
+               [bundleType isEqualToString:LSSystemApplicationType]) {
+               return true;
+       } else if ([bundleType isEqualToString:LSPlugInKitType] &&
+               _mdns_trust_checks_system_trusted_app(bundle_proxy)) {
+               return true;
+       } else {
+               return false;
+       }
+#else // !USE_IOS_LIBS
+       return _mdns_trust_checks_codesigned_by_apple(bundle_proxy);
+#endif // USE_IOS_LIBS
+}
+
+static bool
+_mdns_trust_checks_app_sdk_is_minimum_version(const LSBundleProxy *bundle_proxy)
+{
+#if TARGET_OS_OSX
+       #define MIN_SDK_VERSION "10.16"
+#elif TARGET_OS_WATCH
+       #define MIN_SDK_VERSION "7.0"
+#else
+       #define MIN_SDK_VERSION "14.0"
+#endif
+       NSString * min_vers = [NSString stringWithUTF8String:MIN_SDK_VERSION];
+       NSComparisonResult compare_result = [[bundle_proxy sdkVersion] compare:min_vers options:NSNumericSearch];
+       bool result = ((compare_result == NSOrderedSame) || (compare_result == NSOrderedDescending));
+
+       return (result != false);
+}
+
+//======================================================================================================================
+// MARK: - mdns_trust_checks Policy checks
+
+static trust_policy_state_t
+_mdns_trust_checks_app_is_local_network_allowed(const LSBundleProxy *bundle_proxy)
+{
+       bool denyMulticast = false;
+       bool userPrompted = false;
+       if ([NEPathController class]) {
+               NSArray<NEPathRule *> *aggregateRules = [NEPathController copyAggregatePathRules];
+               for (NEPathRule *pathRule in aggregateRules) {
+                       if ([pathRule.matchSigningIdentifier isEqualToString:bundle_proxy.bundleIdentifier]) {
+#ifdef NE_PATH_RULE_SUPPORTS_DENY_MULTICAST
+                               denyMulticast = (pathRule.denyMulticast != NO);
+#endif // NE_PATH_RULE_SUPPORTS_DENY_MULTICAST
+#ifdef NE_PATH_RULE_SUPPORTS_MULTICAST_PREFERENCE_SET
+                               userPrompted = (pathRule.multicastPreferenceSet != NO);
+#else
+                               userPrompted = true;
+#endif // NE_PATH_RULE_SUPPORTS_MULTICAST_PREFERENCE_SET
+                               break;
+                       }
+               }
+       }
+
+       trust_policy_state_t state = denyMulticast ?
+                                                                       (userPrompted ? trust_policy_state_denied : trust_policy_state_pending) :
+                                                                       trust_policy_state_granted;
+       return state;
+}
+
+static void
+_mdns_trust_checks_policy_check(const mdns_trust_check_t me, const LSBundleProxy *bundle_proxy)
+{
+       me->policy_state = _mdns_trust_checks_app_is_local_network_allowed(bundle_proxy);
+       if (me->policy_state != trust_policy_state_granted) {
+               os_log_info(_mdns_trust_log(), "Local network access to %{public}s(%{private}s) policy \'%{public}s\' for (%{public}@).",
+                                       _mdns_trust_checks_request_to_string(me->request), me->service_name ?: me->query_name,
+                                       _mdns_trust_checks_policy_to_string(me->policy_state), bundle_proxy.localizedShortName);
+       }
+}
+
+//======================================================================================================================
+// MARK: - mdns_trust_checks Entitlement checks
+
+static bool
+_mdns_trust_checks_check_on_demand_entitlement(const mdns_trust_check_t me, const LSBundleProxy *bundle_proxy)
+{
+       // The presense of this entitlement disallows these functions
+       xpc_object_t entitlement = xpc_copy_entitlement_for_token(ON_DEMAND_ENTITLEMENT, me->audit_token);
+       if (entitlement) {
+               os_log_info(_mdns_trust_log(), "Entitlement \'%{public}s\' disallows request for (%{public}@)", ON_DEMAND_ENTITLEMENT, bundle_proxy.localizedShortName);
+               return false;
+       } else {
+               return true;
+       }
+}
+
+static bool
+_mdns_trust_checks_check_browse_all_entitlement(const mdns_trust_check_t me)
+{
+       xpc_object_t entitlement = xpc_copy_entitlement_for_token(BROWSE_ALL_SERVICES_PRIVATE_ENTITLEMENT, me->audit_token);
+       if (entitlement) {
+               bool allowed = false;
+               if (xpc_get_type(entitlement) == XPC_TYPE_BOOL) {
+                       allowed = (xpc_bool_get_value(entitlement) != false);
+               }
+               return allowed;
+       } else {
+               return false;
+       }
+}
+
+static bool
+_mdns_trust_checks_app_info_has_bonjour_service(const LSBundleProxy *bundle_proxy, const NSArray *services, const char * const service)
+{
+       for (NSString * next in services) {
+               if (_mdns_trust_checks_is_same_domain_name(service, next.UTF8String)) {
+                       return true;
+               }
+       }
+
+       if (_mdns_trust_checks_is_same_domain_name(service, TRUST_BYPASS_GAME_CENTER_SERVICE)) {
+               return true;
+       }
+
+       os_log_error(_mdns_trust_log(), "App Info.plist(NSBonjourServices) does not allow \'%{public}s\' for (%{public}@)", service, bundle_proxy.localizedShortName);
+       return false;
+}
+
+static void
+_mdns_trust_checks_entitlement_check(const mdns_trust_check_t me, const LSBundleProxy *bundle_proxy)
+{
+       me->entitlement_allowed = _mdns_trust_checks_check_on_demand_entitlement(me, bundle_proxy);
+       require_quiet(me->entitlement_allowed, exit);
+
+       if (os_feature_enabled(mDNSResponder, bonjour_privacy)  &&
+               _mdns_trust_checks_app_sdk_is_minimum_version(bundle_proxy)) {
+               require_quiet(me->service_name, exit); // No service_name is allowed to skip entitlement checks
+               NSArray *services = nil;
+               me->entitlement_allowed = _mdns_trust_checks_check_browse_all_entitlement(me);
+               if (me->entitlement_allowed) {
+                       me->flags |= mdns_trust_flags_entitlement;
+               } else {
+                       //      Only check if previous is false
+                       services = [bundle_proxy objectForInfoDictionaryKey:@"NSBonjourServices" ofClass:[NSArray class]];
+                       me->entitlement_allowed = _mdns_trust_checks_app_info_has_bonjour_service(bundle_proxy, services, me->service_name);
+               }
+               _mdns_trust_post_analytic(me, bundle_proxy, services);
+       }
+
+exit:
+       return;
+}
+
+//======================================================================================================================
+// MARK: - mdns_trust_checks Internal checks
+
+static bool
+_mdns_trust_checks_should_check_trust(const mdns_trust_check_t me)
+{
+       bool should_check = true;
+
+       if (me->request == trust_request_query) {
+               should_check = (me->force_multicast || _mdns_trust_checks_should_check_query_type(me->query_name, me->request_type));
+       } else if (me->request == trust_request_reg_service) {
+               should_check = (_mdns_trust_checks_is_same_domain_name(me->service_name, TRUST_BYPASS_GAME_CENTER_SERVICE) == false);
+       }
+
+       return should_check;
+}
+
+static mdns_trust_status_t
+_mdns_trust_checks_get_status(const mdns_trust_check_t me)
+{
+       mdns_trust_status_t status;
+
+       if (me->entitlement_allowed) {
+               if (me->policy_state == trust_policy_state_granted) {
+                       status = mdns_trust_status_granted;
+               } else if (me->policy_state == trust_policy_state_pending) {
+                       status = mdns_trust_status_pending;
+               } else {
+                       status = mdns_trust_status_denied;
+               }
+       } else {
+               status = mdns_trust_status_no_entitlement;
+       }
+       return status;
+}
+
+static void
+_mdns_trust_checks_perform_all_trust_checks(const mdns_trust_check_t me)
+{
+       LSBundleProxy *bundle_proxy = _mdns_trust_checks_bundle_proxy_for_app(me->audit_token);
+       require_quiet(bundle_proxy, exit);
+
+       bool check_more = (_mdns_trust_checks_app_is_apple_internal(bundle_proxy) == false);
+       require_quiet(check_more, exit);                                        // Internal always allowed
+
+       _mdns_trust_checks_entitlement_check(me, bundle_proxy);
+       check_more = (me->entitlement_allowed != false);
+       require_quiet(check_more, exit);                                        // Continue if allowed by entitlement
+
+       _mdns_trust_checks_policy_check(me, bundle_proxy);      //      Can interact with user so check policy last
+
+exit:
+       return;
+}
+
+//======================================================================================================================
+// MARK: - mdns_trust_checks Public functions
+
+static _Atomic bool g_is_initialized = false;
+
+void
+mdns_trust_checks_init(void)
+{
+       static dispatch_once_t s_once;
+       dispatch_once(&s_once, ^{
+               os_log_info(_mdns_trust_log(), "Initializing Launch Services -- PENDING");
+               dispatch_queue_t queue = dispatch_queue_create("com.apple.dnssd.trust.init", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
+               dispatch_async(queue, ^{
+                       os_log_info(_mdns_trust_log(), "Initializing Launch Services -- START");
+                       if (__builtin_available(macOS 10.16, ios 14.0, watchos 7.0, tvos 14.0, *)) {
+                               // Issue a query to LaunchServices to ensure we only proceed after their database is built during migration
+                               (void)[[LSApplicationRecord alloc] initWithBundleIdentifier:@"com.apple.dummy.nonexistent" allowPlaceholder:NO error:nil];
+                       }
+                       atomic_store(&g_is_initialized, true);
+                       os_log_info(_mdns_trust_log(), "Initializing Launch Services -- COMPLETE");
+               });
+       });
+}
+
+void
+mdns_trust_checks_local_network_access_policy_update(audit_token_t *audit_token, dispatch_queue_t queue,
+       const char * _Nullable query, mdns_trust_flags_t flags, _mdns_trust_checks_update_handler_t handler)
+{
+#ifndef NE_HAS_SHOW_LOCAL_NETWORK_ALERT_FOR_APP_EXTENDED
+       (void)query;
+       (void)flags;
+#endif
+       @autoreleasepool {
+               LSBundleProxy *bundle_proxy = _mdns_trust_checks_bundle_proxy_for_app(audit_token);
+               __block trust_policy_state_t state = (bundle_proxy != nil) ? _mdns_trust_checks_app_is_local_network_allowed(bundle_proxy) : trust_policy_state_granted;
+#ifdef NE_HAS_SHOW_LOCAL_NETWORK_ALERT_FOR_APP
+               if ([NEConfigurationManager class] && state == trust_policy_state_pending) {
+                       os_log_info(_mdns_trust_log(), "Local network alert for (%{public}@) query(%{public}s).", bundle_proxy.localizedShortName, query ?: "local");
+                       NEConfigurationManager *sharedManager = [NEConfigurationManager sharedManagerForAllUsers];
+                       [sharedManager showLocalNetworkAlertForApp:bundle_proxy.bundleIdentifier
+                                                                  withCompletionQueue:queue
+#ifdef NE_HAS_SHOW_LOCAL_NETWORK_ALERT_FOR_APP_EXTENDED
+                                                                                                query:query ? [NSString stringWithUTF8String:query] : @"local"
+                                                                               hasEntitlement:(flags & mdns_trust_flags_entitlement) ? YES : NO
+#endif
+                                                                                          handler:^(BOOL allowed) {
+                               state = allowed ? trust_policy_state_granted : trust_policy_state_denied;
+                               os_log_info(_mdns_trust_log(), "Local network alert policy status \'%{public}s\' for (%{public}@).", _mdns_trust_checks_policy_to_string(state), bundle_proxy.localizedShortName);
+                               handler(state);
+                       }];
+               } else
+#endif // HAVE_SHOW_LOCAL_NETWORK_ALERT_FOR_APP
+               {
+                       if (bundle_proxy == nil) {
+                               os_log_info(_mdns_trust_log(), "No bundle found for local network access policy update for PID(%d).", audit_token_to_pid(*audit_token));
+                       }
+                       dispatch_async(queue, ^{
+                               handler(state);
+                       });
+               }
+       }
+}
+
+mdns_trust_status_t
+mdns_trust_checks_check(audit_token_t *audit_token, trust_request_t request, const char * _Nullable query_name,
+       const char * _Nullable service_name, uint16_t qtype, bool force_multicast, mdns_trust_flags_t * _Nullable flags)
+{
+       struct mdns_trust_check_s ref;
+       ref.audit_token                 = audit_token;
+       ref.request                     = request;
+       ref.query_name                  = query_name;
+       ref.service_name                = service_name;
+       ref.request_type                = qtype;
+       ref.flags                               = mdns_trust_flags_none;
+       ref.entitlement_allowed = true;                                                 // Default allow
+       ref.force_multicast             = force_multicast;
+       ref.policy_state                = trust_policy_state_granted;   // Default granted
+
+       require_quiet(atomic_load(&g_is_initialized), exit);
+
+       @autoreleasepool {
+               bool check_more = (_mdns_trust_checks_should_check_trust(&ref) != false);
+               require_quiet(check_more, exit);
+
+               _mdns_trust_checks_perform_all_trust_checks(&ref);
+       }
+
+exit:
+       if (flags != nil) {
+               *flags = ref.flags;
+       }
+       return _mdns_trust_checks_get_status(&ref);
+}
diff --git a/mDNSMacOSX/mdns_objects/mdns_xpc.c b/mDNSMacOSX/mdns_objects/mdns_xpc.c
new file mode 100644 (file)
index 0000000..ddca5db
--- /dev/null
@@ -0,0 +1,51 @@
+
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mdns_xpc.h"
+
+#include <CoreFoundation/CFXPCBridge.h>
+#include <CoreUtils/CoreUtils.h>
+
+//======================================================================================================================
+// MARK: - Public Functions
+
+xpc_object_t
+mdns_xpc_create_dictionary_from_plist_data(const uint8_t * const bytes, const size_t length, OSStatus * const out_error)
+{
+       xpc_object_t dictionary = NULL;
+       CFPropertyListRef plist = NULL;
+       OSStatus err;
+       CFDataRef data = CFDataCreate(NULL, bytes, (CFIndex)length);
+       require_action_quiet(data, exit, err = kNoMemoryErr);
+
+       plist = CFPropertyListCreateWithData(NULL, data, kCFPropertyListImmutable, NULL, NULL);
+       ForgetCF(&data);
+       require_action_quiet(plist, exit, err = kFormatErr);
+       require_action_quiet(CFGetTypeID(plist) == CFDictionaryGetTypeID(), exit, err = kTypeErr);
+
+       dictionary = _CFXPCCreateXPCObjectFromCFObject(plist);
+       require_action_quiet(dictionary, exit, err = kUnknownErr);
+
+       err = kNoErr;
+
+exit:
+       if (out_error) {
+               *out_error = err;
+       }
+       ForgetCF(&plist);
+       return dictionary;
+}
diff --git a/mDNSMacOSX/mdns_objects/mdns_xpc.h b/mDNSMacOSX/mdns_objects/mdns_xpc.h
new file mode 100644 (file)
index 0000000..0465315
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __MDNS_XPC_H__
+#define __MDNS_XPC_H__
+
+#include "mdns_base.h"
+
+#include <MacTypes.h>
+#include <xpc/xpc.h>
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
+
+MDNS_RETURNS_RETAINED MDNS_WARN_RESULT
+xpc_object_t _Nullable
+mdns_xpc_create_dictionary_from_plist_data(const uint8_t *bytes, size_t length, OSStatus * _Nullable out_error);
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#endif // __MDNS_XPC_H__
diff --git a/mDNSMacOSX/mdns_private.h b/mDNSMacOSX/mdns_private.h
deleted file mode 100644 (file)
index 6000d9d..0000000
+++ /dev/null
@@ -1,374 +0,0 @@
-/*
- * Copyright (c) 2019 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __MDNS_PRIVATE_H__
-#define __MDNS_PRIVATE_H__
-
-#include <CoreFoundation/CoreFoundation.h>
-#include <dispatch/dispatch.h>
-#include <MacTypes.h>
-#include <os/object.h>
-
-#if OS_OBJECT_USE_OBJC
-       #define MDNS_DECL(NAME)                 OS_OBJECT_DECL_SUBCLASS(mdns_ ## NAME, mdns_object)
-       #define MDNS_RETURNS_RETAINED   OS_OBJECT_RETURNS_RETAINED
-
-       OS_OBJECT_DECL(mdns_object,);
-#else
-       #define MDNS_DECL(NAME)                 typedef struct mdns_ ## NAME ## _s *    mdns_ ## NAME ## _t
-       #define MDNS_RETURNS_RETAINED
-
-       MDNS_DECL(object);
-#endif
-
-// mdns Object Declarations
-
-MDNS_DECL(interface_monitor);
-
-OS_ASSUME_NONNULL_BEGIN
-
-#if OS_OBJECT_USE_OBJC
-       typedef mdns_object_t   mdns_any_t;
-#else
-       #if !defined(__cplusplus)
-               typedef union {
-                       mdns_object_t                           base;
-                       mdns_interface_monitor_t        interface_monitor;
-               } mdns_any_t __attribute__((__transparent_union__));
-       #else
-               typedef void *  mdns_any_t;
-       #endif
-#endif
-
-__BEGIN_DECLS
-
-/*!
- *     @brief
- *             Increments the reference count of an mdns object.
- *
- *     @param object
- *             The mdns object.
- */
-void
-mdns_retain(mdns_any_t object);
-#if OS_OBJECT_USE_OBJC_RETAIN_RELEASE
-       #undef mdns_retain
-       #define mdns_retain(object)     [(object) retain]
-#endif
-
-/*!
- *     @brief
- *             Decrements the reference count of an mdns object.
- *
- *     @param object
- *             The mdns object.
- */
-void
-mdns_release(mdns_any_t object);
-#if OS_OBJECT_USE_OBJC_RETAIN_RELEASE
-       #undef mdns_release
-       #define mdns_release(object)    [(object) release]
-#endif
-
-/*!
- *     @brief
- *             Creates a human-readable description of an mdns object as a C string.
- *
- *     @param object
- *             The mdns object.
- *
- *     @result
- *             A C string that must be freed with free(3).
- */
-OS_WARN_RESULT
-char * _Nullable
-mdns_copy_description(mdns_any_t object);
-
-/*!
- *     @brief
- *             CFArray callbacks for mdns objects.
- */
-extern const CFArrayCallBacks mdns_cfarray_callbacks;
-
-/*!
- *     @brief
- *             Creates an interface monitor.
- *
- *     @param interface_index
- *             Index of the interface to monitor.
- *
- *     @result
- *             A new interface monitor or NULL if there was a lack of resources.
- *
- *     @discussion
- *             An interface monitor provides up-to-date information about an interface's properties, such as IPv4
- *             connectivity, IPv6 connectivity, whether the interface is expensive, and whether the interface is constrained.
- *
- *             If this function returns non-NULL, then the caller has an ownership reference to the newly created interface
- *             monitor, which can be relinquished with <code>mdns_release()</code>.
- */
-MDNS_RETURNS_RETAINED mdns_interface_monitor_t _Nullable
-mdns_interface_monitor_create(uint32_t interface_index);
-
-/*!
- *     @brief
- *             Activates an interface monitor.
- *
- *     @param monitor
- *             The interface monitor.
- *
- *     @discussion
- *             Successful activation enables interface monitor updates.
- *
- *             This function has no effect on an interface monitor that has already been activated or one that has been
- *             invalidated.
- */
-void
-mdns_interface_monitor_activate(mdns_interface_monitor_t monitor);
-
-/*!
- *     @brief
- *             Asynchronously invalidates an interface monitor.
- *
- *     @param monitor
- *             The interface monitor.
- *
- *     @discussion
- *             This function should be called when the interface monitor is no longer needed.
- *
- *             As a result of calling this function, the interface monitor's event handler will be invoked with a
- *             <code>mdns_event_invalidated</code> event, after which the interface monitor's event and update handlers will
- *             never be invoked again.
- *
- *             This function has no effect on an interface monitor that has already been invalidated.
- */
-void
-mdns_interface_monitor_invalidate(mdns_interface_monitor_t monitor);
-
-/*!
- *     @brief
- *             Specifies the queue on which to invoke the interface monitor's event and update handlers.
- *
- *     @param monitor
- *             The interface monitor.
- *
- *     @param queue
- *             A serial queue.
- *
- *     @discussion
- *             This function must be called before activating the interface monitor.
- *
- *             This function has no effect on an interface monitor that has been activated or invalidated.
- */
-void
-mdns_interface_monitor_set_queue(mdns_interface_monitor_t monitor, dispatch_queue_t queue);
-
-/*!
- *     @brief
- *             Generic events that can occur during the lifetime of an mdns object.
- */
-OS_CLOSED_ENUM(mdns_event, int,
-       /*! @const mdns_event_error A fatal error has occurred. */
-       mdns_event_error                = 1,
-       /*! @const mdns_event_invalidated The object has been invalidated. */
-       mdns_event_invalidated  = 2
-);
-
-static inline const char *
-mdns_event_to_string(mdns_event_t event)
-{
-       switch (event) {
-               case mdns_event_error:                  return "Error";
-               case mdns_event_invalidated:    return "Invalidated";
-               default:                                                return "?";
-       }
-}
-
-/*!
- *     @brief
- *             Generic event handler for mdns objects.
- *
- *     @param event
- *             The event.
- *
- *     @param error
- *             The error associated with a <code>mdns_event_error</code> event. This argument should be ignored for all other
- *             types of events.
- *
- *     @discussion
- *             After an <code>mdns_event_invalidated</code> event, none of the object's handlers will ever be invoked again.
- */
-typedef void (^mdns_event_handler_t)(mdns_event_t event, OSStatus error);
-
-/*!
- *     @brief
- *             Sets an interface monitor's event handler.
- *
- *     @param monitor
- *             The interface monitor.
- *
- *     @param handler
- *             The event handler.
- *
- *     @discussion
- *             The event handler will never be invoked prior to activation.
- *
- *             The event handler will be invoked on the dispatch queue specified by
- *             <code>mdns_interface_monitor_set_queue()</code> with event <code>mdns_event_error</code> when a fatal error
- *             occurs and with event <code>mdns_event_invalidated</code> when the interface monitor has been invalidated.
- *
- *             After an <code>mdns_event_invalidated</code> event, the event handler will never be invoked again.
- */
-void
-mdns_interface_monitor_set_event_handler(mdns_interface_monitor_t monitor, mdns_event_handler_t _Nullable handler);
-
-/*!
- *     @brief
- *             Flags that represent the properties of a monitored interface.
- *
- *     @discussion
- *             These flags don't represent the actual values of properties. The meaning of these flags depends on the context
- *             in which they're used. For example, as a parameter of mdns_interface_monitor_update_handler_t, a set flag
- *             means that the value of a given property has changed.
- */
-OS_CLOSED_OPTIONS(mdns_interface_flags, uint32_t,
-       mdns_interface_flag_null                                = 0,
-       mdns_interface_flag_ipv4_connectivity   = (1U <<  0),
-       mdns_interface_flag_ipv6_connectivity   = (1U <<  1),
-       mdns_interface_flag_expensive                   = (1U <<  2),
-       mdns_interface_flag_constrained                 = (1U <<  3),
-       mdns_interface_flag_clat46                              = (1U <<  4),
-       mdns_interface_flag_reserved                    = (1U << 31)
-);
-
-/*!
- *     @brief
- *             Update handler for an interface monitor.
- *
- *     @param change_flags
- *             Each flag bit represents a property of a monitored interface. If the bit is set, then the value of that
- *             property has changed. If the bit is clear, then the value of that property has not changed.
- */
-typedef void (^mdns_interface_monitor_update_handler_t)(mdns_interface_flags_t change_flags);
-
-/*!
- *     @brief
- *             Sets an interface monitor's update handler.
- *
- *     @param monitor
- *             The interface monitor.
- *
- *     @param handler
- *             The update handler.
- *
- *     @discussion
- *             The update handler will never be invoked prior to activation.
- *
- *             The update handler will be invoked on the dispatch queue specified by
- *             <code>mdns_interface_monitor_set_queue()</code> when any of the monitored interface's properties have been
- *             updated.
- *
- *             After an <code>mdns_event_invalidated</code> event, the update handler will ever be invoked again.
- */
-void
-mdns_interface_monitor_set_update_handler(mdns_interface_monitor_t monitor,
-       mdns_interface_monitor_update_handler_t _Nullable handler);
-
-/*!
- *     @brief
- *             Returns the index of the monitored interface.
- *
- *     @param monitor
- *             The interface monitor.
- */
-uint32_t
-mdns_interface_monitor_get_interface_index(mdns_interface_monitor_t monitor);
-
-/*!
- *     @brief
- *             Determines whether the monitored interface currently has IPv4 connectivity.
- *
- *     @param monitor
- *             The interface monitor.
- *
- *     @discussion
- *             mdns_interface_flag_ipv4_connectivity will be set in the update handler's change_flags argument when the value
- *             of this property has changed.
- */
-bool
-mdns_interface_monitor_has_ipv4_connectivity(mdns_interface_monitor_t monitor);
-
-/*!
- *     @brief
- *             Determines whether the monitored interface currently has IPv6 connectivity.
- *
- *     @param monitor
- *             The interface monitor.
- *
- *     @discussion
- *             mdns_interface_flag_ipv6_connectivity will be set in the update handler's change_flags argument when the value
- *             of this property has changed.
- */
-bool
-mdns_interface_monitor_has_ipv6_connectivity(mdns_interface_monitor_t monitor);
-
-/*!
- *     @brief
- *             Determines whether the monitored interface is currently expensive.
- *
- *     @param monitor
- *             The interface monitor.
- *
- *     @discussion
- *             mdns_interface_flag_expensive will be set in the update handler's change_flags argument when the value
- *             of this property has changed.
- */
-bool
-mdns_interface_monitor_is_expensive(mdns_interface_monitor_t monitor);
-
-/*!
- *     @brief
- *             Determines whether the monitored interface is currently constrained.
- *
- *     @param monitor
- *             The interface monitor.
- *
- *     @discussion
- *             mdns_interface_flag_constrained will be set in the update handler's change_flags argument when the value
- *             of this property has changed.
- */
-bool
-mdns_interface_monitor_is_constrained(mdns_interface_monitor_t monitor);
-
-/*!
- *     @brief
- *             Determines whether the monitored interface has CLAT46 support.
- *
- *     @param monitor
- *             The interface monitor.
- *
- *     @discussion
- *             mdns_interface_flag_clat46 will be set in the update handler's change_flags argument when the value
- *             of this property has changed.
- */
-bool
-mdns_interface_monitor_is_clat46(mdns_interface_monitor_t monitor);
-
-__END_DECLS
-
-OS_ASSUME_NONNULL_END
-
-#endif // __MDNS_PRIVATE_H__
diff --git a/mDNSMacOSX/ra-tester-entitlements.plist b/mDNSMacOSX/ra-tester-entitlements.plist
new file mode 100644 (file)
index 0000000..e0706b6
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>com.apple.networkd_privileged</key>
+       <true/>
+       <key>com.apple.private.network.reserved-port</key>
+       <true/>
+       <key>com.apple.private.network.socket-delegate</key>
+       <true/>
+       <key>com.apple.security.network.client</key>
+       <true/>
+       <key>com.apple.security.network.server</key>
+       <true/>
+</dict>
+</plist>
diff --git a/mDNSMacOSX/srp-mdns-proxy-entitlements.plist b/mDNSMacOSX/srp-mdns-proxy-entitlements.plist
new file mode 100644 (file)
index 0000000..2035b53
--- /dev/null
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+  <dict>
+    <key>seatbelt-profiles</key>
+    <array>
+      <string>temporary-sandbox</string>
+    </array>
+    <key>platform-application</key>
+    <true/>
+
+    <key>com.apple.security.network.client</key>
+    <true/>
+
+    <key>com.apple.security.network.server</key>
+    <true/>
+
+    <key>com.apple.networkd_privileged</key>
+    <true/>
+
+    <key>com.apple.private.network.reserved-port</key>
+    <true/>
+
+    <key>com.apple.private.wpantund.service.xpc</key>
+    <true/>
+
+    <key>com.apple.private.fillmore.service.add</key>
+    <true/>
+
+    <key>com.apple.private.fillmore.service.remove</key>
+    <true/>
+
+    <key>com.apple.private.fillmore.prefix.modification</key>
+    <true/>
+
+    <key>com.apple.security.exception.mach-lookup.global-name</key>
+    <array>
+      <string>com.apple.wpantund.xpc</string>
+      <string>com.apple.network.IPConfiguration</string>
+    </array>
+
+    <key>com.apple.security.exception.sysctl.read-write</key>
+    <array>
+      <string>net.inet6.ip6.forwarding</string>
+    </array>
+
+    <key>com.apple.SystemConfiguration.SCDynamicStore-write-access</key>
+    <true/>
+
+    <key>com.apple.security.exception.shared-preference.read-write</key>
+    <array>
+      <string>com.apple.srp-mdns-proxy.preferences</string>
+    </array>
+
+    <key>com.apple.security.exception.files.absolute-path.read-write</key>
+    <array>
+      <string>/private/var/preferences/SystemConfiguration/preferences.plist</string>
+    </array>
+
+    <key>com.apple.security.exception.files.absolute-path.read-only</key>
+    <array>
+      <string>/usr/libexec</string>
+      <string>/dev/fd</string>
+    </array>
+
+  </dict>
+</plist>
diff --git a/mDNSMacOSX/srp-mdns-proxy.plist b/mDNSMacOSX/srp-mdns-proxy.plist
new file mode 100644 (file)
index 0000000..323abe2
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>CFBundleIdentifier</key>
+        <string>com.apple.srp-mdns-proxy</string>
+        <key>CFBundleName</key>
+        <string>DNS-SD Service Registration Protocol Proxy for Multicast DNS</string>
+</dict>
+</plist>
diff --git a/mDNSMacOSX/uDNSPathEvaluation.c b/mDNSMacOSX/uDNSPathEvaluation.c
new file mode 100644 (file)
index 0000000..643730b
--- /dev/null
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2013-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mDNSMacOSX.h"
+#include <libproc.h>
+#include <nw/private.h>
+
+#include "dns_sd_internal.h"
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+#include "QuerierSupport.h"
+#endif
+
+#define _nw_forget(PTR)         \
+    do                          \
+    {                           \
+        if (*(PTR))             \
+        {                       \
+            nw_release(*(PTR)); \
+            *(PTR) = NULL;      \
+        }                       \
+    } while (0)
+
+//Gets the DNSPolicy from NW PATH EVALUATOR
+mDNSexport void mDNSPlatformGetDNSRoutePolicy(DNSQuestion *q)
+{
+    nw_endpoint_t host = NULL;
+    nw_parameters_t parameters = NULL;
+    nw_path_evaluator_t evaluator = NULL;
+    nw_path_t path = NULL;
+    mDNSBool isBlocked = mDNSfalse;
+    q->ServiceID = -1; // initialize the ServiceID to default value of -1
+
+    // Return for non-unicast DNS queries, or invalid PID.
+    if (mDNSOpaque16IsZero(q->TargetQID) || (q->pid < 0))
+    {
+        goto exit;
+    }
+
+    mDNSs32 service_id;
+    mDNSu32 client_ifindex, dnspol_ifindex;
+    int retval;
+    struct proc_uniqidentifierinfo info;
+    mDNSBool isUUIDSet;
+
+    char unenc_name[MAX_ESCAPED_DOMAIN_NAME];
+    ConvertDomainNameToCString(&q->qname, unenc_name);
+
+    host = nw_endpoint_create_host(unenc_name, "0");
+    if (host == NULL)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+            "[Q%u] mDNSPlatformGetDNSRoutePolicy: Query for " PRI_DM_NAME " (" PUB_S "), PID[%d], EUID[%d], ServiceID[%d]"
+            " host is NULL",
+            mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), q->pid, q->euid, q->ServiceID);
+        goto exit;
+    }
+    parameters = nw_parameters_create();
+    if (parameters == NULL)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+            "[Q%u] mDNSPlatformGetDNSRoutePolicy: Query for " PRI_DM_NAME " (" PUB_S "), PID[%d], EUID[%d], ServiceID[%d]"
+            " parameters is NULL",
+            mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), q->pid, q->euid, q->ServiceID);
+        goto exit;
+    }
+#if TARGET_OS_WATCH
+    static xpc_object_t prohibited_interface_subtypes = NULL;
+    // Companion interface on watchOS does not support DNS, so we don't want path evalution to return it to us.
+    if (prohibited_interface_subtypes == NULL)
+    {
+        prohibited_interface_subtypes = xpc_array_create(NULL, 0);
+        if (prohibited_interface_subtypes != NULL)
+        {
+            xpc_array_set_uint64(prohibited_interface_subtypes, XPC_ARRAY_APPEND, nw_interface_subtype_companion);
+        }
+    }
+    if (prohibited_interface_subtypes == NULL)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_UDNS, MDNS_LOG_ERROR,
+                  "mDNSPlatformGetDNSRoutePolicy: DNS Route Policy: prohibited_interface_subtypes returned by xpc_array_create() is NULL");
+    }
+    else
+    {
+        nw_parameters_set_prohibited_interface_subtypes(parameters, prohibited_interface_subtypes);
+    }
+#endif // TARGET_OS_WATCH
+
+    // Check for all the special (negative) internal value interface indices before initializing client_ifindex
+    if (   (q->InterfaceID == mDNSInterface_Any)
+        || (q->InterfaceID == mDNSInterface_LocalOnly)
+        || (q->InterfaceID == mDNSInterfaceMark)
+        || (q->InterfaceID == mDNSInterface_P2P)
+        || (q->InterfaceID == mDNSInterface_BLE)
+        || (q->InterfaceID == uDNSInterfaceMark))
+    {
+        client_ifindex = 0;
+    }
+    else
+    {
+        client_ifindex = (mDNSu32)(uintptr_t)q->InterfaceID;
+    }
+
+    if (client_ifindex > 0)
+    {
+        nw_interface_t client_intf = nw_interface_create_with_index(client_ifindex);
+        if (client_intf)
+        {
+            nw_parameters_require_interface(parameters, client_intf);
+            _nw_forget(&client_intf);
+        }
+        else
+        {
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+                "[Q%u] mDNSPlatformGetDNSRoutePolicy: nw_interface_create_with_index() returned NULL for index %u",
+                mDNSVal16(q->TargetQID), client_ifindex);
+        }
+    }
+
+    nw_parameters_set_uid(parameters,(uid_t)q->euid);
+
+    if (q->pid != 0)
+    {
+        nw_parameters_set_pid(parameters, q->pid);
+        retval = proc_pidinfo(q->pid, PROC_PIDUNIQIDENTIFIERINFO, 1, &info, sizeof(info));
+        if (retval == (int)sizeof(info))
+        {
+            nw_parameters_set_e_proc_uuid(parameters, info.p_uuid);
+            isUUIDSet = mDNStrue;
+        }
+        else
+        {
+            debugf("mDNSPlatformGetDNSRoutePolicy: proc_pidinfo returned %d", retval);
+            isUUIDSet = mDNSfalse;
+        }
+    }
+    else
+    {
+        nw_parameters_set_e_proc_uuid(parameters, q->uuid);
+        isUUIDSet = mDNStrue;
+    }
+
+    evaluator = nw_path_create_evaluator_for_endpoint(host, parameters);
+    if (evaluator == NULL)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+            "[Q%u] mDNSPlatformGetDNSRoutePolicy: Query for " PRI_DM_NAME " (" PUB_S "), PID[%d], EUID[%d], ServiceID[%d]"
+            " evaluator is NULL",
+            mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), q->pid, q->euid, q->ServiceID);
+        goto exit;
+    }
+    _nw_forget(&host);
+    _nw_forget(&parameters);
+
+    path = nw_path_evaluator_copy_path(evaluator);
+    if (path == NULL)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+            "[Q%u] mDNSPlatformGetDNSRoutePolicy: Query for " PRI_DM_NAME " (" PUB_S "), PID[%d], EUID[%d], ServiceID[%d]"
+            " path is NULL",
+            mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), q->pid, q->euid, q->ServiceID);
+        goto exit;
+    }
+    service_id = nw_path_get_flow_divert_unit(path);
+    if (service_id != 0)
+    {
+        q->ServiceID = service_id;
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+            "[Q%u] mDNSPlatformGetDNSRoutePolicy: Query for " PRI_DM_NAME " service ID is set ->service_ID:[%d] ",
+            mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), service_id);
+    }
+    else
+    {
+        nw_interface_t nwpath_intf = nw_path_copy_scoped_interface(path);
+        if (nwpath_intf != NULL)
+        {
+            // Use the new scoped interface given by NW PATH EVALUATOR
+            dnspol_ifindex = nw_interface_get_index(nwpath_intf);
+            q->InterfaceID = (mDNSInterfaceID)(uintptr_t)dnspol_ifindex;
+
+            _nw_forget(&nwpath_intf);
+
+            if (dnspol_ifindex != client_ifindex)
+            {
+                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+                    "[Q%u] mDNSPlatformGetDNSRoutePolicy: DNS Route Policy has changed the scoped ifindex from [%d] to [%d]",
+                    mDNSVal16(q->TargetQID), client_ifindex, dnspol_ifindex);
+            }
+        }
+        else
+        {
+            debugf("mDNSPlatformGetDNSRoutePolicy: Query for %##s (%s), PID[%d], EUID[%d], ServiceID[%d] nw_interface_t nwpath_intf is NULL ", q->qname.c, DNSTypeName(q->qtype), q->pid, q->euid, q->ServiceID);
+        }
+    }
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    uuid_clear(q->ResolverUUID);
+    if (__builtin_available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *))
+    {
+        if (path != NULL)
+        {
+            __block nw_resolver_config_t best_config = NULL;
+            __block nw_resolver_class_t best_class = nw_resolver_class_default_direct;
+            nw_path_enumerate_resolver_configs(path,
+            ^bool(nw_resolver_config_t config)
+            {
+                const nw_resolver_class_t class = nw_resolver_config_get_class(config);
+                if (class != nw_resolver_class_default_direct &&
+                    (best_class == nw_resolver_class_default_direct || class < best_class))
+                {
+                    best_class = class;
+                    best_config = config;
+                }
+                return true;
+            });
+            if (best_config != NULL)
+            {
+                nw_resolver_config_get_identifier(best_config, q->ResolverUUID);
+            }
+        }
+    }
+    if (!uuid_is_null(q->ResolverUUID))
+    {
+        Querier_RegisterPathResolver(q->ResolverUUID);
+    }
+#endif
+    if (isUUIDSet && path && (nw_path_get_status(path) == nw_path_status_unsatisfied) && (nw_path_get_reason(path) != nw_path_reason_no_route))
+    {
+        isBlocked = mDNStrue;
+    }
+
+exit:
+    _nw_forget(&host);
+    _nw_forget(&parameters);
+    _nw_forget(&path);
+    _nw_forget(&evaluator);
+    q->BlockedByPolicy = isBlocked;
+}
diff --git a/mDNSMacOSX/uDNSPathEvalulation.c b/mDNSMacOSX/uDNSPathEvalulation.c
deleted file mode 100644 (file)
index 9923446..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-/* -*- Mode: C; tab-width: 4; c-file-style: "bsd"; c-basic-offset: 4; fill-column: 108; indent-tabs-mode: nil; -*-
- *
- * Copyright (c) 2013-2018 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "mDNSMacOSX.h"
-#include <libproc.h>
-
-#if __has_include(<nw/private.h>)
-    #include <nw/private.h>
-#else
-    #include <network/private.h>
-    #define nw_release(X) network_release(X)
-#endif
-
-#include "dns_sd_internal.h"
-
-//Gets the DNSPolicy from NW PATH EVALUATOR
-mDNSexport void mDNSPlatformGetDNSRoutePolicy(DNSQuestion *q, mDNSBool *isBlocked)
-{
-    if (__builtin_available(macOS 10.14, *))
-    {
-        q->ServiceID = -1; // initialize the ServiceID to default value of -1
-
-        // Return for non-unicast DNS queries, invalid pid, if NWPathEvaluation is already done by the client, or NWPathEvaluation not available on this OS
-        if (mDNSOpaque16IsZero(q->TargetQID) || (q->pid < 0) || (q->flags & kDNSServiceFlagsPathEvaluationDone) || !nw_endpoint_create_host)
-        {
-            *isBlocked = mDNSfalse;
-            return;
-        }
-        
-        mDNSs32 service_id;
-        mDNSu32 client_ifindex, dnspol_ifindex;
-        int retval;
-        struct proc_uniqidentifierinfo info;
-        mDNSBool isUUIDSet;
-        
-        char unenc_name[MAX_ESCAPED_DOMAIN_NAME];
-        ConvertDomainNameToCString(&q->qname, unenc_name);
-        
-        nw_endpoint_t host = nw_endpoint_create_host(unenc_name, "0");
-        if (host == NULL)
-            LogMsg("mDNSPlatformGetDNSRoutePolicy: DNS Route Policy: Query for %##s (%s), PID[%d], EUID[%d], ServiceID[%d] nw_endpoint_t host is NULL", q->qname.c,
-                   DNSTypeName(q->qtype), q->pid, q->euid, q->ServiceID);
-        
-        nw_parameters_t parameters = nw_parameters_create();
-        if (parameters == NULL)
-            LogMsg("mDNSPlatformGetDNSRoutePolicy: DNS Route Policy: Query for %##s (%s), PID[%d], EUID[%d], ServiceID[%d] nw_endpoint_t parameters is NULL", q->qname.c,
-                   DNSTypeName(q->qtype), q->pid, q->euid, q->ServiceID);
-
-#if TARGET_OS_WATCH
-        // Companion interface on watchOS does not support DNS, so we don't want path evalution to return it to us.
-        nw_parameters_set_companion_preference(parameters, nw_parameters_agent_preference_avoid);
-#endif // TARGET_OS_WATCH
-        
-        // Check for all the special (negative) internal value interface indices before initializing client_ifindex
-        if (   (q->InterfaceID == mDNSInterface_Any)
-            || (q->InterfaceID == mDNSInterface_LocalOnly)
-            || (q->InterfaceID == mDNSInterfaceMark)
-            || (q->InterfaceID == mDNSInterface_P2P)
-            || (q->InterfaceID == mDNSInterface_BLE)
-            || (q->InterfaceID == uDNSInterfaceMark))
-        {
-            client_ifindex = 0;
-        }
-        else
-        {
-            client_ifindex = (mDNSu32)(uintptr_t)q->InterfaceID;
-        }
-
-        if (client_ifindex > 0)
-        {
-            nw_interface_t client_intf = nw_interface_create_with_index(client_ifindex);
-            nw_parameters_require_interface(parameters, client_intf);
-            if (client_intf != NULL)
-                nw_release(client_intf);
-            else
-                LogInfo("mDNSPlatformGetDNSRoutePolicy: DNS Route Policy: client_intf returned by nw_interface_create_with_index() is NULL");
-        }
-        
-        nw_parameters_set_uid(parameters,(uid_t)q->euid);
-
-        if (q->pid != 0)
-        {
-            nw_parameters_set_pid(parameters, q->pid);
-            retval = proc_pidinfo(q->pid, PROC_PIDUNIQIDENTIFIERINFO, 1, &info, sizeof(info));
-            if (retval == (int)sizeof(info))
-            {
-                nw_parameters_set_e_proc_uuid(parameters, info.p_uuid);
-                isUUIDSet = mDNStrue;
-            }
-            else
-            {
-                debugf("mDNSPlatformGetDNSRoutePolicy: proc_pidinfo returned %d", retval);
-                isUUIDSet = mDNSfalse;
-            }
-        }
-        else
-        {
-            nw_parameters_set_e_proc_uuid(parameters, q->uuid);
-            isUUIDSet = mDNStrue;
-        }
-        
-        nw_path_evaluator_t evaluator = nw_path_create_evaluator_for_endpoint(host, parameters);
-        if (evaluator == NULL)
-            LogMsg("mDNSPlatformGetDNSRoutePolicy: DNS Route Policy: Query for %##s (%s), PID[%d], EUID[%d], ServiceID[%d] nw_path_evaluator_t evaluator is NULL", q->qname.c,
-                    DNSTypeName(q->qtype), q->pid, q->euid, q->ServiceID);
-        
-        if (host != NULL)
-            nw_release(host);
-        if (parameters != NULL)
-            nw_release(parameters);
-        
-        nw_path_t path = nw_path_evaluator_copy_path(evaluator);
-        if (path == NULL)
-            LogMsg("mDNSPlatformGetDNSRoutePolicy: DNS Route Policy: Query for %##s (%s), PID[%d], EUID[%d], ServiceID[%d] nw_path_t path is NULL", q->qname.c,
-                   DNSTypeName(q->qtype), q->pid, q->euid, q->ServiceID);
-        
-        service_id = nw_path_get_flow_divert_unit(path);
-        if (service_id != 0)
-        {
-            q->ServiceID = service_id;
-            LogInfo("mDNSPlatformGetDNSRoutePolicy: DNS Route Policy: Query for %##s service ID is set ->service_ID:[%d] ", q->qname.c, service_id);
-        }
-        else
-        {
-            nw_interface_t nwpath_intf = nw_path_copy_scoped_interface(path);
-            if (nwpath_intf != NULL)
-            {
-                // Use the new scoped interface given by NW PATH EVALUATOR
-                dnspol_ifindex = nw_interface_get_index(nwpath_intf);
-                q->InterfaceID = (mDNSInterfaceID)(uintptr_t)dnspol_ifindex;
-                
-                nw_release(nwpath_intf);
-                
-                if (dnspol_ifindex != client_ifindex)
-                    LogInfo("mDNSPlatformGetDNSRoutePolicy: DNS Route Policy has changed the scoped ifindex from [%d] to [%d]",
-                            client_ifindex, dnspol_ifindex);
-            }
-            else
-            {
-                debugf("mDNSPlatformGetDNSRoutePolicy: Query for %##s (%s), PID[%d], EUID[%d], ServiceID[%d] nw_interface_t nwpath_intf is NULL ", q->qname.c, DNSTypeName(q->qtype), q->pid, q->euid, q->ServiceID);
-            }
-        }
-        
-        if (isUUIDSet && path && (nw_path_get_status(path) == nw_path_status_unsatisfied) && (nw_path_get_reason(path) != nw_path_reason_no_route))
-            *isBlocked = mDNStrue;
-        else
-            *isBlocked = mDNSfalse;
-
-        if (path != NULL)
-            nw_release(path);
-        if (evaluator != NULL)
-            nw_release(evaluator);
-    }
-    else
-    {
-        *isBlocked = mDNSfalse;
-    }
-}
diff --git a/mDNSMacOSX/utilities/bundle_utilities.h b/mDNSMacOSX/utilities/bundle_utilities.h
new file mode 100644 (file)
index 0000000..2cd8643
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+* Copyright (c) 2019 Apple Inc. All rights reserved.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#ifndef BUNDLE_UTILITIES_H
+#define BUNDLE_UTILITIES_H
+
+#include <stdbool.h>
+
+bool bundle_sdk_is_ios14_or_later(void);
+
+#endif /* BUNDLE_UTILITIES_H */
diff --git a/mDNSMacOSX/utilities/bundle_utilities.m b/mDNSMacOSX/utilities/bundle_utilities.m
new file mode 100644 (file)
index 0000000..348bd09
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+* Copyright (c) 2019 Apple Inc. All rights reserved.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#if !defined(__i386__)
+#include "bundle_utilities.h"
+
+#import <CoreServices/CoreServicesPriv.h>
+#import <CoreUtils/SoftLinking.h>
+
+SOFT_LINK_FRAMEWORK_EX(Frameworks, CoreServices);
+SOFT_LINK_CLASS_EX(CoreServices, LSBundleRecord);
+#define LSBundleRecordSoft getLSBundleRecordClass()
+
+SOFT_LINK_FRAMEWORK_EX(Frameworks, Foundation);
+SOFT_LINK_CLASS_EX(Foundation, NSString);
+#define NSStringSoft getNSStringClass()
+
+bool bundle_sdk_is_ios14_or_later(void)
+{
+#if TARGET_OS_OSX
+       #define MIN_SDK_VERSION "10.16"
+#elif TARGET_OS_WATCH
+       #define MIN_SDK_VERSION "7.0"
+#else
+       #define MIN_SDK_VERSION "14.0"
+#endif
+       BOOL result = NO;
+
+       if (LSBundleRecordSoft && NSStringSoft) {
+               @autoreleasepool {
+                       id rec = [LSBundleRecordSoft bundleRecordForCurrentProcess];
+                       if (rec) {
+                               NSString * min_vers = [NSStringSoft stringWithUTF8String:MIN_SDK_VERSION];
+                               NSComparisonResult compare_result = [[rec SDKVersion] compare:min_vers options:NSNumericSearch];
+                               result = ((compare_result == NSOrderedSame) || (compare_result == NSOrderedDescending));
+                       }
+               }
+       }
+
+       return (result != NO);
+}
+#endif
diff --git a/mDNSMacOSX/utilities/setup_assistant_helper.h b/mDNSMacOSX/utilities/setup_assistant_helper.h
new file mode 100644 (file)
index 0000000..63c35fb
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+* Copyright (c) 2020 Apple Inc. All rights reserved.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#ifndef SETUP_ASSISTANT_HELPER_H
+#define SETUP_ASSISTANT_HELPER_H
+
+#include <stdbool.h>
+#include <os/base.h>
+
+__BEGIN_DECLS
+
+OS_ASSUME_NONNULL_BEGIN
+
+OS_CLOSED_ENUM(buddy_state, int,
+       buddy_state_indeterminate               = 0,
+       buddy_state_in_process                  = 1,
+       buddy_state_done                                = 2
+);
+
+static inline const char *
+buddy_state_to_string(buddy_state_t state)
+{
+       switch (state) {
+               case buddy_state_indeterminate: return "indeterminate";
+               case buddy_state_in_process:    return "in_process";
+               case buddy_state_done:                  return "done";
+               default:                                                return "<INVALID STATE>";
+       }
+}
+
+buddy_state_t
+assistant_helper_get_buddy_state(void);
+
+typedef void
+(^buddy_done_handler_t)(void);
+
+void
+assistant_helper_notify_when_buddy_done(buddy_done_handler_t handler);
+
+OS_ASSUME_NONNULL_END
+
+__END_DECLS
+
+#endif // __ASetupAssistantHelperDotH__
diff --git a/mDNSMacOSX/utilities/setup_assistant_helper.m b/mDNSMacOSX/utilities/setup_assistant_helper.m
new file mode 100644 (file)
index 0000000..192180a
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+* Copyright (c) 2020 Apple Inc. All rights reserved.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#import "setup_assistant_helper.h"
+
+#import <SoftLinking/SoftLinking.h>
+
+#if TARGET_OS_OSX
+
+#import <SetupAssistantFramework/SAUserSetupState.h>
+#import <SystemConfiguration/SystemConfiguration.h>
+
+SOFT_LINK_FRAMEWORK(PrivateFrameworks, SetupAssistantFramework)
+SOFT_LINK_CLASS(SetupAssistantFramework, SAUserSetupState)
+
+#endif // TARGET_OS_OSX
+
+#if TARGET_OS_OSX
+static uid_t s_last_uid = 0;
+static void
+_update_console_user_id(void)
+{
+       CFStringRef userName = SCDynamicStoreCopyConsoleUser(NULL, &s_last_uid, NULL);
+       if (userName) {
+               CFRelease(userName);
+       }
+}
+#endif // TARGET_OS_OSX
+
+buddy_state_t
+assistant_helper_get_buddy_state(void)
+{
+#if TARGET_OS_OSX
+       buddy_state_t buddy_state = buddy_state_indeterminate;
+       _update_console_user_id();
+       SAUserSetupStateEnum state = [SAUserSetupState getSetupStateForUser:s_last_uid];
+       switch (state) {
+               case SAUserSetupStateSetupDone:
+                       buddy_state = buddy_state_done;
+                       break;
+
+               case SAUserSetupStateSetupUser:
+               case SAUserSetupStateSetupInProcess:
+                       buddy_state = buddy_state_in_process;
+                       break;
+
+               case SAUserSetupStateIndeterminate:
+               default:
+                       buddy_state = buddy_state_indeterminate;
+                       break;
+       }
+       return buddy_state;
+#else
+       return buddy_state_done;
+#endif // TARGET_OS_OSX
+}
+
+void
+assistant_helper_notify_when_buddy_done(buddy_done_handler_t handler)
+{
+#if TARGET_OS_OSX
+    [SAUserSetupState notifyWhenUserIsSetup:s_last_uid withCompletionBlock:handler];
+#else
+       (void)handler;
+#endif // TARGET_OS_OSX
+}
+
diff --git a/mDNSMacOSX/utilities/system_utilities.c b/mDNSMacOSX/utilities/system_utilities.c
deleted file mode 100644 (file)
index 834e8b2..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-//
-//  system_utilities.c
-//  mDNSResponder
-//
-//  Copyright (c) 2019 Apple Inc. All rights reserved.
-//
-
-#include <os/variant_private.h> // os_variant_has_internal_diagnostics
-#include "mDNSEmbeddedAPI.h"
-#include "system_utilities.h"
-
-mDNSexport mDNSBool IsAppleInternalBuild(void)
-{
-       return (os_variant_has_internal_diagnostics("com.apple.mDNSResponder") ? mDNStrue : mDNSfalse);
-}
index 789be4277184a95228759b34e70f5434bda39db3..e1417c0e2df3135bd2aaffdb85d86e1aae5f39e0 100644 (file)
@@ -1,13 +1,52 @@
-//
-//  system_utilities.h
-//  mDNSResponder
-//
-//  Copyright (c) 2019 Apple Inc. All rights reserved.
-//
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 #ifndef SYSTEM_UTILITIES_H
 #define SYSTEM_UTILITIES_H
 
-mDNSexport mDNSBool IsAppleInternalBuild(void);
+#include <stdbool.h>
+#include <stdint.h>
+#include <os/base.h>
+
+__BEGIN_DECLS
+OS_ASSUME_NONNULL_BEGIN
+
+bool IsAppleInternalBuild(void);
+
+OS_CLOSED_ENUM(util_enclosure_color, int,
+       util_enclosure_color_none                       = 0,    //      No enclosure color available
+       util_enclosure_color_rgb                        = 1,    //      Enclosure color is rgb value    "int,int,int"
+       util_enclosure_color_index                      = 2             //      Enclosure color is index value  "int"
+);
+
+/**
+       \brief Get the enclosure color of the current device, if available.
+
+       \param out_str On success, the enclosure color of the current
+        device as a string. On failure, the value is unspecified.
+
+       \param len The available length of out_str.
+
+       \result The util_enclosure_color_t of color string returned.
+               \a *out_str.
+*/
+
+util_enclosure_color_t
+util_get_enclosure_color_str(char * const out_str, uint8_t len, uint8_t *out_size);
+
+OS_ASSUME_NONNULL_END
 
 #endif /* SYSTEM_UTILITIES_H */
diff --git a/mDNSMacOSX/utilities/system_utilities.m b/mDNSMacOSX/utilities/system_utilities.m
new file mode 100644 (file)
index 0000000..35a907c
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "system_utilities.h"
+#import <os/variant_private.h> // os_variant_has_internal_diagnostics
+#import <TargetConditionals.h>
+
+#if TARGET_OS_OSX
+#import <UniformTypeIdentifiers/UniformTypeIdentifiersPriv.h>
+#import <IOKit/platform/IOPlatformSupportPrivate.h>
+#endif // TARGET_OS_OSX
+
+bool IsAppleInternalBuild(void)
+{
+       return (os_variant_has_internal_diagnostics("com.apple.mDNSResponder") != false);
+}
+
+#if TARGET_OS_OSX
+util_enclosure_color_t
+util_get_enclosure_color_str(char * const out_str, uint8_t len, uint8_t *out_size)
+{
+       util_enclosure_color_t color_type = util_enclosure_color_none;
+       if (@available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)) {
+               UTHardwareColor enclosureColor;
+               if (_UTHardwareColorGetCurrentEnclosureColor(&enclosureColor)) {
+                       switch (enclosureColor.type) {
+                               case UTHardwareColorTypeRGB: {
+                                       int size = snprintf(out_str, len, "%u,%u,%u",
+                                                                               enclosureColor.rgb.r, enclosureColor.rgb.g, enclosureColor.rgb.b);
+                                       if (size < len) {
+                                               color_type = util_enclosure_color_rgb;
+                                               *out_size = size;
+                                       }
+                                       break;
+                               }
+                               case UTHardwareColorTypeIndexed: {
+                                       int size = snprintf(out_str, len, "%i", enclosureColor.index);
+                                       if (size < len) {
+                                               color_type = util_enclosure_color_index;
+                                               *out_size = size;
+                                       }
+                                       break;
+                               }
+                               default:
+                                       *out_size = 0;
+                                       break;
+                       }
+               }
+       } else {
+               uint8_t   red      = 0;
+               uint8_t   green    = 0;
+               uint8_t   blue     = 0;
+
+               IOReturn rGetDeviceColor = IOPlatformGetDeviceColor(kIOPlatformDeviceEnclosureColorKey,
+                                                                                                                               &red, &green, &blue);
+               if (kIOReturnSuccess == rGetDeviceColor)
+               {
+                       int size = snprintf(out_str, len, "%u,%u,%u", red, green, blue);
+                       if (size < len) {
+                               color_type = util_enclosure_color_rgb;
+                               *out_size = size;
+                       }
+               }
+       }
+       return color_type;
+}
+#else // TARGET_OS_OSX
+util_enclosure_color_t
+util_get_enclosure_color_str(char * const __unused out_str, uint8_t __unused len, uint8_t * __unused out_size)
+{
+       return util_enclosure_color_none;
+}
+#endif // TARGET_OS_OSX
diff --git a/mDNSMacOSX/xpc_services/xpc_client_advertising_proxy.h b/mDNSMacOSX/xpc_services/xpc_client_advertising_proxy.h
new file mode 100644 (file)
index 0000000..f73af2f
--- /dev/null
@@ -0,0 +1,34 @@
+//
+//  xpc_client_advertising_proxy.h
+//  mDNSResponder
+//
+//  Copyright (c) 2019 Apple Inc. All rights reserved.
+//
+
+#ifndef XPC_CLIENT_ADVERTISING_PROXY_H
+#define XPC_CLIENT_ADVERTISING_PROXY_H
+
+#define kDNSAdvertisingProxyService          "com.apple.srp-mdns-proxy.proxy"
+#define kDNSAdvertisingProxyCommand          "xpc-command"
+#define kDNSAdvertisingProxyResponseStatus   "status"
+
+#define kDNSAdvertisingProxyEnable           "enable"
+#define kDNSAdvertisingProxyListServiceTypes "list service types"
+#define kDNSAdvertisingProxyListServices     "list services"
+#define kDNSAdvertisingProxyListHosts        "list hosts"
+#define kDNSAdvertisingProxyGetHost          "get host"
+#define kDNSAdvertisingProxyFlushEntries     "flush entries"
+#define kDNSAdvertisingProxyBlockService     "block service"
+#define kDNSAdvertisingProxyUnblockService   "unblock service"
+#define kDNSAdvertisingProxyRegenerateULA    "regenerate ULA"
+
+#endif /* XPC_CLIENT_ADVERTISING_PROXY_H */
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
index 1676299be29a05936fff35dd7c7069ad4c2daaaf..aafbcd842464230e795260363b25a3baf5d462f6 100644 (file)
@@ -2,7 +2,7 @@
 //  xpc_client_dns_proxy.h
 //  mDNSResponder
 //
-//  Copyright (c) 2019 Apple Inc. All rights reserved.
+//  Copyright (c) 2019-2020 Apple Inc. All rights reserved.
 //
 
 #ifndef XPC_CLIENT_DNS_PROXY_H
@@ -19,4 +19,8 @@
 
 #define kDNSOutIfindex          "OutputInterfaceIndex"
 
+#define kDNSProxyDNS64IPv6Prefix         "ipv6_prefix"
+#define kDNSProxyDNS64IPv6PrefixBitLen   "ipv6_prefix_bitlen"
+#define kDNSProxyDNS64ForceAAAASynthesis "force_aaaa_synthesis"
+
 #endif /* XPC_CLIENT_DNS_PROXY_H */
index 7a127465751c1391aa6d38bf156dcb1b0efb8523..047530dee468df85dcf9f587d2eb462f7ab84197 100644 (file)
@@ -10,6 +10,7 @@
 
 #include "xpc_client_dns_proxy.h"
 #include "xpc_client_log_utility.h"
+#include "xpc_client_advertising_proxy.h"
 
 #define kDNSDaemonReply "DaemonReplyStatusToClient"
 
index 47e837a8e80f0bf3e8d755967eee6f1575b41a73..9fee8672fc5713aacb93f04be97078e78398a4ac 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,8 +28,13 @@ static dispatch_queue_t dps_queue = NULL;
 
 mDNSlocal void accept_dps_client(xpc_connection_t conn);
 mDNSlocal void handle_dps_request(xpc_object_t req);
-mDNSlocal void handle_dps_terminate();
+mDNSlocal void handle_dps_terminate(void);
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+mDNSlocal void ActivateDNSProxy(mDNSu32 IpIfArr[MaxIp], mDNSu32 OpIf, const mDNSu8 IPv6Prefix[16], int IPv6PrefixBitLen,
+                                mDNSBool forceAAAASynthesis, mDNSBool proxy_off);
+#else
 mDNSlocal void ActivateDNSProxy(mDNSu32 IpIfArr[MaxIp], mDNSu32 OpIf, mDNSBool proxy_off);
+#endif
 
 mDNSexport void log_dnsproxy_info(mDNS *const m)
 {
@@ -196,6 +201,12 @@ mDNSlocal void handle_dps_request(xpc_object_t req)
     if (xpc_dictionary_get_uint64(req, kDNSProxyParameters))
     {
         mDNSu32 inIf[MaxIp], outIf;
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+        const mDNSu8 *ipv6Prefix;
+        size_t dataLen;
+        int ipv6PrefixLen;
+        mDNSBool forceAAAASynthesis;
+#endif
 
         inIf[0]   = (mDNSu32)xpc_dictionary_get_uint64(req, kDNSInIfindex0);
         inIf[1]   = (mDNSu32)xpc_dictionary_get_uint64(req, kDNSInIfindex1);
@@ -204,11 +215,36 @@ mDNSlocal void handle_dps_request(xpc_object_t req)
         inIf[4]   = (mDNSu32)xpc_dictionary_get_uint64(req, kDNSInIfindex4);
         outIf     = (mDNSu32)xpc_dictionary_get_uint64(req, kDNSOutIfindex);
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+        ipv6Prefix = xpc_dictionary_get_data(req, kDNSProxyDNS64IPv6Prefix, &dataLen);
+        if (ipv6Prefix && (dataLen == 16))
+        {
+            int64_t prefixLen = xpc_dictionary_get_int64(req, kDNSProxyDNS64IPv6PrefixBitLen);
+            if (prefixLen < INT_MIN)
+            {
+                prefixLen = INT_MIN;
+            }
+            else if (prefixLen > INT_MAX)
+            {
+                prefixLen = INT_MAX;
+            }
+            ipv6PrefixLen = (int)prefixLen;
+            forceAAAASynthesis = xpc_dictionary_get_bool(req, kDNSProxyDNS64ForceAAAASynthesis);
+        }
+        else
+        {
+            ipv6Prefix = NULL;
+            ipv6PrefixLen = 0;
+            forceAAAASynthesis = mDNSfalse;
+        }
+        ActivateDNSProxy(inIf, outIf, ipv6Prefix, ipv6PrefixLen, forceAAAASynthesis, proxy_off);
+#else
         ActivateDNSProxy(inIf, outIf, proxy_off);
+#endif
     }
 }
 
-mDNSlocal void handle_dps_terminate()
+mDNSlocal void handle_dps_terminate(void)
 {
 
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
@@ -224,14 +260,23 @@ mDNSlocal void handle_dps_terminate()
     KQueueUnlock("DNSProxy Deactivated");
 }
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+mDNSlocal void ActivateDNSProxy(mDNSu32 IpIfArr[MaxIp], mDNSu32 OpIf, const mDNSu8 IPv6Prefix[16], int IPv6PrefixBitLen,
+                                mDNSBool forceAAAASynthesis, mDNSBool proxy_off)
+#else
 mDNSlocal void ActivateDNSProxy(mDNSu32 IpIfArr[MaxIp], mDNSu32 OpIf, mDNSBool proxy_off)
+#endif
 {
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
               "ActivateDNSProxy: InterfaceIndex List by Client: Input[%d, %d, %d, %d, %d] Output[%d]",
               IpIfArr[0], IpIfArr[1], IpIfArr[2], IpIfArr[3], IpIfArr[4], OpIf);
 
     KQueueLock();
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
+    DNSProxyInit(IpIfArr, OpIf, IPv6Prefix, IPv6PrefixBitLen, forceAAAASynthesis);
+#else
     DNSProxyInit(IpIfArr, OpIf);
+#endif
     if (proxy_off) // Open skts only if proxy was OFF else we may end up opening extra skts
         mDNSPlatformInitDNSProxySkts(ProxyUDPCallback, ProxyTCPCallback);
     KQueueUnlock("DNSProxy Activated");
index 47348cbd5fee7fd77eedaed5f07d8b3642022324..6342de7a1104af569ca4a32fa43c31fdc3988f2d 100644 (file)
@@ -1,11 +1,11 @@
 /*
- * Copyright (c) 2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *             http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  */
 
 #include <xpc/xpc.h>
-#include <dirent.h>                     // opendir
-#include <sys/stat.h>                   // stat
+#include <dirent.h>                                            // opendir
+#include <sys/stat.h>                                  // stat
 #include <archive.h>
 #include <archive_entry.h>
-#include <AssertMacros.h>               // require, require_action
+#include <AssertMacros.h>                              // require, require_action
 
 #include "mDNSMacOSX.h"
 #include "helper.h"
 #include "xpc_services.h"
 #include "xpc_service_log_utility.h"
 #include "xpc_clients.h"
-#include "system_utilities.h"           // IsAppleInternalBuild
+#include "system_utilities.h"                  // IsAppleInternalBuild
 
 #define STATE_DUMP_PLAIN_SUFFIX "txt"
 #define STATE_DUMP_COMPRESSED_SUFFIX "tar.bz2"
 
 // global variables
-extern mDNS             mDNSStorage;
+extern mDNS                            mDNSStorage;
 static dispatch_queue_t log_utility_server_queue = NULL;
 
 // function declaration
-extern void         dump_state_to_fd(int fd);
-mDNSlocal void      accept_client(xpc_connection_t conn);
-mDNSlocal mDNSs8    handle_requests(xpc_object_t req);
-mDNSlocal mDNSs8    check_permission(xpc_connection_t connection);
-mDNSlocal mDNSs8    handle_state_dump(mDNSu32 dump_option, char *full_file_name, mDNSu32 name_buffer_len,
-                                      int client_fd, mDNSs32 *time_ms_used);
-mDNSlocal mDNSs32   find_oldest_state_dump(const char *dump_dir, const char *file_name, char *full_file_name,
-                                           mDNSu32 buffer_len, char *oldest_file_name);
-mDNSlocal mDNSs8    remove_state_dump_if_too_many(const char *dump_dir, const char *oldest_file_name, mDNSs32 dump_file_count,
-                                                  mDNSs32 max_allowed);
-mDNSlocal int       create_new_state_dump_file(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 buffer_len);
-mDNSlocal mDNSs8    handle_state_dump_to_fd(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 buffer_len,
-                                            mDNSBool if_compress);
-mDNSlocal mDNSs8    compress_state_dump_and_delete(char *input_file, mDNSu32 buffer_len);
-mDNSlocal mDNSs32   timediff_ms(struct timeval* t1, struct timeval* t2);
+extern void
+dump_state_to_fd(int fd);
+
+mDNSlocal void
+accept_client(xpc_connection_t conn);
+
+mDNSlocal mDNSs8
+handle_requests(xpc_object_t req);
+
+mDNSlocal mDNSs8
+check_permission(xpc_connection_t connection);
+
+mDNSlocal mDNSs8
+handle_state_dump(mDNSu32 dump_option, char *full_file_name, mDNSu32 name_buffer_len, int client_fd,
+       mDNSs32 *time_ms_used);
+
+mDNSlocal mDNSs32
+find_oldest_state_dump(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 buffer_len,
+       char *oldest_file_name);
+
+mDNSlocal mDNSs8
+remove_state_dump_if_too_many(const char *dump_dir, const char *oldest_file_name, mDNSs32 dump_file_count,
+       mDNSs32 max_allowed);
+
+mDNSlocal int
+create_new_state_dump_file(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 buffer_len);
+
+mDNSlocal mDNSs8
+handle_state_dump_to_fd(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 buffer_len,
+       mDNSBool if_compress);
+
+mDNSlocal mDNSs8
+compress_state_dump_and_delete(char *input_file, mDNSu32 buffer_len);
+
+mDNSlocal mDNSs32
+timediff_ms(struct timeval *t1, struct timeval *t2);
 
 // function definition
-mDNSexport void init_log_utility_service(void)
+mDNSexport void
+init_log_utility_service(void)
 {
-    xpc_connection_t log_utility_listener = xpc_connection_create_mach_service(kDNSLogUtilityService, NULL, XPC_CONNECTION_MACH_SERVICE_LISTENER);
-    if (!log_utility_listener || xpc_get_type(log_utility_listener) != XPC_TYPE_CONNECTION) {
-        LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_ERROR, "Error Creating XPC Listener for Log Utility Server!");
-        return;
-    }
-
-    log_utility_server_queue = dispatch_queue_create("com.apple.mDNSResponder.log_utility_server_queue", NULL);
-
-    xpc_connection_set_event_handler(log_utility_listener, ^(xpc_object_t eventmsg) {
-        xpc_type_t type = xpc_get_type(eventmsg);
-
-        if (type == XPC_TYPE_CONNECTION) {
-            LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_INFO, "C%p {action='receives connection'}", eventmsg);
-            accept_client(eventmsg);
-        } else if (type == XPC_TYPE_ERROR) {
-            LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_ERROR, "C%p {xpc_error=\n" PUB_S "\n}", eventmsg,
-                      xpc_dictionary_get_string(eventmsg, XPC_ERROR_KEY_DESCRIPTION));
-        } else {
-            LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_ERROR, "C%p {error='receives unknown xpc request'}", eventmsg);
-        }
-    });
-
-    xpc_connection_resume(log_utility_listener);
+       xpc_connection_t log_utility_listener = xpc_connection_create_mach_service(kDNSLogUtilityService, NULL, XPC_CONNECTION_MACH_SERVICE_LISTENER);
+       if (!log_utility_listener || xpc_get_type(log_utility_listener) != XPC_TYPE_CONNECTION) {
+               LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_ERROR, "Error Creating XPC Listener for Log Utility Server!");
+               return;
+       }
+
+       log_utility_server_queue = dispatch_queue_create("com.apple.mDNSResponder.log_utility_server_queue", NULL);
+
+       xpc_connection_set_event_handler(log_utility_listener, ^(xpc_object_t eventmsg) {
+               xpc_type_t type = xpc_get_type(eventmsg);
+
+               if (type == XPC_TYPE_CONNECTION) {
+                       LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_INFO, "C%p {action='receives connection'}", eventmsg);
+                       accept_client(eventmsg);
+               } else if (type == XPC_TYPE_ERROR) {
+                       LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_ERROR, "C%p {xpc_error=\n" PUB_S "\n}", eventmsg,
+                               xpc_dictionary_get_string(eventmsg, XPC_ERROR_KEY_DESCRIPTION));
+               } else {
+                       LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_ERROR, "C%p {error='receives unknown xpc request'}", eventmsg);
+               }
+       });
+
+       xpc_connection_resume(log_utility_listener);
 }
 
-mDNSlocal void accept_client(xpc_connection_t conn)
+mDNSlocal void
+accept_client(xpc_connection_t conn)
 {
-    xpc_retain(conn);
-    xpc_connection_set_target_queue(conn, log_utility_server_queue);
-    xpc_connection_set_event_handler(conn, ^(xpc_object_t req_msg) {
-        xpc_type_t type = xpc_get_type(req_msg);
-
-        if (type == XPC_TYPE_DICTIONARY) {
-            handle_requests(req_msg);
-        } else { // We hit this case ONLY if Client Terminated Connection OR Crashed
-            LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT, "C%p {status='client closed the connection'}", conn);
-            xpc_release(conn);
-        }
-    });
-
-    xpc_connection_resume(conn);
+       xpc_retain(conn);
+       xpc_connection_set_target_queue(conn, log_utility_server_queue);
+       xpc_connection_set_event_handler(conn, ^(xpc_object_t req_msg) {
+               xpc_type_t type = xpc_get_type(req_msg);
+
+               if (type == XPC_TYPE_DICTIONARY) {
+                       handle_requests(req_msg);
+               } else { // We hit this case ONLY if Client Terminated Connection OR Crashed
+                       LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT, "C%p {status='client closed the connection'}", conn);
+                       xpc_release(conn);
+               }
+       });
+
+       xpc_connection_resume(conn);
 }
 
-mDNSlocal mDNSs8 handle_requests(xpc_object_t req)
+mDNSlocal mDNSs8
+handle_requests(xpc_object_t req)
 {
-    mDNSs8              ret = 0;
-    xpc_connection_t    remote_conn = xpc_dictionary_get_remote_connection(req);
-
-    LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_INFO, "C%p {action='handling log utility request'}", remote_conn);
-
-    // create the dictionary for response purpose
-    xpc_object_t response = xpc_dictionary_create_reply(req);
-    if (response == mDNSNULL) {
-        LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_ERROR, "C%p {error='cannot create reply response dictionary'}", remote_conn);
-        return -1;
-    }
-
-    mDNSu32 reply_value;
-    ret = check_permission(remote_conn);
-    if (ret < 0) {
-        // permission error
-        reply_value = kDNSMsg_Error;
-        if (ret == -1) {
-            xpc_dictionary_set_string(response, kDNSErrorDescription, "Client must be running as root");
-        } else if (ret == -2) {
-            xpc_dictionary_set_string(response, kDNSErrorDescription, "Client is missing the entitlement");
-        }
-    } else if (xpc_dictionary_get_uint64(req, kDNSStateDump)) {
-        mDNSu32     dump_option = (mDNSs32)xpc_dictionary_get_uint64(req, kDNSStateDump);
-        char        full_file_name[PATH_MAX];
-        mDNSs32     time_used;
-
-        // We do not dump state in the customer build due to privacy consideration.
-        if (IsAppleInternalBuild()) {
-            int client_fd = xpc_dictionary_dup_fd(req, kDNSStateDumpFD);
-            ret = handle_state_dump(dump_option, full_file_name, sizeof(full_file_name), client_fd, &time_used);
-            if (ret == 0) {
-                reply_value = kDNSMsg_NoError;
-                xpc_dictionary_set_int64(response, kDNSStateDumpTimeUsed, time_used);
-
-                if (dump_option != full_state_to_stdout) {
-                    xpc_dictionary_set_string(response, kDNSDumpFilePath, full_file_name);
-                }
-            } else {
-                reply_value = kDNSMsg_Error;
-                xpc_dictionary_set_string(response, kDNSErrorDescription, "State dump fails");
-            }
-            close(client_fd);
-        } else {
-            reply_value = kDNSMsg_Error;
-            xpc_dictionary_set_string(response, kDNSErrorDescription, "State dump is only enabled in internal builds");
-        }
-    } else {
-        LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_ERROR,
-                  "C%p {error='unknown log utility request from client'}", remote_conn);
-        reply_value = kDNSMsg_UnknownRequest;
-        xpc_dictionary_set_string(response, kDNSErrorDescription, "unknown log utility request from client");
-    }
-
-    xpc_dictionary_set_uint64(response, kDNSDaemonReply, reply_value);
-    xpc_connection_send_message(remote_conn, response);
-    xpc_release(response);
-
-    return 0;
+       mDNSs8                          ret = 0;
+       xpc_connection_t        remote_conn = xpc_dictionary_get_remote_connection(req);
+
+       LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_INFO, "C%p {action='handling log utility request'}", remote_conn);
+
+       // create the dictionary for response purpose
+       xpc_object_t response = xpc_dictionary_create_reply(req);
+       if (response == mDNSNULL) {
+               LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_ERROR, "C%p {error='cannot create reply response dictionary'}", remote_conn);
+               return -1;
+       }
+
+       mDNSu32 reply_value;
+       ret = check_permission(remote_conn);
+       if (ret < 0) {
+               // permission error
+               reply_value = kDNSMsg_Error;
+               if (ret == -1) {
+                       xpc_dictionary_set_string(response, kDNSErrorDescription, "Client must be running as root");
+               } else if (ret == -2) {
+                       xpc_dictionary_set_string(response, kDNSErrorDescription, "Client is missing the entitlement");
+               }
+       } else if (xpc_dictionary_get_uint64(req, kDNSStateDump)) {
+               mDNSu32         dump_option = (mDNSs32)xpc_dictionary_get_uint64(req, kDNSStateDump);
+               char            full_file_name[PATH_MAX];
+               mDNSs32         time_used;
+
+               // We do not dump state in the customer build due to privacy consideration.
+               if (IsAppleInternalBuild()) {
+                       int client_fd = xpc_dictionary_dup_fd(req, kDNSStateDumpFD);
+                       ret = handle_state_dump(dump_option, full_file_name, sizeof(full_file_name), client_fd, &time_used);
+                       if (ret == 0) {
+                               reply_value = kDNSMsg_NoError;
+                               xpc_dictionary_set_int64(response, kDNSStateDumpTimeUsed, time_used);
+
+                               if (dump_option != full_state_to_stdout) {
+                                       xpc_dictionary_set_string(response, kDNSDumpFilePath, full_file_name);
+                               }
+                       } else {
+                               reply_value = kDNSMsg_Error;
+                               xpc_dictionary_set_string(response, kDNSErrorDescription, "State dump fails");
+                       }
+                       close(client_fd);
+               } else {
+                       reply_value = kDNSMsg_Error;
+                       xpc_dictionary_set_string(response, kDNSErrorDescription, "State dump is only enabled in internal builds");
+               }
+       } else {
+               LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_ERROR,
+                                       "C%p {error='unknown log utility request from client'}", remote_conn);
+               reply_value = kDNSMsg_UnknownRequest;
+               xpc_dictionary_set_string(response, kDNSErrorDescription, "unknown log utility request from client");
+       }
+
+       xpc_dictionary_set_uint64(response, kDNSDaemonReply, reply_value);
+       xpc_connection_send_message(remote_conn, response);
+       xpc_release(response);
+
+       return 0;
 }
 
-mDNSlocal mDNSs8 check_permission(xpc_connection_t connection)
+mDNSlocal mDNSs8
+check_permission(xpc_connection_t connection)
 {
-    uid_t   client_euid = xpc_connection_get_euid(connection);
-    int     client_pid = xpc_connection_get_pid(connection);
-    mDNSs8  ret = 0;
-
-    if (client_euid != 0) {
-        LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT,
-                  "C%p {client_pid=%d,error='not running as root'}", connection, client_pid);
-        ret = -1;
-    }
-
-    if (!IsEntitled(connection, kDNSLogUtilityService)){
-        LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT,
-                  "C%p {client_pid=%d,error='Client is missing entitlement'}", connection, client_pid);
-        ret = -2;
-    }
-
-    return ret;
+       uid_t   client_euid = xpc_connection_get_euid(connection);
+       int             client_pid = xpc_connection_get_pid(connection);
+       mDNSs8  ret = 0;
+
+       if (client_euid != 0) {
+               LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT,
+                       "C%p {client_pid=%d,error='not running as root'}", connection, client_pid);
+               ret = -1;
+       }
+
+       if (!IsEntitled(connection, kDNSLogUtilityService)){
+               LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT,
+                       "C%p {client_pid=%d,error='Client is missing entitlement'}", connection, client_pid);
+               ret = -2;
+       }
+
+       return ret;
 }
 
 /*
@@ -187,286 +212,287 @@ mDNSlocal mDNSs8 check_permission(xpc_connection_t connection)
  * with the full path to the dumped file, and time_used is filled with time duration(ms) when mDNSResponder is
  * blocked. if_get_lock indicates if we should lock the kqueue before dumping the state.
  */
-mDNSexport mDNSs8 handle_state_dump(mDNSu32 dump_option, char *full_file_name, mDNSu32 name_buffer_len,
-                                    int client_fd, mDNSs32 *time_ms_used)
+mDNSlocal mDNSs8
+handle_state_dump(mDNSu32 dump_option, char *full_file_name, mDNSu32 name_buffer_len, int client_fd,
+       mDNSs32 *time_ms_used)
 {
-    mDNSs8 ret;
-
-    // record the start time, and lock the kqueue
-    struct timeval time_start;
-    gettimeofday(&time_start, mDNSNULL);
-    KQueueLock();
-
-    if (dump_option == full_state_to_stdout) {
-        dump_state_to_fd(client_fd);
-        ret = 0;
-    } else {
-        // dump_option == full_state || dump_option == full_state_with_compression
-        ret = handle_state_dump_to_fd(MDSNRESPONDER_STATE_DUMP_DIR, MDSNRESPONDER_STATE_DUMP_FILE_NAME,
-                                        full_file_name, name_buffer_len,
-                                        dump_option == full_state_with_compression ? mDNStrue : mDNSfalse);
-    }
-
-    // unlock the kqueue, record the end time and calculate the duration.
-    KQueueUnlock("State Dump");
-    struct timeval time_end;
-    gettimeofday(&time_end, mDNSNULL);
-    *time_ms_used = timediff_ms(&time_end, &time_start);
-
-    return ret;
+       mDNSs8 ret;
+
+       // record the start time, and lock the kqueue
+       struct timeval time_start;
+       gettimeofday(&time_start, mDNSNULL);
+       KQueueLock();
+
+       if (dump_option == full_state_to_stdout) {
+               dump_state_to_fd(client_fd);
+               ret = 0;
+       } else {
+               // dump_option == full_state || dump_option == full_state_with_compression
+               ret = handle_state_dump_to_fd(MDSNRESPONDER_STATE_DUMP_DIR, MDSNRESPONDER_STATE_DUMP_FILE_NAME,
+                       full_file_name, name_buffer_len, dump_option == full_state_with_compression ? mDNStrue : mDNSfalse);
+       }
+
+       // unlock the kqueue, record the end time and calculate the duration.
+       KQueueUnlock("State Dump");
+       struct timeval time_end;
+       gettimeofday(&time_end, mDNSNULL);
+       *time_ms_used = timediff_ms(&time_end, &time_start);
+
+       return ret;
 }
 
 #define MAX_NUM_DUMP_FILES 5 // controls how many files we are allowed to created for the state dump
-mDNSlocal mDNSs8 handle_state_dump_to_fd(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 name_buffer_len,
-                                         mDNSBool if_compress)
-{
-    char            oldest_file_name[PATH_MAX];
-    mDNSs32         dump_file_count = 0;
-    int             ret;
+mDNSlocal mDNSs8
+handle_state_dump_to_fd(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 buffer_len,
+       mDNSBool if_compress){
 
-    dump_file_count = find_oldest_state_dump(dump_dir, file_name, full_file_name, name_buffer_len, oldest_file_name);
-    require_action(dump_file_count >= 0, error,
-                   LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "find_oldest_state_dump fails"));
+       char                    oldest_file_name[PATH_MAX];
+       mDNSs32                 dump_file_count = 0;
+       int                             ret;
 
-    ret = remove_state_dump_if_too_many(dump_dir, oldest_file_name, dump_file_count, MAX_NUM_DUMP_FILES);
-    require_action(ret == 0, error,
-                   LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "remove_state_dump_if_too_many fails"));
+       dump_file_count = find_oldest_state_dump(dump_dir, file_name, full_file_name, buffer_len, oldest_file_name);
+       require_action(dump_file_count >= 0, error,
+               LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "find_oldest_state_dump fails"));
 
-    int fd = create_new_state_dump_file(dump_dir, file_name, full_file_name, name_buffer_len);
-    require_action(fd >= 0, error,
-                   LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "create_new_state_dump_file fails"));
+       ret = remove_state_dump_if_too_many(dump_dir, oldest_file_name, dump_file_count, MAX_NUM_DUMP_FILES);
+       require_action(ret == 0, error,
+               LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "remove_state_dump_if_too_many fails"));
 
-    dump_state_to_fd(fd);
-    close(fd); // create_new_state_dump_file open the file, we have to close it here
+       int fd = create_new_state_dump_file(dump_dir, file_name, full_file_name, buffer_len);
+       require_action(fd >= 0, error,
+               LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "create_new_state_dump_file fails"));
 
-    if (if_compress) {
-        ret = compress_state_dump_and_delete(full_file_name, name_buffer_len);
-        require_action(ret == 0, error,
-                       LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT, "State Dump: Error happens when trying to compress the state dump, reason: %s", strerror(errno)));
-    }
+       dump_state_to_fd(fd);
+       close(fd); // create_new_state_dump_file open the file, we have to close it here
 
-    return 0;
+       if (if_compress) {
+               ret = compress_state_dump_and_delete(full_file_name, buffer_len);
+               require_action(ret == 0, error,
+                       LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT, "State Dump: Error happens when trying to compress the state dump, reason: %s", strerror(errno)));
+       }
+
+       return 0;
 
 error:
-    return -1;
+       return -1;
 }
 
 /*
  * Scan the directory, find all the files that start with <mDNSResponder state dump file name>. Return the number of
  * state dump files and the name of the oldest file created.
  */
-mDNSlocal mDNSs32 find_oldest_state_dump(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 buffer_len,
-                                         char *oldest_file_name)
+mDNSlocal mDNSs32
+find_oldest_state_dump(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 buffer_len,
+       char *oldest_file_name)
 {
-    int ret;
-
-    full_file_name[0] = '\0';
-    full_file_name[buffer_len - 1]= '\0';
-    snprintf(full_file_name, buffer_len - 1, "%s/%s", dump_dir, file_name);
-
-    DIR *dir_p = opendir(dump_dir);
-    if (dir_p == mDNSNULL) {
-        LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT,
-                  "State Dump: directory " PUB_S " cannot be opened, reason: " PUB_S, dump_dir, strerror(errno));
-        return -1;
-    }
-
-    // scan every entry under directory, if starts with <mDNSResponder state dump file name>, check its create time.
-    struct dirent   *dir_entry_p = mDNSNULL;
-    mDNSu32         file_name_len = strnlen(file_name, MAXPATHLEN);
-    mDNSu8          dump_file_count = 0;
-    struct timespec oldest_time = {LONG_MAX, LONG_MAX};
-
-    while ((dir_entry_p = readdir(dir_p)) != mDNSNULL) {
-        if (dir_entry_p->d_namlen <= file_name_len)
-            continue;
-
-        if (strncmp(dir_entry_p->d_name, file_name, file_name_len) == 0) {
-            struct stat file_state;
-            snprintf(full_file_name, buffer_len - 1, "%s/%s", dump_dir, dir_entry_p->d_name);
-
-            // use stat to get creation time
-            ret = stat(full_file_name, &file_state);
-            if (ret != 0) {
-                LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT,
-                          "State Dump: error when reading file properties, reason: " PUB_S, strerror(errno));
-                return -1;
-            }
-            // if the file is older than the current record
-            if (oldest_time.tv_sec > file_state.st_birthtimespec.tv_sec
-                || (oldest_time.tv_sec == file_state.st_birthtimespec.tv_sec
-                    && oldest_time.tv_sec > file_state.st_birthtimespec.tv_sec)) {
-                oldest_time = file_state.st_birthtimespec;
-                strncpy(oldest_file_name, dir_entry_p->d_name, MIN(PATH_MAX - 1, dir_entry_p->d_namlen + 1));
-            }
-
-            dump_file_count++;
-        }
-    }
-    closedir(dir_p);
-
-    return dump_file_count;
+       int ret;
+
+       full_file_name[0] = '\0';
+       full_file_name[buffer_len - 1]= '\0';
+       snprintf(full_file_name, buffer_len - 1, "%s/%s", dump_dir, file_name);
+
+       DIR *dir_p = opendir(dump_dir);
+       if (dir_p == mDNSNULL) {
+               LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT,
+                       "State Dump: directory " PUB_S " cannot be opened, reason: " PUB_S, dump_dir, strerror(errno));
+               return -1;
+       }
+
+       // scan every entry under directory, if starts with <mDNSResponder state dump file name>, check its create time.
+       struct dirent   *dir_entry_p = mDNSNULL;
+       size_t                  file_name_len = strnlen(file_name, MAXPATHLEN);
+       mDNSu8                  dump_file_count = 0;
+       struct timespec oldest_time = {LONG_MAX, LONG_MAX};
+
+       while ((dir_entry_p = readdir(dir_p)) != mDNSNULL) {
+               if (dir_entry_p->d_namlen <= file_name_len)
+                       continue;
+
+               if (strncmp(dir_entry_p->d_name, file_name, file_name_len) == 0) {
+                       struct stat file_state;
+                       snprintf(full_file_name, buffer_len - 1, "%s/%s", dump_dir, dir_entry_p->d_name);
+
+                       // use stat to get creation time
+                       ret = stat(full_file_name, &file_state);
+                       if (ret != 0) {
+                               LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT,
+                                       "State Dump: error when reading file properties, reason: " PUB_S, strerror(errno));
+                               return -1;
+                       }
+                       // if the file is older than the current record
+                       if (oldest_time.tv_sec > file_state.st_birthtimespec.tv_sec
+                               || (oldest_time.tv_sec == file_state.st_birthtimespec.tv_sec
+                                       && oldest_time.tv_sec > file_state.st_birthtimespec.tv_sec)) {
+                               oldest_time = file_state.st_birthtimespec;
+                               strncpy(oldest_file_name, dir_entry_p->d_name, MIN(PATH_MAX - 1, dir_entry_p->d_namlen + 1));
+                       }
+
+                       dump_file_count++;
+               }
+       }
+       closedir(dir_p);
+
+       return dump_file_count;
 }
 
-mDNSlocal mDNSs8 remove_state_dump_if_too_many(const char *dump_dir, const char *oldest_file_name, mDNSs32 dump_file_count,
-                                               mDNSs32 max_allowed)
+mDNSlocal mDNSs8
+remove_state_dump_if_too_many(const char *dump_dir, const char *oldest_file_name, mDNSs32 dump_file_count,
+       mDNSs32 max_allowed)
 {
-    char path_file_to_remove[PATH_MAX];
-    path_file_to_remove[PATH_MAX - 1] = '\0';
-    // If the number of state dump files has reached the maximum value, we delete the oldest one.
-    if (dump_file_count == max_allowed) {
-        // construct the full name
-        snprintf(path_file_to_remove, PATH_MAX - 1, "%s/%s", dump_dir, oldest_file_name);
-        if (remove(path_file_to_remove) != 0) {
-            LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT,
-                      "State Dump: file " PUB_S " cannot be deleted, reason: " PUB_S, path_file_to_remove, strerror(errno));
-            return -1;
-        }
-    }
-
-    return 0;
+       char path_file_to_remove[PATH_MAX];
+       path_file_to_remove[PATH_MAX - 1] = '\0';
+       // If the number of state dump files has reached the maximum value, we delete the oldest one.
+       if (dump_file_count == max_allowed) {
+               // construct the full name
+               snprintf(path_file_to_remove, PATH_MAX - 1, "%s/%s", dump_dir, oldest_file_name);
+               if (remove(path_file_to_remove) != 0) {
+                       LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEFAULT,
+                               "State Dump: file " PUB_S " cannot be deleted, reason: " PUB_S, path_file_to_remove, strerror(errno));
+                       return -1;
+               }
+       }
+
+       return 0;
 }
 
 /*
  * Generate the file name of state dump with current time stamp, and return the FILE pointer, anyone who calls this
  * function must call fclose() to release the FILE pointer.
  */
-mDNSlocal int create_new_state_dump_file(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 buffer_len)
+mDNSlocal int
+create_new_state_dump_file(const char *dump_dir, const char *file_name, char *full_file_name, mDNSu32 buffer_len)
 {
-    struct timeval      now;
-    struct tm           local_time;
-    char                date_time_str[32];
-    char                time_zone_str[32];
-
-    gettimeofday(&now, NULL);
-    localtime_r(&now.tv_sec, &local_time);
-
-    // 2008-08-08_20-00-00
-    strftime(date_time_str, sizeof(date_time_str), "%F_%H-%M-%S", &local_time);
-    // +0800
-    strftime(time_zone_str, sizeof(time_zone_str), "%z", &local_time);
-    // /private/var/log/mDNSResponder/mDNSResponder_state_dump_2008-08-08_20-00-00-000000+0800.txt
-    snprintf(full_file_name, buffer_len, "%s/%s_%s-%06lu%s." STATE_DUMP_PLAIN_SUFFIX,
-             dump_dir, file_name, date_time_str, (unsigned long)now.tv_usec, time_zone_str);
-
-    int fd = open(full_file_name, O_WRONLY | O_CREAT, 0644); // 0644 means * (owning) User: read & write * Group: read * Other: read
-    if (fd < 0) {
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
-                  "State Dump: file " PUB_S " cannot be opened, reason: " PUB_S, full_file_name, strerror(errno));
-        return -1;
-    }
-
-    return fd;
+       struct timeval          now;
+       struct tm                       local_time;
+       char                            date_time_str[32];
+       char                            time_zone_str[32];
+
+       gettimeofday(&now, NULL);
+       localtime_r(&now.tv_sec, &local_time);
+
+       // 2008-08-08_20-00-00
+       strftime(date_time_str, sizeof(date_time_str), "%F_%H-%M-%S", &local_time);
+       // +0800
+       strftime(time_zone_str, sizeof(time_zone_str), "%z", &local_time);
+       // /private/var/log/mDNSResponder/mDNSResponder_state_dump_2008-08-08_20-00-00-000000+0800.txt
+       snprintf(full_file_name, buffer_len, "%s/%s_%s-%06lu%s." STATE_DUMP_PLAIN_SUFFIX,
+               dump_dir, file_name, date_time_str, (unsigned long)now.tv_usec, time_zone_str);
+
+       int fd = open(full_file_name, O_WRONLY | O_CREAT, 0644); // 0644 means * (owning) User: read & write * Group: read * Other: read
+       if (fd < 0) {
+               LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+                       "State Dump: file " PUB_S " cannot be opened, reason: " PUB_S, full_file_name, strerror(errno));
+               return -1;
+       }
+
+       return fd;
 }
 
 /*
  * Compress the state dump from pliantext to tar.bz2, remove the original one, and change the content of input_file to
  * newly created compressed file if compression succeeds.
  */
-mDNSlocal mDNSs8 compress_state_dump_and_delete(char *input_file, mDNSu32 buffer_len)
+mDNSlocal mDNSs8
+compress_state_dump_and_delete(char *input_file, mDNSu32 buffer_len)
 {
-    struct archive          *a = mDNSNULL;
-    struct archive_entry    *entry = mDNSNULL;
-    struct stat             st;
-    int                     fd = -1;
-    char                    output_file[PATH_MAX];
-    void                    *mapped_pointer = mDNSNULL;
-    int                     ret;
-
-    output_file[PATH_MAX - 1] = '\0';
-
-    a = archive_write_new();
-    require_action(a != mDNSNULL, error,
-                   LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "archive_write_new fails: " PUB_S, archive_error_string(a)));
-    archive_write_add_filter_bzip2(a);
-    archive_write_set_format_ustar(a);
-
-    // remove the .txt suffix, and append .tar.bz2 suffix
-    mDNSu32 plain_file_name_len = strlen(input_file); // input_file is guaranteed to be '\0'-terminated
-    strncpy(output_file, input_file, plain_file_name_len - sizeof(STATE_DUMP_PLAIN_SUFFIX));
-    output_file[plain_file_name_len - sizeof(STATE_DUMP_PLAIN_SUFFIX)] = '\0';
-    strncat(output_file, "." STATE_DUMP_COMPRESSED_SUFFIX, 1 + sizeof(STATE_DUMP_COMPRESSED_SUFFIX));
-
-    // open/create the archive for the given path name
-    ret = archive_write_open_filename(a, output_file);
-    require_action(ret == ARCHIVE_OK, error,
-                   LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "archive_write_open_filename fails: " PUB_S, archive_error_string(a)));
-
-    // get the state of file to be compressed
-    stat(input_file, &st);
-
-    // entry is required to create an archive
-    entry = archive_entry_new();
-    require_action(entry != mDNSNULL, error,
-                   LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "archive_entry_new fails: " PUB_S, strerror(errno)));
-
-    // set the name of file in the compressed file
-    const char *file_name_with_timestamp = strstr(input_file, MDSNRESPONDER_STATE_DUMP_FILE_NAME);
-    if (file_name_with_timestamp == mDNSNULL) {
-        file_name_with_timestamp = MDSNRESPONDER_STATE_DUMP_FILE_NAME "." STATE_DUMP_PLAIN_SUFFIX;
-    }
-
-    // copy the original file state to entry
-    archive_entry_copy_stat(entry, &st);
-    archive_entry_set_pathname(entry, file_name_with_timestamp);
-
-    // write entry into archive
-    do {
-        ret = archive_write_header(a, entry);
-    } while (ret == ARCHIVE_RETRY);
-    require_action(ret == ARCHIVE_OK, error,
-                   LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "archive_write_header fails: " PUB_S, archive_error_string(a)));
-
-    // if the original file has something to compress, use mmap to read its content
-    if (st.st_size > 0) {
-        fd = open(input_file, O_RDONLY);
-
-        mapped_pointer = mmap(NULL, st.st_size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0);
-        require_action(mapped_pointer != MAP_FAILED, error,
-                       LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "mmap fails: " PUB_S, strerror(errno)));
-
-        mDNSu32 amount_written = (mDNSu32)archive_write_data(a, mapped_pointer, st.st_size);
-        require_action(amount_written == (mDNSu32)st.st_size, error,
-                       LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "archive_write_data fails: amount_written(%u) != (%u)", amount_written, (mDNSu32)st.st_size));
-
-        int munmap_result = munmap(mapped_pointer, st.st_size);
-        require_action(munmap_result == 0, error,
-                       LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "munmap fails: " PUB_S, strerror(errno)));
-        mapped_pointer = mDNSNULL;
-
-        close(fd);
-        fd = -1; // set the file descriptor to -1 to avoid double free
-    }
-    archive_entry_free(entry);
-    entry = mDNSNULL;
-
-    archive_write_close(a);
-    archive_write_free(a);
-    a = mDNSNULL;
-
-    // remove the original one, return the newly created compressed file name
-    remove(input_file);
-    strncpy(input_file, output_file, buffer_len);
-    input_file[buffer_len - 1] = '\0';
-
-    return 0;
-
-error:
-    if (a != mDNSNULL) {
-        archive_write_close(a);
-        archive_write_free(a);
-    }
-    if (entry != mDNSNULL) {
-        archive_entry_free(entry);
-    }
-    if (fd != -1) {
-        close(fd);
-    }
-    if (mapped_pointer != mDNSNULL) {
-        munmap(mapped_pointer, st.st_size);
-    }
-    remove(input_file);
-    return -1;
+       struct archive                  *a                              = mDNSNULL;
+       mDNSBool                                archive_opened  = mDNSfalse;
+       struct archive_entry    *entry                  = mDNSNULL;
+       struct stat                             st;
+       int                                             fd = -1;
+       char                                    output_file[PATH_MAX];
+       void                                    *mapped_pointer = MAP_FAILED;
+       int                                             ret;
+       int                                             error;
+       mDNSu32                                 file_size;
+
+       output_file[PATH_MAX - 1] = '\0';
+
+       a = archive_write_new();
+       require_action(a != mDNSNULL, exit, error = -1;
+               LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "archive_write_new fails: " PUB_S, archive_error_string(a)));
+       archive_write_add_filter_bzip2(a);
+       archive_write_set_format_ustar(a);
+
+       // remove the .txt suffix, and append .tar.bz2 suffix
+       size_t plain_file_name_len = strlen(input_file); // input_file is guaranteed to be '\0'-terminated
+       strncpy(output_file, input_file, plain_file_name_len - sizeof(STATE_DUMP_PLAIN_SUFFIX));
+       output_file[plain_file_name_len - sizeof(STATE_DUMP_PLAIN_SUFFIX)] = '\0';
+       strncat(output_file, "." STATE_DUMP_COMPRESSED_SUFFIX, 1 + sizeof(STATE_DUMP_COMPRESSED_SUFFIX));
+
+       // open/create the archive for the given path name
+       ret = archive_write_open_filename(a, output_file);
+       archive_opened = (ret == ARCHIVE_OK);
+       require_action(archive_opened, exit, error = -1;
+               LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "archive_write_open_filename fails: " PUB_S, archive_error_string(a)));
+
+       // get the state of file to be compressed
+       stat(input_file, &st);
+
+       // entry is required to create an archive
+       entry = archive_entry_new();
+       require_action(entry != mDNSNULL, exit, error = -1;
+               LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "archive_entry_new fails: " PUB_S, strerror(errno)));
+
+       // set the name of file in the compressed file
+       const char *file_name_with_timestamp = strstr(input_file, MDSNRESPONDER_STATE_DUMP_FILE_NAME);
+       if (file_name_with_timestamp == mDNSNULL) {
+               file_name_with_timestamp = MDSNRESPONDER_STATE_DUMP_FILE_NAME "." STATE_DUMP_PLAIN_SUFFIX;
+       }
+
+       // copy the original file state to entry
+       archive_entry_copy_stat(entry, &st);
+       archive_entry_set_pathname(entry, file_name_with_timestamp);
+
+       // write entry into archive
+       do {
+               ret = archive_write_header(a, entry);
+       } while (ret == ARCHIVE_RETRY);
+       require_action(ret == ARCHIVE_OK, exit, error = -1;
+               LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "archive_write_header fails: " PUB_S, archive_error_string(a)));
+
+       // if the original file has something to compress, use mmap to read its content
+       require_action_quiet(st.st_size > 0, exit, error = -1);
+       require_action_quiet(st.st_size <= UINT32_MAX, exit, error = -1);
+
+       file_size = (mDNSu32)st.st_size;
+       fd = open(input_file, O_RDONLY);
+       require_action_quiet(fd != -1, exit, error = -1);
+
+       mapped_pointer = mmap(NULL, file_size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0);
+       require_action(mapped_pointer != MAP_FAILED, exit, error = -1;
+               LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "mmap fails: " PUB_S, strerror(errno)));
+
+       const ssize_t amount_written = archive_write_data(a, mapped_pointer, file_size);
+       require_action((amount_written >= 0) && (((size_t)amount_written) == file_size), exit, error = -1;
+               LogRedact(MDNS_LOG_CATEGORY_XPC, MDNS_LOG_DEBUG, "archive_write_data fails: amount_written(%ld) != (%u)",
+                       (long)amount_written, file_size));
+
+       error = 0;
+
+exit:
+       if (mapped_pointer != MAP_FAILED) {
+               munmap(mapped_pointer, file_size); // undo mmap
+       }
+       if (fd != -1) {
+               close(fd);                                                      // undo open
+       }
+       if (entry != mDNSNULL) {
+               archive_entry_free(entry);                      // undo archive_entry_new
+       }
+       if (archive_opened) {
+               archive_write_close(a);                         // undo archive_write_open_filename
+       }
+       if (a != mDNSNULL) {
+               archive_write_free(a);                          // undo archive_write_new
+       }
+       remove(input_file);
+       if (error == 0) {
+               strncpy(input_file, output_file, buffer_len);
+       } else {
+               input_file[0] = '\0';
+       }
+       return error;
 }
 
 /*
@@ -474,21 +500,23 @@ error:
  */
 #define US_PER_S 1000000
 #define MS_PER_S 1000
-mDNSlocal mDNSs32 timediff_ms(struct timeval* t1, struct timeval* t2)
+mDNSlocal mDNSs32
+timediff_ms(struct timeval *t1, struct timeval *t2)
 {
-    int usec, ms, sec;
-
-    if (t1->tv_sec < t2->tv_sec || (t1->tv_sec == t2->tv_sec && t1->tv_usec < t2->tv_usec))
-        return -timediff_ms(t2, t1);
-
-    sec = (int)(t1->tv_sec - t2->tv_sec);
-    if (t1->tv_usec >= t2->tv_usec)
-        usec = t1->tv_usec - t2->tv_usec;
-    else {
-        usec = t1->tv_usec + US_PER_S - t2->tv_usec;
-        sec -= 1;
-    }
-    ms = sec * MS_PER_S;
-    ms += usec / MS_PER_S;
-    return ms;
+       int usec, ms, sec;
+
+       if (t1->tv_sec < t2->tv_sec || (t1->tv_sec == t2->tv_sec && t1->tv_usec < t2->tv_usec)) {
+               return -timediff_ms(t2, t1);
+       }
+
+       sec = (int)(t1->tv_sec - t2->tv_sec);
+       if (t1->tv_usec >= t2->tv_usec) {
+               usec = t1->tv_usec - t2->tv_usec;
+       } else {
+               usec = t1->tv_usec + US_PER_S - t2->tv_usec;
+               sec -= 1;
+       }
+       ms = sec * MS_PER_S;
+       ms += usec / MS_PER_S;
+       return ms;
 }
index 5dd92017b602499dc877f8c991018c5a95a3b01b..2f23ff0755b8943b205b4a985a097acf02049971 100644 (file)
@@ -2,7 +2,7 @@
 //  xpc_service_log_utility.h
 //  mDNSResponder
 //
-//  Copyright (c) 2019 Apple Inc. All rights reserved.
+//  Copyright (c) 2019-2020 Apple Inc. All rights reserved.
 //
 
 #ifndef XPC_SERVICE_LOG_UTILITY_H
@@ -13,6 +13,7 @@
 #define MDSNRESPONDER_STATE_DUMP_DIR "/private/var/log/mDNSResponder"
 #define MDSNRESPONDER_STATE_DUMP_FILE_NAME "mDNSResponder_state_dump"
 
-mDNSexport void init_log_utility_service(void);
+mDNSexport void
+init_log_utility_service(void);
 
 #endif /* XPC_SERVICE_LOG_UTILITY_H */
index 16477ceaf282b6385e19623787c4bdabb5d86fd4..583772278b6f2aeb95c7a630e7aa13d8f49413f7 100755 (executable)
@@ -222,3 +222,324 @@ ifeq ($(wildcard /etc/init.d/rc2.d/), /etc/init.d/rc2.d/)
 STARTUPSCRIPTDIR = /etc/init.d
 RUNLEVELSCRIPTSDIR = /etc/init.d
 else
+# Else if directory /etc/rc.d/init.d/ exists, then we install into that (old Linux)
+ifeq ($(wildcard /etc/rc.d/init.d/), /etc/rc.d/init.d/)
+STARTUPSCRIPTDIR = /etc/rc.d/init.d
+RUNLEVELSCRIPTSDIR = /etc/rc.d
+else
+# Else if directory /etc/init.d/ exists, then we install into that (new Linux)
+ifeq ($(wildcard /etc/init.d/), /etc/init.d/)
+STARTUPSCRIPTDIR = /etc/init.d
+RUNLEVELSCRIPTSDIR = /etc
+else
+# Else install into /etc/rc.d/ (*BSD)
+STARTUPSCRIPTDIR = $(INSTBASE)/etc/rc.d
+endif
+endif
+endif
+
+MDNSCFLAGS = $(CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_OS) $(CFLAGS_DEBUGGING) $(CFLAGS_OPEN_SOURCE)
+
+#############################################################################
+
+all: setup Daemon libdns_sd Clients SAClient SAResponder SAProxyResponder NetMonitor $(OPTIONALTARG)
+
+install: setup InstalledStartup InstalledDaemon InstalledLib InstalledManPages InstalledClients $(OPTINSTALL)
+
+# 'setup' sets up the build directory structure the way we want
+setup:
+       @if test ! -d $(OBJDIR)   ; then mkdir -p $(OBJDIR)   ; fi
+       @if test ! -d $(BUILDDIR) ; then mkdir -p $(BUILDDIR) ; fi
+
+# clean removes targets and objects
+clean:
+       @if test -d $(OBJDIR)   ; then rm -r $(OBJDIR)   ; fi
+       @if test -d $(BUILDDIR) ; then rm -r $(BUILDDIR) ; fi
+       @$(MAKE) -C ../Clients clean
+
+#############################################################################
+
+# daemon target builds the daemon
+DAEMONOBJS = $(OBJDIR)/PosixDaemon.c.o $(OBJDIR)/mDNSPosix.c.o $(OBJDIR)/mDNSUNP.c.o $(OBJDIR)/mDNS.c.o \
+             $(OBJDIR)/DNSDigest.c.o $(OBJDIR)/uDNS.c.o $(OBJDIR)/DNSCommon.c.o $(OBJDIR)/uds_daemon.c.o \
+             $(OBJDIR)/mDNSDebug.c.o $(OBJDIR)/dnssd_ipc.c.o $(OBJDIR)/GenLinkedList.c.o \
+             $(OBJDIR)/PlatformCommon.c.o $(OBJDIR)/ClientRequests.c.o \
+             $(OBJDIR)/dso.c.o $(OBJDIR)/dso-transport.c.o $(OBJDIR)/dnssd_clientshim.c.o \
+             $(OBJDIR)/posix_utilities.c.o
+
+# dnsextd target build dnsextd
+DNSEXTDOBJ = $(OBJDIR)/mDNSPosix.c.o $(OBJDIR)/mDNSUNP.c.o $(OBJDIR)/mDNSDebug.c.o $(OBJDIR)/GenLinkedList.c.o $(OBJDIR)/DNSDigest.c.o \
+             $(OBJDIR)/DNSCommon.c.o $(OBJDIR)/PlatformCommon.c.o $(OBJDIR)/dnsextd_parser.y.o $(OBJDIR)/dnsextd_lexer.l.o
+
+Daemon: setup $(BUILDDIR)/mdnsd
+       @echo "Responder daemon done"
+
+$(BUILDDIR)/mdnsd: $(DAEMONOBJS)
+       $(CC) -o $@ $+ $(LINKOPTS)
+       $(STRIP) $@
+
+# libdns_sd target builds the client library
+libdns_sd: setup $(BUILDDIR)/libdns_sd.$(LDSUFFIX)
+       @echo "Client library done"
+
+CLIENTLIBOBJS = $(OBJDIR)/dnssd_clientlib.c.so.o $(OBJDIR)/dnssd_clientstub.c.so.o $(OBJDIR)/dnssd_ipc.c.so.o
+
+$(BUILDDIR)/libdns_sd.$(LDSUFFIX): $(CLIENTLIBOBJS)
+       $(LD) $(SOOPTS) $(LINKOPTS) -o $@ $+
+       $(STRIP) $@
+
+Clients: setup libdns_sd ../Clients/build/dns-sd
+       @echo "Clients done"
+
+../Clients/build/dns-sd: ../Clients/dns-sd.c
+       $(MAKE) -C ../Clients DEBUG=$(DEBUG) SUPMAKE_CFLAGS="$(MDNSCFLAGS)"
+
+# nss_mdns target builds the Name Service Switch module
+nss_mdns: setup $(BUILDDIR)/$(NSSLIBFILE)
+       @echo "Name Service Switch module done"
+
+$(BUILDDIR)/$(NSSLIBFILE): $(CLIENTLIBOBJS) $(OBJDIR)/nss_mdns.c.so.o
+       $(LD) $(SOOPTS) $(LINKOPTS) -o $@ $+
+       $(STRIP) $@
+
+#############################################################################
+
+# The Install targets place built stuff in their proper places
+InstalledDaemon: $(INSTBASE)/sbin/mdnsd
+       @echo $+ " installed"
+
+InstalledLib: $(INSTBASE)/lib/libdns_sd.$(LDSUFFIX).$(LIBVERS) $(INSTBASE)/include/dns_sd.h
+       @echo $+ " installed"
+
+InstalledStartup: $(STARTUPSCRIPTDIR)/$(STARTUPSCRIPTNAME)
+       @echo $+ " installed"
+
+InstalledManPages: $(MANPATH)/man8/mdnsd.8
+       @echo $+ " installed"
+
+InstalledClients: $(INSTBASE)/bin/dns-sd
+       @echo $+ " installed"
+
+InstalledNSS: $(NSSINSTPATH)/$(NSSLINKNAME) /etc/nss_mdns.conf $(MANPATH)/man5/nss_mdns.conf.5 $(MANPATH)/man8/libnss_mdns.8
+       @echo $+ " installed"
+
+# Note: If daemon already installed, we make sure it's stopped before overwriting it
+$(INSTBASE)/sbin/mdnsd: $(BUILDDIR)/mdnsd $(STARTUPSCRIPTDIR)/$(STARTUPSCRIPTNAME)
+       if test -x $@; then $(STARTUPSCRIPTDIR)/$(STARTUPSCRIPTNAME) stop; fi
+       $(CP) $< $@
+       $(STARTUPSCRIPTDIR)/$(STARTUPSCRIPTNAME) start
+
+$(INSTBASE)/lib/libdns_sd.$(LDSUFFIX).$(LIBVERS): $(BUILDDIR)/libdns_sd.$(LDSUFFIX)
+       $(CP) $< $@
+       $(LN) $@ $(INSTBASE)/lib/libdns_sd.$(LDSUFFIX)
+ifdef LDCONFIG
+       # -m means 'merge into existing database', -R means 'rescan directories'
+       $(LDCONFIG) -mR
+endif
+
+$(INSTBASE)/include/dns_sd.h: $(SHAREDDIR)/dns_sd.h
+       $(CP) $< $@
+
+$(STARTUPSCRIPTDIR)/$(STARTUPSCRIPTNAME): mdnsd.sh $(STARTUPSCRIPTDIR)
+       $(CP) $< $@
+       chmod ugo+x $@
+ifdef RUNLEVELSCRIPTSDIR
+ifeq ($(wildcard $(RUNLEVELSCRIPTSDIR)/runlevels/default), $(RUNLEVELSCRIPTSDIR)/runlevels/default)
+       $(LN) $@ $(RUNLEVELSCRIPTSDIR)/runlevels/default/mdns
+else
+       $(LN) $@ $(RUNLEVELSCRIPTSDIR)/rc2.d/S52mdns
+       $(LN) $@ $(RUNLEVELSCRIPTSDIR)/rc3.d/S52mdns
+       $(LN) $@ $(RUNLEVELSCRIPTSDIR)/rc4.d/S52mdns
+       $(LN) $@ $(RUNLEVELSCRIPTSDIR)/rc5.d/S52mdns
+       $(LN) $@ $(RUNLEVELSCRIPTSDIR)/rc0.d/K16mdns
+       $(LN) $@ $(RUNLEVELSCRIPTSDIR)/rc6.d/K16mdns
+endif
+endif
+
+$(MANPATH)/man5/%.5: %.5
+       cp $< $@
+       chmod 444 $@
+
+$(MANPATH)/man8/%.8: %.8
+       cp $< $@
+       chmod 444 $@
+
+$(MANPATH)/man8/mdnsd.8: $(SHAREDDIR)/mDNSResponder.8
+       cp $< $@
+       chmod 444 $@
+
+$(INSTBASE)/bin/dns-sd: ../Clients/build/dns-sd
+       $(CP) $< $@
+
+$(NSSINSTPATH)/$(NSSLINKNAME): $(NSSINSTPATH)/$(NSSLIBFILE)
+       $(LN) $< $@
+       ldconfig
+
+$(NSSINSTPATH)/$(NSSLIBFILE): $(BUILDDIR)/$(NSSLIBFILE)
+       $(CP) $< $@
+       chmod 444 $@
+
+/etc/nss_mdns.conf: nss_mdns.conf
+       $(CP) $< $@
+       chmod 444 $@
+# Check the nsswitch.conf file.
+# If 'mdns' does not already appear on the "hosts:" line, then add it right before 'dns'
+       cp -f /etc/nsswitch.conf /etc/nsswitch.conf.pre-mdns
+       sed -e '/mdns/!s/^\(hosts:.*\)dns\(.*\)/\1mdns dns\2/' /etc/nsswitch.conf.pre-mdns > /etc/nsswitch.conf
+
+#############################################################################
+
+# The following targets build Java wrappers for the dns-sd.h API.
+# Note that the JavaForXcode targets are used when building the project for OS X using Xcode
+
+JAVAC = $(JDK)/bin/javac
+JAVAH = $(JDK)/bin/javah
+JAVADOC = $(JDK)/bin/javadoc
+JAR = $(JDK)/bin/jar
+JAVACFLAGS = $(MDNSCFLAGS) $(JAVACFLAGS_OS) -I$(JDK)/include
+
+JavaForXcode_: setup $(BUILDDIR)/dns_sd.jar $(PROJECT_DERIVED_FILE_DIR)/DNSSD.java.h
+       @echo $@ done
+
+$(PROJECT_DERIVED_FILE_DIR)/DNSSD.java.h: $(OBJDIR)/DNSSD.java.h
+       @if test ! -d $(PROJECT_DERIVED_FILE_DIR) ; then mkdir -p $(PROJECT_DERIVED_FILE_DIR) ; fi
+       $(CP) $< $@
+
+JavaForXcode_clean:
+       @if test -d $(OBJDIR) ; then rm -r $(OBJDIR) ; fi
+       @if test -f $(PROJECT_DERIVED_FILE_DIR)/DNSSD.java.h ; then $(RM) $(PROJECT_DERIVED_FILE_DIR)/DNSSD.java.h ; fi
+       @if test -f $(BUILDDIR)/dns_sd.jar ; then $(RM) $(BUILDDIR)/dns_sd.jar ; fi
+       @echo $@ done
+
+JavaForXcode_installhdrs:
+       @echo $@ NOOP
+
+JavaForXcode_install: JavaForXcode_ $(DSTROOT)/$(SYSTEM_LIBRARY_DIR)/Java/Extensions/dns_sd.jar
+       @echo $@ done
+
+$(DSTROOT)/$(SYSTEM_LIBRARY_DIR)/Java/Extensions/dns_sd.jar: $(BUILDDIR)/dns_sd.jar
+       @if test ! -d $(DSTROOT)/$(SYSTEM_LIBRARY_DIR)/Java/Extensions ; then mkdir -p $(DSTROOT)/$(SYSTEM_LIBRARY_DIR)/Java/Extensions ; fi
+       $(CP) $< $@
+
+Java: setup $(BUILDDIR)/dns_sd.jar $(BUILDDIR)/libjdns_sd.$(LDSUFFIX)
+       @echo "Java wrappers done"
+
+JAVASRC        = $(SHAREDDIR)/Java
+JARCONTENTS =  $(OBJDIR)/com/apple/dnssd/DNSSDService.class \
+                               $(OBJDIR)/com/apple/dnssd/DNSSDException.class \
+                               $(OBJDIR)/com/apple/dnssd/DNSRecord.class \
+                               $(OBJDIR)/com/apple/dnssd/TXTRecord.class \
+                               $(OBJDIR)/com/apple/dnssd/DNSSDRegistration.class \
+                               $(OBJDIR)/com/apple/dnssd/BaseListener.class \
+                               $(OBJDIR)/com/apple/dnssd/BrowseListener.class \
+                               $(OBJDIR)/com/apple/dnssd/ResolveListener.class \
+                               $(OBJDIR)/com/apple/dnssd/RegisterListener.class \
+                               $(OBJDIR)/com/apple/dnssd/QueryListener.class \
+                               $(OBJDIR)/com/apple/dnssd/DomainListener.class \
+                               $(OBJDIR)/com/apple/dnssd/RegisterRecordListener.class \
+                               $(OBJDIR)/com/apple/dnssd/DNSSDRecordRegistrar.class \
+                               $(OBJDIR)/com/apple/dnssd/DNSSD.class
+
+$(BUILDDIR)/dns_sd.jar: $(JARCONTENTS) setup
+       $(JAR) -cf $@ -C $(OBJDIR) com
+
+$(BUILDDIR)/libjdns_sd.$(LDSUFFIX): $(JAVASRC)/JNISupport.c $(OBJDIR)/DNSSD.java.h setup libdns_sd
+       $(CC) -o $@ $< $(JAVACFLAGS) -I$(OBJDIR) -L$(BUILDDIR)
+
+$(OBJDIR)/com/apple/dnssd/%.class:     $(JAVASRC)/%.java
+       $(JAVAC) -d $(OBJDIR) -classpath $(OBJDIR) $<
+
+$(OBJDIR)/DNSSD.java.h: $(OBJDIR)/com/apple/dnssd/DNSSD.class
+       $(JAVAH) -force -classpath $(OBJDIR) -o $@ \
+               com.apple.dnssd.AppleDNSSD \
+               com.apple.dnssd.AppleBrowser \
+               com.apple.dnssd.AppleResolver \
+               com.apple.dnssd.AppleRegistration \
+               com.apple.dnssd.AppleQuery \
+               com.apple.dnssd.AppleDomainEnum \
+               com.apple.dnssd.AppleService \
+               com.apple.dnssd.AppleDNSRecord \
+               com.apple.dnssd.AppleRecordRegistrar
+
+#############################################################################
+
+# The following target builds documentation for the Java wrappers.
+
+JavaDoc: Java setup
+       $(JAVADOC) $(JAVASRC)/*.java -classpath $(OBJDIR) -d $(BUILDDIR) -public
+
+#############################################################################
+
+# The following targets build embedded example programs
+SPECIALOBJ = $(OBJDIR)/mDNSPosix.c.o $(OBJDIR)/mDNSUNP.c.o $(OBJDIR)/mDNSDebug.c.o $(OBJDIR)/GenLinkedList.c.o \
+       $(OBJDIR)/DNSDigest.c.o $(OBJDIR)/uDNS.c.o $(OBJDIR)/DNSCommon.c.o $(OBJDIR)/PlatformCommon.c.o \
+       $(OBJDIR)/dso.c.o $(OBJDIR)/dso-transport.c.o $(OBJDIR)/dnssd_clientshim.c.o
+COMMONOBJ  = $(SPECIALOBJ) $(OBJDIR)/mDNS.c.o
+APPOBJ     = $(COMMONOBJ) $(OBJDIR)/ExampleClientApp.c.o
+
+SAClient: setup $(BUILDDIR)/mDNSClientPosix
+       @echo "Embedded Standalone Client done"
+
+SAResponder: setup $(BUILDDIR)/mDNSResponderPosix
+       @echo "Embedded Standalone Responder done"
+
+SAProxyResponder: setup $(BUILDDIR)/mDNSProxyResponderPosix
+       @echo "Embedded Standalone ProxyResponder done"
+
+NetMonitor: setup $(BUILDDIR)/mDNSNetMonitor
+       @echo "NetMonitor done"
+
+dnsextd: setup $(BUILDDIR)/dnsextd
+       @echo "dnsextd done"
+
+$(BUILDDIR)/mDNSClientPosix:         $(APPOBJ)     $(OBJDIR)/Client.c.o
+       $(CC) $+ -o $@ $(LINKOPTS)
+
+$(BUILDDIR)/mDNSResponderPosix:      $(COMMONOBJ)  $(OBJDIR)/Responder.c.o
+       $(CC) $+ -o $@ $(LINKOPTS)
+
+$(BUILDDIR)/mDNSProxyResponderPosix: $(COMMONOBJ)  $(OBJDIR)/ProxyResponder.c.o
+       $(CC) $+ -o $@ $(LINKOPTS)
+
+$(BUILDDIR)/mDNSNetMonitor:          $(SPECIALOBJ) $(OBJDIR)/NetMonitor.c.o
+       $(CC) $+ -o $@ $(LINKOPTS)
+
+$(OBJDIR)/NetMonitor.c.o:            $(COREDIR)/mDNS.c # Note: NetMonitor.c textually imports mDNS.c
+
+$(BUILDDIR)/dnsextd:                 $(DNSEXTDOBJ) $(OBJDIR)/dnsextd.c.threadsafe.o
+       $(CC) $+ -o $@ $(LINKOPTS) $(LINKOPTS_PTHREAD)
+
+#############################################################################
+
+# Implicit rules
+$(OBJDIR)/%.c.o:       %.c
+       $(CC) $(MDNSCFLAGS) -c -o $@ $<
+
+$(OBJDIR)/%.c.o:       $(COREDIR)/%.c
+       $(CC) $(MDNSCFLAGS) -c -o $@ $<
+
+$(OBJDIR)/%.c.o:       $(SHAREDDIR)/%.c
+       $(CC) $(MDNSCFLAGS) -c -o $@ $<
+
+$(OBJDIR)/%.c.o:       $(DSODIR)/%.c
+       $(CC) $(MDNSCFLAGS) -c -o $@ $<
+
+$(OBJDIR)/%.c.threadsafe.o:    %.c
+       $(CC) $(MDNSCFLAGS) $(MDNSCFLAGS_PTHREAD) -D_REENTRANT -c -o $@ $<
+
+$(OBJDIR)/%.c.threadsafe.o:    $(SHAREDDIR)/%.c
+       $(CC) $(MDNSCFLAGS) $(MDNSCFLAGS_PTHREAD) -D_REENTRANT -c -o $@ $<
+
+$(OBJDIR)/%.c.so.o:    %.c
+       $(CC) $(MDNSCFLAGS) -c -fPIC -o $@ $<
+
+$(OBJDIR)/%.c.so.o:    $(SHAREDDIR)/%.c
+       $(CC) $(MDNSCFLAGS) -c -fPIC -o $@ $<
+
+$(OBJDIR)/%.y.o: $(SHAREDDIR)/%.y
+       $(BISON)              -o $(OBJDIR)/$*.c -d $<
+       $(CC) $(MDNSCFLAGS) -c -o $@ $(OBJDIR)/$*.c
+
+$(OBJDIR)/%.l.o: $(SHAREDDIR)/%.l
+       $(FLEX) $(FLEXFLAGS_OS) -i             -o$(OBJDIR)/$*.l.c $<
+       $(CC) $(MDNSCFLAGS) -Wno-error -c -o $@ $(OBJDIR)/$*.l.c
index 9793195e84ab32da12c870a097ad2314383dad37..d1bec5f5c06495c42b171f94e5e08b4c7acff58e 100755 (executable)
@@ -21,8 +21,6 @@
 #include "mDNSPosix.h"               // Defines the specific types needed to run mDNS on this platform
 #include "PlatformCommon.h"
 #include "dns_sd.h"
-#include "dnssec.h"
-#include "nsec.h"
 
 #include <assert.h>
 #include <stdio.h>
@@ -1318,7 +1316,8 @@ mDNSlocal int SetupInterfaceList(mDNS *const m)
         struct ifaddrs *i = intfList;
         while (i)
         {
-            if (     ((i->ifa_addr->sa_family == AF_INET)
+            if (     i->ifa_addr != NULL &&
+                     ((i->ifa_addr->sa_family == AF_INET)
 #if HAVE_IPV6
                       || (i->ifa_addr->sa_family == AF_INET6)
 #endif
@@ -1798,31 +1797,6 @@ mDNSexport void mDNSPlatformQsort(void *base, int nel, int width, int (*compar)(
     return (qsort(base, nel, width, compar));
 }
 
-// DNSSEC stub functions
-mDNSexport void VerifySignature(mDNS *const m, DNSSECVerifier *dv, DNSQuestion *q)
-{
-    (void)m;
-    (void)dv;
-    (void)q;
-}
-
-mDNSexport mDNSBool AddNSECSForCacheRecord(mDNS *const m, CacheRecord *crlist, CacheRecord *negcr, mDNSu8 rcode)
-{
-    (void)m;
-    (void)crlist;
-    (void)negcr;
-    (void)rcode;
-    return mDNSfalse;
-}
-
-mDNSexport void BumpDNSSECStats(mDNS *const m, DNSSECStatsAction action, DNSSECStatsType type, mDNSu32 value)
-{
-    (void)m;
-    (void)action;
-    (void)type;
-    (void)value;
-}
-
 // Proxy stub functions
 mDNSexport mDNSu8 *DNSProxySetAttributes(DNSQuestion *q, DNSMessageHeader *h, DNSMessage *msg, mDNSu8 *ptr, mDNSu8 *limit)
 {
old mode 100755 (executable)
new mode 100644 (file)
index c93f95a..f1884eb
@@ -1,5 +1,7 @@
-Microsoft Visual Studio Solution File, Format Version 11.00\r
-# Visual Studio 2010\r
+Microsoft Visual Studio Solution File, Format Version 12.00\r
+# Visual Studio Version 16\r
+VisualStudioVersion = 16.0.29911.84\r
+MinimumVisualStudioVersion = 10.0.40219.1\r
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DLL", "mDNSWindows\DLL\dnssd.vcxproj", "{AB581101-18F0-46F6-B56A-83A6B1EA657E}"\r
 EndProject\r
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mDNSResponder", "mDNSWindows\SystemService\Service.vcxproj", "{C1D98254-BA27-4427-A3BE-A68CA2CC5F69}"\r
@@ -37,18 +39,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dns-sd", "Clients\DNS-SD.Vi
                {AB581101-18F0-46F6-B56A-83A6B1EA657E} = {AB581101-18F0-46F6-B56A-83A6B1EA657E}\r
        EndProjectSection\r
 EndProject\r
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Java", "mDNSWindows\Java\Java.vcxproj", "{9CE2568A-3170-41C6-9F20-A0188A9EC114}"\r
-EndProject\r
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JavaSamples", "Clients\Java\JavaSamples.vcxproj", "{A987A0C1-344F-475C-869C-F082EB11EEBA}"\r
-EndProject\r
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DLLStub", "mDNSWindows\DLLStub\DLLStub.vcxproj", "{3A2B6325-3053-4236-84BD-AA9BE2E323E5}"\r
 EndProject\r
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DLLX", "mDNSWindows\DLLX\DLLX.vcxproj", "{78FBFCC5-2873-4AE2-9114-A08082F71124}"\r
 EndProject\r
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DNSServiceBrowser.NET", "Clients\DNSServiceBrowser.NET\DNSServiceBrowser.NET.csproj", "{DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}"\r
-EndProject\r
-Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "DNSServiceBrowser.VB", "Clients\DNSServiceBrowser.VB\DNSServiceBrowser.VB.vbproj", "{FB79E297-5703-435C-A829-51AA51CD71C2}"\r
-EndProject\r
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mDNSNetMonitor", "Clients\mDNSNetMonitor.VisualStudio\mDNSNetMonitor.vcxproj", "{AF35C285-528D-46A1-8A0E-47B0733DC718}"\r
 EndProject\r
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ControlPanelLocRes", "mDNSWindows\ControlPanel\ControlPanelLocRes.vcxproj", "{4490229E-025A-478F-A2CF-51154DA83E39}"\r
@@ -63,320 +57,291 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ControlPanel", "mDNSWindows
                {4490229E-025A-478F-A2CF-51154DA83E39} = {4490229E-025A-478F-A2CF-51154DA83E39}\r
        EndProjectSection\r
 EndProject\r
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FirefoxExtension", "Clients\FirefoxExtension\FirefoxExtension.vcxproj", "{7826EA27-D4CC-4FAA-AD23-DF813823227B}"\r
-EndProject\r
 Global\r
        GlobalSection(SolutionConfigurationPlatforms) = preSolution\r
-               Debug|Any CPU = Debug|Any CPU\r
+               Debug|ARM64 = Debug|ARM64\r
                Debug|Mixed Platforms = Debug|Mixed Platforms\r
                Debug|Win32 = Debug|Win32\r
                Debug|x64 = Debug|x64\r
-               Release|Any CPU = Release|Any CPU\r
+               Release|ARM64 = Release|ARM64\r
                Release|Mixed Platforms = Release|Mixed Platforms\r
                Release|Win32 = Release|Win32\r
                Release|x64 = Release|x64\r
        EndGlobalSection\r
        GlobalSection(ProjectConfigurationPlatforms) = postSolution\r
-               {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|Any CPU.ActiveCfg = Debug|x64\r
+               {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|ARM64.Build.0 = Debug|ARM64\r
                {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
                {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
                {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|Win32.Build.0 = Debug|Win32\r
                {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|x64.ActiveCfg = Debug|x64\r
                {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|x64.Build.0 = Debug|x64\r
-               {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|Any CPU.ActiveCfg = Release|x64\r
+               {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|ARM64.Build.0 = Release|ARM64\r
                {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
                {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|Mixed Platforms.Build.0 = Release|x64\r
                {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|Win32.ActiveCfg = Release|Win32\r
                {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|Win32.Build.0 = Release|Win32\r
                {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|x64.ActiveCfg = Release|x64\r
                {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|x64.Build.0 = Release|x64\r
-               {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|Any CPU.ActiveCfg = Debug|x64\r
+               {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|ARM64.Build.0 = Debug|ARM64\r
                {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
                {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
                {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|Win32.Build.0 = Debug|Win32\r
                {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|x64.ActiveCfg = Debug|x64\r
                {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|x64.Build.0 = Debug|x64\r
-               {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|Any CPU.ActiveCfg = Release|x64\r
+               {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|ARM64.Build.0 = Release|ARM64\r
                {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
                {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|Mixed Platforms.Build.0 = Release|x64\r
                {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|Win32.ActiveCfg = Release|Win32\r
                {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|Win32.Build.0 = Release|Win32\r
                {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|x64.ActiveCfg = Release|x64\r
                {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|x64.Build.0 = Release|x64\r
-               {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|Any CPU.ActiveCfg = Debug|x64\r
+               {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|ARM64.Build.0 = Debug|ARM64\r
                {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
                {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
                {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|Win32.Build.0 = Debug|Win32\r
                {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|x64.ActiveCfg = Debug|x64\r
                {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|x64.Build.0 = Debug|x64\r
-               {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|Any CPU.ActiveCfg = Release|x64\r
+               {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|ARM64.Build.0 = Release|ARM64\r
                {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
                {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|Mixed Platforms.Build.0 = Release|x64\r
                {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|Win32.ActiveCfg = Release|Win32\r
                {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|Win32.Build.0 = Release|Win32\r
                {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|x64.ActiveCfg = Release|x64\r
                {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|x64.Build.0 = Release|x64\r
-               {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|Any CPU.ActiveCfg = Debug|x64\r
+               {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|ARM64.Build.0 = Debug|ARM64\r
                {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
                {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
                {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|Win32.Build.0 = Debug|Win32\r
                {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|x64.ActiveCfg = Debug|x64\r
                {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|x64.Build.0 = Debug|x64\r
-               {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|Any CPU.ActiveCfg = Release|x64\r
+               {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|ARM64.Build.0 = Release|ARM64\r
                {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
                {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|Mixed Platforms.Build.0 = Release|x64\r
                {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|Win32.ActiveCfg = Release|Win32\r
                {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|Win32.Build.0 = Release|Win32\r
                {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|x64.ActiveCfg = Release|x64\r
                {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|x64.Build.0 = Release|x64\r
-               {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|Any CPU.ActiveCfg = Debug|x64\r
+               {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|ARM64.Build.0 = Debug|ARM64\r
                {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
                {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
                {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|Win32.Build.0 = Debug|Win32\r
                {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|x64.ActiveCfg = Debug|x64\r
                {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|x64.Build.0 = Debug|x64\r
-               {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|Any CPU.ActiveCfg = Release|x64\r
+               {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|ARM64.Build.0 = Release|ARM64\r
                {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
                {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|Mixed Platforms.Build.0 = Release|x64\r
                {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|Win32.ActiveCfg = Release|Win32\r
                {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|Win32.Build.0 = Release|Win32\r
                {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|x64.ActiveCfg = Release|x64\r
                {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|x64.Build.0 = Release|x64\r
-               {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|Any CPU.ActiveCfg = Debug|x64\r
+               {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|ARM64.Build.0 = Debug|ARM64\r
                {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
                {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
                {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|Win32.Build.0 = Debug|Win32\r
                {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|x64.ActiveCfg = Debug|x64\r
                {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|x64.Build.0 = Debug|x64\r
-               {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|Any CPU.ActiveCfg = Release|x64\r
+               {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|ARM64.Build.0 = Release|ARM64\r
                {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
                {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|Mixed Platforms.Build.0 = Release|x64\r
                {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|Win32.ActiveCfg = Release|Win32\r
                {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|Win32.Build.0 = Release|Win32\r
                {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|x64.ActiveCfg = Release|x64\r
                {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|x64.Build.0 = Release|x64\r
-               {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|Any CPU.ActiveCfg = Debug|x64\r
+               {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|ARM64.Build.0 = Debug|ARM64\r
                {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
                {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
                {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|Win32.Build.0 = Debug|Win32\r
                {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|x64.ActiveCfg = Debug|x64\r
                {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|x64.Build.0 = Debug|x64\r
-               {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|Any CPU.ActiveCfg = Release|x64\r
+               {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|ARM64.Build.0 = Release|ARM64\r
                {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
                {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|Mixed Platforms.Build.0 = Release|x64\r
                {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|Win32.ActiveCfg = Release|Win32\r
                {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|Win32.Build.0 = Release|Win32\r
                {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|x64.ActiveCfg = Release|x64\r
                {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|x64.Build.0 = Release|x64\r
-               {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|Any CPU.ActiveCfg = Debug|x64\r
+               {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|ARM64.Build.0 = Debug|ARM64\r
                {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
                {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
                {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|Win32.Build.0 = Debug|Win32\r
                {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|x64.ActiveCfg = Debug|x64\r
                {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|x64.Build.0 = Debug|x64\r
-               {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|Any CPU.ActiveCfg = Release|x64\r
+               {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|ARM64.Build.0 = Release|ARM64\r
                {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
                {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|Mixed Platforms.Build.0 = Release|x64\r
                {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|Win32.ActiveCfg = Release|Win32\r
                {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|Win32.Build.0 = Release|Win32\r
                {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|x64.ActiveCfg = Release|x64\r
                {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|x64.Build.0 = Release|x64\r
-               {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|Any CPU.ActiveCfg = Debug|x64\r
+               {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|ARM64.Build.0 = Debug|ARM64\r
                {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
                {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
                {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|Win32.Build.0 = Debug|Win32\r
                {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|x64.ActiveCfg = Debug|x64\r
                {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|x64.Build.0 = Debug|x64\r
-               {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|Any CPU.ActiveCfg = Release|x64\r
+               {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|ARM64.Build.0 = Release|ARM64\r
                {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
                {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|Mixed Platforms.Build.0 = Release|x64\r
                {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|Win32.ActiveCfg = Release|Win32\r
                {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|Win32.Build.0 = Release|Win32\r
                {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|x64.ActiveCfg = Release|x64\r
                {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|x64.Build.0 = Release|x64\r
-               {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|Any CPU.ActiveCfg = Debug|x64\r
+               {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|ARM64.Build.0 = Debug|ARM64\r
                {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
                {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
                {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|Win32.Build.0 = Debug|Win32\r
                {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|x64.ActiveCfg = Debug|x64\r
                {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|x64.Build.0 = Debug|x64\r
-               {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|Any CPU.ActiveCfg = Release|x64\r
+               {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|ARM64.Build.0 = Release|ARM64\r
                {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
                {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|Mixed Platforms.Build.0 = Release|x64\r
                {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|Win32.ActiveCfg = Release|Win32\r
                {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|Win32.Build.0 = Release|Win32\r
                {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|x64.ActiveCfg = Release|x64\r
                {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|x64.Build.0 = Release|x64\r
-               {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|Any CPU.ActiveCfg = Debug|x64\r
+               {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|ARM64.Build.0 = Debug|ARM64\r
                {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
                {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
                {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|Win32.Build.0 = Debug|Win32\r
                {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|x64.ActiveCfg = Debug|x64\r
                {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|x64.Build.0 = Debug|x64\r
-               {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|Any CPU.ActiveCfg = Release|x64\r
+               {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|ARM64.Build.0 = Release|ARM64\r
                {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
                {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|Mixed Platforms.Build.0 = Release|x64\r
                {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|Win32.ActiveCfg = Release|Win32\r
                {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|Win32.Build.0 = Release|Win32\r
                {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|x64.ActiveCfg = Release|x64\r
                {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|x64.Build.0 = Release|x64\r
-               {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Debug|Any CPU.ActiveCfg = Debug|x64\r
-               {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
-               {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
-               {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Debug|Win32.ActiveCfg = Debug|Win32\r
-               {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Debug|Win32.Build.0 = Debug|Win32\r
-               {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Debug|x64.ActiveCfg = Debug|x64\r
-               {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Debug|x64.Build.0 = Debug|x64\r
-               {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Release|Any CPU.ActiveCfg = Release|x64\r
-               {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
-               {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Release|Mixed Platforms.Build.0 = Release|x64\r
-               {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Release|Win32.ActiveCfg = Release|Win32\r
-               {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Release|Win32.Build.0 = Release|Win32\r
-               {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Release|x64.ActiveCfg = Release|x64\r
-               {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Release|x64.Build.0 = Release|x64\r
-               {A987A0C1-344F-475C-869C-F082EB11EEBA}.Debug|Any CPU.ActiveCfg = Debug|x64\r
-               {A987A0C1-344F-475C-869C-F082EB11EEBA}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
-               {A987A0C1-344F-475C-869C-F082EB11EEBA}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
-               {A987A0C1-344F-475C-869C-F082EB11EEBA}.Debug|Win32.ActiveCfg = Debug|Win32\r
-               {A987A0C1-344F-475C-869C-F082EB11EEBA}.Debug|Win32.Build.0 = Debug|Win32\r
-               {A987A0C1-344F-475C-869C-F082EB11EEBA}.Debug|x64.ActiveCfg = Debug|x64\r
-               {A987A0C1-344F-475C-869C-F082EB11EEBA}.Release|Any CPU.ActiveCfg = Release|x64\r
-               {A987A0C1-344F-475C-869C-F082EB11EEBA}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
-               {A987A0C1-344F-475C-869C-F082EB11EEBA}.Release|Mixed Platforms.Build.0 = Release|x64\r
-               {A987A0C1-344F-475C-869C-F082EB11EEBA}.Release|Win32.ActiveCfg = Release|Win32\r
-               {A987A0C1-344F-475C-869C-F082EB11EEBA}.Release|Win32.Build.0 = Release|Win32\r
-               {A987A0C1-344F-475C-869C-F082EB11EEBA}.Release|x64.ActiveCfg = Release|x64\r
-               {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|Any CPU.ActiveCfg = Debug|x64\r
+               {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|ARM64.Build.0 = Debug|ARM64\r
                {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
                {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
                {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|Win32.Build.0 = Debug|Win32\r
                {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|x64.ActiveCfg = Debug|x64\r
                {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|x64.Build.0 = Debug|x64\r
-               {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|Any CPU.ActiveCfg = Release|x64\r
+               {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|ARM64.Build.0 = Release|ARM64\r
                {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
                {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|Mixed Platforms.Build.0 = Release|x64\r
                {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|Win32.ActiveCfg = Release|Win32\r
                {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|Win32.Build.0 = Release|Win32\r
                {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|x64.ActiveCfg = Release|x64\r
                {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|x64.Build.0 = Release|x64\r
-               {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|Any CPU.ActiveCfg = Debug|x64\r
+               {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|ARM64.Build.0 = Debug|ARM64\r
                {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
                {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
                {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|Win32.Build.0 = Debug|Win32\r
                {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|x64.ActiveCfg = Debug|x64\r
                {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|x64.Build.0 = Debug|x64\r
-               {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|Any CPU.ActiveCfg = Release|x64\r
+               {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|ARM64.Build.0 = Release|ARM64\r
                {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
                {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|Mixed Platforms.Build.0 = Release|x64\r
                {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|Win32.ActiveCfg = Release|Win32\r
                {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|Win32.Build.0 = Release|Win32\r
                {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|x64.ActiveCfg = Release|x64\r
                {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|x64.Build.0 = Release|x64\r
-               {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\r
-               {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Debug|Any CPU.Build.0 = Debug|Any CPU\r
-               {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU\r
-               {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU\r
-               {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Debug|Win32.ActiveCfg = Debug|Any CPU\r
-               {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Debug|x64.ActiveCfg = Debug|Any CPU\r
-               {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Release|Any CPU.ActiveCfg = Release|Any CPU\r
-               {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Release|Any CPU.Build.0 = Release|Any CPU\r
-               {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU\r
-               {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Release|Mixed Platforms.Build.0 = Release|Any CPU\r
-               {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Release|Win32.ActiveCfg = Release|Any CPU\r
-               {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Release|x64.ActiveCfg = Release|Any CPU\r
-               {FB79E297-5703-435C-A829-51AA51CD71C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\r
-               {FB79E297-5703-435C-A829-51AA51CD71C2}.Debug|Any CPU.Build.0 = Debug|Any CPU\r
-               {FB79E297-5703-435C-A829-51AA51CD71C2}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU\r
-               {FB79E297-5703-435C-A829-51AA51CD71C2}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU\r
-               {FB79E297-5703-435C-A829-51AA51CD71C2}.Debug|Win32.ActiveCfg = Debug|Any CPU\r
-               {FB79E297-5703-435C-A829-51AA51CD71C2}.Debug|x64.ActiveCfg = Debug|Any CPU\r
-               {FB79E297-5703-435C-A829-51AA51CD71C2}.Release|Any CPU.ActiveCfg = Release|Any CPU\r
-               {FB79E297-5703-435C-A829-51AA51CD71C2}.Release|Any CPU.Build.0 = Release|Any CPU\r
-               {FB79E297-5703-435C-A829-51AA51CD71C2}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU\r
-               {FB79E297-5703-435C-A829-51AA51CD71C2}.Release|Mixed Platforms.Build.0 = Release|Any CPU\r
-               {FB79E297-5703-435C-A829-51AA51CD71C2}.Release|Win32.ActiveCfg = Release|Any CPU\r
-               {FB79E297-5703-435C-A829-51AA51CD71C2}.Release|x64.ActiveCfg = Release|Any CPU\r
-               {AF35C285-528D-46A1-8A0E-47B0733DC718}.Debug|Any CPU.ActiveCfg = Debug|Win32\r
+               {AF35C285-528D-46A1-8A0E-47B0733DC718}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
                {AF35C285-528D-46A1-8A0E-47B0733DC718}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32\r
                {AF35C285-528D-46A1-8A0E-47B0733DC718}.Debug|Mixed Platforms.Build.0 = Debug|Win32\r
                {AF35C285-528D-46A1-8A0E-47B0733DC718}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {AF35C285-528D-46A1-8A0E-47B0733DC718}.Debug|Win32.Build.0 = Debug|Win32\r
                {AF35C285-528D-46A1-8A0E-47B0733DC718}.Debug|x64.ActiveCfg = Debug|x64\r
-               {AF35C285-528D-46A1-8A0E-47B0733DC718}.Release|Any CPU.ActiveCfg = Release|Win32\r
+               {AF35C285-528D-46A1-8A0E-47B0733DC718}.Release|ARM64.ActiveCfg = Release|ARM64\r
                {AF35C285-528D-46A1-8A0E-47B0733DC718}.Release|Mixed Platforms.ActiveCfg = Release|Win32\r
                {AF35C285-528D-46A1-8A0E-47B0733DC718}.Release|Mixed Platforms.Build.0 = Release|Win32\r
                {AF35C285-528D-46A1-8A0E-47B0733DC718}.Release|Win32.ActiveCfg = Release|Win32\r
                {AF35C285-528D-46A1-8A0E-47B0733DC718}.Release|Win32.Build.0 = Release|Win32\r
                {AF35C285-528D-46A1-8A0E-47B0733DC718}.Release|x64.ActiveCfg = Release|Win32\r
-               {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|Any CPU.ActiveCfg = Debug|x64\r
+               {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|ARM64.Build.0 = Debug|ARM64\r
                {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
                {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
                {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|Win32.Build.0 = Debug|Win32\r
                {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|x64.ActiveCfg = Debug|x64\r
                {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|x64.Build.0 = Debug|x64\r
-               {4490229E-025A-478F-A2CF-51154DA83E39}.Release|Any CPU.ActiveCfg = Release|x64\r
+               {4490229E-025A-478F-A2CF-51154DA83E39}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {4490229E-025A-478F-A2CF-51154DA83E39}.Release|ARM64.Build.0 = Release|ARM64\r
                {4490229E-025A-478F-A2CF-51154DA83E39}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
                {4490229E-025A-478F-A2CF-51154DA83E39}.Release|Mixed Platforms.Build.0 = Release|x64\r
                {4490229E-025A-478F-A2CF-51154DA83E39}.Release|Win32.ActiveCfg = Release|Win32\r
                {4490229E-025A-478F-A2CF-51154DA83E39}.Release|Win32.Build.0 = Release|Win32\r
                {4490229E-025A-478F-A2CF-51154DA83E39}.Release|x64.ActiveCfg = Release|x64\r
                {4490229E-025A-478F-A2CF-51154DA83E39}.Release|x64.Build.0 = Release|x64\r
-               {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|Any CPU.ActiveCfg = Debug|x64\r
+               {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|ARM64.Build.0 = Debug|ARM64\r
                {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
                {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
                {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|Win32.Build.0 = Debug|Win32\r
                {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|x64.ActiveCfg = Debug|x64\r
                {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|x64.Build.0 = Debug|x64\r
-               {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|Any CPU.ActiveCfg = Release|x64\r
+               {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|ARM64.Build.0 = Release|ARM64\r
                {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
                {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|Mixed Platforms.Build.0 = Release|x64\r
                {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|Win32.ActiveCfg = Release|Win32\r
                {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|Win32.Build.0 = Release|Win32\r
                {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|x64.ActiveCfg = Release|x64\r
                {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|x64.Build.0 = Release|x64\r
-               {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|Any CPU.ActiveCfg = Debug|x64\r
+               {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|ARM64.ActiveCfg = Debug|ARM64\r
+               {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|ARM64.Build.0 = Debug|ARM64\r
                {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|Mixed Platforms.ActiveCfg = Debug|x64\r
                {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|Mixed Platforms.Build.0 = Debug|x64\r
                {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|Win32.ActiveCfg = Debug|Win32\r
                {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|Win32.Build.0 = Debug|Win32\r
                {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|x64.ActiveCfg = Debug|x64\r
                {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|x64.Build.0 = Debug|x64\r
-               {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|Any CPU.ActiveCfg = Release|x64\r
+               {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|ARM64.ActiveCfg = Release|ARM64\r
+               {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|ARM64.Build.0 = Release|ARM64\r
                {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|Mixed Platforms.ActiveCfg = Release|x64\r
                {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|Mixed Platforms.Build.0 = Release|x64\r
                {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|Win32.ActiveCfg = Release|Win32\r
                {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|Win32.Build.0 = Release|Win32\r
                {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|x64.ActiveCfg = Release|x64\r
                {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|x64.Build.0 = Release|x64\r
-               {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Debug|Any CPU.ActiveCfg = Debug|Win32\r
-               {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32\r
-               {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Debug|Mixed Platforms.Build.0 = Debug|Win32\r
-               {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Debug|Win32.ActiveCfg = Debug|Win32\r
-               {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Debug|Win32.Build.0 = Debug|Win32\r
-               {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Debug|x64.ActiveCfg = Debug|Win32\r
-               {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Release|Any CPU.ActiveCfg = Release|Win32\r
-               {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Release|Mixed Platforms.ActiveCfg = Release|Win32\r
-               {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Release|Mixed Platforms.Build.0 = Release|Win32\r
-               {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Release|Win32.ActiveCfg = Release|Win32\r
-               {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Release|Win32.Build.0 = Release|Win32\r
-               {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Release|x64.ActiveCfg = Release|Win32\r
        EndGlobalSection\r
        GlobalSection(SolutionProperties) = preSolution\r
                HideSolutionNode = FALSE\r
        EndGlobalSection\r
+       GlobalSection(ExtensibilityGlobals) = postSolution\r
+               SolutionGuid = {3AB8E02C-A6A4-4530-908D-882B34FC7BA4}\r
+       EndGlobalSection\r
 EndGlobal\r
index 6c139f4be3b8806962b3cb83609aa6c3872dd07c..4ea8101b64e8302dc1b42190954bf649dcc52462 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018-2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2018-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "DNSCommon.h"
 #include "uDNS.h"
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+#include "QuerierSupport.h"
+#endif
+
 #if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
 #include "D2D.h"
 #endif
@@ -40,6 +44,10 @@ int WCFNameResolvesToAddr(WCFConnection *conn, char* domainName, struct sockaddr
 int WCFNameResolvesToName(WCFConnection *conn, char* fromName, char* toName, uid_t userid) __attribute__((weak_import));
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include "dnssec_v2.h"
+#endif
+
 #define RecordTypeIsAddress(TYPE)   (((TYPE) == kDNSType_A) || ((TYPE) == kDNSType_AAAA))
 
 extern mDNS mDNSStorage;
@@ -55,12 +63,43 @@ mDNSBool AlwaysAppendSearchDomains = mDNSfalse;
 // Control enabling optimistic DNS - Phil
 mDNSBool EnableAllowExpired = mDNStrue;
 
+
+typedef struct
+{
+    mDNSu32                 requestID;
+    const domainname *      qname;
+    mDNSu16                 qtype;
+    mDNSu16                 qclass;
+    mDNSInterfaceID         interfaceID;
+    mDNSs32                 serviceID;
+    mDNSu32                 flags;
+    mDNSBool                appendSearchDomains;
+    mDNSs32                 effectivePID;
+    const mDNSu8 *          effectiveUUID;
+    mDNSu32                 peerUID;
+    mDNSBool                isInAppBrowserRequest;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    const mDNSu8 *          resolverUUID;
+       mdns_dns_service_id_t   customID;
+    mDNSBool                needEncryption;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+    const audit_token_t *   peerAuditToken;
+    const audit_token_t *   delegatorAuditToken;
+#endif
+
+}   QueryRecordOpParams;
+
+mDNSlocal void QueryRecordOpParamsInit(QueryRecordOpParams *inParams)
+{
+       mDNSPlatformMemZero(inParams, (mDNSu32)sizeof(*inParams));
+    inParams->serviceID = -1;
+}
+
 mDNSlocal mStatus QueryRecordOpCreate(QueryRecordOp **outOp);
 mDNSlocal void QueryRecordOpFree(QueryRecordOp *operation);
-mDNSlocal mStatus QueryRecordOpStart(QueryRecordOp *inOp, mDNSu32 inReqID, const domainname *inQName, mDNSu16 inQType,
-    mDNSu16 inQClass, mDNSInterfaceID inInterfaceID, mDNSs32 inServiceID, mDNSu32 inFlags, mDNSBool inAppendSearchDomains,
-    mDNSs32 inPID, const mDNSu8 inUUID[UUID_SIZE], mDNSu32 inUID, QueryRecordResultHandler inResultHandler,
-    void *inResultContext);
+mDNSlocal mStatus QueryRecordOpStart(QueryRecordOp *inOp, const QueryRecordOpParams *inParams,
+    QueryRecordResultHandler inResultHandler, void *inResultContext);
 mDNSlocal void QueryRecordOpStop(QueryRecordOp *op);
 mDNSlocal mDNSBool QueryRecordOpIsMulticast(const QueryRecordOp *op);
 mDNSlocal void QueryRecordOpCallback(mDNS *m, DNSQuestion *inQuestion, const ResourceRecord *inAnswer,
@@ -81,41 +120,45 @@ mDNSlocal mDNSBool DomainNameIsInSearchList(const domainname *domain, mDNSBool i
 mDNSlocal void NotifyWebContentFilter(const ResourceRecord *inAnswer, uid_t inUID);
 #endif
 
-mDNSexport mStatus GetAddrInfoClientRequestStart(GetAddrInfoClientRequest *inRequest, mDNSu32 inReqID,
-    const char *inHostnameStr, mDNSu32 inInterfaceIndex, DNSServiceFlags inFlags, mDNSu32 inProtocols, mDNSs32 inPID,
-       const mDNSu8 inUUID[UUID_SIZE], mDNSu32 inUID, QueryRecordResultHandler inResultHandler,
-    void *inResultContext)
+mDNSexport void GetAddrInfoClientRequestParamsInit(GetAddrInfoClientRequestParams *inParams)
+{
+       mDNSPlatformMemZero(inParams, (mDNSu32)sizeof(*inParams));
+}
+
+mDNSexport mStatus GetAddrInfoClientRequestStart(GetAddrInfoClientRequest *inRequest,
+    const GetAddrInfoClientRequestParams *inParams, QueryRecordResultHandler inResultHandler, void *inResultContext)
 {
     mStatus             err;
     domainname          hostname;
     mDNSBool            appendSearchDomains;
     mDNSInterfaceID     interfaceID;
     DNSServiceFlags     flags;
-       mDNSs32                         serviceID;
+    mDNSs32             serviceID;
+    QueryRecordOpParams opParams;
 
-    if (!MakeDomainNameFromDNSNameString(&hostname, inHostnameStr))
+    if (!MakeDomainNameFromDNSNameString(&hostname, inParams->hostnameStr))
     {
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
-               "[R%u] ERROR: bad hostname '" PRI_S "'", inReqID, inHostnameStr);
+               "[R%u] ERROR: bad hostname '" PRI_S "'", inParams->requestID, inParams->hostnameStr);
         err = mStatus_BadParamErr;
         goto exit;
     }
 
-    if (inProtocols & ~(kDNSServiceProtocol_IPv4|kDNSServiceProtocol_IPv6))
+    if (inParams->protocols & ~(kDNSServiceProtocol_IPv4|kDNSServiceProtocol_IPv6))
     {
         err = mStatus_BadParamErr;
         goto exit;
     }
 
-    flags = inFlags;
-    if (!inProtocols)
+    flags = inParams->flags;
+    if (inParams->protocols == 0)
     {
         flags |= kDNSServiceFlagsSuppressUnusable;
         inRequest->protocols = kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6;
     }
     else
     {
-        inRequest->protocols = inProtocols;
+        inRequest->protocols = inParams->protocols;
     }
 
     if (flags & kDNSServiceFlagsServiceIndex)
@@ -124,18 +167,18 @@ mDNSexport mStatus GetAddrInfoClientRequestStart(GetAddrInfoClientRequest *inReq
         LogInfo("GetAddrInfoClientRequestStart: kDNSServiceFlagsServiceIndex is SET by the client");
 
         // If kDNSServiceFlagsServiceIndex is SET, interpret the interfaceID as the serviceId and set the interfaceID to 0.
-        serviceID      = (mDNSs32)inInterfaceIndex;
-        interfaceID    = mDNSNULL;
+        serviceID   = (mDNSs32)inParams->interfaceIndex;
+        interfaceID = mDNSNULL;
     }
-       else
-       {
-               serviceID = -1;
-        err = InterfaceIndexToInterfaceID(inInterfaceIndex, &interfaceID);
+    else
+    {
+        serviceID = -1;
+        err = InterfaceIndexToInterfaceID(inParams->interfaceIndex, &interfaceID);
         if (err) goto exit;
-       }
+    }
     inRequest->interfaceID = interfaceID;
 
-    if (!StringEndsWithDot(inHostnameStr) && (AlwaysAppendSearchDomains || DomainNameIsSingleLabel(&hostname)))
+    if (!StringEndsWithDot(inParams->hostnameStr) && (AlwaysAppendSearchDomains || DomainNameIsSingleLabel(&hostname)))
     {
         appendSearchDomains = mDNStrue;
     }
@@ -143,26 +186,43 @@ mDNSexport mStatus GetAddrInfoClientRequestStart(GetAddrInfoClientRequest *inReq
     {
         appendSearchDomains = mDNSfalse;
     }
-
+    QueryRecordOpParamsInit(&opParams);
+    opParams.requestID              = inParams->requestID;
+    opParams.qname                  = &hostname;
+    opParams.qclass                 = kDNSClass_IN;
+    opParams.interfaceID            = inRequest->interfaceID;
+    opParams.serviceID              = serviceID;
+    opParams.flags                  = flags;
+    opParams.appendSearchDomains    = appendSearchDomains;
+    opParams.effectivePID           = inParams->effectivePID;
+    opParams.effectiveUUID          = inParams->effectiveUUID;
+    opParams.peerUID                = inParams->peerUID;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    opParams.resolverUUID           = inParams->resolverUUID;
+    opParams.customID               = inParams->customID;
+    opParams.needEncryption         = inParams->needEncryption;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+    opParams.peerAuditToken         = inParams->peerAuditToken;
+    opParams.delegatorAuditToken    = inParams->delegatorAuditToken;
+    opParams.isInAppBrowserRequest  = inParams->isInAppBrowserRequest;
+#endif
     if (inRequest->protocols & kDNSServiceProtocol_IPv6)
     {
         err = QueryRecordOpCreate(&inRequest->op6);
         if (err) goto exit;
 
-        err = QueryRecordOpStart(inRequest->op6, inReqID, &hostname, kDNSType_AAAA, kDNSServiceClass_IN,
-            inRequest->interfaceID, serviceID, flags, appendSearchDomains, inPID, inUUID, inUID, inResultHandler,
-            inResultContext);
+        opParams.qtype = kDNSType_AAAA;
+        err = QueryRecordOpStart(inRequest->op6, &opParams, inResultHandler, inResultContext);
         if (err) goto exit;
     }
-
     if (inRequest->protocols & kDNSServiceProtocol_IPv4)
     {
         err = QueryRecordOpCreate(&inRequest->op4);
         if (err) goto exit;
 
-        err = QueryRecordOpStart(inRequest->op4, inReqID, &hostname, kDNSType_A, kDNSServiceClass_IN,
-            inRequest->interfaceID, serviceID, flags, appendSearchDomains, inPID, inUUID, inUID, inResultHandler,
-            inResultContext);
+        opParams.qtype = kDNSType_A;
+        err = QueryRecordOpStart(inRequest->op4, &opParams, inResultHandler, inResultContext);
         if (err) goto exit;
     }
     err = mStatus_NoError;
@@ -243,27 +303,35 @@ mDNSexport mDNSBool GetAddrInfoClientRequestIsMulticast(const GetAddrInfoClientR
     return mDNSfalse;
 }
 
-mDNSexport mStatus QueryRecordClientRequestStart(QueryRecordClientRequest *inRequest, mDNSu32 inReqID,
-    const char *inQNameStr, mDNSu32 inInterfaceIndex, DNSServiceFlags inFlags, mDNSu16 inQType, mDNSu16 inQClass,
-    mDNSs32 inPID, mDNSu8 inUUID[UUID_SIZE], mDNSu32 inUID, QueryRecordResultHandler inResultHandler, void *inResultContext)
+mDNSexport void QueryRecordClientRequestParamsInit(QueryRecordClientRequestParams *inParams)
+{
+       mDNSPlatformMemZero(inParams, (mDNSu32)sizeof(*inParams));
+}
+
+mDNSexport mStatus QueryRecordClientRequestStart(QueryRecordClientRequest *inRequest,
+    const QueryRecordClientRequestParams *inParams, QueryRecordResultHandler inResultHandler, void *inResultContext)
 {
     mStatus             err;
     domainname          qname;
     mDNSInterfaceID     interfaceID;
     mDNSBool            appendSearchDomains;
+    QueryRecordOpParams opParams;
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    dnssec_context_t *  dnssecContext = mDNSNULL;
+#endif
 
-    err = InterfaceIndexToInterfaceID(inInterfaceIndex, &interfaceID);
+    err = InterfaceIndexToInterfaceID(inParams->interfaceIndex, &interfaceID);
     if (err) goto exit;
 
-    if (!MakeDomainNameFromDNSNameString(&qname, inQNameStr))
+    if (!MakeDomainNameFromDNSNameString(&qname, inParams->qnameStr))
     {
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
-               "[R%u] ERROR: bad domain name '" PRI_S "'", inReqID, inQNameStr);
+               "[R%u] ERROR: bad domain name '" PRI_S "'", inParams->requestID, inParams->qnameStr);
         err = mStatus_BadParamErr;
         goto exit;
     }
 
-    if (RecordTypeIsAddress(inQType) && !StringEndsWithDot(inQNameStr) &&
+    if (RecordTypeIsAddress(inParams->qtype) && !StringEndsWithDot(inParams->qnameStr) &&
         (AlwaysAppendSearchDomains || DomainNameIsSingleLabel(&qname)))
     {
         appendSearchDomains = mDNStrue;
@@ -272,9 +340,52 @@ mDNSexport mStatus QueryRecordClientRequestStart(QueryRecordClientRequest *inReq
     {
         appendSearchDomains = mDNSfalse;
     }
+    QueryRecordOpParamsInit(&opParams);
+    opParams.requestID              = inParams->requestID;
+    opParams.qname                  = &qname;
+    opParams.qtype                  = inParams->qtype;
+    opParams.qclass                 = inParams->qclass;
+    opParams.interfaceID            = interfaceID;
+    opParams.appendSearchDomains    = appendSearchDomains;
+    opParams.effectivePID           = inParams->effectivePID;
+    opParams.effectiveUUID          = inParams->effectiveUUID;
+    opParams.peerUID                = inParams->peerUID;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    opParams.resolverUUID           = inParams->resolverUUID;
+    opParams.customID               = inParams->customID;
+    opParams.needEncryption         = inParams->needEncryption;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+    opParams.peerAuditToken         = inParams->peerAuditToken;
+    opParams.delegatorAuditToken    = inParams->delegatorAuditToken;
+    opParams.isInAppBrowserRequest  = inParams->isInAppBrowserRequest;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    // Query ends with ".local." and query for RRSIG or ANY type cannot be validated by DNSSEC even if the user sets the
+    // kDNSServiceFlagsEnableDNSSEC flag.
+    if (FLAGS_CONTAIN_DNSOK_BIT(inParams->flags) && is_eligible_for_dnssec(&qname, inParams->qtype))
+    {
+        opParams.flags = inParams->flags | kDNSServiceFlagsReturnIntermediates; // to handle CNAME reference
+        err = create_dnssec_context_t(inRequest, inParams->requestID, &qname, inParams->qtype, inParams->qclass,
+            interfaceID, -1, inParams->flags, appendSearchDomains, inParams->effectivePID, inParams->effectiveUUID,
+            inParams->peerUID,
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+            inParams->peerAuditToken, inParams->delegatorAuditToken,
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            mDNSNULL, inParams->needEncryption, inParams->customID,
+#endif
+            inResultHandler, inResultContext, mDNSNULL, &dnssecContext);
+        require_action(err == mStatus_NoError, exit, log_debug("create_dnssec_context_t failed; error_description='%s'",
+            mStatusDescription(err)));
 
-    err = QueryRecordOpStart(&inRequest->op, inReqID, &qname, inQType, inQClass, interfaceID, -1, inFlags,
-        appendSearchDomains, inPID, inUUID, inUID, inResultHandler, inResultContext);
+        err = QueryRecordOpStart(&inRequest->op, &opParams, query_record_result_reply_with_dnssec, dnssecContext);
+    } else
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    {
+        opParams.flags = inParams->flags;
+        err = QueryRecordOpStart(&inRequest->op, &opParams, inResultHandler, inResultContext);
+    }
 
 exit:
     if (err) QueryRecordClientRequestStop(inRequest);
@@ -284,6 +395,11 @@ exit:
 mDNSexport void QueryRecordClientRequestStop(QueryRecordClientRequest *inRequest)
 {
     QueryRecordOpStop(&inRequest->op);
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    stop_dnssec_if_enable_dnssec(inRequest);
+#endif
+
 #if MDNSRESPONDER_SUPPORTS(APPLE, REACHABILITY_TRIGGER)
     if (inRequest->op.answered)
     {
@@ -310,6 +426,61 @@ mDNSexport mDNSBool QueryRecordClientRequestIsMulticast(QueryRecordClientRequest
 {
     return (QueryRecordOpIsMulticast(&inRequest->op) ? mDNStrue : mDNSfalse);
 }
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+mDNSexport mStatus QueryRecordOpStartForClientRequest(
+    QueryRecordOp *             inOp,
+    mDNSu32                     inReqID,
+    const domainname *          inQName,
+    mDNSu16                     inQType,
+    mDNSu16                     inQClass,
+    mDNSInterfaceID             inInterfaceID,
+    mDNSs32                     inServiceID,
+    mDNSu32                     inFlags,
+    mDNSBool                    inAppendSearchDomains,
+    mDNSs32                     inPID,
+    const mDNSu8                inUUID[UUID_SIZE],
+    mDNSu32                     inUID,
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+    const audit_token_t *       inPeerAuditTokenPtr,
+    const audit_token_t *       inDelegateAuditTokenPtr,
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    const mDNSu8                inResolverUUID[UUID_SIZE],
+    mDNSBool                    inNeedEncryption,
+    const mdns_dns_service_id_t inCustomID,
+#endif
+    QueryRecordResultHandler    inResultHandler,
+    void *                      inResultContext) {
+    QueryRecordOpParams opParams;
+    QueryRecordOpParamsInit(&opParams);
+    opParams.requestID           = inReqID;
+    opParams.qname               = inQName;
+    opParams.qtype               = inQType;
+    opParams.qclass              = inQClass;
+    opParams.interfaceID         = inInterfaceID;
+    opParams.serviceID           = inServiceID;
+    opParams.flags               = inFlags;
+    opParams.appendSearchDomains = inAppendSearchDomains;
+    opParams.effectivePID        = inPID;
+    opParams.effectiveUUID       = inUUID;
+    opParams.peerUID             = inUID;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    opParams.resolverUUID        = inResolverUUID;
+    opParams.customID            = inCustomID;
+    opParams.needEncryption      = inNeedEncryption;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+    opParams.peerAuditToken      = inPeerAuditTokenPtr;
+    opParams.delegatorAuditToken = inDelegateAuditTokenPtr;
+#endif
+    return QueryRecordOpStart(inOp, &opParams, inResultHandler, inResultContext);
+}
+
+mDNSexport void QueryRecordOpStopForClientRequest(QueryRecordOp *op) {
+    QueryRecordOpStop(op);
+}
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
 
 mDNSlocal mStatus QueryRecordOpCreate(QueryRecordOp **outOp)
 {
@@ -338,10 +509,8 @@ mDNSlocal void QueryRecordOpFree(QueryRecordOp *operation)
     (SameDomainLabel((T)->c, (const mDNSu8 *)"\x4_tcp") || SameDomainLabel((T)->c, (const mDNSu8 *)"\x4_udp"))
 #define VALID_MSAD_SRV(Q) ((Q)->qtype == kDNSType_SRV && VALID_MSAD_SRV_TRANSPORT(SecondLabel(&(Q)->qname)))
 
-mDNSlocal mStatus QueryRecordOpStart(QueryRecordOp *inOp, mDNSu32 inReqID, const domainname *inQName, mDNSu16 inQType,
-    mDNSu16 inQClass, mDNSInterfaceID inInterfaceID, mDNSs32 inServiceID, mDNSu32 inFlags, mDNSBool inAppendSearchDomains,
-    mDNSs32 inPID, const mDNSu8 inUUID[UUID_SIZE], mDNSu32 inUID, QueryRecordResultHandler inResultHandler,
-    void *inResultContext)
+mDNSlocal mStatus QueryRecordOpStart(QueryRecordOp *inOp, const QueryRecordOpParams *inParams,
+    QueryRecordResultHandler inResultHandler, void *inResultContext)
 {
     mStatus                 err;
     DNSQuestion * const     q = &inOp->q;
@@ -349,23 +518,23 @@ mDNSlocal mStatus QueryRecordOpStart(QueryRecordOp *inOp, mDNSu32 inReqID, const
 
     // Save the original qname.
 
-    len = DomainNameLength(inQName);
+    len = DomainNameLength(inParams->qname);
     inOp->qname = (domainname *) mDNSPlatformMemAllocate(len);
     if (!inOp->qname)
     {
         err = mStatus_NoMemoryErr;
         goto exit;
     }
-    mDNSPlatformMemCopy(inOp->qname, inQName, len);
+    mDNSPlatformMemCopy(inOp->qname, inParams->qname, len);
 
-    inOp->interfaceID   = inInterfaceID;
-    inOp->reqID         = inReqID;
+    inOp->interfaceID   = inParams->interfaceID;
+    inOp->reqID         = inParams->requestID;
     inOp->resultHandler = inResultHandler;
     inOp->resultContext = inResultContext;
 
     // Set up DNSQuestion.
 
-    if (EnableAllowExpired && (inFlags & kDNSServiceFlagsAllowExpiredAnswers))
+    if (EnableAllowExpired && (inParams->flags & kDNSServiceFlagsAllowExpiredAnswers))
     {
         q->allowExpired = AllowExpired_AllowExpiredAnswers;
     }
@@ -373,41 +542,54 @@ mDNSlocal mStatus QueryRecordOpStart(QueryRecordOp *inOp, mDNSu32 inReqID, const
     {
         q->allowExpired = AllowExpired_None;
     }
-    q->ServiceID            = inServiceID;
-    q->InterfaceID          = inInterfaceID;
-    q->flags                = inFlags;
-    AssignDomainName(&q->qname, inQName);
-    q->qtype                = inQType;
-    q->qclass               = inQClass;
-    q->LongLived            = (inFlags & kDNSServiceFlagsLongLivedQuery)            ? mDNStrue : mDNSfalse;
-    q->ForceMCast           = (inFlags & kDNSServiceFlagsForceMulticast)            ? mDNStrue : mDNSfalse;
-    q->ReturnIntermed       = (inFlags & kDNSServiceFlagsReturnIntermediates)       ? mDNStrue : mDNSfalse;
-    q->SuppressUnusable     = (inFlags & kDNSServiceFlagsSuppressUnusable)          ? mDNStrue : mDNSfalse;
-    q->TimeoutQuestion      = (inFlags & kDNSServiceFlagsTimeout)                   ? mDNStrue : mDNSfalse;
-    q->UseBackgroundTraffic = (inFlags & kDNSServiceFlagsBackgroundTrafficClass)    ? mDNStrue : mDNSfalse;
-    q->AppendSearchDomains  = inAppendSearchDomains;
+    q->ServiceID = inParams->serviceID;
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+    q->inAppBrowserRequest = inParams->isInAppBrowserRequest;
+    if (inParams->peerAuditToken)
+    {
+        q->peerAuditToken = *inParams->peerAuditToken;
+    }
+    if (inParams->delegatorAuditToken)
+    {
+        q->delegateAuditToken = *inParams->delegatorAuditToken;
+    }
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    if (inParams->resolverUUID)
+    {
+        mDNSPlatformMemCopy(q->ResolverUUID, inParams->resolverUUID, UUID_SIZE);
+    }
+#endif
+    q->InterfaceID          = inParams->interfaceID;
+    q->flags                = inParams->flags;
+    AssignDomainName(&q->qname, inParams->qname);
+    q->qtype                = inParams->qtype;
+    q->qclass               = inParams->qclass;
+    q->LongLived            = (inParams->flags & kDNSServiceFlagsLongLivedQuery)            ? mDNStrue : mDNSfalse;
+    q->ForceMCast           = (inParams->flags & kDNSServiceFlagsForceMulticast)            ? mDNStrue : mDNSfalse;
+    q->ReturnIntermed       = (inParams->flags & kDNSServiceFlagsReturnIntermediates)       ? mDNStrue : mDNSfalse;
+    q->SuppressUnusable     = (inParams->flags & kDNSServiceFlagsSuppressUnusable)          ? mDNStrue : mDNSfalse;
+    q->TimeoutQuestion      = (inParams->flags & kDNSServiceFlagsTimeout)                   ? mDNStrue : mDNSfalse;
+    q->UseBackgroundTraffic = (inParams->flags & kDNSServiceFlagsBackgroundTrafficClass)    ? mDNStrue : mDNSfalse;
+    q->AppendSearchDomains  = inParams->appendSearchDomains;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    q->RequireEncryption    = inParams->needEncryption;
+    q->CustomID             = inParams->customID;
+#endif
     q->InitialCacheMiss     = mDNSfalse;
 
-    // Turn off dnssec validation for local domains and Question Types: RRSIG/ANY(ANY Type is not supported yet) - Mohan
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    err = initialize_dnssec_status_t(&q->DNSSECStatus, inParams->qname, inParams->qtype, inParams->flags, inResultContext);
+    require_action(err == mStatus_NoError, exit, log_debug("initialize_dnssec_status failed; error_description='%s'", mStatusDescription(err)));
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
 
-    q->ValidationRequired = DNSSEC_VALIDATION_NONE;
-    if (!IsLocalDomain(&q->qname) && (inQType != kDNSServiceType_RRSIG) && (inQType != kDNSServiceType_ANY))
+    q->pid              = inParams->effectivePID;
+    if (inParams->effectiveUUID)
     {
-        if (inFlags & kDNSServiceFlagsValidate)
-        {
-            q->ValidationRequired   = DNSSEC_VALIDATION_SECURE;
-            q->AppendSearchDomains  = mDNSfalse;
-        }
-        else if (inFlags & kDNSServiceFlagsValidateOptional)
-        {
-            q->ValidationRequired = DNSSEC_VALIDATION_SECURE_OPTIONAL;
-        }
+        mDNSPlatformMemCopy(q->uuid, inParams->effectiveUUID, UUID_SIZE);
     }
-
-    q->pid              = inPID;
-    if (inUUID) mDNSPlatformMemCopy(q->uuid, inUUID, UUID_SIZE);
-    q->euid             = inUID;
-    q->request_id       = inReqID;
+    q->euid             = inParams->peerUID;
+    q->request_id       = inParams->requestID;
     q->QuestionCallback = QueryRecordOpCallback;
     q->ResetHandler     = QueryRecordOpResetHandler;
 
@@ -421,7 +603,7 @@ mDNSlocal mStatus QueryRecordOpStart(QueryRecordOp *inOp, mDNSu32 inReqID, const
 #if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
     if (callExternalHelpers(q->InterfaceID, &q->qname, q->flags))
     {
-        external_start_browsing_for_service(q->InterfaceID, &q->qname, q->qtype, q->flags);
+        external_start_browsing_for_service(q->InterfaceID, &q->qname, q->qtype, q->flags, q->pid);
     }
 #endif
 
@@ -461,7 +643,7 @@ mDNSlocal mStatus QueryRecordOpStart(QueryRecordOp *inOp, mDNSu32 inReqID, const
 
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                "[R%u] QueryRecordOpStart: starting parallel unicast query for " PRI_DM_NAME " " PUB_S,
-               inOp->reqID, DM_NAME_PARAM(q2->qname.c), DNSTypeName(q2->qtype));
+               inOp->reqID, DM_NAME_PARAM(&q2->qname), DNSTypeName(q2->qtype));
 
         err = QueryRecordOpStartQuestion(inOp, q2);
         if (err) goto exit;
@@ -482,7 +664,7 @@ mDNSlocal void QueryRecordOpStop(QueryRecordOp *op)
 #if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
         if (callExternalHelpers(op->q.InterfaceID, op->qname, op->q.flags))
         {
-            external_stop_browsing_for_service(op->q.InterfaceID, &op->q.qname, op->q.qtype, op->q.flags);
+            external_stop_browsing_for_service(op->q.InterfaceID, &op->q.qname, op->q.qtype, op->q.flags, op->q.pid);
         }
 #endif
     }
@@ -555,7 +737,7 @@ mDNSlocal void QueryRecordOpCallback(mDNS *m, DNSQuestion *inQuestion, const Res
     {
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
                "[R%u] QueryRecordOpCallback: Suppressed question " PRI_DM_NAME " (" PUB_S ")",
-               op->reqID, DM_NAME_PARAM(inQuestion->qname.c), DNSTypeName(inQuestion->qtype));
+               op->reqID, DM_NAME_PARAM(&inQuestion->qname), DNSTypeName(inQuestion->qtype));
 
         resultErr = kDNSServiceErr_NoSuchRecord;
     }
@@ -565,13 +747,13 @@ mDNSlocal void QueryRecordOpCallback(mDNS *m, DNSQuestion *inQuestion, const Res
         {
             LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                    "[R%u] QueryRecordOpCallback: Question " PRI_DM_NAME " (" PUB_S ") timing out, InterfaceID %p",
-                   op->reqID, DM_NAME_PARAM(inQuestion->qname.c), DNSTypeName(inQuestion->qtype),
+                   op->reqID, DM_NAME_PARAM(&inQuestion->qname), DNSTypeName(inQuestion->qtype),
                    inQuestion->InterfaceID);
             resultErr = kDNSServiceErr_Timeout;
         }
         else
         {
-            if (inQuestion->AppendSearchDomains && (op->searchListIndex >= 0) && inAddRecord && (inAddRecord != QC_dnssec))
+            if (inQuestion->AppendSearchDomains && (op->searchListIndex >= 0) && inAddRecord)
             {
                 domain = NextSearchDomain(op);
                 if (domain || DomainNameIsSingleLabel(op->qname))
@@ -590,7 +772,7 @@ mDNSlocal void QueryRecordOpCallback(mDNS *m, DNSQuestion *inQuestion, const Res
                 {
                     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                            "[R%u] QueryRecordOpCallback: Question " PRI_DM_NAME " (" PUB_S ") answering local with negative unicast response",
-                           op->reqID, DM_NAME_PARAM(inQuestion->qname.c), DNSTypeName(inQuestion->qtype));
+                           op->reqID, DM_NAME_PARAM(&inQuestion->qname), DNSTypeName(inQuestion->qtype));
                 }
                 else
                 {
@@ -617,7 +799,7 @@ mDNSlocal void QueryRecordOpCallback(mDNS *m, DNSQuestion *inQuestion, const Res
     if (resultErr == kDNSServiceErr_Timeout) QueryRecordOpStopQuestion(inQuestion);
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, WEB_CONTENT_FILTER)
-       NotifyWebContentFilter(inAnswer, inQuestion->euid);
+    NotifyWebContentFilter(inAnswer, inQuestion->euid);
 #endif
 
 exit:
@@ -650,7 +832,7 @@ mDNSlocal mStatus QueryRecordOpStartQuestion(QueryRecordOp *inOp, DNSQuestion *i
     {
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
                "[R%u] ERROR: QueryRecordOpStartQuestion mDNS_StartQuery for " PRI_DM_NAME " " PUB_S " failed with error %d",
-               inOp->reqID, DM_NAME_PARAM(inQuestion->qname.c), DNSTypeName(inQuestion->qtype), err);
+               inOp->reqID, DM_NAME_PARAM(&inQuestion->qname), DNSTypeName(inQuestion->qtype), err);
         inQuestion->QuestionContext = mDNSNULL;
     }
     return err;
index 82f8d775efa9033fd8dbbef31dcf180ede905bf1..c3a42f43377c7709122d73f47330fecde41da0c8 100644 (file)
@@ -61,25 +61,103 @@ typedef struct
 
 }   QueryRecordClientRequest;
 
+typedef struct
+{
+    mDNSu32                 requestID;
+    const char *            hostnameStr;
+    mDNSu32                 interfaceIndex;
+    DNSServiceFlags         flags;
+    mDNSu32                 protocols;
+    mDNSs32                 effectivePID;
+    const mDNSu8 *          effectiveUUID;
+    mDNSu32                 peerUID;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mDNSBool                needEncryption;
+    const mDNSu8 *          resolverUUID;
+    mdns_dns_service_id_t   customID;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+    const audit_token_t *   peerAuditToken;
+    const audit_token_t *   delegatorAuditToken;
+    mDNSBool                isInAppBrowserRequest;
+#endif
+
+}   GetAddrInfoClientRequestParams;
+
+typedef struct
+{
+    mDNSu32                 requestID;
+    const char *            qnameStr;
+    mDNSu32                 interfaceIndex;
+    DNSServiceFlags         flags;
+    mDNSu16                 qtype;
+    mDNSu16                 qclass;
+    mDNSs32                 effectivePID;
+    const mDNSu8 *          effectiveUUID;
+    mDNSu32                 peerUID;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mDNSBool                needEncryption;
+    const mDNSu8 *          resolverUUID;
+       mdns_dns_service_id_t   customID;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+    const audit_token_t *   peerAuditToken;
+    const audit_token_t *   delegatorAuditToken;
+    mDNSBool                isInAppBrowserRequest;
+#endif
+
+}   QueryRecordClientRequestParams;
+
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-mDNSexport mStatus GetAddrInfoClientRequestStart(GetAddrInfoClientRequest *inRequest, mDNSu32 inReqID,
-    const char *inHostnameStr, mDNSu32 inInterfaceIndex, DNSServiceFlags inFlags, mDNSu32 inProtocols, mDNSs32 inPID,
-    const mDNSu8 inUUID[UUID_SIZE], mDNSu32 inUID, QueryRecordResultHandler inResultHandler, void *inResultContext);
+mDNSexport void GetAddrInfoClientRequestParamsInit(GetAddrInfoClientRequestParams *inParams);
+mDNSexport mStatus GetAddrInfoClientRequestStart(GetAddrInfoClientRequest *inRequest,
+    const GetAddrInfoClientRequestParams *inParams, QueryRecordResultHandler inResultHandler, void *inResultContext);
 mDNSexport void GetAddrInfoClientRequestStop(GetAddrInfoClientRequest *inRequest);
 mDNSexport const domainname * GetAddrInfoClientRequestGetQName(const GetAddrInfoClientRequest *inRequest);
 mDNSexport mDNSBool GetAddrInfoClientRequestIsMulticast(const GetAddrInfoClientRequest *inRequest);
 
-mDNSexport mStatus QueryRecordClientRequestStart(QueryRecordClientRequest *inRequest, mDNSu32 inReqID,
-    const char *inQNameStr, mDNSu32 inInterfaceIndex, DNSServiceFlags inFlags, mDNSu16 inQType, mDNSu16 inQClass,
-    mDNSs32 inPID, mDNSu8 inUUID[UUID_SIZE], mDNSu32 inUID, QueryRecordResultHandler inResultHandler, void *inResultContext);
+mDNSexport void QueryRecordClientRequestParamsInit(QueryRecordClientRequestParams *inParams);
+mDNSexport mStatus QueryRecordClientRequestStart(QueryRecordClientRequest *inRequest,
+    const QueryRecordClientRequestParams *inParams, QueryRecordResultHandler inResultHandler, void *inResultContext);
 mDNSexport void QueryRecordClientRequestStop(QueryRecordClientRequest *inRequest);
 mDNSexport const domainname * QueryRecordClientRequestGetQName(const QueryRecordClientRequest *inRequest);
 mDNSexport mDNSu16 QueryRecordClientRequestGetType(const QueryRecordClientRequest *inRequest);
 mDNSexport mDNSBool QueryRecordClientRequestIsMulticast(QueryRecordClientRequest *inRequest);
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+// This is a "mDNSexport" wrapper around the "static" QueryRecordOpStart that cannot be called by outside, which can be
+// called by the outside(dnssec related function).
+mDNSexport mStatus QueryRecordOpStartForClientRequest(
+    QueryRecordOp *             inOp,
+    mDNSu32                     inReqID,
+    const domainname *          inQName,
+    mDNSu16                     inQType,
+    mDNSu16                     inQClass,
+    mDNSInterfaceID             inInterfaceID,
+    mDNSs32                     inServiceID,
+    mDNSu32                     inFlags,
+    mDNSBool                    inAppendSearchDomains,
+    mDNSs32                     inPID,
+    const mDNSu8                inUUID[UUID_SIZE],
+    mDNSu32                     inUID,
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+    const audit_token_t *       inPeerAuditTokenPtr,
+    const audit_token_t *       inDelegateAuditTokenPtr,
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    const mDNSu8                inResolverUUID[UUID_SIZE],
+    mDNSBool                    inNeedEncryption,
+    const mdns_dns_service_id_t inCustomID,
+#endif
+    QueryRecordResultHandler    inResultHandler,
+    void *                      inResultContext);
+
+mDNSexport void QueryRecordOpStopForClientRequest(QueryRecordOp *op);
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
 #ifdef __cplusplus
 }
 #endif
index 55353bc79452b10ef2059c098818bf11a6f2e2a5..096fc7bfc7419d50aab433efb83a67ee265b86a0 100644 (file)
@@ -238,6 +238,11 @@ extern "C" {
 
     #elif ( defined( _MSC_VER ) )
 
+        #if ( _MSC_VER >= 1900 )
+            #include    <stdint.h>
+            #include    <stdbool.h>
+        #endif
+
         #pragma warning( disable:4127 ) // Disable "conditional expression is constant" warning for debug macros.
         #pragma warning( disable:4706 ) // Disable "assignment within conditional expression" for Microsoft headers.
 
index f3b2aab8829b38f481755b455062656d7b35579a..6b7f838a2eddeec8d9dda775e9263f65d3e86fb8 100644 (file)
@@ -213,7 +213,7 @@ exit:
 mDNSlocal mDNSBool GetConfigOption(char *dst, const char *option, FILE *f)
 {
     char buf[32+1+MAX_ESCAPED_DOMAIN_NAME]; // Option name, one space, option value
-    unsigned int len = strlen(option);
+    size_t len = strlen(option);
     if (len + 1 + MAX_ESCAPED_DOMAIN_NAME > sizeof(buf)-1) { LogMsg("GetConfigOption: option %s too long", option); return mDNSfalse; }
     fseek(f, 0, SEEK_SET);  // set position to beginning of stream
     while (fgets(buf, sizeof(buf), f))      // Read at most sizeof(buf)-1 bytes from file, and append '\0' C-string terminator
index e1080ff71392e7b6789407b7b2c1d76f3fecfbd9..d6d1bc84625b1a804c666b3a4d40a83d0e0b813e 100644 (file)
@@ -66,7 +66,7 @@
  */
 
 #ifndef _DNS_SD_H
-#define _DNS_SD_H 10970003
+#define _DNS_SD_H 13104042
 
 #ifdef  __cplusplus
 extern "C" {
@@ -406,6 +406,12 @@ enum
     * Include AWDL interface when kDNSServiceInterfaceIndexAny is specified.
     */
 
+    kDNSServiceFlagsEnableDNSSEC           = 0x200000,
+    /*
+     * Perform DNSSEC validation on the client request when kDNSServiceFlagsEnableDNSSEC is specified
+     * Since the client API has not been finalized, we will use it as a temporary flag to turn on the DNSSEC validation.
+     */
+
     kDNSServiceFlagsValidate               = 0x200000,
    /*
     * This flag is meaningful in DNSServiceGetAddrInfo and DNSServiceQueryRecord. This is the ONLY flag to be valid 
@@ -427,7 +433,7 @@ enum
     *
     * The following four flags indicate the status of the DNSSEC validation and marked in the flags field of the callback.
     * When any of the four flags is set, kDNSServiceFlagsValidate will also be set. To check the validation status, the 
-    * other applicable output flags should be masked. See kDNSServiceOutputFlags below.
+    * other applicable output flags should be masked.
     */
 
     kDNSServiceFlagsSecure                 = 0x200010,
@@ -559,9 +565,6 @@ enum
 
 };
 
-#define kDNSServiceOutputFlags (kDNSServiceFlagsValidate | kDNSServiceFlagsValidateOptional | kDNSServiceFlagsMoreComing | kDNSServiceFlagsAdd | kDNSServiceFlagsDefault)
-   /* All the output flags excluding the DNSSEC Status flags. Typically used to check DNSSEC Status */
-
 /* Possible protocol values */
 enum
 {
@@ -651,6 +654,9 @@ enum
 
     kDNSServiceType_HIP        = 55,     /* Host Identity Protocol */
 
+    kDNSServiceType_SVCB       = 64,     /* Service Binding. */
+    kDNSServiceType_HTTPS      = 65,      /* HTTPS Service Binding. */
+
     kDNSServiceType_SPF        = 99,     /* Sender Policy Framework for E-Mail */
     kDNSServiceType_UINFO      = 100,    /* IANA-Reserved */
     kDNSServiceType_UID        = 101,    /* IANA-Reserved */
@@ -663,7 +669,7 @@ enum
     kDNSServiceType_AXFR       = 252,    /* Transfer zone of authority. */
     kDNSServiceType_MAILB      = 253,    /* Transfer mailbox records. */
     kDNSServiceType_MAILA      = 254,    /* Transfer mail agent records. */
-    kDNSServiceType_ANY        = 255     /* Wildcard match. */
+    kDNSServiceType_ANY        = 255    /* Wildcard match. */
 };
 
 /* possible error code values */
@@ -701,7 +707,8 @@ enum
     kDNSServiceErr_NoRouter                  = -65566,  /* No router currently configured (probably no network connectivity) */
     kDNSServiceErr_PollingMode               = -65567,
     kDNSServiceErr_Timeout                   = -65568,
-    kDNSServiceErr_DefunctConnection         = -65569   /* Connection to daemon returned a SO_ISDEFUNCT error result */
+    kDNSServiceErr_DefunctConnection         = -65569,  /* Connection to daemon returned a SO_ISDEFUNCT error result */
+    kDNSServiceErr_PolicyDenied              = -65570
 
                                                /* mDNS Error codes are in the range
                                                 * FFFE FF00 (-65792) to FFFE FFFF (-65537) */
index 22931f241270598395db2d4b570159bf64e4029e..a3755a1f61eaf3cefad17defa390d2b8e962d146 100644 (file)
@@ -1,15 +1,28 @@
-/* -*- Mode: C; tab-width: 4 -*-
- * 
- * Copyright (c) 2016 Apple Inc. All rights reserved.
+/*
+ * Copyright (c) 2016-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef _DNS_SD_INTERNAL_H
 #define _DNS_SD_INTERNAL_H
 
-#if !APPLE_OSX_mDNSResponder
-#define DNSSD_NO_CREATE_DELEGATE_CONNECTION 1
+// The mDNSResponder daemon doesn't call the private DNS-SD API.
+
+#if !defined(DNS_SD_EXCLUDE_PRIVATE_API)
+    #define DNS_SD_EXCLUDE_PRIVATE_API  1
 #endif
 
 #include "dns_sd_private.h"
 
-#endif
+#endif  // _DNS_SD_INTERNAL_H
index 19de052ffa3e90646c240f2940d1150312ba4861..1c4baabfb28fea75b8a1c0edb15dd48af253e5a6 100644 (file)
@@ -1,6 +1,17 @@
-/* -*- Mode: C; tab-width: 4 -*-
- * 
- * Copyright (c) 2015-2019 Apple Inc. All rights reserved.
+/*
+ * Copyright (c) 2015-2020 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef _DNS_SD_PRIVATE_H
@@ -8,6 +19,14 @@
 
 #include <dns_sd.h>
 
+#if !defined(DNS_SD_EXCLUDE_PRIVATE_API)
+    #if defined(__APPLE__)
+        #define DNS_SD_EXCLUDE_PRIVATE_API  0
+    #else
+        #define DNS_SD_EXCLUDE_PRIVATE_API  1
+    #endif
+#endif
+
 // Private flags (kDNSServiceFlagsPrivateOne, kDNSServiceFlagsPrivateTwo, kDNSServiceFlagsPrivateThree, kDNSServiceFlagsPrivateFour, kDNSServiceFlagsPrivateFive) from dns_sd.h
 enum
 {
@@ -42,7 +61,7 @@ enum
      */
 };
 
-#if !defined(DNSSD_NO_CREATE_DELEGATE_CONNECTION) || !DNSSD_NO_CREATE_DELEGATE_CONNECTION
+#if !DNS_SD_EXCLUDE_PRIVATE_API
 /* DNSServiceCreateDelegateConnection()
  *
  * Parameters:
@@ -66,7 +85,6 @@ enum
  */
 DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceCreateDelegateConnection(DNSServiceRef *sdRef, int32_t pid, uuid_t uuid);
-#endif
 
 // Map the source port of the local UDP socket that was opened for sending the DNS query
 // to the process ID of the application that triggered the DNS resolution.
@@ -106,7 +124,38 @@ DNSServiceErrorType DNSSD_API DNSServiceSleepKeepalive_sockaddr
     void *                          context
 );
 
+/*!
+ *  @brief
+ *      Sets the default DNS resolver settings for the caller's process.
+ *
+ *  @param plist_data_ptr
+ *      Pointer to an nw_resolver_config's binary property list data.
+ *
+ *  @param plist_data_len
+ *      Byte-length of the binary property list data. Ignored if plist_data_ptr is NULL.
+ *
+ *  @param require_encryption
+ *      Pass true if the process requires that DNS queries use an encrypted DNS service, such as DNS over HTTPS.
+ *
+ *  @result
+ *      This function returns kDNSServiceErr_NoError on success, kDNSServiceErr_Invalid if plist_data_len
+ *      exceeds 32,768, and kDNSServiceErr_NoMemory if it fails to allocate memory.
+ *
+ *  @discussion
+ *      These settings only apply to the calling process's DNSServiceGetAddrInfo and DNSServiceQueryRecord
+ *      requests. This function exists for code that may still use the legacy DNS-SD API for resolving
+ *      hostnames, i.e., it implements the functionality of dnssd_getaddrinfo_set_need_encrypted_query(), but at
+ *      a process-wide level of granularity.
+ *
+ *      Due to underlying IPC limitations, there's currently a 32 KB limit on the size of the binary property
+ *      list data.
+ */
+SPI_AVAILABLE(macos(10.16), ios(14.0), watchos(7.0), tvos(14.0))
+DNSServiceErrorType DNSSD_API DNSServiceSetResolverDefaults(const void *plist_data_ptr, size_t plist_data_len,
+    bool require_encryption);
+#endif  // !DNS_SD_EXCLUDE_PRIVATE_API
+
 #define kDNSServiceCompPrivateDNS   "PrivateDNS"
 #define kDNSServiceCompMulticastDNS "MulticastDNS"
 
-#endif
+#endif  // _DNS_SD_PRIVATE_H
index d3e9a44605f9172a65d945f7d152f4d6525a31ec..3c0371e4fd7cea8e0cfe6ba35993c5d3cfcd7b0e 100644 (file)
@@ -536,8 +536,6 @@ DNSServiceErrorType DNSServiceResolve
     x->qSRV.TimeoutQuestion     = 0;
     x->qSRV.WakeOnResolve       = 0;
     x->qSRV.UseBackgroundTraffic = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0;
-    x->qSRV.ValidationRequired  = 0;
-    x->qSRV.ValidatingResponse  = 0;
     x->qSRV.ProxyQuestion       = 0;
     x->qSRV.pid                 = mDNSPlatformGetPID();
     x->qSRV.QuestionCallback    = FoundServiceInfo;
@@ -558,8 +556,6 @@ DNSServiceErrorType DNSServiceResolve
     x->qTXT.TimeoutQuestion     = 0;
     x->qTXT.WakeOnResolve       = 0;
     x->qTXT.UseBackgroundTraffic = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0;
-    x->qTXT.ValidationRequired  = 0;
-    x->qTXT.ValidatingResponse  = 0;
     x->qTXT.ProxyQuestion       = 0;
     x->qTXT.pid                 = mDNSPlatformGetPID();
     x->qTXT.QuestionCallback    = FoundServiceInfo;
@@ -687,8 +683,6 @@ DNSServiceErrorType DNSServiceQueryRecord
     x->q.TimeoutQuestion      = 0;
     x->q.WakeOnResolve        = 0;
     x->q.UseBackgroundTraffic = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0;
-    x->q.ValidationRequired   = 0;
-    x->q.ValidatingResponse   = 0;
     x->q.ProxyQuestion        = 0;
     x->q.pid                  = mDNSPlatformGetPID();
     x->q.QuestionCallback     = DNSServiceQueryRecordResponse;
@@ -866,8 +860,6 @@ DNSServiceErrorType DNSSD_API DNSServiceGetAddrInfo(
     x->a.TimeoutQuestion      = 0;
     x->a.WakeOnResolve        = 0;
     x->a.UseBackgroundTraffic = (inFlags & kDNSServiceFlagsBackgroundTrafficClass) != 0;
-    x->a.ValidationRequired   = 0;
-    x->a.ValidatingResponse   = 0;
     x->a.ProxyQuestion        = 0;
     x->a.pid                  = mDNSPlatformGetPID();
     x->a.QuestionCallback     = DNSServiceGetAddrInfoResponse;
index a5111a26e7eba5000825accaa81c5de79a0784d6..074db5a7b4ee4e927d0a6846c9585844e02af55c 100644 (file)
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2003-2015 Apple Inc. All rights reserved.
+ * Copyright (c) 2003-2020 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
 #include <mach-o/dyld.h>
 #include <uuid/uuid.h>
 #include <TargetConditionals.h>
-#include "dns_sd_internal.h"
+#include "dns_sd_private.h"
+#include "dnssd_clientstub_apple.h"
+#include <CoreUtils/CommonServices.h>
+#if !defined(__i386__)
+#define CHECK_BUNDLE_VERSION  1
+#else
+#define CHECK_BUNDLE_VERSION  0
+#endif
 #endif
 
 #if defined(_WIN32)
@@ -90,6 +97,11 @@ static void syslog( int priority, const char * message, ...)
 
 #endif
 
+#if CHECK_BUNDLE_VERSION
+#include "bundle_utilities.h"
+#include <os/feature_private.h>
+#endif
+
 #if defined(_WIN32)
 // <rdar://problem/4096913> Specifies how many times we'll try and connect to the server.
 
@@ -174,6 +186,19 @@ struct _DNSRecordRef_t
     DNSServiceOp *sdr;
 };
 
+#if CHECK_BUNDLE_VERSION
+static bool _should_return_noauth_error(void)
+{
+    static dispatch_once_t  s_once      = 0;
+    static bool        s_should    = false;
+    dispatch_once(&s_once,
+    ^{
+        s_should = bundle_sdk_is_ios14_or_later();
+    });
+    return s_should;
+}
+#endif
+
 #if !defined(USE_TCP_LOOPBACK)
 static void SetUDSPath(struct sockaddr_un *saddr, const char *path)
 {
@@ -542,7 +567,7 @@ static DNSServiceErrorType ConnectToServer(DNSServiceRef *ref, DNSServiceFlags f
     sdr->disp_queue    = NULL;
 #endif
     sdr->kacontext     = NULL;
-
+    
     if (flags & kDNSServiceFlagsShareConnection)
     {
         DNSServiceOp **p = &(*ref)->next;       // Append ourselves to end of primary's list
@@ -1410,12 +1435,24 @@ DNSServiceErrorType DNSSD_API DNSServiceResolve
     put_string(domain, &ptr);
 
     err = deliver_request(hdr, *sdRef);     // Will free hdr for us
+#if CHECK_BUNDLE_VERSION
+    if (err == kDNSServiceErr_NoAuth && !_should_return_noauth_error())
+    {
+        err = kDNSServiceErr_NoError;
+    }
+#endif
     if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; }
     return err;
 }
 
 static void handle_query_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *const end)
 {
+#if CHECK_BUNDLE_VERSION
+    if (cbh->cb_err == kDNSServiceErr_PolicyDenied && !_should_return_noauth_error())
+    {
+        return;
+    }
+#endif
     uint32_t ttl;
     char name[kDNSServiceMaxDomainName];
     uint16_t rrtype, rrclass, rdlen;
@@ -1433,6 +1470,37 @@ static void handle_query_response(DNSServiceOp *const sdr, const CallbackHeader
     // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function
 }
 
+#if APPLE_OSX_mDNSResponder
+static size_t get_required_length_for_defaults(const xpc_object_t defaults)
+{
+    size_t required_len = 0;
+    size_t plist_data_len = 0;
+    // Add length for IPC_TLV_TYPE_RESOLVER_CONFIG_PLIST_DATA.
+    if (xpc_dictionary_get_data(defaults, kDNSServiceDefaultsKey_ResolverConfigPListData, &plist_data_len))
+    {
+        required_len += get_required_tlv16_length(plist_data_len);
+    }
+    // Add length for IPC_TLV_TYPE_REQUIRE_PRIVACY.
+    required_len += get_required_tlv16_length(sizeof(uint8_t));
+    return required_len;
+}
+
+static void put_tlvs_for_defaults(const xpc_object_t defaults, ipc_msg_hdr *const hdr, char **ptr)
+{
+    uint8_t require_privacy;
+    size_t plist_data_len = 0;
+    const uint8_t *const plist_data_ptr = xpc_dictionary_get_data(defaults,
+        kDNSServiceDefaultsKey_ResolverConfigPListData, &plist_data_len);
+    if (plist_data_ptr)
+    {
+        put_tlv16(IPC_TLV_TYPE_RESOLVER_CONFIG_PLIST_DATA, (uint16_t)plist_data_len, plist_data_ptr, ptr);
+    }
+    require_privacy = xpc_dictionary_get_bool(defaults, kDNSServiceDefaultsKey_RequirePrivacy) ? 1 : 0;
+    put_tlv16(IPC_TLV_TYPE_REQUIRE_PRIVACY, sizeof(require_privacy), &require_privacy, ptr);
+    hdr->ipc_flags |= IPC_FLAGS_TRAILING_TLVS;
+}
+#endif
+
 DNSServiceErrorType DNSSD_API DNSServiceQueryRecord
 (
     DNSServiceRef              *sdRef,
@@ -1449,7 +1517,9 @@ DNSServiceErrorType DNSSD_API DNSServiceQueryRecord
     size_t len;
     ipc_msg_hdr *hdr;
     DNSServiceErrorType err;
-
+#if APPLE_OSX_mDNSResponder
+    xpc_object_t defaults;
+#endif
     // NULL name handled below.
     if (!sdRef || !callBack) return kDNSServiceErr_BadParam;
 
@@ -1466,23 +1536,54 @@ DNSServiceErrorType DNSSD_API DNSServiceQueryRecord
     len += sizeof(uint32_t);  // interfaceIndex
     len += strlen(name) + 1;
     len += 2 * sizeof(uint16_t);  // rrtype, rrclass
-
+#if APPLE_OSX_mDNSResponder
+    defaults = DNSServiceGetRetainedResolverDefaults();
+    if (defaults)
+    {
+        len += get_required_length_for_defaults(defaults);
+    }
+#endif
     hdr = create_hdr(query_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef);
-    if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; }
-
+    if (!hdr)
+    {
+        DNSServiceRefDeallocate(*sdRef);
+        *sdRef = NULL;
+#if APPLE_OSX_mDNSResponder
+        xpc_forget(&defaults);
+#endif
+        return kDNSServiceErr_NoMemory;
+    }
     put_flags(flags, &ptr);
     put_uint32(interfaceIndex, &ptr);
     put_string(name, &ptr);
     put_uint16(rrtype, &ptr);
     put_uint16(rrclass, &ptr);
-
+#if APPLE_OSX_mDNSResponder
+    if (defaults)
+    {
+        put_tlvs_for_defaults(defaults, hdr, &ptr);
+        xpc_forget(&defaults);
+    }
+#endif
     err = deliver_request(hdr, *sdRef);     // Will free hdr for us
+#if CHECK_BUNDLE_VERSION
+    if (err == kDNSServiceErr_NoAuth && !_should_return_noauth_error())
+    {
+        err = kDNSServiceErr_NoError;
+    }
+#endif
     if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; }
     return err;
 }
 
 static void handle_addrinfo_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *const end)
 {
+#if CHECK_BUNDLE_VERSION
+    if (cbh->cb_err == kDNSServiceErr_PolicyDenied && !_should_return_noauth_error())
+    {
+        return;
+    }
+#endif
     char hostname[kDNSServiceMaxDomainName];
     uint16_t rrtype, rrclass, rdlen;
     const char *rdata;
@@ -1532,17 +1633,12 @@ static void handle_addrinfo_response(DNSServiceOp *const sdr, const CallbackHead
                 if (IN6_IS_ADDR_LINKLOCAL(&sa6.sin6_addr)) sa6.sin6_scope_id = cbh->cb_interface;
             }
         }
-        // Validation results are always delivered separately from the actual results of the
-        // DNSServiceGetAddrInfo. Set the "addr" to NULL as per the documentation.
-        //
-        // Note: If we deliver validation results along with the "addr" in the future, we need
-        // a way to differentiate the negative response from validation-only response as both
-        // has zero address.
-        if (!(cbh->cb_flags & kDNSServiceFlagsValidate))
-            ((DNSServiceGetAddrInfoReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, hostname, sa, ttl, sdr->AppContext);
-        else
-            ((DNSServiceGetAddrInfoReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, hostname, NULL, 0, sdr->AppContext);
-        // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function
+
+               ((DNSServiceGetAddrInfoReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, hostname, sa, ttl, sdr->AppContext);
+    }
+    else if (cbh->cb_err == kDNSServiceErr_PolicyDenied)
+    {
+        ((DNSServiceGetAddrInfoReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, hostname, NULL, ttl, sdr->AppContext);
     }
 }
 
@@ -1561,6 +1657,9 @@ DNSServiceErrorType DNSSD_API DNSServiceGetAddrInfo
     size_t len;
     ipc_msg_hdr *hdr;
     DNSServiceErrorType err;
+#if APPLE_OSX_mDNSResponder
+    xpc_object_t defaults;
+#endif
 
     if (!sdRef || !hostname || !callBack) return kDNSServiceErr_BadParam;
 
@@ -1575,22 +1674,53 @@ DNSServiceErrorType DNSSD_API DNSServiceGetAddrInfo
     len += sizeof(uint32_t);      // interfaceIndex
     len += sizeof(uint32_t);      // protocol
     len += strlen(hostname) + 1;
-
+#if APPLE_OSX_mDNSResponder
+    defaults = DNSServiceGetRetainedResolverDefaults();
+    if (defaults)
+    {
+        len += get_required_length_for_defaults(defaults);
+    }
+#endif
     hdr = create_hdr(addrinfo_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef);
-    if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; }
-
+    if (!hdr)
+    {
+        DNSServiceRefDeallocate(*sdRef);
+        *sdRef = NULL;
+#if APPLE_OSX_mDNSResponder
+        xpc_forget(&defaults);
+#endif
+        return kDNSServiceErr_NoMemory;
+    }
     put_flags(flags, &ptr);
     put_uint32(interfaceIndex, &ptr);
     put_uint32(protocol, &ptr);
     put_string(hostname, &ptr);
-
+#if APPLE_OSX_mDNSResponder
+    if (defaults)
+    {
+        put_tlvs_for_defaults(defaults, hdr, &ptr);
+        xpc_forget(&defaults);
+    }
+#endif
     err = deliver_request(hdr, *sdRef);     // Will free hdr for us
+#if CHECK_BUNDLE_VERSION
+    if (err == kDNSServiceErr_NoAuth && !_should_return_noauth_error())
+    {
+        err = kDNSServiceErr_NoError;
+    }
+#endif
     if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; }
     return err;
 }
 
 static void handle_browse_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *const end)
 {
+#if CHECK_BUNDLE_VERSION
+    if (cbh->cb_err == kDNSServiceErr_PolicyDenied && !_should_return_noauth_error())
+    {
+        return;
+    }
+#endif
     char replyName[256], replyType[kDNSServiceMaxDomainName], replyDomain[kDNSServiceMaxDomainName];
     get_string(&data, end, replyName, 256);
     get_string(&data, end, replyType, kDNSServiceMaxDomainName);
@@ -1640,6 +1770,12 @@ DNSServiceErrorType DNSSD_API DNSServiceBrowse
     put_string(domain, &ptr);
 
     err = deliver_request(hdr, *sdRef);     // Will free hdr for us
+#if CHECK_BUNDLE_VERSION
+    if (err == kDNSServiceErr_NoAuth && !_should_return_noauth_error())
+    {
+        err = kDNSServiceErr_NoError;
+    }
+#endif
     if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; }
     return err;
 }
@@ -1670,6 +1806,12 @@ DNSServiceErrorType DNSSD_API DNSServiceSetDefaultDomainForUser(DNSServiceFlags
 
 static void handle_regservice_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *const end)
 {
+#if CHECK_BUNDLE_VERSION
+    if (cbh->cb_err == kDNSServiceErr_PolicyDenied && !_should_return_noauth_error())
+    {
+        return;
+    }
+#endif
     char name[256], regtype[kDNSServiceMaxDomainName], domain[kDNSServiceMaxDomainName];
     get_string(&data, end, name, 256);
     get_string(&data, end, regtype, kDNSServiceMaxDomainName);
@@ -1738,6 +1880,12 @@ DNSServiceErrorType DNSSD_API DNSServiceRegister
     put_rdata(txtLen, txtRecord, &ptr);
 
     err = deliver_request(hdr, *sdRef);     // Will free hdr for us
+#if CHECK_BUNDLE_VERSION
+    if (err == kDNSServiceErr_NoAuth && !_should_return_noauth_error())
+    {
+        err = kDNSServiceErr_NoError;
+    }
+#endif
     if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; }
     return err;
 }
@@ -1811,6 +1959,12 @@ static void ConnectionResponse(DNSServiceOp *const sdr, const CallbackHeader *co
     }
     else
     {
+#if CHECK_BUNDLE_VERSION
+        if (cbh->cb_err == kDNSServiceErr_PolicyDenied && !_should_return_noauth_error())
+        {
+            return;
+        }
+#endif
         DNSRecordRef rec;
         for (rec = sdr->rec; rec; rec = rec->recnext)
         {
@@ -1947,6 +2101,7 @@ DNSServiceErrorType DNSSD_API DNSServiceRegisterRecord
     void                          *context
 )
 {
+    DNSServiceErrorType err;
     char *ptr;
     size_t len;
     ipc_msg_hdr *hdr = NULL;
@@ -2025,7 +2180,14 @@ DNSServiceErrorType DNSSD_API DNSServiceRegisterRecord
     while (*p) p = &(*p)->recnext;
     *p = rref;
 
-    return deliver_request(hdr, sdRef);     // Will free hdr for us
+    err = deliver_request(hdr, sdRef);     // Will free hdr for us
+#if CHECK_BUNDLE_VERSION
+    if (err == kDNSServiceErr_NoAuth && !_should_return_noauth_error())
+    {
+        err = kDNSServiceErr_NoError;
+    }
+#endif
+    return err;
 }
 
 // sdRef returned by DNSServiceRegister()
diff --git a/mDNSShared/dnssd_clientstub_apple.c b/mDNSShared/dnssd_clientstub_apple.c
new file mode 100644 (file)
index 0000000..8f5efe6
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Inc. ("Apple") nor the names of its
+ *     contributors may be used to endorse or promote products derived from this
+ *     software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "dnssd_clientstub_apple.h"
+#include "dns_sd_private.h"
+
+#include <CoreUtils/CommonServices.h>
+#include <CoreUtils/DebugServices.h>
+#include <os/lock.h>
+
+static xpc_object_t            g_defaults_dict = NULL;
+static os_unfair_lock  g_defaults_lock = OS_UNFAIR_LOCK_INIT;
+
+DNSServiceErrorType DNSSD_API
+DNSServiceSetResolverDefaults(const void * const plist_data_ptr, const size_t plist_data_len, bool require_privacy)
+{
+       DNSServiceErrorType err;
+    require_action_quiet(plist_data_len <= (32 * CUBytesPerKibiByte), exit, err = kDNSServiceErr_Invalid);
+
+    const xpc_object_t new_dict = xpc_dictionary_create(NULL, NULL, 0);
+    require_action_quiet(new_dict, exit, err = kDNSServiceErr_NoMemory);
+
+    xpc_dictionary_set_bool(new_dict, kDNSServiceDefaultsKey_RequirePrivacy, require_privacy);
+       if (plist_data_ptr && (plist_data_len > 0)) {
+        xpc_dictionary_set_data(new_dict, kDNSServiceDefaultsKey_ResolverConfigPListData, plist_data_ptr,
+            plist_data_len);
+       }
+       os_unfair_lock_lock(&g_defaults_lock);
+       xpc_object_t old_dict = g_defaults_dict;
+       g_defaults_dict = new_dict;
+       os_unfair_lock_unlock(&g_defaults_lock);
+
+       xpc_forget(&old_dict);
+       err = kDNSServiceErr_NoError;
+
+exit:
+       return err;
+}
+
+xpc_object_t
+DNSServiceGetRetainedResolverDefaults(void)
+{
+       os_unfair_lock_lock(&g_defaults_lock);
+       const xpc_object_t dict_copy = g_defaults_dict ? xpc_retain(g_defaults_dict) : NULL;
+       os_unfair_lock_unlock(&g_defaults_lock);
+       return dict_copy;
+}
diff --git a/mDNSShared/dnssd_clientstub_apple.h b/mDNSShared/dnssd_clientstub_apple.h
new file mode 100644 (file)
index 0000000..1330dcf
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Inc. ("Apple") nor the names of its
+ *     contributors may be used to endorse or promote products derived from this
+ *     software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <xpc/xpc.h>
+
+#define kDNSServiceDefaultsKey_RequirePrivacy          "require_privacy"
+#define kDNSServiceDefaultsKey_ResolverConfigPListData "resolver_config_plist_data"
+
+xpc_object_t
+DNSServiceGetRetainedResolverDefaults(void);
index 625c88e4d28dad703da083a2b319217e67b3b04d..a149678ef6bdefab26d4476fbeee2b298caa6dea 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003-2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2003-2020 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -26,6 +26,9 @@
  */
 
 #include "dnssd_ipc.h"
+#if APPLE_OSX_mDNSResponder
+#include "mdns_tlv.h"
+#endif
 
 #if defined(_WIN32)
 
@@ -152,6 +155,20 @@ const char *get_rdata(const char **ptr, const char *end, int rdlen)
     }
 }
 
+#if APPLE_OSX_mDNSResponder
+size_t get_required_tlv16_length(const uint16_t valuelen)
+{
+    return mdns_tlv16_get_required_length(valuelen);
+}
+
+void put_tlv16(const uint16_t type, const uint16_t length, const uint8_t *value, char **ptr)
+{
+       uint8_t *dst = (uint8_t *)*ptr;
+       mdns_tlv16_set(dst, NULL, type, length, value, &dst);
+    *ptr = (char *)dst;
+}
+#endif
+
 void ConvertHeaderBytes(ipc_msg_hdr *hdr)
 {
     hdr->version   = htonl(hdr->version);
index 96466a944c6e0815a11ef9719082c5e5f33876ce..4d9090c2ab43d0009031f81f1331af73d0ced98e 100644 (file)
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2003-2015 Apple Inc. All rights reserved.
+ * Copyright (c) 2003-2020 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -100,7 +100,11 @@ extern char *win32_strerror(int inErrorCode);
 
 // IPC data encoding constants and types
 #define VERSION 1
-#define IPC_FLAGS_NOREPLY 1 // set flag if no asynchronous replies are to be sent to client
+#define IPC_FLAGS_NOREPLY       (1U << 0) // Set flag if no asynchronous replies are to be sent to client.
+#define IPC_FLAGS_TRAILING_TLVS (1U << 1) // Set flag if TLVs follow the standard request data.
+
+#define IPC_TLV_TYPE_RESOLVER_CONFIG_PLIST_DATA 1   // An nw_resolver_config as a binary property list.
+#define IPC_TLV_TYPE_REQUIRE_PRIVACY            2   // A uint8. Non-zero means privacy is required, zero means not required.
 
 // Structure packing macro. If we're not using GNUC, it's not fatal. Most compilers naturally pack the on-the-wire
 // structures correctly anyway, so a plain "struct" is usually fine. In the event that structures are not packed
@@ -207,6 +211,11 @@ void put_rdata(const int rdlen, const unsigned char *rdata, char **ptr);
 const char *get_rdata(const char **ptr, const char *end, int rdlen);  // return value is rdata pointed to by *ptr -
 // rdata is not copied from buffer.
 
+#if APPLE_OSX_mDNSResponder
+size_t get_required_tlv16_length(const uint16_t valuelen);
+void put_tlv16(const uint16_t type, const uint16_t length, const uint8_t *value, char **ptr);
+#endif
+
 void ConvertHeaderBytes(ipc_msg_hdr *hdr);
 
 struct CompileTimeAssertionChecks_dnssd_ipc
index 1982982ec1cba1f997fa48fd68f194fc3bd1c4eb..834d4db735817466afe69ef3c4cc7ff7ee8372e1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003-2019 Apple Inc. All rights reserved.
+ * Copyright (c) 2003-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "BLE.h"
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+#include "mDNSMacOSX.h"
+#include <os/feature_private.h>
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+#include <bsm/libbsm.h>
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+#include "QuerierSupport.h"
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER) && MDNSRESPONDER_SUPPORTS(APPLE, IPC_TLV)
+#include "mdns_tlv.h"
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+#include "dnssec_v2.h"
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSD_XPC_SERVICE)
+#include "dnssd_server.h"
+#endif
+
 // User IDs 0-500 are system-wide processes, not actual users in the usual sense
 // User IDs for real user accounts start at 501 and count up from there
 #define SystemUID(X) ((X) <= 500)
@@ -100,7 +125,6 @@ mDNSu32 curr_num_regservices = 0;
 mDNSu32 max_num_regservices = 0;
 #endif
 
-
 // Note asymmetry here between registration and browsing.
 // For service registrations we only automatically register in domains that explicitly appear in local configuration data
 // (so AutoRegistrationDomains could equally well be called SCPrefRegDomains)
@@ -131,6 +155,16 @@ mDNSexport DNameListElem *AutoBrowseDomains;        // List created from those l
 #pragma mark - General Utility Functions
 #endif
 
+mDNSlocal mDNSu32 GetNewRequestID(void)
+{
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSD_XPC_SERVICE)
+    return dnssd_server_get_new_request_id();
+#else
+    static mDNSu32 s_last_id = 0;
+    return ++s_last_id;
+#endif
+}
+
 mDNSlocal void FatalError(char *errmsg)
 {
     LogMsg("%s: %s", errmsg, dnssd_strerror(dnssd_errno));
@@ -299,6 +333,13 @@ mDNSlocal void abort_request(request_state *req)
     // If this is actually a shared connection operation, then its req->terminate function will scan
     // the all_requests list and terminate any subbordinate operations sharing this file descriptor
     if (req->terminate) req->terminate(req);
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    if (req->custom_service_id != 0)
+    {
+        Querier_DeregisterCustomDNSService(req->custom_service_id);
+        req->custom_service_id = 0;
+    }
+#endif
 
     if (!dnssd_SocketValid(req->sd))
     {
@@ -369,7 +410,20 @@ mDNSlocal void AbortUnlinkAndFree(request_state *req)
     request_state **p = &all_requests;
     abort_request(req);
     while (*p && *p != req) p=&(*p)->next;
-    if (*p) { *p = req->next; freeL("request_state/AbortUnlinkAndFree", req); }
+    if (*p)
+    {
+        *p = req->next;
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+        if (req->trust)
+        {
+            void * context = mdns_trust_get_context(req->trust);
+            mdns_trust_set_context(req->trust, NULL);
+            if (context) freeL("context/AbortUnlinkAndFree", context);
+            mdns_trust_forget(&req->trust);
+        }
+#endif
+        freeL("request_state/AbortUnlinkAndFree", req);
+    }
     else LogMsg("AbortUnlinkAndFree: ERROR: Attempt to abort operation %p not in list", req);
 }
 
@@ -430,7 +484,7 @@ mDNSlocal mStatus GenerateNTDResponse(const domainname *const servicename, const
     domainlabel name;
     domainname type, dom;
     *rep = NULL;
-    if (!DeconstructServiceName(servicename, &name, &type, &dom))
+    if (servicename && !DeconstructServiceName(servicename, &name, &type, &dom))
         return kDNSServiceErr_Invalid;
     else
     {
@@ -440,9 +494,18 @@ mDNSlocal mStatus GenerateNTDResponse(const domainname *const servicename, const
         int len;
         char *data;
 
-        ConvertDomainLabelToCString_unescaped(&name, namestr);
-        ConvertDomainNameToCString(&type, typestr);
-        ConvertDomainNameToCString(&dom, domstr);
+        if (servicename)
+        {
+            ConvertDomainLabelToCString_unescaped(&name, namestr);
+            ConvertDomainNameToCString(&type, typestr);
+            ConvertDomainNameToCString(&dom, domstr);
+        }
+        else
+        {
+            namestr[0] = 0;
+            typestr[0] = 0;
+            domstr[0] = 0;
+        }
 
         // Calculate reply data length
         len = sizeof(DNSServiceFlags);
@@ -479,11 +542,19 @@ mDNSlocal void GenerateBrowseReply(const domainname *const servicename, const mD
 
     *rep = NULL;
 
-    // 1. Put first label in namestr
-    ConvertDomainLabelToCString_unescaped((const domainlabel *)servicename, namestr);
+    if (servicename)
+    {
+        // 1. Put first label in namestr
+        ConvertDomainLabelToCString_unescaped((const domainlabel *)servicename, namestr);
 
-    // 2. Put second label and "local" into typestr
-    mDNS_snprintf(typestr, sizeof(typestr), "%#s.local.", SecondLabel(servicename));
+        // 2. Put second label and "local" into typestr
+        mDNS_snprintf(typestr, sizeof(typestr), "%#s.local.", SecondLabel(servicename));
+    }
+    else
+    {
+        namestr[0] = 0;
+        typestr[0] = 0;
+    }
 
     // Calculate reply data length
     len = sizeof(DNSServiceFlags);
@@ -518,9 +589,9 @@ mDNSlocal AuthRecord *read_rr_from_ipc_msg(request_state *request, int GetTTL, i
     mDNSu16 type    = get_uint16(&request->msgptr, request->msgend);
     mDNSu16     class   = get_uint16(&request->msgptr, request->msgend);
     mDNSu16 rdlen   = get_uint16(&request->msgptr, request->msgend);
-    const char *rdata   = get_rdata (&request->msgptr, request->msgend, rdlen);
+    const mDNSu8 *const rdata = (const mDNSu8 *)get_rdata (&request->msgptr, request->msgend, rdlen);
     mDNSu32 ttl   = GetTTL ? get_uint32(&request->msgptr, request->msgend) : 0;
-    size_t storage_size = rdlen > sizeof(RDataBody) ? rdlen : sizeof(RDataBody);
+    size_t rdcapacity;
     AuthRecord *rr;
     mDNSInterfaceID InterfaceID;
     AuthRecType artype;
@@ -557,7 +628,8 @@ mDNSlocal AuthRecord *read_rr_from_ipc_msg(request_state *request, int GetTTL, i
         return NULL;
 #endif
     }
-    rr = (AuthRecord *) callocL("AuthRecord/read_rr_from_ipc_msg", sizeof(AuthRecord) - sizeof(RDataBody) + storage_size);
+    rdcapacity = (rdlen > sizeof(RDataBody2)) ? rdlen : sizeof(RDataBody2);
+    rr = (AuthRecord *) callocL("AuthRecord/read_rr_from_ipc_msg", sizeof(*rr) - sizeof(RDataBody) + rdcapacity);
     if (!rr) FatalError("ERROR: calloc");
 
     if (InterfaceID == mDNSInterface_LocalOnly)
@@ -593,8 +665,15 @@ mDNSlocal AuthRecord *read_rr_from_ipc_msg(request_state *request, int GetTTL, i
     if (flags & kDNSServiceFlagsAllowRemoteQuery) rr->AllowRemoteQuery = mDNStrue;
     rr->resrec.rrclass = class;
     rr->resrec.rdlength = rdlen;
-    rr->resrec.rdata->MaxRDLength = rdlen;
-    mDNSPlatformMemCopy(rr->resrec.rdata->u.data, rdata, rdlen);
+    rr->resrec.rdata->MaxRDLength = (mDNSu16)rdcapacity;
+    if (!SetRData(mDNSNULL, rdata, rdata + rdlen, &rr->resrec, rdlen))
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+            "[R%u] read_rr_from_ipc_msg: SetRData failed for " PRI_DM_NAME " (" PUB_S ")",
+            request->request_id, DM_NAME_PARAM(rr->resrec.name), DNSTypeName(type));
+        freeL("AuthRecord/read_rr_from_ipc_msg", rr);
+        return NULL;
+    }
     if (GetTTL) rr->resrec.rroriginalttl = ttl;
     rr->resrec.namehash = DomainNameHashValue(rr->resrec.name);
     SetNewRData(&rr->resrec, mDNSNULL, 0);  // Sets rr->rdatahash for us
@@ -615,13 +694,15 @@ mDNSlocal int build_domainname_from_strings(domainname *srv, char *name, char *r
 
 mDNSlocal void send_all(dnssd_sock_t s, const char *ptr, int len)
 {
-    int n = send(s, ptr, len, 0);
+    const ssize_t n = send(s, ptr, len, 0);
     // On a freshly-created Unix Domain Socket, the kernel should *never* fail to buffer a small write for us
     // (four bytes for a typical error code return, 12 bytes for DNSServiceGetProperty(DaemonVersion)).
     // If it does fail, we don't attempt to handle this failure, but we do log it so we know something is wrong.
     if (n < len)
-        LogMsg("ERROR: send_all(%d) wrote %d of %d errno %d (%s)",
-               s, n, len, dnssd_errno, dnssd_strerror(dnssd_errno));
+    {
+        LogMsg("ERROR: send_all(%d) wrote %ld of %d errno %d (%s)",
+            s, (long)n, len, dnssd_errno, dnssd_strerror(dnssd_errno));
+    }
 }
 
 #if 0
@@ -653,6 +734,28 @@ mDNSlocal mDNSBool AuthorizedDomain(const request_state * const request, const d
 }
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+mDNSlocal void SetupAuditTokenForRequest(request_state *request)
+{
+
+    pid_t audit_pid = audit_token_to_pid(request->audit_token);
+    if (audit_pid == 0)
+    {
+#if !defined(LOCAL_PEERTOKEN)
+#define LOCAL_PEERTOKEN         0x006           /* retrieve peer audit token */
+#endif
+        socklen_t len = sizeof(audit_token_t);
+        int ret = getsockopt(request->sd, SOL_LOCAL, LOCAL_PEERTOKEN, &request->audit_token, &len);
+        if (ret != 0)
+        {
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+                      "SetupAuditTokenForRequest: No audit_token using LOCAL_PEERTOKEN (%s PID %d) for op %d ret(%d)",
+                      request->pid_name, request->process_id, request->hdr.op, ret);
+        }
+    }
+}
+#endif
+
 // ***************************************************************************
 #if COMPILER_LIKES_PRAGMA_MARK
 #pragma mark -
@@ -665,6 +768,7 @@ mDNSlocal void external_start_advertising_helper(service_instance *const instanc
     AuthRecord *st = instance->subtypes;
     ExtraResourceRecord *e;
     int i;
+    const pid_t requestPID = instance->request->process_id;
 
     if (mDNSIPPortIsZero(instance->request->u.servicereg.port))
     {
@@ -675,14 +779,14 @@ mDNSlocal void external_start_advertising_helper(service_instance *const instanc
     if (instance->external_advertise) LogMsg("external_start_advertising_helper: external_advertise already set!");
 
     for ( i = 0; i < instance->request->u.servicereg.num_subtypes; i++)
-        external_start_advertising_service(&st[i].resrec, instance->request->flags);
+        external_start_advertising_service(&st[i].resrec, instance->request->flags, requestPID);
 
-    external_start_advertising_service(&instance->srs.RR_PTR.resrec, instance->request->flags);
-    external_start_advertising_service(&instance->srs.RR_SRV.resrec, instance->request->flags);
-    external_start_advertising_service(&instance->srs.RR_TXT.resrec, instance->request->flags);
+    external_start_advertising_service(&instance->srs.RR_PTR.resrec, instance->request->flags, requestPID);
+    external_start_advertising_service(&instance->srs.RR_SRV.resrec, instance->request->flags, requestPID);
+    external_start_advertising_service(&instance->srs.RR_TXT.resrec, instance->request->flags, requestPID);
 
     for (e = instance->srs.Extras; e; e = e->next)
-        external_start_advertising_service(&e->r.resrec, instance->request->flags);
+        external_start_advertising_service(&e->r.resrec, instance->request->flags, requestPID);
 
     instance->external_advertise = mDNStrue;
 }
@@ -699,18 +803,19 @@ mDNSlocal void external_stop_advertising_helper(service_instance *const instance
 
     if (instance->request)
     {
+        const pid_t requestPID = instance->request->process_id;
         for (i = 0; i < instance->request->u.servicereg.num_subtypes; i++)
         {
-            external_stop_advertising_service(&st[i].resrec, instance->request->flags);
+            external_stop_advertising_service(&st[i].resrec, instance->request->flags, requestPID);
         }
 
-        external_stop_advertising_service(&instance->srs.RR_PTR.resrec, instance->request->flags);
-        external_stop_advertising_service(&instance->srs.RR_SRV.resrec, instance->request->flags);
-        external_stop_advertising_service(&instance->srs.RR_TXT.resrec, instance->request->flags);
+        external_stop_advertising_service(&instance->srs.RR_PTR.resrec, instance->request->flags, requestPID);
+        external_stop_advertising_service(&instance->srs.RR_SRV.resrec, instance->request->flags, requestPID);
+        external_stop_advertising_service(&instance->srs.RR_TXT.resrec, instance->request->flags, requestPID);
 
         for (e = instance->srs.Extras; e; e = e->next)
         {
-            external_stop_advertising_service(&e->r.resrec, instance->request->flags);
+            external_stop_advertising_service(&e->r.resrec, instance->request->flags, requestPID);
         }
     }
 
@@ -718,6 +823,20 @@ mDNSlocal void external_stop_advertising_helper(service_instance *const instance
 }
 #endif  // MDNSRESPONDER_SUPPORTS(APPLE, D2D)
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+mDNSlocal dispatch_queue_t _get_trust_results_dispatch_queue(void)
+{
+    static dispatch_once_t  once    = 0;
+    static dispatch_queue_t queue   = NULL;
+
+    dispatch_once(&once, ^{
+        dispatch_queue_attr_t const attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
+        queue = dispatch_queue_create("com.apple.mDNSResponder.trust_results-queue", attr);
+    });
+    return queue;
+}
+#endif
+
 // ***************************************************************************
 #if COMPILER_LIKES_PRAGMA_MARK
 #pragma mark -
@@ -864,7 +983,7 @@ mDNSlocal void regservice_callback(mDNS *const m, ServiceRecordSet *const srs, m
                 break;
         }
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "[R%u] DNSServiceRegister(" PRI_DM_NAME ", %u) %s",
-                  request_id, DM_NAME_PARAM(srs->RR_SRV.resrec.name->c), mDNSVal16(srs->RR_SRV.resrec.rdata->u.srv.port), result_description);
+                  request_id, DM_NAME_PARAM(srs->RR_SRV.resrec.name), mDNSVal16(srs->RR_SRV.resrec.rdata->u.srv.port), result_description);
     }
 
     if (!instance->request && result != mStatus_MemFree)
@@ -886,7 +1005,7 @@ mDNSlocal void regservice_callback(mDNS *const m, ServiceRecordSet *const srs, m
         }
 
         if (GenerateNTDResponse(srs->RR_SRV.resrec.name, srs->RR_SRV.resrec.InterfaceID, instance->request, &rep, reg_service_reply_op, kDNSServiceFlagsAdd, result) != mStatus_NoError)
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[R%u] regservice_callback: " PRI_DM_NAME " is not valid DNS-SD SRV name", instance->request->request_id, DM_NAME_PARAM(srs->RR_SRV.resrec.name->c));
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[R%u] regservice_callback: " PRI_DM_NAME " is not valid DNS-SD SRV name", instance->request->request_id, DM_NAME_PARAM(srs->RR_SRV.resrec.name));
         else { append_reply(instance->request, rep); instance->clientnotified = mDNStrue; }
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
@@ -942,7 +1061,7 @@ mDNSlocal void regservice_callback(mDNS *const m, ServiceRecordSet *const srs, m
             if (!SuppressError)
             {
                 if (GenerateNTDResponse(srs->RR_SRV.resrec.name, srs->RR_SRV.resrec.InterfaceID, instance->request, &rep, reg_service_reply_op, kDNSServiceFlagsAdd, result) != mStatus_NoError)
-                    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[R%u] regservice_callback: " PRI_DM_NAME " is not valid DNS-SD SRV name", instance->request->request_id, DM_NAME_PARAM(srs->RR_SRV.resrec.name->c));
+                    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[R%u] regservice_callback: " PRI_DM_NAME " is not valid DNS-SD SRV name", instance->request->request_id, DM_NAME_PARAM(srs->RR_SRV.resrec.name));
                 else { append_reply(instance->request, rep); instance->clientnotified = mDNStrue; }
             }
             unlink_and_free_service_instance(instance);
@@ -953,7 +1072,7 @@ mDNSlocal void regservice_callback(mDNS *const m, ServiceRecordSet *const srs, m
         if (!SuppressError)
         {
             if (GenerateNTDResponse(srs->RR_SRV.resrec.name, srs->RR_SRV.resrec.InterfaceID, instance->request, &rep, reg_service_reply_op, kDNSServiceFlagsAdd, result) != mStatus_NoError)
-                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[R%u] regservice_callback: " PRI_DM_NAME " is not valid DNS-SD SRV name", instance->request->request_id, DM_NAME_PARAM(srs->RR_SRV.resrec.name->c));
+                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[R%u] regservice_callback: " PRI_DM_NAME " is not valid DNS-SD SRV name", instance->request->request_id, DM_NAME_PARAM(srs->RR_SRV.resrec.name));
             else { append_reply(instance->request, rep); instance->clientnotified = mDNStrue; }
         }
     }
@@ -1056,7 +1175,7 @@ mDNSlocal void regrecord_callback(mDNS *const m, AuthRecord *rr, mStatus result)
             {
                 LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                           "[R%u] regrecord_callback: calling external_start_advertising_service", request->request_id);
-                external_start_advertising_service(&rr->resrec, request->flags);
+                external_start_advertising_service(&rr->resrec, request->flags, request->process_id);
                 re->external_advertise = mDNStrue;
             }
 #endif
@@ -1111,6 +1230,15 @@ mDNSlocal void connection_termination(request_state *request)
             if (tmp->replies) LogMsg("connection_termination ERROR How can subordinate req %p %d have replies queued?", tmp, tmp->sd);
             abort_request(tmp);
             *req = tmp->next;
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+            if (tmp->trust)
+            {
+                void * context = mdns_trust_get_context(tmp->trust);
+                mdns_trust_set_context(tmp->trust, NULL);
+                if (context) freeL("context/connection_termination", context);
+                mdns_trust_forget(&tmp->trust);
+            }
+#endif
             freeL("request_state/connection_termination", tmp);
         }
         else
@@ -1130,7 +1258,7 @@ mDNSlocal void connection_termination(request_state *request)
         {
             ptr->external_advertise = mDNSfalse;
 #if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
-            external_stop_advertising_service(&ptr->rr->resrec, request->flags);
+            external_stop_advertising_service(&ptr->rr->resrec, request->flags, request->process_id);
 #endif
         }
         LogMcastS(ptr->rr, request, reg_stop);
@@ -1154,6 +1282,15 @@ mDNSlocal void handle_cancel_request(request_state *request)
             request_state *tmp = *req;
             abort_request(tmp);
             *req = tmp->next;
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+            if (tmp->trust)
+            {
+                void * context = mdns_trust_get_context(tmp->trust);
+                mdns_trust_set_context(tmp->trust, NULL);
+                if (context) freeL("context/handle_cancel_request", context);
+                mdns_trust_forget(&tmp->trust);
+            }
+#endif
             freeL("request_state/handle_cancel_request", tmp);
         }
         else
@@ -1161,6 +1298,161 @@ mDNSlocal void handle_cancel_request(request_state *request)
     }
 }
 
+mDNSlocal mStatus _handle_regrecord_request_start(request_state *request, AuthRecord * rr)
+{
+    mStatus err;
+    registered_record_entry *re;
+    // Don't allow non-local domains to be regsitered as LocalOnly. Allowing this would permit
+    // clients to register records such as www.bigbank.com A w.x.y.z to redirect Safari.
+    if (rr->resrec.InterfaceID == mDNSInterface_LocalOnly && !IsLocalDomain(rr->resrec.name) &&
+        rr->resrec.rrclass == kDNSClass_IN && (rr->resrec.rrtype == kDNSType_A || rr->resrec.rrtype == kDNSType_AAAA ||
+                                               rr->resrec.rrtype == kDNSType_CNAME))
+    {
+        freeL("AuthRecord/handle_regrecord_request", rr);
+        return (mStatus_BadParamErr);
+    }
+    // allocate registration entry, link into list
+    re = (registered_record_entry *) callocL("registered_record_entry", sizeof(*re));
+    if (!re) FatalError("ERROR: calloc");
+    re->key                   = request->hdr.reg_index;
+    re->rr                    = rr;
+    re->regrec_client_context = request->hdr.client_context;
+    re->request               = request;
+    re->external_advertise    = mDNSfalse;
+    rr->RecordContext         = re;
+    rr->RecordCallback        = regrecord_callback;
+
+    re->origInterfaceID = rr->resrec.InterfaceID;
+    if (rr->resrec.InterfaceID == mDNSInterface_P2P)
+        rr->resrec.InterfaceID = mDNSInterface_Any;
+#if 0
+    if (!AuthorizedDomain(request, rr->resrec.name, AutoRegistrationDomains)) return (mStatus_NoError);
+#endif
+    if (rr->resrec.rroriginalttl == 0)
+        rr->resrec.rroriginalttl = DefaultTTLforRRType(rr->resrec.rrtype);
+
+    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+           "[R%d] DNSServiceRegisterRecord(0x%X, %d, " PRI_S ") START PID[%d](" PUB_S ")",
+           request->request_id, request->flags, request->interfaceIndex, RRDisplayString(&mDNSStorage, &rr->resrec), request->process_id,
+           request->pid_name);
+
+    err = mDNS_Register(&mDNSStorage, rr);
+    if (err)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+               "[R%d] DNSServiceRegisterRecord(0x%X, %d," PRI_S ") ERROR (%d)",
+               request->request_id, request->flags, request->interfaceIndex, RRDisplayString(&mDNSStorage, &rr->resrec), err);
+        freeL("registered_record_entry", re);
+        freeL("registered_record_entry/AuthRecord", rr);
+    }
+    else
+    {
+        LogMcastS(rr, request, reg_start);
+        re->next = request->u.reg_recs;
+        request->u.reg_recs = re;
+    }
+    return err;
+}
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+
+mDNSlocal void _return_regrecord_request_error(request_state *request, mStatus error)
+{
+    reply_state *rep;
+    if (GenerateNTDResponse(NULL, 0, request, &rep, reg_record_reply_op, 0, error) != mStatus_NoError)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[R%u] DNSServiceRegisterRecord _return_regrecord_request_error: error(%d)", request->request_id, error);
+    }
+    else
+    {
+        append_reply(request, rep);
+    }
+}
+
+mDNSlocal mStatus _handle_regrecord_request_with_trust(request_state *request, AuthRecord * rr)
+{
+    mStatus err;
+    if (audit_token_to_pid(request->audit_token) == 0)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_WARNING, "[R%u] _handle_regrecord_request_with_trust: no audit token for pid(%s %d)", request->request_id, request->pid_name, request->process_id);
+        err = _handle_regrecord_request_start(request, rr);
+    }
+    else
+    {
+        const char *service_ptr = NULL;
+        char type_str[MAX_ESCAPED_DOMAIN_NAME] = "";
+        domainlabel name;
+        domainname type, domain;
+        bool good = DeconstructServiceName(rr->resrec.name, &name, &type, &domain);
+        if (good)
+        {
+            ConvertDomainNameToCString(&type, type_str);
+            service_ptr = type_str;
+        }
+
+        mdns_trust_flags_t flags = mdns_trust_flags_none;
+        mdns_trust_status_t status = mdns_trust_check_bonjour(request->audit_token, service_ptr, &flags);
+        switch (status)
+        {
+            case mdns_trust_status_denied:
+            case mdns_trust_status_pending:
+            {
+                mdns_trust_t trust = mdns_trust_create(request->audit_token, service_ptr, flags);
+                if (!trust)
+                {
+                    freeL("AuthRecord/_handle_regrecord_request_with_trust", rr);
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
+                mdns_trust_set_context(trust, rr);
+                mdns_trust_set_queue(trust, _get_trust_results_dispatch_queue());
+                mdns_trust_set_event_handler(trust, ^(mdns_trust_event_t event, mdns_trust_status_t update)
+                {
+                    if (event == mdns_trust_event_result)
+                    {
+                        mStatus error = (update != mdns_trust_status_granted) ? mStatus_PolicyDenied : mStatus_NoError;
+                        KQueueLock();
+                        AuthRecord * _rr =  mdns_trust_get_context(trust);
+                        if (_rr)
+                        {
+                            if (!error)
+                            {
+                                mdns_trust_set_context(trust, NULL); // _handle_regrecord_request_start handles free
+                                error = _handle_regrecord_request_start(request, _rr);
+                                // No context means the request was canceled before we got here
+                            }
+                            if (error) // (not else if) Always check for error result
+                            {
+                                _return_regrecord_request_error(request, error);
+                            }
+                        }
+                        KQueueUnlock("_handle_regrecord_request_with_trust");
+                    }
+                });
+                request->trust = trust;
+                mdns_trust_activate(trust);
+                err = mStatus_NoError;
+                break;
+            }
+
+            case mdns_trust_status_no_entitlement:
+                err = mStatus_NoAuth;
+                break;
+
+            case mdns_trust_status_granted:
+                err = _handle_regrecord_request_start(request, rr);
+                break;
+
+            default:
+                err = mStatus_UnknownErr;
+                break;
+        }
+     }
+exit:
+    return err;
+}
+#endif // TRUST_ENFORCEMENT
+
 mDNSlocal mStatus handle_regrecord_request(request_state *request)
 {
     mStatus err = mStatus_BadParamErr;
@@ -1172,56 +1464,19 @@ mDNSlocal mStatus handle_regrecord_request(request_state *request)
     rr = read_rr_from_ipc_msg(request, 1, 1);
     if (rr)
     {
-        registered_record_entry *re;
-        // Don't allow non-local domains to be regsitered as LocalOnly. Allowing this would permit
-        // clients to register records such as www.bigbank.com A w.x.y.z to redirect Safari.
-        if (rr->resrec.InterfaceID == mDNSInterface_LocalOnly && !IsLocalDomain(rr->resrec.name) &&
-            rr->resrec.rrclass == kDNSClass_IN && (rr->resrec.rrtype == kDNSType_A || rr->resrec.rrtype == kDNSType_AAAA ||
-                                                   rr->resrec.rrtype == kDNSType_CNAME))
-        {
-            freeL("AuthRecord/handle_regrecord_request", rr);
-            return (mStatus_BadParamErr);
-        }
-        // allocate registration entry, link into list
-        re = (registered_record_entry *) callocL("registered_record_entry", sizeof(*re));
-        if (!re) FatalError("ERROR: calloc");
-        re->key                   = request->hdr.reg_index;
-        re->rr                    = rr;
-        re->regrec_client_context = request->hdr.client_context;
-        re->request               = request;
-        re->external_advertise    = mDNSfalse;
-        rr->RecordContext         = re;
-        rr->RecordCallback        = regrecord_callback;
-
-        re->origInterfaceID = rr->resrec.InterfaceID;
-        if (rr->resrec.InterfaceID == mDNSInterface_P2P)
-            rr->resrec.InterfaceID = mDNSInterface_Any;
-#if 0
-        if (!AuthorizedDomain(request, rr->resrec.name, AutoRegistrationDomains)) return (mStatus_NoError);
-#endif
-        if (rr->resrec.rroriginalttl == 0)
-            rr->resrec.rroriginalttl = DefaultTTLforRRType(rr->resrec.rrtype);
-
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-               "[R%d] DNSServiceRegisterRecord(0x%X, %d, " PRI_S ") START PID[%d](" PUB_S ")",
-               request->request_id, request->flags, request->interfaceIndex, RRDisplayString(&mDNSStorage, &rr->resrec), request->process_id,
-               request->pid_name);
-
-        err = mDNS_Register(&mDNSStorage, rr);
-        if (err)
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+        if (os_feature_enabled(mDNSResponder, bonjour_privacy) &&
+            IsLocalDomain(rr->resrec.name))
         {
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-                   "[R%d] DNSServiceRegisterRecord(0x%X, %d," PRI_S ") ERROR (%d)",
-                   request->request_id, request->flags, request->interfaceIndex, RRDisplayString(&mDNSStorage, &rr->resrec), err);
-            freeL("registered_record_entry", re);
-            freeL("registered_record_entry/AuthRecord", rr);
+            err = _handle_regrecord_request_with_trust(request, rr);
         }
         else
         {
-            LogMcastS(rr, request, reg_start);
-            re->next = request->u.reg_recs;
-            request->u.reg_recs = re;
+            err = _handle_regrecord_request_start(request, rr);
         }
+#else
+        err = _handle_regrecord_request_start(request, rr);
+#endif
     }
     return(err);
 }
@@ -1241,7 +1496,7 @@ mDNSlocal void regservice_termination_callback(request_state *request)
         request->u.servicereg.instances = request->u.servicereg.instances->next;
         // only safe to free memory if registration is not valid, i.e. deregister fails (which invalidates p)
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "[R%d] DNSServiceRegister(" PRI_DM_NAME ", %u) STOP PID[%d](" PUB_S ")",
-               request->request_id, DM_NAME_PARAM(p->srs.RR_SRV.resrec.name->c),
+               request->request_id, DM_NAME_PARAM(p->srs.RR_SRV.resrec.name),
                mDNSVal16(p->srs.RR_SRV.resrec.rdata->u.srv.port), request->process_id, request->pid_name);
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
@@ -1284,19 +1539,29 @@ mDNSlocal request_state *LocateSubordinateRequest(request_state *request)
     return(request);
 }
 
-mDNSlocal mStatus add_record_to_service(request_state *request, service_instance *instance, mDNSu16 rrtype, mDNSu16 rdlen, const char *rdata, mDNSu32 ttl)
+mDNSlocal mStatus add_record_to_service(request_state *request, service_instance *instance, mDNSu16 rrtype, mDNSu16 rdlen,
+    const mDNSu8 *const rdata, mDNSu32 ttl)
 {
     ServiceRecordSet *srs = &instance->srs;
     mStatus result;
-    size_t size = rdlen > sizeof(RDataBody) ? rdlen : sizeof(RDataBody);
-    ExtraResourceRecord *extra = (ExtraResourceRecord *) mallocL("ExtraResourceRecord", sizeof(*extra) - sizeof(RDataBody) + size);
-    if (!extra) { my_perror("ERROR: malloc"); return mStatus_NoMemoryErr; }
+    const size_t rdcapacity = (rdlen > sizeof(RDataBody2)) ? rdlen : sizeof(RDataBody2);
+    ExtraResourceRecord *extra = (ExtraResourceRecord *)callocL("ExtraResourceRecord", sizeof(*extra) - sizeof(RDataBody) + rdcapacity);
+    if (!extra) { my_perror("ERROR: calloc"); return mStatus_NoMemoryErr; }
 
-    mDNSPlatformMemZero(extra, sizeof(*extra));  // OK if oversized rdata not zero'd
     extra->r.resrec.rrtype = rrtype;
-    extra->r.rdatastorage.MaxRDLength = (mDNSu16) size;
+    extra->r.resrec.rdata = &extra->r.rdatastorage;
+    extra->r.resrec.rdata->MaxRDLength = (mDNSu16)rdcapacity;
     extra->r.resrec.rdlength = rdlen;
-    mDNSPlatformMemCopy(&extra->r.rdatastorage.u.data, rdata, rdlen);
+    if (!SetRData(mDNSNULL, rdata, rdata + rdlen, &extra->r.resrec, rdlen))
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+            "[R%u] read_rr_from_ipc_msg: SetRData failed for " PRI_DM_NAME " (" PUB_S ")",
+            request->request_id, DM_NAME_PARAM(request->u.servicereg.instances ?
+            request->u.servicereg.instances->srs.RR_SRV.resrec.name : mDNSNULL), DNSTypeName(rrtype));
+        freeL("ExtraResourceRecord/add_record_to_service", extra);
+        return mStatus_BadParamErr;
+    }
+    SetNewRData(&extra->r.resrec, mDNSNULL, 0);  // Sets rr->rdatahash for us
     // use InterfaceID value from DNSServiceRegister() call that created the original service
     extra->r.resrec.InterfaceID = request->u.servicereg.InterfaceID;
 
@@ -1314,7 +1579,7 @@ mDNSlocal mStatus add_record_to_service(request_state *request, service_instance
            && callExternalHelpers(request->u.servicereg.InterfaceID, &instance->domain, request->flags))
     {
         LogInfo("add_record_to_service: calling external_start_advertising_service");
-        external_start_advertising_service(&extra->r.resrec, request->flags);
+        external_start_advertising_service(&extra->r.resrec, request->flags, request->process_id);
     }
 #endif
     return result;
@@ -1327,7 +1592,7 @@ mDNSlocal mStatus handle_add_request(request_state *request)
     DNSServiceFlags flags  = get_flags (&request->msgptr, request->msgend);
     mDNSu16 rrtype = get_uint16(&request->msgptr, request->msgend);
     mDNSu16 rdlen  = get_uint16(&request->msgptr, request->msgend);
-    const char     *rdata  = get_rdata (&request->msgptr, request->msgend, rdlen);
+    const mDNSu8 *const rdata = (const mDNSu8 *)get_rdata(&request->msgptr, request->msgend, rdlen);
     mDNSu32 ttl    = get_uint32(&request->msgptr, request->msgend);
     if (!ttl) ttl = DefaultTTLforRRType(rrtype);
     (void)flags; // Unused
@@ -1360,7 +1625,7 @@ mDNSlocal mStatus handle_add_request(request_state *request)
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
            "[R%d] DNSServiceAddRecord(%X, " PRI_DM_NAME ", " PUB_S ", %d) PID[%d](" PUB_S ")",
            request->request_id, flags,
-           DM_NAME_PARAM((request->u.servicereg.instances) ? (request->u.servicereg.instances->srs.RR_SRV.resrec.name->c) : mDNSNULL),
+           DM_NAME_PARAM((request->u.servicereg.instances) ? (request->u.servicereg.instances->srs.RR_SRV.resrec.name) : mDNSNULL),
            DNSTypeName(rrtype), rdlen, request->process_id, request->pid_name);
 
     for (i = request->u.servicereg.instances; i; i = i->next)
@@ -1397,33 +1662,48 @@ mDNSlocal void update_callback(mDNS *const m, AuthRecord *const rr, RData *oldrd
         if (ext.rdlength == oldrdlen && mDNSPlatformMemSame(&ext.rdata->u, &oldrd->u, oldrdlen)) goto exit;
         SetNewRData(&ext, oldrd, oldrdlen);
 #if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
-        external_stop_advertising_service(&ext, flags);
+        external_stop_advertising_service(&ext, flags, 0);
         LogInfo("update_callback: calling external_start_advertising_service");
-        external_start_advertising_service(&rr->resrec, flags);
+        external_start_advertising_service(&rr->resrec, flags, 0);
 #endif
     }
 exit:
     if (oldrd != &rr->rdatastorage) freeL("RData/update_callback", oldrd);
 }
 
-mDNSlocal mStatus update_record(AuthRecord *rr, mDNSu16 rdlen, const char *rdata, mDNSu32 ttl, const mDNSBool *const external_advertise)
+mDNSlocal mStatus update_record(AuthRecord *ar, mDNSu16 rdlen, const mDNSu8 *const rdata, mDNSu32 ttl,
+    const mDNSBool *const external_advertise, const mDNSu32 request_id)
 {
+    ResourceRecord rr;
     mStatus result;
-    const size_t rdsize = (rdlen > sizeof(RDataBody)) ? rdlen : sizeof(RDataBody);
-    RData *newrd = (RData *) mallocL("RData/update_record", sizeof(RData) - sizeof(RDataBody) + rdsize);
-    if (!newrd) FatalError("ERROR: malloc");
-    newrd->MaxRDLength = (mDNSu16) rdsize;
-    mDNSPlatformMemCopy(&newrd->u, rdata, rdlen);
-
+    const size_t rdcapacity = (rdlen > sizeof(RDataBody2)) ? rdlen : sizeof(RDataBody2);
+    RData *newrd = (RData *) callocL("RData/update_record", sizeof(*newrd) - sizeof(RDataBody) + rdcapacity);
+    if (!newrd) FatalError("ERROR: calloc");
+    mDNSPlatformMemZero(&rr, (mDNSu32)sizeof(rr));
+    rr.name     = ar->resrec.name;
+    rr.rrtype   = ar->resrec.rrtype;
+    rr.rrclass  = ar->resrec.rrclass;
+    rr.rdata    = newrd;
+    rr.rdata->MaxRDLength = (mDNSu16)rdcapacity;
+    rr.rdlength = rdlen;
+    if (!SetRData(mDNSNULL, rdata, rdata + rdlen, &rr, rdlen))
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+            "[R%u] update_record: SetRData failed for " PRI_DM_NAME " (" PUB_S ")",
+            request_id, DM_NAME_PARAM(rr.name), DNSTypeName(rr.rrtype));
+        freeL("RData/update_record", newrd);
+        return mStatus_BadParamErr;
+    }
+    rdlen = GetRDLength(&rr, mDNSfalse);
     // BIND named (name daemon) doesn't allow TXT records with zero-length rdata. This is strictly speaking correct,
     // since RFC 1035 specifies a TXT record as "One or more <character-string>s", not "Zero or more <character-string>s".
     // Since some legacy apps try to create zero-length TXT records, we'll silently correct it here.
-    if (rr->resrec.rrtype == kDNSType_TXT && rdlen == 0) { rdlen = 1; newrd->u.txt.c[0] = 0; }
+    if (ar->resrec.rrtype == kDNSType_TXT && rdlen == 0) { rdlen = 1; newrd->u.txt.c[0] = 0; }
 
-    if (external_advertise) rr->UpdateContext = (void *)external_advertise;
+    if (external_advertise) ar->UpdateContext = (void *)external_advertise;
 
-    result = mDNS_Update(&mDNSStorage, rr, ttl, rdlen, newrd, update_callback);
-    if (result) { LogMsg("update_record: Error %d for %s", (int)result, ARDisplayString(&mDNSStorage, rr)); freeL("RData/update_record", newrd); }
+    result = mDNS_Update(&mDNSStorage, ar, ttl, rdlen, newrd, update_callback);
+    if (result) { LogMsg("update_record: Error %d for %s", (int)result, ARDisplayString(&mDNSStorage, ar)); freeL("RData/update_record", newrd); }
     return result;
 }
 
@@ -1437,7 +1717,7 @@ mDNSlocal mStatus handle_update_request(request_state *request)
     // get the message data
     DNSServiceFlags flags = get_flags (&request->msgptr, request->msgend);  // flags unused
     mDNSu16 rdlen = get_uint16(&request->msgptr, request->msgend);
-    const char     *rdata = get_rdata (&request->msgptr, request->msgend, rdlen);
+    const mDNSu8 *const rdata = (const mDNSu8 *)get_rdata(&request->msgptr, request->msgend, rdlen);
     mDNSu32 ttl   = get_uint32(&request->msgptr, request->msgend);
     (void)flags; // Unused
 
@@ -1459,10 +1739,10 @@ mDNSlocal mStatus handle_update_request(request_state *request)
         {
             if (reptr->key == hdr->reg_index)
             {
-                result = update_record(reptr->rr, rdlen, rdata, ttl, &reptr->external_advertise);
+                result = update_record(reptr->rr, rdlen, rdata, ttl, &reptr->external_advertise, request->request_id);
                 LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
                        "[R%d] DNSServiceUpdateRecord(" PRI_DM_NAME ", " PUB_S ") PID[%d](" PUB_S ")",
-                       request->request_id, DM_NAME_PARAM(reptr->rr->resrec.name->c),
+                       request->request_id, DM_NAME_PARAM(reptr->rr->resrec.name),
                        reptr->rr ? DNSTypeName(reptr->rr->resrec.rrtype) : "<NONE>",
                        request->process_id, request->pid_name);
                 goto end;
@@ -1513,7 +1793,7 @@ mDNSlocal mStatus handle_update_request(request_state *request)
         }
 
         if (!rr) { result = mStatus_BadReferenceErr; goto end; }
-        result = update_record(rr, rdlen, rdata, ttl, &i->external_advertise);
+        result = update_record(rr, rdlen, rdata, ttl, &i->external_advertise, request->request_id);
         if (result && i->default_local) goto end;
         else result = mStatus_NoError;  // suppress non-local default errors
     }
@@ -1545,7 +1825,7 @@ mDNSlocal mStatus remove_record(request_state *request)
     if (e->external_advertise)
     {
 #if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
-        external_stop_advertising_service(&e->rr->resrec, request->flags);
+        external_stop_advertising_service(&e->rr->resrec, request->flags, request->process_id);
 #endif
         e->external_advertise = mDNSfalse;
     }
@@ -1571,7 +1851,10 @@ mDNSlocal mStatus remove_extra(const request_state *const request, service_insta
         {
             *rrtype = ptr->r.resrec.rrtype;
 #if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
-            if (serv->external_advertise) external_stop_advertising_service(&ptr->r.resrec, request->flags);
+            if (serv->external_advertise)
+            {
+                external_stop_advertising_service(&ptr->r.resrec, request->flags, request->process_id);
+            }
 #endif
             err = mDNS_RemoveRecordFromService(&mDNSStorage, &serv->srs, ptr, FreeExtraRR, ptr);
             break;
@@ -1609,7 +1892,7 @@ mDNSlocal mStatus handle_removerecord_request(request_state *request)
         mDNSu16 rrtype = 0;
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "[R%d] DNSServiceRemoveRecord(" PRI_DM_NAME ", " PUB_S ") PID[%d](" PUB_S ")",
                request->request_id,
-               DM_NAME_PARAM((request->u.servicereg.instances) ? (request->u.servicereg.instances->srs.RR_SRV.resrec.name->c) : mDNSNULL),
+               DM_NAME_PARAM((request->u.servicereg.instances) ? (request->u.servicereg.instances->srs.RR_SRV.resrec.name) : mDNSNULL),
                rrtype ? DNSTypeName(rrtype) : "<NONE>", request->process_id, request->pid_name);
         for (i = request->u.servicereg.instances; i; i = i->next)
         {
@@ -1852,6 +2135,139 @@ mDNSlocal mDNSBool PreDefinedInterfaceIndex(mDNSu32 interfaceIndex)
     }
 }
 
+mDNSlocal mStatus _handle_regservice_request_start(request_state *request, const domainname * const d)
+{
+    mStatus err;
+
+    request->terminate = regservice_termination_callback;
+    err = register_service_instance(request, d);
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
+    ++curr_num_regservices;
+    if (curr_num_regservices > max_num_regservices)
+        max_num_regservices = curr_num_regservices;
+#endif
+
+#if 0
+    err = AuthorizedDomain(request, d, AutoRegistrationDomains) ? register_service_instance(request, d) : mStatus_NoError;
+#endif
+    if (!err)
+    {
+        if (request->u.servicereg.autoname) UpdateDeviceInfoRecord(&mDNSStorage);
+
+        if (request->u.servicereg.default_domain)
+        {
+            DNameListElem *ptr;
+            // Note that we don't report errors for non-local, non-explicit domains
+            for (ptr = AutoRegistrationDomains; ptr; ptr = ptr->next)
+                if (!ptr->uid || SystemUID(request->uid) || request->uid == ptr->uid)
+                    register_service_instance(request, &ptr->name);
+        }
+    }
+    return err;
+}
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+
+mDNSlocal void _return_regservice_request_error(request_state *request, mStatus error)
+{
+    if (request->u.servicereg.txtdata)
+    {
+        freeL("service_info txtdata", request->u.servicereg.txtdata);
+        request->u.servicereg.txtdata = NULL;
+    }
+
+    reply_state *rep;
+    if (GenerateNTDResponse(NULL, 0, request, &rep, reg_service_reply_op, 0, error) != mStatus_NoError)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[R%u] DNSServiceRegister _return_regservice_request_error: error(%d)", request->request_id, error);
+    }
+    else
+    {
+        append_reply(request, rep);
+    }
+}
+
+mDNSlocal mStatus _handle_regservice_request_with_trust(request_state *request, const domainname * const d)
+{
+    mStatus err;
+    if (audit_token_to_pid(request->audit_token) == 0)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_WARNING, "[R%u] _handle_regservice_request_with_trust: no audit token for pid(%s %d)", request->request_id, request->pid_name, request->process_id);
+        err = _handle_regservice_request_start(request, d);
+    }
+    else
+    {
+        mdns_trust_flags_t flags = mdns_trust_flags_none;
+        mdns_trust_status_t status = mdns_trust_check_register_service(request->audit_token, request->u.servicereg.type_as_string, &flags);
+        switch (status) {
+            case mdns_trust_status_denied:
+            case mdns_trust_status_pending:
+            {
+                mdns_trust_t trust = mdns_trust_create(request->audit_token, request->u.servicereg.type_as_string, flags);
+                if (!trust)
+                {
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
+                void * context = mallocL("context/_handle_regservice_request_with_trust", sizeof(domainname));
+                if (!context)
+                {
+                    my_perror("ERROR: mallocL context/_handle_regservice_request_with_trust");
+                    mdns_release(trust);
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
+                memcpy(context, d, sizeof(domainname));
+                mdns_trust_set_context(trust, context);
+
+                mdns_trust_set_queue(trust, _get_trust_results_dispatch_queue());
+                mdns_trust_set_event_handler(trust, ^(mdns_trust_event_t event, mdns_trust_status_t update)
+                {
+                    if (event == mdns_trust_event_result)
+                    {
+                        mStatus error = (update != mdns_trust_status_granted) ? mStatus_PolicyDenied : mStatus_NoError;
+                        KQueueLock();
+                        const domainname * _d = mdns_trust_get_context(trust);
+                        if (_d)
+                        {
+                            if (!error)
+                            {
+                                error = _handle_regservice_request_start(request, _d);
+                                // No context means the request was canceled before we got here
+                            }
+                            if (error) // (not else if) Always check for error result
+                            {
+                                _return_regservice_request_error(request, error);
+                            }
+                        }
+                        KQueueUnlock("_register_service_instance_with_trust");
+                    }
+                });
+                request->trust = trust;
+                mdns_trust_activate(trust);
+                err = mStatus_NoError;
+                break;
+            }
+
+            case mdns_trust_status_no_entitlement:
+                err = mStatus_NoAuth;
+                break;
+
+            case mdns_trust_status_granted:
+                err = _handle_regservice_request_start(request, d);
+                break;
+
+            default:
+                err = mStatus_UnknownErr;
+                break;
+        }
+    }
+exit:
+    return err;
+}
+#endif // TRUST_ENFORCEMENT
+
 mDNSlocal mStatus handle_regservice_request(request_state *request)
 {
     char name[256]; // Lots of spare space for extra-long names that we'll auto-truncate down to 63 bytes
@@ -2010,32 +2426,26 @@ mDNSlocal mStatus handle_regservice_request(request_state *request)
     // We also need to set request->terminate first, before adding additional service instances,
     // because the udsserver_validatelists uses the request->terminate function pointer to determine
     // what kind of request this is, and therefore what kind of list validation is required.
-    request->terminate = regservice_termination_callback;
-
-    err = register_service_instance(request, &d);
-    
-#if MDNSRESPONDER_SUPPORTS(APPLE, METRICS)
-    ++curr_num_regservices;
-    if (curr_num_regservices > max_num_regservices)
-        max_num_regservices = curr_num_regservices;
-#endif
+    request->terminate = NULL;
 
-#if 0
-    err = AuthorizedDomain(request, &d, AutoRegistrationDomains) ? register_service_instance(request, &d) : mStatus_NoError;
-#endif
-    if (!err)
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+    if (os_feature_enabled(mDNSResponder, bonjour_privacy) &&
+        (request->u.servicereg.default_domain || IsLocalDomain(&d)))
     {
-        if (request->u.servicereg.autoname) UpdateDeviceInfoRecord(&mDNSStorage);
-
-        if (!*domain)
+        err = _handle_regservice_request_with_trust(request, &d);
+        if (err == mStatus_NoAuth && request->u.servicereg.txtdata)
         {
-            DNameListElem *ptr;
-            // Note that we don't report errors for non-local, non-explicit domains
-            for (ptr = AutoRegistrationDomains; ptr; ptr = ptr->next)
-                if (!ptr->uid || SystemUID(request->uid) || request->uid == ptr->uid)
-                    register_service_instance(request, &ptr->name);
+            freeL("service_info txtdata", request->u.servicereg.txtdata);
+            request->u.servicereg.txtdata = NULL;
         }
     }
+    else
+    {
+        err = _handle_regservice_request_start(request, &d);
+    }
+#else
+    err = _handle_regservice_request_start(request, &d);
+#endif
 
     return(err);
 
@@ -2092,7 +2502,7 @@ validReply:
 
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
            "[R%d->Q%d] DNSServiceBrowse(" PRI_DM_NAME ", " PUB_S ") RESULT " PUB_S " interface %d: " PRI_S,
-           req->request_id, mDNSVal16(question->TargetQID), DM_NAME_PARAM(question->qname.c), DNSTypeName(question->qtype),
+           req->request_id, mDNSVal16(question->TargetQID), DM_NAME_PARAM(&question->qname), DNSTypeName(question->qtype),
            AddRecord ? "ADD" : "RMV", mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNSfalse),
            RRDisplayString(m, answer));
 
@@ -2169,7 +2579,7 @@ mDNSlocal mStatus add_domain_to_browser(request_state *info, const domainname *d
             domainname tmp;
             ConstructServiceName(&tmp, NULL, &info->u.browser.regtype, &b->domain);
             LogDebug("add_domain_to_browser: calling external_start_browsing_for_service()");
-            external_start_browsing_for_service(info->u.browser.interface_id, &tmp, kDNSType_PTR, info->flags);
+            external_start_browsing_for_service(info->u.browser.interface_id, &tmp, kDNSType_PTR, info->flags, info->process_id);
         }
 #endif
     }
@@ -2194,12 +2604,12 @@ mDNSlocal void browse_termination_callback(request_state *info)
             domainname tmp;
             ConstructServiceName(&tmp, NULL, &info->u.browser.regtype, &ptr->domain);
             LogInfo("browse_termination_callback: calling external_stop_browsing_for_service()");
-            external_stop_browsing_for_service(ptr->q.InterfaceID, &tmp, kDNSType_PTR, ptr->q.flags);
+            external_stop_browsing_for_service(ptr->q.InterfaceID, &tmp, kDNSType_PTR, ptr->q.flags, info->process_id);
         }
 #endif
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
                "[R%d] DNSServiceBrowse(%X, %d, \"" PRI_DM_NAME "\") STOP PID[%d](" PUB_S ")",
-               info->request_id, info->flags, info->interfaceIndex, DM_NAME_PARAM(ptr->q.qname.c),
+               info->request_id, info->flags, info->interfaceIndex, DM_NAME_PARAM(&ptr->q.qname),
                info->process_id, info->pid_name);
 
         info->u.browser.browsers = ptr->next;
@@ -2507,27 +2917,157 @@ mDNSlocal void AutomaticBrowseDomainChange(mDNS *const m, DNSQuestion *q, const
     else RmvAutoBrowseDomain(0, &answer->rdata->u.name);
 }
 
-mDNSlocal mStatus handle_browse_request(request_state *request)
+mDNSlocal mStatus _handle_browse_request_start(request_state *request, const char * domain)
 {
-    // Note that regtype may include a trailing subtype
-    char regtype[MAX_ESCAPED_DOMAIN_NAME], domain[MAX_ESCAPED_DOMAIN_NAME];
-    domainname typedn, d, temp;
-    mDNSs32 NumSubTypes;
+    domainname d;
     mStatus err = mStatus_NoError;
 
-    DNSServiceFlags flags = get_flags(&request->msgptr, request->msgend);
-    mDNSu32 interfaceIndex = get_uint32(&request->msgptr, request->msgend);
-    mDNSInterfaceID InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex);
+    request->terminate = browse_termination_callback;
 
-    // The browse is scoped to a specific interface index, but the 
-    // interface is not currently in our list.
-    if (interfaceIndex && !InterfaceID)
+    if (domain[0])
     {
-        // If it's one of the specially defined inteface index values, just return an error.
-        if (PreDefinedInterfaceIndex(interfaceIndex))
-        {
-            LogInfo("handle_browse_request: bad interfaceIndex %d", interfaceIndex);
-            return(mStatus_BadParamErr);
+        if (!MakeDomainNameFromDNSNameString(&d, domain)) return(mStatus_BadParamErr);
+        err = add_domain_to_browser(request, &d);
+    }
+    else
+    {
+        DNameListElem *sdom;
+        for (sdom = AutoBrowseDomains; sdom; sdom = sdom->next)
+            if (!sdom->uid || SystemUID(request->uid) || request->uid == sdom->uid)
+            {
+                err = add_domain_to_browser(request, &sdom->name);
+                if (err)
+                {
+                    if (SameDomainName(&sdom->name, &localdomain)) break;
+                    else err = mStatus_NoError;  // suppress errors for non-local "default" domains
+                }
+            }
+    }
+
+    return(err);
+}
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+
+mDNSlocal void _return_browse_request_error(request_state *request, mStatus error)
+{
+    reply_state *rep;
+
+    GenerateBrowseReply(NULL, 0, request, &rep, browse_reply_op, 0, error);
+
+    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+           "[R%d] DNSServiceBrowse _return_browse_request_error: error (%d)", request->request_id, error);
+
+    append_reply(request, rep);
+}
+
+mDNSlocal mStatus _handle_browse_request_with_trust(request_state *request, const char * domain)
+{
+    mStatus err;
+    if (audit_token_to_pid(request->audit_token) == 0)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_WARNING, "[R%u] _handle_browse_request_with_trust: no audit token for pid(%s %d)", request->request_id, request->pid_name, request->process_id);
+        err = _handle_browse_request_start(request, domain);
+    }
+    else
+    {
+        char typestr[MAX_ESCAPED_DOMAIN_NAME];
+        typestr[0] = 0;
+        (void)ConvertDomainNameToCString(&request->u.browser.regtype, typestr);
+        mdns_trust_flags_t flags = mdns_trust_flags_none;
+        mdns_trust_status_t status = mdns_trust_check_bonjour(request->audit_token, typestr, &flags);
+        switch (status)
+        {
+            case mdns_trust_status_denied:
+            case mdns_trust_status_pending:
+            {
+                mdns_trust_t trust = mdns_trust_create(request->audit_token, typestr, flags);
+                if (!trust )
+                {
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
+
+                size_t len = strlen(domain) + 1;
+                void * context = mallocL("context/_handle_browse_request_with_trust", len);
+                if (!context)
+                {
+                    my_perror("ERROR: mallocL context/_handle_browse_request_with_trust");
+                    mdns_release(trust);
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
+                memcpy(context, domain, len);
+                mdns_trust_set_context(trust, context);
+
+                mdns_trust_set_queue(trust, _get_trust_results_dispatch_queue());
+                mdns_trust_set_event_handler(trust, ^(mdns_trust_event_t event, mdns_trust_status_t update)
+                {
+                    if (event == mdns_trust_event_result)
+                    {
+                        mStatus error = (update != mdns_trust_status_granted) ? mStatus_PolicyDenied : mStatus_NoError;
+                        KQueueLock();
+                        const char * _domain = mdns_trust_get_context(trust);
+                        if (_domain)
+                        {
+                            if (!error)
+                            {
+                                error = _handle_browse_request_start(request, _domain);
+                                // No context means the request was canceled before we got here
+                            }
+                            if (error) // (not else if) Always check for error result
+                            {
+                                _return_browse_request_error(request, error);
+                            }
+                        }
+                        KQueueUnlock("_handle_browse_request_with_trust");
+                    }
+                });
+                request->trust = trust;
+                mdns_trust_activate(trust);
+                err = mStatus_NoError;
+                break;
+            }
+
+            case mdns_trust_status_no_entitlement:
+                err = mStatus_NoAuth;
+                break;
+
+            case mdns_trust_status_granted:
+                err = _handle_browse_request_start(request, domain);
+                break;
+
+            default:
+                err = mStatus_UnknownErr;
+                break;
+        }
+    }
+exit:
+    return err;
+}
+#endif // TRUST_ENFORCEMENT
+
+mDNSlocal mStatus handle_browse_request(request_state *request)
+{
+    // Note that regtype may include a trailing subtype
+    char regtype[MAX_ESCAPED_DOMAIN_NAME], domain[MAX_ESCAPED_DOMAIN_NAME];
+    domainname typedn, temp;
+    mDNSs32 NumSubTypes;
+    mStatus err = mStatus_NoError;
+
+    DNSServiceFlags flags = get_flags(&request->msgptr, request->msgend);
+    mDNSu32 interfaceIndex = get_uint32(&request->msgptr, request->msgend);
+    mDNSInterfaceID InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex);
+
+    // The browse is scoped to a specific interface index, but the 
+    // interface is not currently in our list.
+    if (interfaceIndex && !InterfaceID)
+    {
+        // If it's one of the specially defined inteface index values, just return an error.
+        if (PreDefinedInterfaceIndex(interfaceIndex))
+        {
+            LogInfo("handle_browse_request: bad interfaceIndex %d", interfaceIndex);
+            return(mStatus_BadParamErr);
         }
 
         // Otherwise, use the specified interface index value and the browse will
@@ -2567,7 +3107,7 @@ mDNSlocal mStatus handle_browse_request(request_state *request)
     request->u.browser.browsers = NULL;
 
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "[R%d] DNSServiceBrowse(%X, %d, \"" PRI_DM_NAME "\", \"" PRI_S "\") START PID[%d](" PUB_S ")",
-           request->request_id, request->flags, interfaceIndex, DM_NAME_PARAM(request->u.browser.regtype.c), domain,
+           request->request_id, request->flags, interfaceIndex, DM_NAME_PARAM(&request->u.browser.regtype), domain,
            request->process_id, request->pid_name);
 
     if (request->u.browser.default_domain)
@@ -2581,27 +3121,24 @@ mDNSlocal mStatus handle_browse_request(request_state *request)
     // We need to unconditionally set request->terminate, because even if we didn't successfully
     // start any browses right now, subsequent configuration changes may cause successful
     // browses to be added, and we'll need to cancel them before freeing this memory.
-    request->terminate = browse_termination_callback;
+    request->terminate = NULL;
 
-    if (domain[0])
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+    domainname d;
+    if (!MakeDomainNameFromDNSNameString(&d, domain)) return(mStatus_BadParamErr);
+
+    if (os_feature_enabled(mDNSResponder, bonjour_privacy) &&
+        (request->u.browser.default_domain || IsLocalDomain(&d) || request->u.browser.ForceMCast))
     {
-        if (!MakeDomainNameFromDNSNameString(&d, domain)) return(mStatus_BadParamErr);
-        err = add_domain_to_browser(request, &d);
+        err = _handle_browse_request_with_trust(request, domain);
     }
     else
     {
-        DNameListElem *sdom;
-        for (sdom = AutoBrowseDomains; sdom; sdom = sdom->next)
-            if (!sdom->uid || SystemUID(request->uid) || request->uid == sdom->uid)
-            {
-                err = add_domain_to_browser(request, &sdom->name);
-                if (err)
-                {
-                    if (SameDomainName(&sdom->name, &localdomain)) break;
-                    else err = mStatus_NoError;  // suppress errors for non-local "default" domains
-                }
-            }
+        err = _handle_browse_request_start(request, domain);
     }
+#else
+    err = _handle_browse_request_start(request, domain);
+#endif
 
     return(err);
 }
@@ -2612,6 +3149,61 @@ mDNSlocal mStatus handle_browse_request(request_state *request)
 #pragma mark - DNSServiceResolve
 #endif
 
+mDNSlocal void resolve_termination_callback(request_state *request)
+{
+    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+           "[R%d] DNSServiceResolve(%X, %d, \"" PRI_DM_NAME "\") STOP PID[%d](" PUB_S ")",
+           request->request_id, request->flags, request->interfaceIndex, DM_NAME_PARAM(&request->u.resolve.qtxt.qname),
+           request->process_id, request->pid_name);
+    mDNS_StopQuery(&mDNSStorage, &request->u.resolve.qtxt);
+    mDNS_StopQuery(&mDNSStorage, &request->u.resolve.qsrv);
+    LogMcastQ(&request->u.resolve.qsrv, request, q_stop);
+#if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
+    if (request->u.resolve.external_advertise)
+    {
+        external_stop_resolving_service(request->u.resolve.qsrv.InterfaceID, &request->u.resolve.qsrv.qname, request->flags, request->process_id);
+    }
+#endif
+}
+
+typedef struct {
+    char            regtype[MAX_ESCAPED_DOMAIN_NAME];
+    domainname      fqdn;
+    mDNSInterfaceID InterfaceID;
+} _resolve_start_params_t;
+
+mDNSlocal mStatus _handle_resolve_request_start(request_state *request, const _resolve_start_params_t * const params)
+{
+    mStatus err;
+
+    err = mDNS_StartQuery(&mDNSStorage, &request->u.resolve.qsrv);
+
+    if (!err)
+    {
+        err = mDNS_StartQuery(&mDNSStorage, &request->u.resolve.qtxt);
+        if (err)
+        {
+            mDNS_StopQuery(&mDNSStorage, &request->u.resolve.qsrv);
+        }
+        else
+        {
+            request->terminate = resolve_termination_callback;
+            LogMcastQ(&request->u.resolve.qsrv, request, q_start);
+#if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
+            if (callExternalHelpers(params->InterfaceID, &params->fqdn, request->flags))
+            {
+                request->u.resolve.external_advertise    = mDNStrue;
+                LogInfo("handle_resolve_request: calling external_start_resolving_service()");
+                external_start_resolving_service(params->InterfaceID, &params->fqdn, request->flags, request->process_id);
+            }
+#else
+            (void)params;
+#endif
+        }
+    }
+    return err;
+}
+
 mDNSlocal void resolve_result_callback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord)
 {
     size_t len = 0;
@@ -2675,31 +3267,133 @@ mDNSlocal void resolve_result_callback(mDNS *const m, DNSQuestion *question, con
     append_reply(req, rep);
 }
 
-mDNSlocal void resolve_termination_callback(request_state *request)
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+
+mDNSlocal void _return_resolve_request_error(request_state * request, mStatus error)
 {
+    size_t len;
+    char * emptystr = "\0";
+    char * data;
+    reply_state *rep;
+
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-           "[R%d] DNSServiceResolve(%X, %d, \"" PRI_DM_NAME "\") STOP PID[%d](" PUB_S ")",
-           request->request_id, request->flags, request->interfaceIndex, DM_NAME_PARAM(request->u.resolve.qtxt.qname.c),
-           request->process_id, request->pid_name);
-    mDNS_StopQuery(&mDNSStorage, &request->u.resolve.qtxt);
-    mDNS_StopQuery(&mDNSStorage, &request->u.resolve.qsrv);
-    LogMcastQ(&request->u.resolve.qsrv, request, q_stop);
-#if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
-    if (request->u.resolve.external_advertise)
-        external_stop_resolving_service(request->u.resolve.qsrv.InterfaceID, &request->u.resolve.qsrv.qname, request->flags);
-#endif
+       "[R%u] DNSServiceResolve _return_resolve_request_error: error(%d)", request->request_id, error);
+
+    // calculate reply length
+    len = sizeof(DNSServiceFlags);
+    len += sizeof(mDNSu32);  // interface index
+    len += sizeof(DNSServiceErrorType);
+    len += 2; // name, target
+    len += 2 * sizeof(mDNSu16);  // port, txtLen
+    len += 0; //req->u.resolve.txt->rdlength;
+
+    rep = create_reply(resolve_reply_op, len, request);
+
+    rep->rhdr->flags = 0;
+    rep->rhdr->ifi   = 0;
+    rep->rhdr->error = dnssd_htonl(error);
+
+    data = (char *)&rep->rhdr[1];
+
+    // write reply data to message
+    put_string(emptystr, &data); // name
+    put_string(emptystr, &data); // target
+    put_uint16(0,        &data); // port
+    put_uint16(0,        &data); // txtLen
+
+    append_reply(request, rep);
 }
 
+mDNSlocal mStatus _handle_resolve_request_with_trust(request_state *request, const _resolve_start_params_t * const params)
+{
+    mStatus err;
+    if (audit_token_to_pid(request->audit_token) == 0)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_WARNING, "[R%u] _handle_resolve_request_with_trust: no audit token for pid(%s %d)", request->request_id, request->pid_name, request->process_id);
+        err = _handle_resolve_request_start(request, params);
+    }
+    else
+    {
+        mdns_trust_flags_t flags = mdns_trust_flags_none;
+        mdns_trust_status_t status = mdns_trust_check_bonjour(request->audit_token, params->regtype, &flags);
+        switch (status)
+        {
+            case mdns_trust_status_denied:
+            case mdns_trust_status_pending:
+            {
+                mdns_trust_t trust = mdns_trust_create(request->audit_token, params->regtype, flags);
+                if (!trust )
+                {
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
+
+                void * context = mallocL("context/_handle_resolve_request_with_trust", sizeof(_resolve_start_params_t));
+                if (!context)
+                {
+                    my_perror("ERROR: mallocL context/_handle_resolve_request_with_trust");
+                    mdns_release(trust);
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
+                memcpy(context, params, sizeof(_resolve_start_params_t));
+                mdns_trust_set_context(trust, context);
+                mdns_trust_set_queue(trust, _get_trust_results_dispatch_queue());
+                mdns_trust_set_event_handler(trust, ^(mdns_trust_event_t event, mdns_trust_status_t update)
+                {
+                    if (event == mdns_trust_event_result)
+                    {
+                        mStatus error = (update != mdns_trust_status_granted) ? mStatus_PolicyDenied : mStatus_NoError;
+                        KQueueLock();
+                        _resolve_start_params_t * _params =  mdns_trust_get_context(trust);
+                        if (_params)
+                        {
+                            if (!error)
+                            {
+                                error = _handle_resolve_request_start(request, _params);
+                                // No context means the request was canceled before we got here
+                            }
+                            if (error) // (not else if) Always check for error result
+                            {
+                                _return_resolve_request_error(request, error);
+                            }
+                        }
+                        KQueueUnlock("_handle_resolve_request_with_trust");
+                    }
+                });
+                request->trust = trust;
+                mdns_trust_activate(trust);
+                err = mStatus_NoError;
+                break;
+            }
+
+            case mdns_trust_status_no_entitlement:
+                err = mStatus_NoAuth;
+                break;
+
+            case mdns_trust_status_granted:
+                err = _handle_resolve_request_start(request, params);
+                break;
+
+            default:
+                err = mStatus_UnknownErr;
+                break;
+        }
+    }
+exit:
+    return err;
+}
+#endif // TRUST_ENFORCEMENT
+
 mDNSlocal mStatus handle_resolve_request(request_state *request)
 {
-    char name[256], regtype[MAX_ESCAPED_DOMAIN_NAME], domain[MAX_ESCAPED_DOMAIN_NAME];
-    domainname fqdn;
+    char name[256], domain[MAX_ESCAPED_DOMAIN_NAME];
+    _resolve_start_params_t params;
     mStatus err;
 
     // extract the data from the message
     DNSServiceFlags flags = get_flags(&request->msgptr, request->msgend);
     mDNSu32 interfaceIndex = get_uint32(&request->msgptr, request->msgend);
-    mDNSInterfaceID InterfaceID;
 
     // Map kDNSServiceInterfaceIndexP2P to kDNSServiceInterfaceIndexAny with the kDNSServiceFlagsIncludeP2P
     // flag set so that the resolve will run over P2P interfaces that are not yet created.
@@ -2710,11 +3404,11 @@ mDNSlocal mStatus handle_resolve_request(request_state *request)
         interfaceIndex = kDNSServiceInterfaceIndexAny;
     }
 
-    InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex);
+    params.InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex);
 
     // The operation is scoped to a specific interface index, but the 
     // interface is not currently in our list.
-    if (interfaceIndex && !InterfaceID)
+    if (interfaceIndex && !params.InterfaceID)
     {
         // If it's one of the specially defined inteface index values, just return an error.
         if (PreDefinedInterfaceIndex(interfaceIndex))
@@ -2725,19 +3419,19 @@ mDNSlocal mStatus handle_resolve_request(request_state *request)
 
         // Otherwise, use the specified interface index value and the operation will
         // be applied to that interface when it comes up.
-        InterfaceID = (mDNSInterfaceID)(uintptr_t)interfaceIndex;
+        params.InterfaceID = (mDNSInterfaceID)(uintptr_t)interfaceIndex;
         LogInfo("handle_resolve_request: resolve pending for interface index %d", interfaceIndex);
     }
 
-    if (get_string(&request->msgptr, request->msgend, name,    sizeof(name   )) < 0 ||
-        get_string(&request->msgptr, request->msgend, regtype, sizeof(regtype)) < 0 ||
-        get_string(&request->msgptr, request->msgend, domain,  sizeof(domain )) < 0)
+    if (get_string(&request->msgptr, request->msgend, name,           sizeof(name   )) < 0 ||
+        get_string(&request->msgptr, request->msgend, params.regtype, sizeof(params.regtype)) < 0 ||
+        get_string(&request->msgptr, request->msgend, domain,         sizeof(domain )) < 0)
     { LogMsg("ERROR: handle_resolve_request - Couldn't read name/regtype/domain"); return(mStatus_BadParamErr); }
 
     if (!request->msgptr) { LogMsg("%3d: DNSServiceResolve(unreadable parameters)", request->sd); return(mStatus_BadParamErr); }
 
-    if (build_domainname_from_strings(&fqdn, name, regtype, domain) < 0)
-    { LogMsg("ERROR: handle_resolve_request bad “%s” “%s” “%s”", name, regtype, domain); return(mStatus_BadParamErr); }
+    if (build_domainname_from_strings(&params.fqdn, name, params.regtype, domain) < 0)
+    { LogMsg("ERROR: handle_resolve_request bad “%s” “%s” “%s”", name, params.regtype, domain); return(mStatus_BadParamErr); }
 
     mDNSPlatformMemZero(&request->u.resolve, sizeof(request->u.resolve));
 
@@ -2754,9 +3448,9 @@ mDNSlocal mStatus handle_resolve_request(request_state *request)
     request->interfaceIndex = interfaceIndex;
 
     // format questions
-    request->u.resolve.qsrv.InterfaceID      = InterfaceID;
+    request->u.resolve.qsrv.InterfaceID      = params.InterfaceID;
     request->u.resolve.qsrv.flags            = flags;
-    AssignDomainName(&request->u.resolve.qsrv.qname, &fqdn);
+    AssignDomainName(&request->u.resolve.qsrv.qname, &params.fqdn);
     request->u.resolve.qsrv.qtype            = kDNSType_SRV;
     request->u.resolve.qsrv.qclass           = kDNSClass_IN;
     request->u.resolve.qsrv.LongLived        = (flags & kDNSServiceFlagsLongLivedQuery     ) != 0;
@@ -2768,17 +3462,15 @@ mDNSlocal mStatus handle_resolve_request(request_state *request)
     request->u.resolve.qsrv.TimeoutQuestion  = 0;
     request->u.resolve.qsrv.WakeOnResolve    = (flags & kDNSServiceFlagsWakeOnResolve) != 0;
     request->u.resolve.qsrv.UseBackgroundTraffic = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0;
-    request->u.resolve.qsrv.ValidationRequired = 0;
-    request->u.resolve.qsrv.ValidatingResponse = 0;
     request->u.resolve.qsrv.ProxyQuestion    = 0;
     request->u.resolve.qsrv.pid              = request->process_id;
     request->u.resolve.qsrv.euid             = request->uid;
     request->u.resolve.qsrv.QuestionCallback = resolve_result_callback;
     request->u.resolve.qsrv.QuestionContext  = request;
 
-    request->u.resolve.qtxt.InterfaceID      = InterfaceID;
+    request->u.resolve.qtxt.InterfaceID      = params.InterfaceID;
     request->u.resolve.qtxt.flags            = flags;
-    AssignDomainName(&request->u.resolve.qtxt.qname, &fqdn);
+    AssignDomainName(&request->u.resolve.qtxt.qname, &params.fqdn);
     request->u.resolve.qtxt.qtype            = kDNSType_TXT;
     request->u.resolve.qtxt.qclass           = kDNSClass_IN;
     request->u.resolve.qtxt.LongLived        = (flags & kDNSServiceFlagsLongLivedQuery     ) != 0;
@@ -2790,8 +3482,6 @@ mDNSlocal mStatus handle_resolve_request(request_state *request)
     request->u.resolve.qtxt.TimeoutQuestion  = 0;
     request->u.resolve.qtxt.WakeOnResolve    = 0;
     request->u.resolve.qtxt.UseBackgroundTraffic = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0;
-    request->u.resolve.qtxt.ValidationRequired = 0;
-    request->u.resolve.qtxt.ValidatingResponse = 0;
     request->u.resolve.qtxt.ProxyQuestion    = 0;
     request->u.resolve.qtxt.pid              = request->process_id;
     request->u.resolve.qtxt.euid             = request->uid;
@@ -2809,32 +3499,26 @@ mDNSlocal mStatus handle_resolve_request(request_state *request)
     // ask the questions
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
            "[R%d] DNSServiceResolve(%X, %d, \"" PRI_DM_NAME "\") START PID[%d](" PUB_S ")",
-           request->request_id, flags, interfaceIndex, DM_NAME_PARAM(request->u.resolve.qsrv.qname.c),
+           request->request_id, flags, interfaceIndex, DM_NAME_PARAM(&request->u.resolve.qsrv.qname),
            request->process_id, request->pid_name);
 
-    err = mDNS_StartQuery(&mDNSStorage, &request->u.resolve.qsrv);
+    request->terminate = NULL;
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+    domainname d;
+    if (!MakeDomainNameFromDNSNameString(&d, domain)) return(mStatus_BadParamErr);
 
-    if (!err)
+    if (os_feature_enabled(mDNSResponder, bonjour_privacy) &&
+        (IsLocalDomain(&d) || request->u.resolve.qsrv.ForceMCast))
     {
-        err = mDNS_StartQuery(&mDNSStorage, &request->u.resolve.qtxt);
-        if (err)
-        {
-            mDNS_StopQuery(&mDNSStorage, &request->u.resolve.qsrv);
-        }
-        else
-        {
-            request->terminate = resolve_termination_callback;
-            LogMcastQ(&request->u.resolve.qsrv, request, q_start);
-#if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
-            if (callExternalHelpers(InterfaceID, &fqdn, flags))
-            {
-                request->u.resolve.external_advertise    = mDNStrue;
-                LogInfo("handle_resolve_request: calling external_start_resolving_service()");
-                external_start_resolving_service(InterfaceID, &fqdn, flags);
-            }
-#endif
-        }
+        err = _handle_resolve_request_with_trust(request, &params);
+    }
+    else
+    {
+        err = _handle_resolve_request_start(request, &params);
     }
+#else
+    err = _handle_resolve_request_start(request, &params);
+#endif
 
     return(err);
 }
@@ -2853,15 +3537,47 @@ mDNSlocal void queryrecord_result_reply(mDNS *const m, DNSQuestion *question, co
     reply_state *rep;
     char *data;
     request_state *req = (request_state *)context;
+    const char *dnssec_result_description = "";
 
     ConvertDomainNameToCString(answer->name, name);
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    if (question->DNSSECStatus.enable_dnssec) {
+        if (answer->dnssec_result == dnssec_secure)
+        {
+            flags |= kDNSServiceFlagsSecure;
+            dnssec_result_description = ", DNSSEC_Secure";
+        }
+        else if (answer->dnssec_result == dnssec_insecure)
+        {
+            flags |= kDNSServiceFlagsInsecure;
+            dnssec_result_description = ", DNSSEC_Insecure";
+        }
+        else if (answer->dnssec_result == dnssec_bogus)
+        {
+            flags |= kDNSServiceFlagsBogus;
+            dnssec_result_description = ", DNSSEC_Bogus";
+        }
+        else if (answer->dnssec_result == dnssec_indeterminate)
+        {
+            flags |= kDNSServiceFlagsIndeterminate;
+            dnssec_result_description = ", DNSSEC_Indeterminated";
+        }
+    } else if (question->DNSSECStatus.tried_dnssec_but_unsigned) {
+        // handle the case where we restart the question without the DNSSEC while the user requires DNSSEC result, for
+        // some reason we failed to get DNSSEC records. In which case, even if we go back to normal query, we should pass
+        // the DNSSEC result
+        flags |= kDNSServiceFlagsInsecure;
+        dnssec_result_description = ", DNSSEC_Insecure";
+    }
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
-       "[R%u->Q%u] DNSService" PUB_S "(" PRI_DM_NAME ", " PUB_S ") RESULT " PUB_S " interface %d: (" PUB_S ")" PRI_S,
+       "[R%u->Q%u] DNSService" PUB_S "(" PRI_DM_NAME ", " PUB_S ") RESULT " PUB_S " interface %d: (" PUB_S PUB_S ")" PRI_S,
        req->request_id, mDNSVal16(question->TargetQID), req->hdr.op == query_request ? "QueryRecord" : "GetAddrInfo",
-       DM_NAME_PARAM(question->qname.c), DNSTypeName(question->qtype), AddRecord ? "ADD" : "RMV",
+       DM_NAME_PARAM(&question->qname), DNSTypeName(question->qtype), AddRecord ? "ADD" : "RMV",
        mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNSfalse),
-       MortalityDisplayString(answer->mortality), RRDisplayString(m, answer));
+       MortalityDisplayString(answer->mortality), dnssec_result_description, RRDisplayString(m, answer));
 
     len = sizeof(DNSServiceFlags);  // calculate reply data length
     len += sizeof(mDNSu32);     // interface index
@@ -2879,30 +3595,6 @@ mDNSlocal void queryrecord_result_reply(mDNS *const m, DNSQuestion *question, co
         flags |= kDNSServiceFlagsExpiredAnswer;
     if (!question->InitialCacheMiss)
         flags |= kDNSServiceFlagAnsweredFromCache;
-    if (question->ValidationStatus != 0)
-    {
-        error =   kDNSServiceErr_NoError;
-        if (question->ValidationRequired && question->ValidationState == DNSSECValDone)
-        {
-            switch (question->ValidationStatus) //Set the dnssec flags to be passed on to the Apps here
-            {
-            case DNSSEC_Secure:
-                flags |= kDNSServiceFlagsSecure;
-                break;
-            case DNSSEC_Insecure:
-                flags |= kDNSServiceFlagsInsecure;
-                break;
-            case DNSSEC_Indeterminate:
-                flags |= kDNSServiceFlagsIndeterminate;
-                break;
-            case DNSSEC_Bogus:
-                flags |= kDNSServiceFlagsBogus;
-                break;
-            default:
-                LogMsg("queryrecord_result_reply unknown status %d for %##s", question->ValidationStatus, question->qname.c);
-            }
-        }
-    }
 
     rep->rhdr->flags = dnssd_htonl(flags);
     // Call mDNSPlatformInterfaceIndexfromInterfaceID, but suppressNetworkChange (last argument). Otherwise, if the
@@ -2937,29 +3629,223 @@ mDNSlocal void queryrecord_termination_callback(request_state *request)
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
            "[R%u] DNSServiceQueryRecord(%X, %d, " PRI_DM_NAME ", " PUB_S ") STOP PID[%d](" PUB_S ")",
            request->request_id, request->flags, request->interfaceIndex,
-           DM_NAME_PARAM(QueryRecordClientRequestGetQName(&request->u.queryrecord)->c),
+           DM_NAME_PARAM(QueryRecordClientRequestGetQName(&request->u.queryrecord)),
            DNSTypeName(QueryRecordClientRequestGetType(&request->u.queryrecord)), request->process_id, request->pid_name);
 
     QueryRecordClientRequestStop(&request->u.queryrecord);
 }
 
+typedef struct {
+    char            qname[MAX_ESCAPED_DOMAIN_NAME];
+    mDNSu32         interfaceIndex;
+    DNSServiceFlags flags;
+    mDNSu16         qtype;
+    mDNSu16         qclass;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mDNSBool        require_privacy;
+#endif
+} _queryrecord_start_params_t;
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER) && MDNSRESPONDER_SUPPORTS(APPLE, IPC_TLV)
+mDNSlocal const mDNSu8 * ipc_tlv_get_resolver_config_plist_data(const mDNSu8 *const start, const mDNSu8 *const end,
+    size_t *outLen)
+{
+    size_t len = 0;
+    const mDNSu8 *value = NULL;
+    mdns_tlv16_get_value(start, end, IPC_TLV_TYPE_RESOLVER_CONFIG_PLIST_DATA, &len, &value, NULL);
+    if (outLen)
+    {
+        *outLen = len;
+    }
+    return value;
+}
+
+mDNSlocal mDNSBool ipc_tlv_get_require_privacy(const mDNSu8 *const start, const mDNSu8 *const end)
+{
+    size_t len = 0;
+    const mDNSu8 *value = NULL;
+    mdns_tlv16_get_value(start, end, IPC_TLV_TYPE_REQUIRE_PRIVACY, &len, &value, NULL);
+    return ((len == 1) && (*value != 0)) ? mDNStrue : mDNSfalse;
+}
+#endif
+
+mDNSlocal mStatus _handle_queryrecord_request_start(request_state *request, const _queryrecord_start_params_t * const params)
+{
+    mStatus err;
+
+    request->terminate = queryrecord_termination_callback;
+
+    QueryRecordClientRequestParams queryParams;
+    QueryRecordClientRequestParamsInit(&queryParams);
+    queryParams.requestID      = request->request_id;
+    queryParams.qnameStr       = params->qname;
+    queryParams.interfaceIndex = params->interfaceIndex;
+    queryParams.flags          = params->flags;
+    queryParams.qtype          = params->qtype;
+    queryParams.qclass         = params->qclass;
+    queryParams.effectivePID   = request->validUUID ? 0 : request->process_id;
+    queryParams.effectiveUUID  = request->validUUID ? request->uuid : mDNSNULL;
+    queryParams.peerUID        = request->uid;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    queryParams.needEncryption = params->require_privacy ? mDNStrue : mDNSfalse;
+    queryParams.customID       = request->custom_service_id;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+    queryParams.peerAuditToken = &request->audit_token;
+#endif
+    err = QueryRecordClientRequestStart(&request->u.queryrecord, &queryParams, queryrecord_result_reply, request);
+    return err;
+}
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+
+mDNSlocal void _return_queryrecord_request_error(request_state * request, mStatus error)
+{
+    size_t len;
+    char * emptystr = "\0";
+    char * data;
+    reply_state *rep;
+
+    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+       "[R%u] DNSService" PUB_S " _return_queryrecord_request_error: error(%d)",
+       request->request_id, request->hdr.op == query_request ? "QueryRecord" : "GetAddrInfo", error);
+
+    len = sizeof(DNSServiceFlags);  // calculate reply data length
+    len += sizeof(mDNSu32);     // interface index
+    len += sizeof(DNSServiceErrorType);
+    len += strlen(emptystr) + 1;
+    len += 3 * sizeof(mDNSu16); // type, class, rdlen
+    len += 0;//answer->rdlength;
+    len += sizeof(mDNSu32);     // TTL
+
+    rep = create_reply(request->hdr.op == query_request ? query_reply_op : addrinfo_reply_op, len, request);
+
+    rep->rhdr->flags = 0;
+    rep->rhdr->ifi   = 0;
+    rep->rhdr->error = dnssd_htonl(error);
+
+    data = (char *)&rep->rhdr[1];
+
+    put_string(emptystr,    &data);
+    put_uint16(0,           &data);
+    put_uint16(0,           &data);
+    put_uint16(0,           &data);
+    data += 0;
+    put_uint32(0,           &data);
+
+    append_reply(request, rep);
+}
+
+mDNSlocal mStatus _handle_queryrecord_request_with_trust(request_state *request, const _queryrecord_start_params_t * const params)
+{
+    mStatus err;
+    if (audit_token_to_pid(request->audit_token) == 0)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_WARNING, "[R%u] _handle_queryrecord_request_with_trust: no audit token for pid(%s %d)", request->request_id, request->pid_name, request->process_id);
+        err = _handle_queryrecord_request_start(request, params);
+    }
+    else
+    {
+        const char *service_ptr = NULL;
+        char type_str[MAX_ESCAPED_DOMAIN_NAME] = "";
+        domainname query_name;
+        if (MakeDomainNameFromDNSNameString(&query_name, params->qname))
+        {
+            domainlabel name;
+            domainname type, domain;
+            bool good = DeconstructServiceName(&query_name, &name, &type, &domain);
+            if (good)
+            {
+                ConvertDomainNameToCString(&type, type_str);
+                service_ptr = type_str;
+            }
+        }
+
+        mdns_trust_flags_t flags = mdns_trust_flags_none;
+        mdns_trust_status_t status = mdns_trust_check_query(request->audit_token, params->qname, service_ptr, params->qtype, (params->flags & kDNSServiceFlagsForceMulticast) != 0, &flags);
+        switch (status)
+        {
+            case mdns_trust_status_denied:
+            case mdns_trust_status_pending:
+            {
+                mdns_trust_t trust = mdns_trust_create(request->audit_token, service_ptr, flags);
+                if (!trust )
+                {
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
+
+                void * context = mallocL("context/_handle_queryrecord_request_with_trust", sizeof(_queryrecord_start_params_t));
+                if (!context)
+                {
+                    my_perror("ERROR: mallocL context/_handle_queryrecord_request_with_trust");
+                    mdns_release(trust);
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
+                memcpy(context, params, sizeof(_queryrecord_start_params_t));
+                mdns_trust_set_context(trust, context);
+                mdns_trust_set_queue(trust, _get_trust_results_dispatch_queue());
+                mdns_trust_set_event_handler(trust, ^(mdns_trust_event_t event, mdns_trust_status_t update)
+                {
+                    if (event == mdns_trust_event_result)
+                    {
+                        mStatus error = (update != mdns_trust_status_granted) ? mStatus_PolicyDenied : mStatus_NoError;
+                        KQueueLock();
+                        _queryrecord_start_params_t * _params =  mdns_trust_get_context(trust);
+                        if (_params)
+                        {
+                            if (!error)
+                            {
+                                error = _handle_queryrecord_request_start(request, _params);
+                                // No context means the request was canceled before we got here
+                            }
+                            if (error) // (not else if) Always check for error result
+                            {
+                                _return_queryrecord_request_error(request, error);
+                            }
+                        }
+                        KQueueUnlock("_handle_queryrecord_request_with_trust");
+                    }
+                });
+                request->trust = trust;
+                mdns_trust_activate(trust);
+                err = mStatus_NoError;
+                break;
+            }
+
+            case mdns_trust_status_no_entitlement:
+                err = mStatus_NoAuth;
+                break;
+
+            case mdns_trust_status_granted:
+                err = _handle_queryrecord_request_start(request, params);
+                break;
+
+            default:
+                err = mStatus_UnknownErr;
+                break;
+        }
+    }
+exit:
+    return err;
+}
+#endif // TRUST_ENFORCEMENT
+
 mDNSlocal mStatus handle_queryrecord_request(request_state *request)
 {
-    mStatus             err;
-    DNSServiceFlags     flags;
-    mDNSu32             interfaceIndex;
-    mDNSu16             qtype, qclass;
-    char                qname[MAX_ESCAPED_DOMAIN_NAME];
+    mStatus err;
+    _queryrecord_start_params_t params;
 
-    flags           = get_flags(&request->msgptr, request->msgend);
-    interfaceIndex  = get_uint32(&request->msgptr, request->msgend);
-    if (get_string(&request->msgptr, request->msgend, qname, sizeof(qname)) < 0)
+    params.flags           = get_flags(&request->msgptr, request->msgend);
+    params.interfaceIndex  = get_uint32(&request->msgptr, request->msgend);
+    if (get_string(&request->msgptr, request->msgend, params.qname, sizeof(params.qname)) < 0)
     {
         err = mStatus_BadParamErr;
         goto exit;
     }
-    qtype           = get_uint16(&request->msgptr, request->msgend);
-    qclass          = get_uint16(&request->msgptr, request->msgend);
+    params.qtype           = get_uint16(&request->msgptr, request->msgend);
+    params.qclass          = get_uint16(&request->msgptr, request->msgend);
 
     if (!request->msgptr)
     {
@@ -2968,23 +3854,46 @@ mDNSlocal mStatus handle_queryrecord_request(request_state *request)
         err = mStatus_BadParamErr;
         goto exit;
     }
-
-    request->flags          = flags;
-    request->interfaceIndex = interfaceIndex;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    params.require_privacy = mDNSfalse;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER) && MDNSRESPONDER_SUPPORTS(APPLE, IPC_TLV)
+    if (request->msgptr && (request->hdr.ipc_flags & IPC_FLAGS_TRAILING_TLVS))
+    {
+        size_t len;
+        const mDNSu8 *const start = (const mDNSu8 *)request->msgptr;
+        const mDNSu8 *const end = (const mDNSu8 *)request->msgend;
+        const mDNSu8 *const data = ipc_tlv_get_resolver_config_plist_data(start, end, &len);
+        if (data)
+        {
+            request->custom_service_id = Querier_RegisterCustomDNSServiceWithPListData(data, len);
+        }
+        params.require_privacy = ipc_tlv_get_require_privacy(start, end);
+    }
+#endif
+    request->flags          = params.flags;
+    request->interfaceIndex = params.interfaceIndex;
 
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
            "[R%d] DNSServiceQueryRecord(%X, %d, " PRI_S ", " PUB_S ") START PID[%d](" PUB_S ")",
-           request->request_id, request->flags, request->interfaceIndex, qname, DNSTypeName(qtype), request->process_id,
+           request->request_id, request->flags, request->interfaceIndex, params.qname, DNSTypeName(params.qtype), request->process_id,
            request->pid_name);
 
     mDNSPlatformMemZero(&request->u.queryrecord, (mDNSu32)sizeof(request->u.queryrecord));
+    request->terminate = NULL;
 
-    err = QueryRecordClientRequestStart(&request->u.queryrecord, request->request_id, qname, interfaceIndex, flags, qtype,
-        qclass, request->validUUID ? 0 : request->process_id, request->validUUID ? request->uuid : mDNSNULL, request->uid,
-        queryrecord_result_reply, request);
-    if (err) goto exit;
-
-    request->terminate = queryrecord_termination_callback;
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+    if (os_feature_enabled(mDNSResponder, bonjour_privacy))
+    {
+        err = _handle_queryrecord_request_with_trust(request, &params);
+    }
+    else
+    {
+        err = _handle_queryrecord_request_start(request, &params);
+    }
+#else
+    err = _handle_queryrecord_request_start(request, &params);
+#endif
 
 exit:
     return(err);
@@ -3200,7 +4109,7 @@ mDNSlocal mStatus handle_release_request(request_state *request)
 
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
            "[R%d] PeerConnectionRelease(%X " PRI_DM_NAME ") START PID[%d](" PUB_S ")",
-           request->request_id, flags, DM_NAME_PARAM(instance.c), request->process_id, request->pid_name);
+           request->request_id, flags, DM_NAME_PARAM(&instance), request->process_id, request->pid_name);
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
     external_connection_release(&instance);
@@ -3449,24 +4358,147 @@ mDNSlocal void addrinfo_termination_callback(request_state *request)
 {
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
            "[R%u] DNSServiceGetAddrInfo(" PRI_DM_NAME ") STOP PID[%d](" PUB_S ")",
-           request->request_id, DM_NAME_PARAM(GetAddrInfoClientRequestGetQName(&request->u.addrinfo)->c),
+           request->request_id, DM_NAME_PARAM(GetAddrInfoClientRequestGetQName(&request->u.addrinfo)),
            request->process_id, request->pid_name);
 
     GetAddrInfoClientRequestStop(&request->u.addrinfo);
 }
 
+typedef struct {
+    mDNSu32     protocols;
+    char        hostname[MAX_ESCAPED_DOMAIN_NAME];
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mDNSBool    require_privacy;
+#endif
+} _addrinfo_start_params_t;
+
+mDNSlocal mStatus _handle_addrinfo_request_start(request_state *request, const _addrinfo_start_params_t * const params)
+{
+    mStatus err;
+
+    request->terminate = addrinfo_termination_callback;
+
+    GetAddrInfoClientRequestParams gaiParams;
+    GetAddrInfoClientRequestParamsInit(&gaiParams);
+    gaiParams.requestID      = request->request_id;
+    gaiParams.hostnameStr    = params->hostname;
+    gaiParams.interfaceIndex = request->interfaceIndex;
+    gaiParams.flags          = request->flags;
+    gaiParams.protocols      = params->protocols;
+    gaiParams.effectivePID   = request->validUUID ? 0 : request->process_id;
+    gaiParams.effectiveUUID  = request->validUUID ? request->uuid : mDNSNULL;
+    gaiParams.peerUID        = request->uid;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    gaiParams.needEncryption = params->require_privacy ? mDNStrue : mDNSfalse;
+    gaiParams.customID       = request->custom_service_id;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+    gaiParams.peerAuditToken = &request->audit_token;
+#endif
+    err = GetAddrInfoClientRequestStart(&request->u.addrinfo, &gaiParams, queryrecord_result_reply, request);
+
+    return err;
+}
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+
+mDNSlocal void _return_addrinfo_request_error(request_state * request, mStatus error)
+{
+    _return_queryrecord_request_error(request, error);
+}
+
+mDNSlocal mStatus _handle_addrinfo_request_with_trust(request_state *request, const _addrinfo_start_params_t * const params)
+{
+    mStatus err;
+    if (audit_token_to_pid(request->audit_token) == 0)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_WARNING, "[R%u] _handle_addrinfo_request_with_trust: no audit token for pid(%s %d)", request->request_id, request->pid_name, request->process_id);
+        err = _handle_addrinfo_request_start(request, params);
+    }
+    else
+    {
+        mdns_trust_flags_t flags = mdns_trust_flags_none;
+        mdns_trust_status_t status = mdns_trust_check_getaddrinfo(request->audit_token, params->hostname, &flags);
+        switch (status)
+        {
+            case mdns_trust_status_denied:
+            case mdns_trust_status_pending:
+            {
+                mdns_trust_t trust = mdns_trust_create(request->audit_token, NULL, flags);
+                if (!trust )
+                {
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
+
+                void * context = mallocL("context/_handle_addrinfo_request_with_trust", sizeof(_addrinfo_start_params_t));
+                if (!context)
+                {
+                    my_perror("ERROR: mallocL context/_handle_addrinfo_request_with_trust");
+                    mdns_release(trust);
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
+                memcpy(context, params, sizeof(_addrinfo_start_params_t));
+                mdns_trust_set_context(trust, context);
+                mdns_trust_set_queue(trust, _get_trust_results_dispatch_queue());
+                mdns_trust_set_event_handler(trust, ^(mdns_trust_event_t event, mdns_trust_status_t update)
+                {
+                    if (event == mdns_trust_event_result)
+                    {
+                        mStatus error = (update != mdns_trust_status_granted) ? mStatus_PolicyDenied : mStatus_NoError;
+                        KQueueLock();
+                        _addrinfo_start_params_t * _params =  mdns_trust_get_context(trust);
+                        if (_params)
+                        {
+                            if (!error)
+                            {
+                                error = _handle_addrinfo_request_start(request, _params);
+                                // No context means the request was canceled before we got here
+                            }
+                            if (error) // (not else if) Always check for error result
+                            {
+                                _return_addrinfo_request_error(request, error);
+                            }
+                        }
+                        KQueueUnlock("_handle_addrinfo_request_with_trust");
+                    }
+                });
+                request->trust = trust;
+                mdns_trust_activate(trust);
+                err = mStatus_NoError;
+                break;
+            }
+
+            case mdns_trust_status_no_entitlement:
+                err = mStatus_NoAuth;
+                break;
+
+            case mdns_trust_status_granted:
+                err = _handle_addrinfo_request_start(request, params);
+                break;
+
+            default:
+                err = mStatus_UnknownErr;
+                break;
+        }
+    }
+exit:
+    return err;
+}
+#endif // TRUST_ENFORCEMENT
+
 mDNSlocal mStatus handle_addrinfo_request(request_state *request)
 {
     mStatus             err;
     DNSServiceFlags     flags;
     mDNSu32             interfaceIndex;
-    mDNSu32             protocols;
-    char                hostname[MAX_ESCAPED_DOMAIN_NAME];
+    _addrinfo_start_params_t params;
 
-    flags           = get_flags(&request->msgptr, request->msgend);
-    interfaceIndex  = get_uint32(&request->msgptr, request->msgend);
-    protocols       = get_uint32(&request->msgptr, request->msgend);
-    if (get_string(&request->msgptr, request->msgend, hostname, sizeof(hostname)) < 0)
+    flags               = get_flags(&request->msgptr, request->msgend);
+    interfaceIndex      = get_uint32(&request->msgptr, request->msgend);
+    params.protocols    = get_uint32(&request->msgptr, request->msgend);
+    if (get_string(&request->msgptr, request->msgend, params.hostname, sizeof(params.hostname)) < 0)
     {
         err = mStatus_BadParamErr;
         goto exit;
@@ -3477,23 +4509,46 @@ mDNSlocal mStatus handle_addrinfo_request(request_state *request)
         err = mStatus_BadParamErr;
         goto exit;
     }
-
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    params.require_privacy = mDNSfalse;
+#endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER) && MDNSRESPONDER_SUPPORTS(APPLE, IPC_TLV)
+    if (request->msgptr && (request->hdr.ipc_flags & IPC_FLAGS_TRAILING_TLVS))
+    {
+        size_t len;
+        const mDNSu8 *const start = (const mDNSu8 *)request->msgptr;
+        const mDNSu8 *const end = (const mDNSu8 *)request->msgend;
+        const mDNSu8 *const data = ipc_tlv_get_resolver_config_plist_data(start, end, &len);
+        if (data)
+        {
+            request->custom_service_id = Querier_RegisterCustomDNSServiceWithPListData(data, len);
+        }
+        params.require_privacy = ipc_tlv_get_require_privacy(start, end);
+    }
+#endif
     request->flags          = flags;
     request->interfaceIndex = interfaceIndex;
 
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
            "[R%u] DNSServiceGetAddrInfo(%X, %d, %u, " PRI_S ") START PID[%d](" PUB_S ")",
-           request->request_id, request->flags, request->interfaceIndex, protocols, hostname, request->process_id,
+           request->request_id, request->flags, request->interfaceIndex, params.protocols, params.hostname, request->process_id,
            request->pid_name);
 
     mDNSPlatformMemZero(&request->u.addrinfo, (mDNSu32)sizeof(request->u.addrinfo));
+    request->terminate = NULL;
 
-    err = GetAddrInfoClientRequestStart(&request->u.addrinfo, request->request_id, hostname, interfaceIndex, flags,
-        protocols, request->validUUID ? 0 : request->process_id, request->validUUID ? request->uuid : mDNSNULL,
-        request->uid, queryrecord_result_reply, request);
-    if (err) goto exit;
-
-    request->terminate = addrinfo_termination_callback;
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+    if (os_feature_enabled(mDNSResponder, bonjour_privacy))
+    {
+        err = _handle_addrinfo_request_with_trust(request, &params);
+    }
+    else
+    {
+        err = _handle_addrinfo_request_start(request, &params);
+    }
+#else
+    err = _handle_addrinfo_request_start(request, &params);
+#endif
 
 exit:
     return(err);
@@ -3599,7 +4654,7 @@ mDNSlocal void read_msg(request_state *req)
     if (req->hdr_bytes == sizeof(ipc_msg_hdr) && req->data_bytes < req->hdr.datalen)
     {
         mDNSu32 nleft = req->hdr.datalen - req->data_bytes;
-        int nread;
+        ssize_t nread;
 #if !defined(_WIN32)
         struct iovec vec = { req->msgbuf + req->data_bytes, nleft };    // Tell recvmsg where we want the bytes put
         struct msghdr msg;
@@ -3768,6 +4823,9 @@ rerror:
 mDNSlocal mStatus handle_client_request(request_state *req)
 {
     mStatus err = mStatus_NoError;
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+    SetupAuditTokenForRequest(req);
+#endif
     switch(req->hdr.op)
     {
             // These are all operations that have their own first-class request_state object
@@ -3895,7 +4953,10 @@ mDNSlocal void request_callback(int fd, void *info)
             newreq->msgbuf  = req->msgbuf;
             newreq->msgptr  = req->msgptr;
             newreq->msgend  = req->msgend;
-            newreq->request_id = mDNSStorage.next_request_id++; 
+            newreq->request_id = GetNewRequestID();
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+            newreq->audit_token = req->audit_token;
+#endif
             // if the parent request is a delegate connection, copy the
             // relevant bits
             if (req->validUUID)
@@ -3997,7 +5058,7 @@ mDNSlocal void connect_callback(int fd, void *info)
         request->ts    = t_morecoming;
         request->sd    = sd;
         request->errsd = sd;
-        request->request_id = mDNSStorage.next_request_id++;
+        request->request_id = GetNewRequestID();
         set_peer_pid(request);
 #if APPLE_OSX_mDNSResponder
         struct xucred x;
@@ -4006,7 +5067,6 @@ mDNSlocal void connect_callback(int fd, void *info)
             request->uid = x.cr_uid; // save the effective userid of the client
         else
             my_perror("ERROR: getsockopt, LOCAL_PEERCRED");
-
         debugf("LOCAL_PEERCRED %d %u %u %d", xucredlen, x.cr_version, x.cr_uid, x.cr_ngroups);
 #endif // APPLE_OSX_mDNSResponder
         LogDebug("%3d: connect_callback: Adding FD for uid %u", request->sd, request->uid);
@@ -4060,11 +5120,10 @@ mDNSlocal mDNSBool uds_socket_setup(dnssd_sock_t skt)
 mDNSlocal void udsserver_validatelists(void *context);
 #endif
 
-mDNSexport int udsserver_init(dnssd_sock_t skts[], mDNSu32 count)
+mDNSexport int udsserver_init(dnssd_sock_t skts[], const size_t count)
 {
     dnssd_sockaddr_t laddr;
     int ret;
-    mDNSu32 i = 0;
 
 #ifndef NO_PID_FILE
     FILE *fp = fopen(PID_FILE, "w");
@@ -4082,6 +5141,7 @@ mDNSexport int udsserver_init(dnssd_sock_t skts[], mDNSu32 count)
 
     if (skts)
     {
+        size_t i;
         for (i = 0; i < count; i++)
             if (dnssd_SocketValid(skts[i]) && !uds_socket_setup(skts[i]))
                 goto error;
@@ -4675,17 +5735,8 @@ mDNSlocal void PrintOneCacheRecordToFD(int fd, const CacheRecord *cr, mDNSu32 sl
 
 mDNSlocal void PrintCachedRecordsToFD(int fd, const CacheRecord *cr, mDNSu32 slot, const mDNSu32 remain, const char *ifname, mDNSu32 *CacheUsed)
 {
-    CacheRecord *nsec;
     CacheRecord *soa;
-    nsec = cr->nsec;
 
-    // The records that are cached under the main cache record like nsec, soa don't have
-    // their own lifetime. If the main cache record expires, they also expire.
-    while (nsec)
-    {
-        PrintOneCacheRecordToFD(fd, nsec, slot, remain, ifname, CacheUsed);
-        nsec = nsec->next;
-    }
     soa = cr->soa;
     if (soa)
     {
@@ -4755,8 +5806,16 @@ mDNSexport void udsserver_info_dump_to_fd(int fd)
                 const char *ifname;
                 mDNSInterfaceID InterfaceID = cr->resrec.InterfaceID;
                 mDNSu32 *const countPtr = InterfaceID ? &mcastRecordCount : &ucastRecordCount;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+                if (!InterfaceID && cr->resrec.dnsservice &&
+                    (mdns_dns_service_get_scope(cr->resrec.dnsservice) == mdns_dns_service_scope_interface))
+                {
+                    InterfaceID = (mDNSInterfaceID)(uintptr_t)mdns_dns_service_get_interface_index(cr->resrec.dnsservice);
+                }
+#else
                 if (!InterfaceID && cr->resrec.rDNSServer && cr->resrec.rDNSServer->scopeType)
                     InterfaceID = cr->resrec.rDNSServer->interface;
+#endif
                 ifname = InterfaceNameForID(m, InterfaceID);
                 if (cr->CRActiveQuestion) CacheActive++;
                 PrintOneCacheRecordToFD(fd, cr, slot, remain, ifname, countPtr);
@@ -4805,13 +5864,18 @@ mDNSexport void udsserver_info_dump_to_fd(int fd)
             char *ifname = InterfaceNameForID(m, q->InterfaceID);
             CacheUsed++;
             if (q->ThisQInterval) CacheActive++;
-            LogToFD(fd, "%6d%6d %-7s%s%s %5d 0x%08x%08x%08x%08x 0x%p 0x%p %1d %2d  %-5s%##s%s",
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            LogToFD(fd, "%6d%6d %-7s%s %5d 0x%p 0x%p %1d %2d  %-5s%##s%s",
+#else
+            LogToFD(fd, "%6d%6d %-7s%s %5d 0x%08x%08x%08x%08x 0x%p 0x%p %1d %2d  %-5s%##s%s",
+#endif
                       i, n,
                       ifname ? ifname : mDNSOpaque16IsZero(q->TargetQID) ? "" : "-U-",
                       mDNSOpaque16IsZero(q->TargetQID) ? (q->LongLived ? "l" : " ") : (q->LongLived ? "L" : "O"),
-                      q->ValidationRequired ? "V" : q->ValidatingResponse ? "R" : " ",
                       q->CurrentAnswers,
+#if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
                       q->validDNSServers.l[3], q->validDNSServers.l[2], q->validDNSServers.l[1], q->validDNSServers.l[0],
+#endif
                       q, q->DuplicateOf,
                       q->SuppressUnusable, q->Suppressed, DNSTypeName(q->qtype), q->qname.c,
                       q->DuplicateOf ? " (dup)" : "");
@@ -4922,54 +5986,6 @@ mDNSexport void udsserver_info_dump_to_fd(int fd)
             LogToFD(fd, "%##s %s", s->domain.c, ifname ? ifname : "");
         }
     }
-    LogToFD(fd, "--- Trust Anchors ---");
-    if (!m->TrustAnchors)
-    {
-        LogToFD(fd, "<None>");
-    }
-    else
-    {
-        TrustAnchor *ta;
-        mDNSu8 fromTimeBuf[64];
-        mDNSu8 untilTimeBuf[64];
-
-        for (ta=m->TrustAnchors; ta; ta=ta->next)
-        {
-            mDNSPlatformFormatTime((unsigned long)ta->validFrom, fromTimeBuf, sizeof(fromTimeBuf));
-            mDNSPlatformFormatTime((unsigned long)ta->validUntil, untilTimeBuf, sizeof(untilTimeBuf));
-            LogToFD(fd, "%##s %d %d %d %d %s %s", ta->zone.c, ta->rds.keyTag,
-                      ta->rds.alg, ta->rds.digestType, ta->digestLen, fromTimeBuf, untilTimeBuf);
-        }
-    }
-
-    LogToFD(fd, "--- DNSSEC Statistics ---");
-
-    LogToFD(fd, "Unicast Cache size              %u", m->rrcache_totalused_unicast);
-    LogToFD(fd, "DNSSEC  Cache size              %u", m->DNSSECStats.TotalMemUsed);
-    if (m->rrcache_totalused_unicast)
-        LogToFD(fd, "DNSSEC  usage percentage        %u", ((unsigned long)(m->DNSSECStats.TotalMemUsed * 100))/m->rrcache_totalused_unicast);
-    LogToFD(fd, "DNSSEC  Extra Packets (0 to 2)  %u", m->DNSSECStats.ExtraPackets0);
-    LogToFD(fd, "DNSSEC  Extra Packets (3 to 6)  %u", m->DNSSECStats.ExtraPackets3);
-    LogToFD(fd, "DNSSEC  Extra Packets (7 to 9)  %u", m->DNSSECStats.ExtraPackets7);
-    LogToFD(fd, "DNSSEC  Extra Packets ( >= 10)  %u", m->DNSSECStats.ExtraPackets10);
-
-    LogToFD(fd, "DNSSEC  Latency (0 to 4ms)      %u", m->DNSSECStats.Latency0);
-    LogToFD(fd, "DNSSEC  Latency (4 to 9ms)      %u", m->DNSSECStats.Latency5);
-    LogToFD(fd, "DNSSEC  Latency (10 to 19ms)    %u", m->DNSSECStats.Latency10);
-    LogToFD(fd, "DNSSEC  Latency (20 to 49ms)    %u", m->DNSSECStats.Latency20);
-    LogToFD(fd, "DNSSEC  Latency (50 to 99ms)    %u", m->DNSSECStats.Latency50);
-    LogToFD(fd, "DNSSEC  Latency (   >=100ms)    %u", m->DNSSECStats.Latency100);
-
-    LogToFD(fd, "DNSSEC  Secure Status           %u", m->DNSSECStats.SecureStatus);
-    LogToFD(fd, "DNSSEC  Insecure Status         %u", m->DNSSECStats.InsecureStatus);
-    LogToFD(fd, "DNSSEC  Indeterminate Status    %u", m->DNSSECStats.IndeterminateStatus);
-    LogToFD(fd, "DNSSEC  Bogus Status            %u", m->DNSSECStats.BogusStatus);
-    LogToFD(fd, "DNSSEC  NoResponse Status       %u", m->DNSSECStats.NoResponseStatus);
-    LogToFD(fd, "DNSSEC  Probes sent             %u", m->DNSSECStats.NumProbesSent);
-    LogToFD(fd, "DNSSEC  Msg Size (<=1024)       %u", m->DNSSECStats.MsgSize0);
-    LogToFD(fd, "DNSSEC  Msg Size (<=2048)       %u", m->DNSSECStats.MsgSize1);
-    LogToFD(fd, "DNSSEC  Msg Size (> 2048)       %u", m->DNSSECStats.MsgSize2);
-
     LogMDNSStatisticsToFD(fd, m);
 
     LogToFD(fd, "---- Task Scheduling Timers ----");
@@ -5244,10 +6260,10 @@ struct CompileTimeAssertionChecks_uds_daemon
     // Check our structures are reasonable sizes. Including overly-large buffers, or embedding
     // other overly-large structures instead of having a pointer to them, can inadvertently
     // cause structure sizes (and therefore memory usage) to balloon unreasonably.
-    char sizecheck_request_state          [(sizeof(request_state)           <= 3696) ? 1 : -1];
+    char sizecheck_request_state          [(sizeof(request_state)           <= 3880) ? 1 : -1];
     char sizecheck_registered_record_entry[(sizeof(registered_record_entry) <=   60) ? 1 : -1];
     char sizecheck_service_instance       [(sizeof(service_instance)        <= 6552) ? 1 : -1];
-    char sizecheck_browser_t              [(sizeof(browser_t)               <= 1432) ? 1 : -1];
+    char sizecheck_browser_t              [(sizeof(browser_t)               <= 1480) ? 1 : -1];
     char sizecheck_reply_hdr              [(sizeof(reply_hdr)               <=   12) ? 1 : -1];
     char sizecheck_reply_state            [(sizeof(reply_state)             <=   64) ? 1 : -1];
 };
index 99ec9040102c939de5c9eb2067444f01b40daed8..d9210579fd06341e9997c4aac7bed94f35b0dde4 100644 (file)
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2002-2018 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2020 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,6 +21,9 @@
 #include "mDNSEmbeddedAPI.h"
 #include "dnssd_ipc.h"
 #include "ClientRequests.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+#include "mdns_private.h"
+#endif
 
 /* Client request: */
 
@@ -98,10 +101,15 @@ struct request_state
        mDNSu8 uuid[UUID_SIZE];
        mDNSBool validUUID;
        dnssd_sock_t errsd;
+#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+    audit_token_t audit_token;
+#endif
        mDNSu32 uid;
     mDNSu32 request_id;
        void * platform_data;
-
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+    mdns_trust_t trust;
+#endif
        // Note: On a shared connection these fields in the primary structure, including hdr, are re-used
        // for each new request. This is because, until we've read the ipc_msg_hdr to find out what the
        // operation is, we don't know if we're going to need to allocate a new request_state or not.
@@ -121,6 +129,9 @@ struct request_state
        req_termination_fn terminate;
        DNSServiceFlags flags;
        mDNSu32 interfaceIndex;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mdns_dns_service_id_t custom_service_id;
+#endif
 
        union
        {
@@ -199,7 +210,7 @@ typedef struct reply_state
 
 #define LogTimerToFD(FILE_DESCRIPTOR, MSG, T) LogToFD((FILE_DESCRIPTOR), MSG " %08X %11d  %08X %11d", (T), (T), (T)-now, (T)-now)
 
-extern int udsserver_init(dnssd_sock_t skts[], mDNSu32 count);
+extern int udsserver_init(dnssd_sock_t skts[], size_t count);
 extern mDNSs32 udsserver_idle(mDNSs32 nextevent);
 extern void udsserver_info_dump_to_fd(int fd);
 extern void udsserver_handle_configchange(mDNS *const m);
index 66216c4f0a8705fb7a6085d3976f3b4e64d1c910..f507e7ae6e49e9eb29ec1e3bf479123586554838 100644 (file)
@@ -117,48 +117,6 @@ mDNSexport void receive_response(const request_state* req, DNSMessage *msg, size
        mDNSCoreReceive(m, msg, end, &srcaddr, srcport, &primary_v4, dstport, primary_interfaceID);
 }
 
-mDNSexport void receive_suspicious_response_ut(const request_state* req, DNSMessage *msg, size_t msgSize, mDNSOpaque16 suspiciousqid, mDNSBool goodLastQID)
-{
-    mDNS *m = &mDNSStorage;
-    mDNSAddr srcaddr;
-    mDNSIPPort srcport, dstport;
-    const mDNSu8 * end;
-    DNSQuestion *q = (DNSQuestion *)&req->u.queryrecord.op.q;
-    UInt8* data = (UInt8*)msg;
-
-    // Used same values for DNS server as specified during init of unit test
-    srcaddr.type                = mDNSAddrType_IPv4;
-    srcaddr.ip.v4.NotAnInteger    = dns_server_ipv4.NotAnInteger;
-    srcport.NotAnInteger        = client_resp_src_port;
-
-    // Used random value for dstport
-    dstport.NotAnInteger = swap16((mDNSu16)client_resp_dst_port);
-
-    // Set DNS message (that was copied from a WireShark packet)
-    end = (const mDNSu8 *)msg + msgSize;
-
-    // Set socket info that mDNSCoreReceive uses to verify socket context
-    q->LocalSocket->ss.port.NotAnInteger = swap16((mDNSu16)client_resp_dst_port);
-    if (suspiciousqid.NotAnInteger)
-    {
-        q->TargetQID.NotAnInteger = swap16(suspiciousqid.NotAnInteger);
-        if (goodLastQID)
-        {
-            q->LastTargetQID.b[0] = data[0];
-            q->LastTargetQID.b[1] = data[1];
-        }
-        else q->LastTargetQID.NotAnInteger = 0;
-    }
-    else
-    {
-        q->TargetQID.b[0] = data[0];
-        q->TargetQID.b[1] = data[1];
-    }
-
-    // Execute mDNSCoreReceive which copies two DNS records into the cache
-    mDNSCoreReceive(m, msg, end, &srcaddr, srcport, &primary_v4, dstport, primary_interfaceID);
-}
-
 mDNSexport  size_t get_reply_len(char* name, uint16_t rdlen)
 {
        size_t len = sizeof(DNSServiceFlags);
index 5755a7527f2d306441f6d1945047fda30c323544..2f9547e0bb3de7b1cd5db3901fc8596472b3c748 100644 (file)
@@ -37,7 +37,6 @@ extern mStatus  init_mdns_storage(void);
 extern size_t   get_reply_len(char* name, uint16_t rdlen);
 extern mStatus  start_client_request(request_state* req, char *msgbuf, size_t msgsz, uint32_t op, UDPSocket* socket);
 extern void     receive_response(const request_state* req, DNSMessage *msg, size_t msgSize);
-extern void     receive_suspicious_response_ut(const request_state* req, DNSMessage *msg, size_t msgSize, mDNSOpaque16 suspiciousqid, mDNSBool goodLastQID);
 extern void     get_ip(const char *const name, struct sockaddr_storage *result);
 extern void     free_req(request_state* req);