From 19fa75a9b9d0f880da91504945516a17ce09a2fb Mon Sep 17 00:00:00 2001 From: Apple Date: Thu, 19 Nov 2020 01:06:26 +0000 Subject: [PATCH] mDNSResponder-1310.40.42.tar.gz --- Clients/DNS-SD.VisualStudio/dns-sd.vcxproj | 117 +- Clients/ExplorerPlugin/ExplorerPlugin.vcxproj | 209 +- .../ExplorerPluginLocRes.vcxproj | 204 +- .../ExplorerPlugin/ExplorerPluginRes.vcxproj | 200 +- .../PrinterSetupWizard.vcxproj | 183 +- .../PrinterSetupWizardLocRes.vcxproj | 176 +- .../PrinterSetupWizardRes.vcxproj | 174 +- Clients/dns-sd.c | 209 +- Clients/dnssdutil/DNSMessage.c | 2633 ++- Clients/dnssdutil/DNSMessage.h | 824 +- Clients/dnssdutil/DNSServerDNSSEC.c | 3676 ++++ Clients/dnssdutil/DNSServerDNSSEC.h | 157 + Clients/dnssdutil/TestUtils.h | 4 +- Clients/dnssdutil/TestUtils.m | 64 +- Clients/dnssdutil/dns-rcode-func-autogen | 257 + Clients/dnssdutil/dns-rr-func-autogen | 287 + .../dnssdutil/dnssdutil-entitlements.plist | 12 +- Clients/dnssdutil/dnssdutil.c | 14183 ++++++++++++---- .../mDNSNetMonitor.vcxproj | 114 +- .../mDNSNetMonitor.vcxproj.filters | 6 - Clients/srputil/srputil-entitlements.plist | 12 + Clients/srputil/srputil.c | 246 + DSO/dso-transport.c | 30 +- DSO/dso.c | 10 +- DSO/dso.h | 4 +- Makefile | 58 +- Platforms/ADK/Thread/Makefile | 21 + Platforms/ADK/Thread/adk-mem-parse.py | 53 + ServiceRegistration/Makefile | 134 +- ServiceRegistration/config-parse.c | 191 + ServiceRegistration/config-parse.h | 33 + ServiceRegistration/dns-msg.h | 269 +- ServiceRegistration/dnssd-proxy.c | 1780 +- ServiceRegistration/dnssd-relay.c | 532 + ServiceRegistration/fromwire.c | 537 +- ServiceRegistration/hmac-macos.c | 99 + ServiceRegistration/hmac-mbedtls.c | 109 + ServiceRegistration/hmac-openssl.c | 147 + ServiceRegistration/ioloop.c | 1008 +- ServiceRegistration/ioloop.h | 242 +- ServiceRegistration/keydump.c | 7 +- ServiceRegistration/log_srp.m | 155 + ServiceRegistration/macos-ioloop.c | 1496 ++ ServiceRegistration/posix.c | 359 + ServiceRegistration/ra-tester/ra-tester.c | 113 + ServiceRegistration/route.c | 5204 ++++++ ServiceRegistration/route.h | 239 + ServiceRegistration/sign-macos.c | 312 + ServiceRegistration/sign-mbedtls.c | 378 +- ServiceRegistration/srp-api.h | 155 + .../srp-client-entitlements.plist | 12 + ServiceRegistration/srp-client.c | 1434 ++ ServiceRegistration/srp-crypto.h | 127 +- ServiceRegistration/srp-dns-proxy.c | 1186 ++ ServiceRegistration/srp-gw.c | 1284 +- ServiceRegistration/srp-gw.h | 123 + ServiceRegistration/srp-ioloop.c | 782 + ServiceRegistration/srp-mdns-proxy.c | 2511 +++ ServiceRegistration/srp-mdns-proxy.h | 171 + ServiceRegistration/srp-parse.c | 799 + ServiceRegistration/srp-proxy.h | 55 + ServiceRegistration/srp-simple.c | 272 - ServiceRegistration/srp-thread.c | 467 + .../srp-thread.h | 22 +- ServiceRegistration/srp-tls.h | 58 + ServiceRegistration/srp.h | 469 +- ServiceRegistration/tls-mbedtls.c | 332 + ServiceRegistration/towire.c | 453 +- ServiceRegistration/verify-macos.c | 295 + ServiceRegistration/verify-mbedtls.c | 22 +- ServiceRegistration/wireutils.c | 528 + mDNSCore/CryptoAlg.c | 279 - mDNSCore/CryptoAlg.h | 62 - mDNSCore/DNSCommon.c | 737 +- mDNSCore/DNSCommon.h | 14 +- mDNSCore/dnsproxy.c | 386 +- mDNSCore/dnsproxy.h | 7 +- mDNSCore/dnssec.c | 4087 ----- mDNSCore/dnssec.h | 157 - mDNSCore/mDNS.c | 2123 +-- mDNSCore/mDNSDebug.h | 84 +- mDNSCore/mDNSEmbeddedAPI.h | 374 +- mDNSCore/nsec.c | 1263 -- mDNSCore/nsec.h | 34 - mDNSCore/nsec3.c | 823 - mDNSCore/nsec3.h | 28 - mDNSCore/uDNS.c | 194 +- mDNSCore/uDNS.h | 24 +- mDNSMacOSX/ApplePlatformFeatures.h | 125 +- mDNSMacOSX/CryptoSupport.c | 779 - mDNSMacOSX/D2D.c | 163 +- mDNSMacOSX/D2D.h | 22 +- mDNSMacOSX/DNS64.c | 154 +- mDNSMacOSX/DNS64.h | 2 + .../{CryptoSupport.h => DNSHeuristics.h} | 17 +- mDNSMacOSX/DNSHeuristics.m | 318 + mDNSMacOSX/DNSHeuristicsInternal.h | 60 + mDNSMacOSX/DNSProxySupport.c | 30 +- mDNSMacOSX/DNSSECSupport.c | 650 - mDNSMacOSX/FeatureFlags/mDNSResponder.plist | 17 + mDNSMacOSX/HTTPUtilities.h | 51 + mDNSMacOSX/HTTPUtilities.m | 293 + .../AppleInternal/com.apple.mdns.plist | 19 + .../com.apple.srp-mdns-proxy.plist | 14 + .../com.apple.mDNSResponder.plist | 7 +- .../com.apple.srp-mdns-proxy.plist | 16 + .../Private/advertising_proxy_services.c | 299 + .../Private/advertising_proxy_services.h | 286 + mDNSMacOSX/Private/cti-services.c | 1519 ++ mDNSMacOSX/Private/cti-services.h | 755 + mDNSMacOSX/Private/dns_services.c | 68 +- mDNSMacOSX/Private/dns_services.h | 66 +- mDNSMacOSX/QuerierSupport.c | 845 + mDNSMacOSX/QuerierSupport.h | 45 + mDNSMacOSX/Scripts/bonjour-mcast-diagnose | 52 +- mDNSMacOSX/SymptomReporter.c | 2 + mDNSMacOSX/SymptomReporter.h | 2 + mDNSMacOSX/Tests/Unit Tests/CNameRecordTest.m | 4 - mDNSMacOSX/Tests/Unit Tests/CacheOrderTest.m | 4 - .../Tests/Unit Tests/DNSHeuristicsTest.m | 201 + .../Crypto/CanonicalMethodsTest.m | 79 + .../Crypto/DigestCalculationTest.m | 146 + .../DNSSEC Unit Tests/Crypto/NSEC3HashTest.m | 137 + .../Utility/BaseNEncodingDecodingTest.m | 88 + .../Utility/ListTMethodsTest.m | 210 + .../Tests/Unit Tests/HelperFunctionTest.m | 8 + .../Tests/Unit Tests/LocalOnlyTimeoutTest.m | 8 - .../Unit Tests/LocalOnlyWithInterfacesTest.m | 44 +- .../Tests/Unit Tests/PathEvaluationTest.m | 13 +- .../Tests/Unit Tests/SuspiciousReplyTest.m | 242 - mDNSMacOSX/Tests/mDNSResponder.plist | 1699 +- .../mDNSResponderTests-Entitlements.plist | 8 + mDNSMacOSX/com.apple.srp-mdns-proxy.plist | 38 + .../dns-sd-entitlements.plist | 5 +- mDNSMacOSX/daemon.c | 145 +- mDNSMacOSX/dnssd.c | 916 +- mDNSMacOSX/dnssd_analytics.c | 351 + mDNSMacOSX/dnssd_analytics.h | 103 + mDNSMacOSX/dnssd_descriptions.m | 70 + mDNSMacOSX/dnssd_object.h | 7 +- mDNSMacOSX/dnssd_object.m | 51 +- mDNSMacOSX/dnssd_private.h | 431 +- mDNSMacOSX/dnssd_server.c | 2131 ++- mDNSMacOSX/dnssd_server.h | 15 +- mDNSMacOSX/dnssd_svcb.c | 365 + mDNSMacOSX/dnssd_svcb.h | 65 + mDNSMacOSX/dnssd_xpc.c | 186 +- mDNSMacOSX/dnssd_xpc.h | 271 +- mDNSMacOSX/dnssec_v2/dnssec_v2.c | 403 + mDNSMacOSX/dnssec_v2/dnssec_v2.h | 123 + mDNSMacOSX/dnssec_v2/dnssec_v2_client.c | 901 + mDNSMacOSX/dnssec_v2/dnssec_v2_client.h | 71 + mDNSMacOSX/dnssec_v2/dnssec_v2_crypto.c | 675 + mDNSMacOSX/dnssec_v2/dnssec_v2_crypto.h | 147 + mDNSMacOSX/dnssec_v2/dnssec_v2_embedded.h | 28 + mDNSMacOSX/dnssec_v2/dnssec_v2_helper.c | 285 + mDNSMacOSX/dnssec_v2/dnssec_v2_helper.h | 218 + mDNSMacOSX/dnssec_v2/dnssec_v2_log.h | 22 + mDNSMacOSX/dnssec_v2/dnssec_v2_retrieval.c | 1741 ++ mDNSMacOSX/dnssec_v2/dnssec_v2_retrieval.h | 209 + mDNSMacOSX/dnssec_v2/dnssec_v2_structs.c | 1663 ++ mDNSMacOSX/dnssec_v2/dnssec_v2_structs.h | 801 + mDNSMacOSX/dnssec_v2/dnssec_v2_trust_anchor.c | 360 + mDNSMacOSX/dnssec_v2/dnssec_v2_trust_anchor.h | 44 + mDNSMacOSX/dnssec_v2/dnssec_v2_validation.c | 3211 ++++ mDNSMacOSX/dnssec_v2/dnssec_v2_validation.h | 35 + .../utilities/base_encoding/base_n.c | 208 + .../utilities/base_encoding/base_n.h | 84 + mDNSMacOSX/dnssec_v2/utilities/list/list.c | 390 + mDNSMacOSX/dnssec_v2/utilities/list/list.h | 339 + mDNSMacOSX/helper-main.c | 2 - mDNSMacOSX/helper-stubs.c | 14 +- mDNSMacOSX/helper.c | 20 + mDNSMacOSX/mDNSMacOSX.c | 556 +- mDNSMacOSX/mDNSMacOSX.h | 9 +- mDNSMacOSX/mDNSResponder-entitlements.plist | 6 + mDNSMacOSX/mDNSResponder.sb | 21 +- .../mDNSResponder.xcodeproj/project.pbxproj | 3796 ++++- .../xcshareddata/xcschemes/Build All.xcscheme | 4 - .../xcschemes/mDNSResponder.xcscheme | 26 +- mDNSMacOSX/mdns_object.h | 40 - mDNSMacOSX/mdns_object.m | 72 - mDNSMacOSX/mdns_objects/log_mdns.m | 242 + mDNSMacOSX/mdns_objects/mdns_address.c | 395 + mDNSMacOSX/mdns_objects/mdns_address.h | 129 + mDNSMacOSX/mdns_objects/mdns_base.h | 56 + mDNSMacOSX/mdns_objects/mdns_dns_service.c | 2901 ++++ mDNSMacOSX/mdns_objects/mdns_dns_service.h | 790 + mDNSMacOSX/mdns_objects/mdns_helpers.c | 131 + mDNSMacOSX/mdns_objects/mdns_helpers.h | 82 + .../mdns_interface_monitor.c} | 318 +- .../mdns_interface_monitor.h} | 162 +- mDNSMacOSX/mdns_objects/mdns_internal.h | 34 + .../mdns_objects/mdns_managed_defaults.c | 219 + .../mdns_objects/mdns_managed_defaults.h | 76 + mDNSMacOSX/mdns_objects/mdns_message.c | 472 + mDNSMacOSX/mdns_objects/mdns_message.h | 361 + mDNSMacOSX/mdns_objects/mdns_object.c | 185 + mDNSMacOSX/mdns_objects/mdns_object.h | 213 + mDNSMacOSX/mdns_objects/mdns_objects.h | 180 + mDNSMacOSX/mdns_objects/mdns_objects.m | 91 + mDNSMacOSX/mdns_objects/mdns_powerlog.c | 301 + mDNSMacOSX/mdns_objects/mdns_powerlog.h | 51 + mDNSMacOSX/mdns_objects/mdns_private.h | 29 + mDNSMacOSX/mdns_objects/mdns_resolver.c | 5619 ++++++ mDNSMacOSX/mdns_objects/mdns_resolver.h | 979 ++ mDNSMacOSX/mdns_objects/mdns_set.c | 280 + mDNSMacOSX/mdns_objects/mdns_set.h | 53 + mDNSMacOSX/mdns_objects/mdns_symptoms.c | 147 + mDNSMacOSX/mdns_objects/mdns_symptoms.h | 46 + mDNSMacOSX/mdns_objects/mdns_tlv.c | 142 + mDNSMacOSX/mdns_objects/mdns_tlv.h | 44 + mDNSMacOSX/mdns_objects/mdns_trust.c | 252 + mDNSMacOSX/mdns_objects/mdns_trust.h | 123 + mDNSMacOSX/mdns_objects/mdns_trust_checks.h | 75 + mDNSMacOSX/mdns_objects/mdns_trust_checks.m | 598 + mDNSMacOSX/mdns_objects/mdns_xpc.c | 51 + mDNSMacOSX/mdns_objects/mdns_xpc.h | 37 + mDNSMacOSX/ra-tester-entitlements.plist | 16 + mDNSMacOSX/srp-mdns-proxy-entitlements.plist | 67 + mDNSMacOSX/srp-mdns-proxy.plist | 10 + mDNSMacOSX/uDNSPathEvaluation.c | 251 + mDNSMacOSX/uDNSPathEvalulation.c | 172 - mDNSMacOSX/utilities/bundle_utilities.h | 24 + mDNSMacOSX/utilities/bundle_utilities.m | 55 + mDNSMacOSX/utilities/setup_assistant_helper.h | 57 + mDNSMacOSX/utilities/setup_assistant_helper.m | 80 + mDNSMacOSX/utilities/system_utilities.c | 15 - mDNSMacOSX/utilities/system_utilities.h | 53 +- mDNSMacOSX/utilities/system_utilities.m | 86 + .../xpc_client_advertising_proxy.h | 34 + .../xpc_services/xpc_client_dns_proxy.h | 6 +- mDNSMacOSX/xpc_services/xpc_clients.h | 1 + .../xpc_services/xpc_service_dns_proxy.c | 51 +- .../xpc_services/xpc_service_log_utility.c | 826 +- .../xpc_services/xpc_service_log_utility.h | 5 +- mDNSPosix/Makefile | 321 + mDNSPosix/mDNSPosix.c | 30 +- mDNSResponder.sln | 185 +- mDNSShared/ClientRequests.c | 360 +- mDNSShared/ClientRequests.h | 90 +- mDNSShared/CommonServices.h | 5 + mDNSShared/PlatformCommon.c | 2 +- mDNSShared/dns_sd.h | 21 +- mDNSShared/dns_sd_internal.h | 25 +- mDNSShared/dns_sd_private.h | 61 +- mDNSShared/dnssd_clientshim.c | 8 - mDNSShared/dnssd_clientstub.c | 210 +- mDNSShared/dnssd_clientstub_apple.c | 71 + mDNSShared/dnssd_clientstub_apple.h | 34 + mDNSShared/dnssd_ipc.c | 19 +- mDNSShared/dnssd_ipc.h | 13 +- mDNSShared/uds_daemon.c | 1716 +- mDNSShared/uds_daemon.h | 17 +- unittests/unittest_common.c | 42 - unittests/unittest_common.h | 1 - 256 files changed, 91514 insertions(+), 20331 deletions(-) mode change 100755 => 100644 Clients/DNS-SD.VisualStudio/dns-sd.vcxproj mode change 100755 => 100644 Clients/ExplorerPlugin/ExplorerPlugin.vcxproj mode change 100755 => 100644 Clients/ExplorerPlugin/ExplorerPluginLocRes.vcxproj mode change 100755 => 100644 Clients/ExplorerPlugin/ExplorerPluginRes.vcxproj mode change 100755 => 100644 Clients/PrinterSetupWizard/PrinterSetupWizard.vcxproj mode change 100755 => 100644 Clients/PrinterSetupWizard/PrinterSetupWizardLocRes.vcxproj mode change 100755 => 100644 Clients/PrinterSetupWizard/PrinterSetupWizardRes.vcxproj create mode 100644 Clients/dnssdutil/DNSServerDNSSEC.c create mode 100644 Clients/dnssdutil/DNSServerDNSSEC.h create mode 100755 Clients/dnssdutil/dns-rcode-func-autogen create mode 100755 Clients/dnssdutil/dns-rr-func-autogen mode change 100755 => 100644 Clients/mDNSNetMonitor.VisualStudio/mDNSNetMonitor.vcxproj mode change 100755 => 100644 Clients/mDNSNetMonitor.VisualStudio/mDNSNetMonitor.vcxproj.filters create mode 100644 Clients/srputil/srputil-entitlements.plist create mode 100644 Clients/srputil/srputil.c create mode 100644 Platforms/ADK/Thread/Makefile create mode 100644 Platforms/ADK/Thread/adk-mem-parse.py create mode 100644 ServiceRegistration/config-parse.c create mode 100644 ServiceRegistration/config-parse.h create mode 100644 ServiceRegistration/dnssd-relay.c create mode 100644 ServiceRegistration/hmac-macos.c create mode 100644 ServiceRegistration/hmac-mbedtls.c create mode 100644 ServiceRegistration/hmac-openssl.c create mode 100644 ServiceRegistration/log_srp.m create mode 100644 ServiceRegistration/macos-ioloop.c create mode 100644 ServiceRegistration/posix.c create mode 100644 ServiceRegistration/ra-tester/ra-tester.c create mode 100644 ServiceRegistration/route.c create mode 100644 ServiceRegistration/route.h create mode 100644 ServiceRegistration/sign-macos.c create mode 100644 ServiceRegistration/srp-api.h create mode 100644 ServiceRegistration/srp-client-entitlements.plist create mode 100644 ServiceRegistration/srp-client.c create mode 100644 ServiceRegistration/srp-dns-proxy.c create mode 100644 ServiceRegistration/srp-gw.h create mode 100644 ServiceRegistration/srp-ioloop.c create mode 100644 ServiceRegistration/srp-mdns-proxy.c create mode 100644 ServiceRegistration/srp-mdns-proxy.h create mode 100644 ServiceRegistration/srp-parse.c create mode 100644 ServiceRegistration/srp-proxy.h delete mode 100644 ServiceRegistration/srp-simple.c create mode 100644 ServiceRegistration/srp-thread.c rename mDNSMacOSX/DNSSECSupport.h => ServiceRegistration/srp-thread.h (58%) create mode 100644 ServiceRegistration/srp-tls.h create mode 100644 ServiceRegistration/tls-mbedtls.c create mode 100644 ServiceRegistration/verify-macos.c create mode 100644 ServiceRegistration/wireutils.c delete mode 100644 mDNSCore/CryptoAlg.c delete mode 100644 mDNSCore/CryptoAlg.h delete mode 100644 mDNSCore/dnssec.c delete mode 100644 mDNSCore/dnssec.h mode change 100755 => 100644 mDNSCore/mDNS.c delete mode 100644 mDNSCore/nsec.c delete mode 100644 mDNSCore/nsec.h delete mode 100644 mDNSCore/nsec3.c delete mode 100644 mDNSCore/nsec3.h delete mode 100644 mDNSMacOSX/CryptoSupport.c rename mDNSMacOSX/{CryptoSupport.h => DNSHeuristics.h} (65%) create mode 100644 mDNSMacOSX/DNSHeuristics.m create mode 100644 mDNSMacOSX/DNSHeuristicsInternal.h delete mode 100644 mDNSMacOSX/DNSSECSupport.c create mode 100644 mDNSMacOSX/FeatureFlags/mDNSResponder.plist create mode 100644 mDNSMacOSX/HTTPUtilities.h create mode 100644 mDNSMacOSX/HTTPUtilities.m create mode 100644 mDNSMacOSX/LoggingProfiles/AppleInternal/com.apple.mdns.plist create mode 100644 mDNSMacOSX/LoggingProfiles/AppleInternal/com.apple.srp-mdns-proxy.plist create mode 100644 mDNSMacOSX/LoggingProfiles/com.apple.srp-mdns-proxy.plist create mode 100644 mDNSMacOSX/Private/advertising_proxy_services.c create mode 100644 mDNSMacOSX/Private/advertising_proxy_services.h create mode 100644 mDNSMacOSX/Private/cti-services.c create mode 100644 mDNSMacOSX/Private/cti-services.h create mode 100644 mDNSMacOSX/QuerierSupport.c create mode 100644 mDNSMacOSX/QuerierSupport.h create mode 100644 mDNSMacOSX/Tests/Unit Tests/DNSHeuristicsTest.m create mode 100644 mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Crypto/CanonicalMethodsTest.m create mode 100644 mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Crypto/DigestCalculationTest.m create mode 100644 mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Crypto/NSEC3HashTest.m create mode 100644 mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Utility/BaseNEncodingDecodingTest.m create mode 100644 mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Utility/ListTMethodsTest.m delete mode 100644 mDNSMacOSX/Tests/Unit Tests/SuspiciousReplyTest.m create mode 100644 mDNSMacOSX/Tests/mDNSResponderTests-Entitlements.plist create mode 100644 mDNSMacOSX/com.apple.srp-mdns-proxy.plist create mode 100644 mDNSMacOSX/dnssd_analytics.c create mode 100644 mDNSMacOSX/dnssd_analytics.h create mode 100644 mDNSMacOSX/dnssd_descriptions.m create mode 100644 mDNSMacOSX/dnssd_svcb.c create mode 100644 mDNSMacOSX/dnssd_svcb.h create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2.c create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2.h create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2_client.c create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2_client.h create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2_crypto.c create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2_crypto.h create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2_embedded.h create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2_helper.c create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2_helper.h create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2_log.h create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2_retrieval.c create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2_retrieval.h create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2_structs.c create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2_structs.h create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2_trust_anchor.c create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2_trust_anchor.h create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2_validation.c create mode 100644 mDNSMacOSX/dnssec_v2/dnssec_v2_validation.h create mode 100644 mDNSMacOSX/dnssec_v2/utilities/base_encoding/base_n.c create mode 100644 mDNSMacOSX/dnssec_v2/utilities/base_encoding/base_n.h create mode 100644 mDNSMacOSX/dnssec_v2/utilities/list/list.c create mode 100644 mDNSMacOSX/dnssec_v2/utilities/list/list.h delete mode 100644 mDNSMacOSX/mdns_object.h delete mode 100644 mDNSMacOSX/mdns_object.m create mode 100644 mDNSMacOSX/mdns_objects/log_mdns.m create mode 100644 mDNSMacOSX/mdns_objects/mdns_address.c create mode 100644 mDNSMacOSX/mdns_objects/mdns_address.h create mode 100644 mDNSMacOSX/mdns_objects/mdns_base.h create mode 100644 mDNSMacOSX/mdns_objects/mdns_dns_service.c create mode 100644 mDNSMacOSX/mdns_objects/mdns_dns_service.h create mode 100644 mDNSMacOSX/mdns_objects/mdns_helpers.c create mode 100644 mDNSMacOSX/mdns_objects/mdns_helpers.h rename mDNSMacOSX/{mdns.c => mdns_objects/mdns_interface_monitor.c} (64%) rename mDNSMacOSX/{mdns_private.h => mdns_objects/mdns_interface_monitor.h} (69%) create mode 100644 mDNSMacOSX/mdns_objects/mdns_internal.h create mode 100644 mDNSMacOSX/mdns_objects/mdns_managed_defaults.c create mode 100644 mDNSMacOSX/mdns_objects/mdns_managed_defaults.h create mode 100644 mDNSMacOSX/mdns_objects/mdns_message.c create mode 100644 mDNSMacOSX/mdns_objects/mdns_message.h create mode 100644 mDNSMacOSX/mdns_objects/mdns_object.c create mode 100644 mDNSMacOSX/mdns_objects/mdns_object.h create mode 100644 mDNSMacOSX/mdns_objects/mdns_objects.h create mode 100644 mDNSMacOSX/mdns_objects/mdns_objects.m create mode 100644 mDNSMacOSX/mdns_objects/mdns_powerlog.c create mode 100644 mDNSMacOSX/mdns_objects/mdns_powerlog.h create mode 100644 mDNSMacOSX/mdns_objects/mdns_private.h create mode 100644 mDNSMacOSX/mdns_objects/mdns_resolver.c create mode 100644 mDNSMacOSX/mdns_objects/mdns_resolver.h create mode 100644 mDNSMacOSX/mdns_objects/mdns_set.c create mode 100644 mDNSMacOSX/mdns_objects/mdns_set.h create mode 100644 mDNSMacOSX/mdns_objects/mdns_symptoms.c create mode 100644 mDNSMacOSX/mdns_objects/mdns_symptoms.h create mode 100644 mDNSMacOSX/mdns_objects/mdns_tlv.c create mode 100644 mDNSMacOSX/mdns_objects/mdns_tlv.h create mode 100644 mDNSMacOSX/mdns_objects/mdns_trust.c create mode 100644 mDNSMacOSX/mdns_objects/mdns_trust.h create mode 100644 mDNSMacOSX/mdns_objects/mdns_trust_checks.h create mode 100644 mDNSMacOSX/mdns_objects/mdns_trust_checks.m create mode 100644 mDNSMacOSX/mdns_objects/mdns_xpc.c create mode 100644 mDNSMacOSX/mdns_objects/mdns_xpc.h create mode 100644 mDNSMacOSX/ra-tester-entitlements.plist create mode 100644 mDNSMacOSX/srp-mdns-proxy-entitlements.plist create mode 100644 mDNSMacOSX/srp-mdns-proxy.plist create mode 100644 mDNSMacOSX/uDNSPathEvaluation.c delete mode 100644 mDNSMacOSX/uDNSPathEvalulation.c create mode 100644 mDNSMacOSX/utilities/bundle_utilities.h create mode 100644 mDNSMacOSX/utilities/bundle_utilities.m create mode 100644 mDNSMacOSX/utilities/setup_assistant_helper.h create mode 100644 mDNSMacOSX/utilities/setup_assistant_helper.m delete mode 100644 mDNSMacOSX/utilities/system_utilities.c create mode 100644 mDNSMacOSX/utilities/system_utilities.m create mode 100644 mDNSMacOSX/xpc_services/xpc_client_advertising_proxy.h mode change 100755 => 100644 mDNSResponder.sln create mode 100644 mDNSShared/dnssd_clientstub_apple.c create mode 100644 mDNSShared/dnssd_clientstub_apple.h diff --git a/Clients/DNS-SD.VisualStudio/dns-sd.vcxproj b/Clients/DNS-SD.VisualStudio/dns-sd.vcxproj old mode 100755 new mode 100644 index 8ed154a..97cf47b --- a/Clients/DNS-SD.VisualStudio/dns-sd.vcxproj +++ b/Clients/DNS-SD.VisualStudio/dns-sd.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -21,23 +29,38 @@ {AA230639-E115-4A44-AA5A-44A61235BA50} Win32Proj + 10.0.18362.0 Application MultiByte + v142 Application MultiByte + v142 Application MultiByte + v142 + + + Application + MultiByte + v142 Application MultiByte + v142 + + + Application + MultiByte + v142 @@ -54,32 +77,45 @@ + + + + + + + + <_ProjectFileVersion>10.0.30319.1 $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ true $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ true + true $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false + false Disabled ../../mDNSShared;%(AdditionalIncludeDirectories) 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) - true EnableFastChecks MultiThreadedDebug @@ -112,7 +148,6 @@ Disabled ../../mDNSShared;%(AdditionalIncludeDirectories) 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) - true EnableFastChecks MultiThreadedDebug @@ -137,6 +172,35 @@ DNS-SD64.manifest;%(AdditionalManifestFiles) + + + + Disabled + ../../mDNSShared;%(AdditionalIncludeDirectories) + 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) + EnableFastChecks + MultiThreadedDebug + + + $(IntDir) + Level3 + ProgramDatabase + + + ../../mDNSWindows;%(AdditionalIncludeDirectories) + + + /NXCOMPAT /DYNAMICBASE %(AdditionalOptions) + ../../mDNSWindows/DLL/$(Platform)/$(Configuration)/dnssd.lib;ws2_32.lib;%(AdditionalDependencies) + $(OutDir)dns-sd.exe + true + $(OutDir)dns-sd.pdb + Console + + + DNS-SD64.manifest;%(AdditionalManifestFiles) + + MaxSpeed @@ -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" xcopy /I/Y "$(TargetPath)" "$(DSTROOT)\WINDOWS\system32\$(Platform)" :END + + + + + + + MaxSpeed + OnlyExplicitInline + true + ../../mDNSShared;%(AdditionalIncludeDirectories) + 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) + true + MultiThreaded + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ../../mDNSWindows;%(AdditionalIncludeDirectories) + + + /NXCOMPAT /DYNAMICBASE %(AdditionalOptions) + ../../mDNSWindows/DLL/$(Platform)/$(Configuration)/dnssd.lib;ws2_32.lib;%(AdditionalDependencies) + $(OutDir)dns-sd.exe + true + Console + true + true + + + DNS-SD64.manifest;%(AdditionalManifestFiles) + + + if not "%RC_XBS%" == "YES" goto END +if not exist "$(DSTROOT)\WINDOWS\system32\$(Platform)" mkdir "$(DSTROOT)\WINDOWS\system32\$(Platform)" +if not exist "$(DSTROOT)\Program Files\Bonjour SDK\Samples\C" mkdir "$(DSTROOT)\Program Files\Bonjour SDK\Samples\C" +xcopy /I/Y "$(TargetPath)" "$(DSTROOT)\WINDOWS\system32\$(Platform)" +:END diff --git a/Clients/ExplorerPlugin/ExplorerPlugin.vcxproj b/Clients/ExplorerPlugin/ExplorerPlugin.vcxproj old mode 100755 new mode 100644 index 810c90e..6f63148 --- a/Clients/ExplorerPlugin/ExplorerPlugin.vcxproj +++ b/Clients/ExplorerPlugin/ExplorerPlugin.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -17,6 +25,10 @@ Release x64 + + Template + ARM64 + Template Win32 @@ -28,30 +40,55 @@ {BB8AC1B5-6587-4163-BDC6-788B157705CA} + 10.0.18362.0 DynamicLibrary Static Unicode + v142 DynamicLibrary Static Unicode + v142 Application + v142 + + + Application + v142 DynamicLibrary Static Unicode + v142 + + + DynamicLibrary + Static + Unicode + v142 DynamicLibrary Static Unicode + v142 + + + DynamicLibrary + Static + Unicode + v142 + + + v142 @@ -67,29 +104,58 @@ + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false + false $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false + false + + + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + + + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + + + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ @@ -109,7 +175,7 @@ Disabled ..\..\mDNSShared;..\..\mDNSWindows;%(AdditionalIncludeDirectories) - _USRDLL;WIN32;_DEBUG;_WINDOWS;DEBUG=1;ENABLE_DOT_LOCAL_NAMES;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(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) true EnableFastChecks MultiThreadedDebug @@ -169,7 +235,7 @@ Disabled ..\..\mDNSShared;..\..\mDNSWindows;%(AdditionalIncludeDirectories) - _USRDLL;WIN32;_DEBUG;_WINDOWS;DEBUG=1;ENABLE_DOT_LOCAL_NAMES;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(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) true EnableFastChecks MultiThreadedDebug @@ -212,6 +278,65 @@ res\ExplorerPlugin64.manifest;%(AdditionalManifestFiles) + + + + + + + %(Outputs) + + + _DEBUG;%(PreprocessorDefinitions) + true + true + $(OutDir)$(ProjectName).tlb + + + Disabled + ..\..\mDNSShared;..\..\mDNSWindows;%(AdditionalIncludeDirectories) + _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) + true + EnableFastChecks + MultiThreadedDebug + true + true + + + + + $(IntDir) + $(IntDir) + $(IntDir)vc80.pdb + true + Level4 + false + true + ProgramDatabase + StdCall + Default + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + ../../mDNSWindows;%(AdditionalIncludeDirectories) + + + /IGNORE:4089 /NXCOMPAT /DYNAMICBASE %(AdditionalOptions) + ../../mDNSWindows/DLLStub/$(Platform)/$(Configuration)/dnssdStatic.lib;uafxcwd.lib;ws2_32.lib;%(AdditionalDependencies) + $(OutDir)ExplorerPlugin.dll + true + uafxcwd.lib;%(IgnoreSpecificDefaultLibraries) + ./$(ProjectName).def + true + $(OutDir)$(ProjectName).pdb + Windows + $(OutDir)$(ProjectName).lib + + + res\ExplorerPlugin64.manifest;%(AdditionalManifestFiles) + + @@ -230,7 +355,7 @@ Size true ..\..\mDNSShared;..\..\mDNSWindows;%(AdditionalIncludeDirectories) - _USRDLL;WIN32;NDEBUG;_WINDOWS;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(PreprocessorDefinitions) + _USRDLL;WIN32;NDEBUG;_WINDOWS;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions) true MultiThreaded true @@ -299,7 +424,7 @@ xcopy /I/Y "$(TargetPath)" Size true ..\..\mDNSShared;..\..\mDNSWindows;%(AdditionalIncludeDirectories) - _USRDLL;WIN32;NDEBUG;_WINDOWS;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(PreprocessorDefinitions) + _USRDLL;WIN32;NDEBUG;_WINDOWS;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions) true MultiThreaded true @@ -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)" xcopy /I/Y "$(TargetPath)" "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)" :END + + + + + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + $(OutDir)$(ProjectName).tlb + + + MaxSpeed + AnySuitable + Size + true + ..\..\mDNSShared;..\..\mDNSWindows;%(AdditionalIncludeDirectories) + _USRDLL;WIN32;NDEBUG;_WINDOWS;WINVER=0x0501;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions) + true + MultiThreaded + true + false + true + + + + + $(IntDir) + $(IntDir) + $(IntDir)vc80.pdb + true + Level4 + false + true + Default + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + ../../mDNSWindows;%(AdditionalIncludeDirectories) + + + /IGNORE:4089 /NXCOMPAT /DYNAMICBASE %(AdditionalOptions) + ../../mDNSWindows/DLLStub/$(Platform)/$(Configuration)/dnssdStatic.lib;ws2_32.lib;uafxcw.lib;%(AdditionalDependencies) + $(OutDir)$(ProjectName).dll + true + uafxcw.lib;%(IgnoreSpecificDefaultLibraries) + ./$(ProjectName).def + true + $(IntDir)$(ProjectName).pdb + Windows + + + + + $(IntDir)$(ProjectName).lib + + + res\ExplorerPlugin64.manifest;%(AdditionalManifestFiles) + + + if not "%RC_XBS%" == "YES" goto END +if not exist "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)" mkdir "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)" +xcopy /I/Y "$(TargetPath)" "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)" +:END diff --git a/Clients/ExplorerPlugin/ExplorerPluginLocRes.vcxproj b/Clients/ExplorerPlugin/ExplorerPluginLocRes.vcxproj old mode 100755 new mode 100644 index fb96fd3..c812c5a --- a/Clients/ExplorerPlugin/ExplorerPluginLocRes.vcxproj +++ b/Clients/ExplorerPlugin/ExplorerPluginLocRes.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -17,6 +25,10 @@ Release x64 + + Template + ARM64 + Template Win32 @@ -29,30 +41,55 @@ {1643427B-F226-4AD6-B413-97DA64D5C6B4} ExplorerPluginLocRes + 10.0.18362.0 DynamicLibrary Static Unicode + v142 DynamicLibrary Static Unicode + v142 Application + v142 + + + Application + v142 DynamicLibrary Static Unicode + v142 + + + DynamicLibrary + Static + Unicode + v142 DynamicLibrary Static Unicode + v142 + + + DynamicLibrary + Static + Unicode + v142 + + + v142 @@ -68,29 +105,46 @@ + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 $(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false $(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false + false $(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false $(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false + false ExplorerPluginLocalized .dll ExplorerPluginLocalized @@ -98,15 +152,23 @@ ExplorerPluginLocalized .dll ExplorerPluginLocalized + ExplorerPluginLocalized .dll + .dll ExplorerPluginLocalized + ExplorerPluginLocalized .dll + .dll ExplorerPluginLocalized + ExplorerPluginLocalized .dll + .dll $(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ $(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ExplorerPlugin.Resources\en.lproj\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ @@ -223,6 +285,62 @@ if not exist $(OutDir)ExplorerPlugin.Resources\en.lproj mkdir $(OutDir)ExplorerP MachineX64 + + + _DEBUG;%(PreprocessorDefinitions) + true + true + $(OutDir)$(ProjectName).tlb + + + Disabled + ..\..\mDNSWindows;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;DEBUG=1;ENABLE_DOT_LOCAL_NAMES;WINVER=0x0400;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + true + + + + + .\Debug/ + .\Debug/ + .\Debug/ + true + Level4 + false + true + ProgramDatabase + StdCall + Default + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + ../../mDNSWindows;%(AdditionalIncludeDirectories) + + + Building Output Directories + if not exist $(OutDir)ExplorerPlugin.Resources mkdir $(OutDir)ExplorerPlugin.Resources +if not exist $(OutDir)ExplorerPlugin.Resources\en.lproj mkdir $(OutDir)ExplorerPlugin.Resources\en.lproj + + + + /MACHINE:I386 /IGNORE:4089 %(AdditionalOptions) + %(AdditionalDependencies) + $(OutDir)ExplorerPluginLocalized.dll + true + %(IgnoreSpecificDefaultLibraries) + + + true + $(OutDir)$(ProjectName).pdb + Windows + true + $(OutDir)$(ProjectName).lib + + NDEBUG;%(PreprocessorDefinitions) @@ -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" xcopy /I/Y "$(TargetPath)" "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources\en.lproj" :END + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + $(OutDir)$(ProjectName).tlb + + + MaxSpeed + AnySuitable + Size + true + ..\..\mDNSWindows;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;WINVER=0x0400;%(PreprocessorDefinitions) + true + MultiThreaded + false + false + true + + + + + .\Release/ + .\Release/ + .\Release/ + true + Level4 + false + true + Default + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + ../../mDNSWindows;%(AdditionalIncludeDirectories) + + + Building Output Directories + if not exist $(OutDir)ExplorerPlugin.Resources mkdir $(OutDir)ExplorerPlugin.Resources +if not exist $(OutDir)ExplorerPlugin.Resources\en.lproj mkdir $(OutDir)ExplorerPlugin.Resources\en.lproj + + + + /MACHINE:I386 /IGNORE:4089 %(AdditionalOptions) + %(AdditionalDependencies) + $(OutDir)ExplorerPluginLocalized.dll + true + %(IgnoreSpecificDefaultLibraries) + + + + + Windows + + + + + true + $(IntDir)$(ProjectName).lib + + + if not "%RC_XBS%" == "YES" goto END +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" +xcopy /I/Y "$(TargetPath)" "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources\en.lproj" +:END @@ -374,6 +561,11 @@ xcopy /I/Y "$(TargetPath)" $(OutDir)ExplorerPluginLocalized.dll + + + $(OutDir)ExplorerPluginLocalized.dll + + diff --git a/Clients/ExplorerPlugin/ExplorerPluginRes.vcxproj b/Clients/ExplorerPlugin/ExplorerPluginRes.vcxproj old mode 100755 new mode 100644 index 4fb6490..690d252 --- a/Clients/ExplorerPlugin/ExplorerPluginRes.vcxproj +++ b/Clients/ExplorerPlugin/ExplorerPluginRes.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -17,6 +25,10 @@ Release x64 + + Template + ARM64 + Template Win32 @@ -28,30 +40,55 @@ {871B1492-B4A4-4B57-9237-FA798484D7D7} + 10.0.18362.0 DynamicLibrary Static Unicode + v142 DynamicLibrary Static Unicode + v142 Application + v142 + + + Application + v142 DynamicLibrary Static Unicode + v142 + + + DynamicLibrary + Static + Unicode + v142 DynamicLibrary Static Unicode + v142 + + + DynamicLibrary + Static + Unicode + v142 + + + v142 @@ -67,45 +104,70 @@ + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 $(Platform)\$(Configuration)\ExplorerPlugin.Resources\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false $(Platform)\$(Configuration)\ExplorerPlugin.Resources\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ExplorerPlugin.Resources\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false + false $(Platform)\$(Configuration)\ExplorerPlugin.Resources\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false $(Platform)\$(Configuration)\ExplorerPlugin.Resources\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ExplorerPlugin.Resources\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false + false ExplorerPluginResources .dll ExplorerPluginResources .dll $(Platform)\$(Configuration)\ExplorerPlugin.Resources\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ ExplorerPluginResources .dll ExplorerPluginResources + ExplorerPluginResources .dll + .dll ExplorerPluginResources + ExplorerPluginResources .dll + .dll $(Platform)\$(Configuration)\ExplorerPlugin.Resources\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ExplorerPlugin.Resources\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ ExplorerPluginResources + ExplorerPluginResources .dll + .dll @@ -216,6 +278,59 @@ MachineX64 + + + _DEBUG;%(PreprocessorDefinitions) + true + true + $(OutDir)$(ProjectName).tlb + + + Disabled + ..\..\mDNSWindows;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;DEBUG=1;ENABLE_DOT_LOCAL_NAMES;WINVER=0x0400;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + true + + + + + .\Debug/ + .\Debug/ + .\Debug/ + true + Level4 + false + true + ProgramDatabase + StdCall + Default + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + ../../mDNSWindows;%(AdditionalIncludeDirectories) + + + Building Output Directories + if not exist Debug\ExplorerPlugin.Resources mkdir Debug\ExplorerPlugin.Resources + + + /MACHINE:I386 /IGNORE:4089 %(AdditionalOptions) + $(OutDir)ExplorerPluginResources.dll + true + %(IgnoreSpecificDefaultLibraries) + + + true + $(OutDir)$(ProjectName).pdb + Windows + true + $(OutDir)$(ProjectName).lib + + NDEBUG;%(PreprocessorDefinitions) @@ -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" xcopy /I/Y "$(TargetPath)" "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources" :END + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + $(OutDir)$(ProjectName).tlb + + + MaxSpeed + AnySuitable + Size + true + ..\..\mDNSShared;..\..\mDNSWindows;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;WINVER=0x0400;%(PreprocessorDefinitions) + true + MultiThreaded + false + false + true + + + + + .\Release/ + .\Release/ + .\Release/ + true + Level4 + false + true + Default + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + ../../mDNSWindows;%(AdditionalIncludeDirectories) + + + Building Output Directories + if not exist Release mkdir Release +if not exist "Release\ExplorerPlugin.Resources" mkdir "Release\ExplorerPlugin.Resources" + + + + /MACHINE:I386 /IGNORE:4089 %(AdditionalOptions) + $(OutDir)ExplorerPluginResources.dll + true + %(IgnoreSpecificDefaultLibraries) + + + + + Windows + + + + + true + $(IntDir)$(ProjectName).lib + + + if not "%RC_XBS%" == "YES" goto END +if not exist "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources" mkdir "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources" +xcopy /I/Y "$(TargetPath)" "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)\ExplorerPlugin.Resources" +:END @@ -365,6 +548,11 @@ xcopy /I/Y "$(TargetPath)" $(OutDir)ExplorerPluginResources.dll + + + $(OutDir)ExplorerPluginResources.dll + + diff --git a/Clients/PrinterSetupWizard/PrinterSetupWizard.vcxproj b/Clients/PrinterSetupWizard/PrinterSetupWizard.vcxproj old mode 100755 new mode 100644 index 563597e..809f78c --- a/Clients/PrinterSetupWizard/PrinterSetupWizard.vcxproj +++ b/Clients/PrinterSetupWizard/PrinterSetupWizard.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -17,6 +25,10 @@ Release x64 + + Template + ARM64 + Template Win32 @@ -29,30 +41,55 @@ {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF} MFCProj + 10.0.18362.0 Application Static MultiByte + v142 Application Static MultiByte + v142 Application + v142 + + + Application + v142 Application Static MultiByte + v142 + + + Application + Static + MultiByte + v142 Application Static MultiByte + v142 + + + Application + Static + MultiByte + v142 + + + v142 @@ -68,36 +105,68 @@ + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ true $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ true + true $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false + false $(IncludePath) PrinterWizard PrinterWizard PrinterWizard PrinterWizard + PrinterWizard PrinterWizard + PrinterWizard PrinterWizard + PrinterWizard + + + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + + + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + + + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ @@ -107,9 +176,8 @@ Disabled .;../../mDNSWindows;../../mDNSShared;%(AdditionalIncludeDirectories) - WIN32;_WINDOWS;_DEBUGS;DEBUG=1;WINVER=0x0501;_WIN32_WINNT=0x0501;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(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) true - true EnableFastChecks MultiThreadedDebug true @@ -150,9 +218,8 @@ Disabled .;../../mDNSWindows;../../mDNSShared;%(AdditionalIncludeDirectories) - WIN32;_WINDOWS;_DEBUGS;DEBUG=1;WINVER=0x0501;_WIN32_WINNT=0x0501;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(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) true - true EnableFastChecks MultiThreadedDebug true @@ -184,6 +251,46 @@ res\PrinterSetupWizard64.manifest;%(AdditionalManifestFiles) + + + _DEBUG;%(PreprocessorDefinitions) + false + + + Disabled + .;../../mDNSWindows;../../mDNSShared;%(AdditionalIncludeDirectories) + 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) + true + EnableFastChecks + MultiThreadedDebug + true + true + + + $(IntDir) + Level4 + ProgramDatabase + Cdecl + 4702;%(DisableSpecificWarnings) + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + $(IntDir);../../mDNSWindows;%(AdditionalIncludeDirectories) + + + /NXCOMPAT /DYNAMICBASE %(AdditionalOptions) + ../../mDNSWindows/DLLStub/$(Platform)/$(Configuration)/dnssdStatic.lib;ws2_32.lib;iphlpapi.lib;winspool.lib;setupapi.lib;%(AdditionalDependencies) + $(OutDir)PrinterWizard.exe + true + $(IntDir)$(ProjectName).pdb + Windows + wWinMainCRTStartup + + + res\PrinterSetupWizard64.manifest;%(AdditionalManifestFiles) + + NDEBUG;%(PreprocessorDefinitions) @@ -194,9 +301,8 @@ OnlyExplicitInline true .;../../mDNSWindows;../../mDNSShared;%(AdditionalIncludeDirectories) - WIN32;_WINDOWS;NDEBUG;WINVER=0x0501;_WIN32_WINNT=0x0501;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(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) true - false MultiThreaded true true @@ -248,9 +354,8 @@ xcopy /I/Y "$(TargetPath)" OnlyExplicitInline true .;../../mDNSWindows;../../mDNSShared;%(AdditionalIncludeDirectories) - WIN32;_WINDOWS;NDEBUG;WINVER=0x0501;_WIN32_WINNT=0x0501;UNICODE;_UNICODE;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1;%(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) true - false MultiThreaded true true @@ -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)" xcopy /I/Y "$(TargetPath)" "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)" :END + + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + MaxSpeed + OnlyExplicitInline + true + .;../../mDNSWindows;../../mDNSShared;%(AdditionalIncludeDirectories) + 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) + true + MultiThreaded + true + true + + + $(IntDir) + Level4 + ProgramDatabase + 4702;%(DisableSpecificWarnings) + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + $(IntDir);../../mDNSWindows;%(AdditionalIncludeDirectories) + + + /NXCOMPAT /DYNAMICBASE %(AdditionalOptions) + ../../mDNSWindows/DLLStub/$(Platform)/$(Configuration)/dnssdStatic.lib;ws2_32.lib;iphlpapi.lib;winspool.lib;setupapi.lib;%(AdditionalDependencies) + $(OutDir)PrinterWizard.exe + true + $(IntDir)$(ProjectName).pdb + Windows + + + + + wWinMainCRTStartup + + + res\PrinterSetupWizard64.manifest;%(AdditionalManifestFiles) + + + if not "%RC_XBS%" == "YES" goto END +if not exist "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)" mkdir "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)" +xcopy /I/Y "$(TargetPath)" "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)" +:END @@ -304,6 +460,7 @@ xcopy /I/Y "$(TargetPath)" 4201;%(DisableSpecificWarnings) 4201;%(DisableSpecificWarnings) + 4201;%(DisableSpecificWarnings) @@ -341,8 +498,10 @@ xcopy /I/Y "$(TargetPath)" true true + true true true + true diff --git a/Clients/PrinterSetupWizard/PrinterSetupWizardLocRes.vcxproj b/Clients/PrinterSetupWizard/PrinterSetupWizardLocRes.vcxproj old mode 100755 new mode 100644 index 86affb8..00b7e7a --- a/Clients/PrinterSetupWizard/PrinterSetupWizardLocRes.vcxproj +++ b/Clients/PrinterSetupWizard/PrinterSetupWizardLocRes.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -17,6 +25,10 @@ Release x64 + + Template + ARM64 + Template Win32 @@ -29,30 +41,55 @@ {967F5375-0176-43D3-ADA3-22EE25551C37} MFCProj + 10.0.18362.0 DynamicLibrary Static MultiByte + v142 DynamicLibrary Static MultiByte + v142 Application + v142 + + + Application + v142 DynamicLibrary Static MultiByte + v142 + + + DynamicLibrary + Static + MultiByte + v142 DynamicLibrary Static MultiByte + v142 + + + DynamicLibrary + Static + MultiByte + v142 + + + v142 @@ -68,33 +105,52 @@ + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 $(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ true $(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ true + true $(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false $(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false + false $(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ $(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\PrinterWizard.Resources\en.lproj\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ PrinterWizardLocalized .dll PrinterWizardLocalized @@ -102,11 +158,17 @@ PrinterWizardLocalized .dll PrinterWizardLocalized + PrinterWizardLocalized .dll + .dll PrinterWizardLocalized + PrinterWizardLocalized .dll + .dll PrinterWizardLocalized + PrinterWizardLocalized .dll + .dll @@ -197,6 +259,49 @@ if not exist $(OutDir)PrinterWizard.Resources\en.lproj mkdir $(OutDir)PrinterWiz MachineX64 + + + _DEBUG;%(PreprocessorDefinitions) + false + + + Disabled + ..\..\mDNSWindows;%(AdditionalIncludeDirectories) + WIN32;_WINDOWS;_DEBUGS;DEBUG=1;WINVER=0x0400;UNICODE;_UNICODE;%(PreprocessorDefinitions) + true + true + EnableFastChecks + MultiThreadedDebug + true + true + + + Level4 + ProgramDatabase + Cdecl + 4702;%(DisableSpecificWarnings) + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + $(IntDir);../../mDNSWindows;%(AdditionalIncludeDirectories) + + + Building Output Directories + if not exist $(OutDir)PrinterWizard.Resources mkdir $(OutDir)PrinterWizard.Resources +if not exist $(OutDir)PrinterWizard.Resources\en.lproj mkdir $(OutDir)PrinterWizard.Resources\en.lproj + + + + $(OutDir)PrinterWizardLocalized.dll + true + Windows + + + true + $(OutDir)Localized.lib + + NDEBUG;%(PreprocessorDefinitions) @@ -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" xcopy /I/Y "$(TargetPath)" "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources\en.lproj" :END + + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + MaxSpeed + OnlyExplicitInline + true + ..\..\mDNSWindows;%(AdditionalIncludeDirectories) + WIN32;_WINDOWS;NDEBUG;WINVER=0x0400;UNICODE;_UNICODE;%(PreprocessorDefinitions) + true + false + MultiThreaded + true + true + + + Level4 + ProgramDatabase + 4702;%(DisableSpecificWarnings) + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + $(IntDir);../../mDNSWindows;%(AdditionalIncludeDirectories) + + + Building Output Directories + if not exist $(OutDir)PrinterWizard.Resources mkdir $(OutDir)PrinterWizard.Resources +if not exist $(OutDir)PrinterWizard.Resources\en.lproj mkdir $(OutDir)PrinterWizard.Resources\en.lproj + + + + $(OutDir)PrinterWizardLocalized.dll + false + Windows + + + + + + + true + $(IntDir)$(TargetName).lib + + + if not "%RC_XBS%" == "YES" goto END +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" +xcopy /I/Y "$(TargetPath)" "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources\en.lproj" +:END @@ -318,6 +477,11 @@ xcopy /I/Y "$(TargetPath)" $(OutDir)PrinterWizardLocalized.dll + + + $(OutDir)PrinterWizardLocalized.dll + + diff --git a/Clients/PrinterSetupWizard/PrinterSetupWizardRes.vcxproj b/Clients/PrinterSetupWizard/PrinterSetupWizardRes.vcxproj old mode 100755 new mode 100644 index 16befa7..2de3d7b --- a/Clients/PrinterSetupWizard/PrinterSetupWizardRes.vcxproj +++ b/Clients/PrinterSetupWizard/PrinterSetupWizardRes.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -17,6 +25,10 @@ Release x64 + + Template + ARM64 + Template Win32 @@ -29,30 +41,55 @@ {CFCCB176-6CAA-472B-B0A2-90511C8E2E52} MFCProj + 10.0.18362.0 DynamicLibrary Static MultiByte + v142 DynamicLibrary Static MultiByte + v142 Application + v142 + + + Application + v142 DynamicLibrary Static MultiByte + v142 + + + DynamicLibrary + Static + MultiByte + v142 DynamicLibrary Static MultiByte + v142 + + + DynamicLibrary + Static + MultiByte + v142 + + + v142 @@ -68,45 +105,70 @@ + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 $(Platform)\$(Configuration)\PrinterWizard.Resources\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ true $(Platform)\$(Configuration)\PrinterWizard.Resources\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\PrinterWizard.Resources\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ true + true $(Platform)\$(Configuration)\PrinterWizard.Resources\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false $(Platform)\$(Configuration)\PrinterWizard.Resources\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\PrinterWizard.Resources\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false + false PrinterWizardResources .dll PrinterWizardResources .dll $(Platform)\$(Configuration)\PrinterWizard.Resources\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ PrinterWizardResources .dll PrinterWizardResources + PrinterWizardResources .dll + .dll PrinterWizardResources + PrinterWizardResources .dll + .dll $(Platform)\$(Configuration)\PrinterWizard.Resources\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\PrinterWizard.Resources\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ PrinterWizardResources + PrinterWizardResources .dll + .dll @@ -193,6 +255,47 @@ MachineX64 + + + _DEBUG;%(PreprocessorDefinitions) + false + + + Disabled + ..\..\mDNSWindows;%(AdditionalIncludeDirectories) + WIN32;_WINDOWS;_DEBUGS;DEBUG=1;WINVER=0x0400;UNICODE;_UNICODE;%(PreprocessorDefinitions) + true + true + EnableFastChecks + MultiThreadedDebug + true + true + + + Level4 + ProgramDatabase + Cdecl + 4702;%(DisableSpecificWarnings) + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + $(IntDir);../../mDNSWindows;%(AdditionalIncludeDirectories) + + + Building Output Directories + if not exist Debug\PrinterWizard.Resources mkdir Debug\PrinterWizard.Resources + + + $(OutDir)PrinterWizardResources.dll + true + Windows + + + true + $(OutDir)Localized.lib + + NDEBUG;%(PreprocessorDefinitions) @@ -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" xcopy /I/Y "$(TargetPath)" "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources" :END + + + + + + NDEBUG;%(PreprocessorDefinitions) + false + + + MaxSpeed + OnlyExplicitInline + true + ..\..\mDNSWindows;%(AdditionalIncludeDirectories) + WIN32;_WINDOWS;NDEBUG;WINVER=0x0400;UNICODE;_UNICODE;%(PreprocessorDefinitions) + true + false + MultiThreaded + true + true + + + Level4 + ProgramDatabase + 4702;%(DisableSpecificWarnings) + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + $(IntDir);../../mDNSWindows;%(AdditionalIncludeDirectories) + + + Building Output Directories + if not exist Release mkdir Release +if not exist "Release\PrinterWizard.Resources" mkdir "Release\PrinterWizard.Resources" + + + + $(OutDir)PrinterWizardResources.dll + false + Windows + + + + + + + true + $(IntDir)$(TargetName).lib + + + if not "%RC_XBS%" == "YES" goto END +if not exist "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources" mkdir "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources" +xcopy /I/Y "$(TargetPath)" "$(DSTROOT)\Program Files\Bonjour Print Services\$(Platform)\PrinterWizard.Resources" +:END @@ -314,6 +471,11 @@ xcopy /I/Y "$(TargetPath)" $(OutDir)PrinterWizardResources.dll + + + $(OutDir)PrinterWizardResources.dll + + diff --git a/Clients/dns-sd.c b/Clients/dns-sd.c index 357a478..947b9dd 100644 --- a/Clients/dns-sd.c +++ b/Clients/dns-sd.c @@ -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 (Query; reconfirming each result)\n", arg0); - fprintf(stderr, "%s -D (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 (Validate address info with DNSSEC)\n", arg0); fprintf(stderr, "%s -i (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 diff --git a/Clients/dnssdutil/DNSMessage.c b/Clients/dnssdutil/DNSMessage.c index 68561ee..dc1137d 100644 --- a/Clients/dnssdutil/DNSMessage.c +++ b/Clients/dnssdutil/DNSMessage.c @@ -1,10 +1,108 @@ /* - Copyright (c) 2016-2019 Apple Inc. All rights reserved. + Copyright (c) 2016-2020 Apple Inc. All rights reserved. */ #include "DNSMessage.h" +#include +#include //=========================================================================================================================== +// 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 . + + 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 . + + require_action_quiet( !optPtr, exit, err = kMalformedErr ); + + // The OPT record's name must be 0 (root domain). + // See . + + 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 : + // + // 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 + // . + + switch( inType ) + { + case kDNSRecordType_CNAME: // + case kDNSRecordType_MB: // + case kDNSRecordType_MD: // + case kDNSRecordType_MF: // + case kDNSRecordType_MG: // + case kDNSRecordType_MR: // + case kDNSRecordType_NS: // + case kDNSRecordType_PTR: // + { + // 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: // + { + 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: // + { + 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: // + { + 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: // + case kDNSRecordType_NULL: // + case kDNSRecordType_TXT: // + { + // 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 +// . + +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 . + +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 and . + + 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 ) + + 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 ) + + 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 ) + + 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 ) + // 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 ) + + 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 ) + + 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 . + +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 + + 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 *) ¶mFields[ 1 ]; + require_action_quiet( ( (size_t)( inRDataEnd - ptr ) ) >= valLen, exit, err = kUnderrunErr ); + + switch( key ) + { + case kDNSSVCParamKey_Mandatory: + { + // List of 16-bit keys + // See . + + 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 . + + 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 . + + 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 . + + 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 . + + 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 . + + 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 ) ); +} diff --git a/Clients/dnssdutil/DNSMessage.h b/Clients/dnssdutil/DNSMessage.h index 56ddeee..f1e4a74 100644 --- a/Clients/dnssdutil/DNSMessage.h +++ b/Clients/dnssdutil/DNSMessage.h @@ -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 +#include -#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 . +#define kMDNSClassCacheFlushBit ( 1U << 15 ) // See . //--------------------------------------------------------------------------------------------------------------------------- /*! @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 ) +#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 typedef struct { @@ -131,7 +148,8 @@ STATIC_INLINE void dns_fixed_fields_question_set_class( inFields, inQClass ); } -// DNS resource record fixed-length fields (see ) +// DNS resource record fixed-length fields +// See 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 ) +// DNS SRV record data fixed-length fields +// See 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 ) +// DNS SOA record data fixed-length fields +// See 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 + +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 ) // + +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 + +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 + +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 + +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 +#define kDNSKeyFlag_SEP ( 1U << ( 15 - 15 ) ) // MSB bit 15 + +#define kDNSKeyProtocol_DNSSEC 3 // Protocol value must be 3. + +// DNSSEC Algoritm Numbers +// See + +#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 + +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 + +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 +#define kDSDigestType_SHA256 2 // SHA-256 + +// DNS DS record data +// See + +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 + +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 + +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 . + +} DNSClassType; + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @group DNS EDNS0 Option Codes +*/ +typedef enum +{ + kDNSEDNS0OptionCode_Padding = 12 // + +} DNSEDNS0OptionCode; + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @group DNS EDNS0 Option Codes + @discussion See . +*/ +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 . +*/ +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 index 0000000..afdd1a2 --- /dev/null +++ b/Clients/dnssdutil/DNSServerDNSSEC.c @@ -0,0 +1,3676 @@ +/* + Copyright (c) 2020 Apple Inc. All rights reserved. +*/ + +#include "DNSServerDNSSEC.h" + +#include "DNSMessage.h" + +#include + +//=========================================================================================================================== +// 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 . + +#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 . + +#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 . + +#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 . + +#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 . + +#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 . + +#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 . + +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 +// . + +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( "" ); + } +} diff --git a/Clients/dnssdutil/DNSServerDNSSEC.h b/Clients/dnssdutil/DNSServerDNSSEC.h new file mode 100644 index 0000000..28ea7ae --- /dev/null +++ b/Clients/dnssdutil/DNSServerDNSSEC.h @@ -0,0 +1,157 @@ +/* + Copyright (c) 2020 Apple Inc. All rights reserved. +*/ + +#ifndef __DNSServerDNSSEC_h +#define __DNSServerDNSSEC_h + +#include + +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 . +*/ +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 . + + @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 diff --git a/Clients/dnssdutil/TestUtils.h b/Clients/dnssdutil/TestUtils.h index c263164..81fbb10 100644 --- a/Clients/dnssdutil/TestUtils.h +++ b/Clients/dnssdutil/TestUtils.h @@ -10,6 +10,7 @@ #include #include +#include #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 } diff --git a/Clients/dnssdutil/TestUtils.m b/Clients/dnssdutil/TestUtils.m index 00b3c4c..35efdbb 100644 --- a/Clients/dnssdutil/TestUtils.m +++ b/Clients/dnssdutil/TestUtils.m @@ -7,59 +7,71 @@ #import "TestUtils.h" -#import -#import +#import #import #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 index 0000000..d8b0a6f --- /dev/null +++ b/Clients/dnssdutil/dns-rcode-func-autogen @@ -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 \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 index 0000000..0c72d41 --- /dev/null +++ b/Clients/dnssdutil/dns-rr-func-autogen @@ -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 ',' 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 \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 "$@" diff --git a/Clients/dnssdutil/dnssdutil-entitlements.plist b/Clients/dnssdutil/dnssdutil-entitlements.plist index 47eba96..143afa4 100644 --- a/Clients/dnssdutil/dnssdutil-entitlements.plist +++ b/Clients/dnssdutil/dnssdutil-entitlements.plist @@ -6,19 +6,23 @@ com.apple.mDNSResponder_Helper + com.apple.networkd_privileged + + com.apple.private.necp.match + + com.apple.private.nehelper.privileged + com.apple.security.network.client com.apple.security.network.server + com.apple.private.network.socket-delegate + com.apple.SystemConfiguration.SCDynamicStore-write-access com.apple.SystemConfiguration.SCPreferences-write-access preferences.plist - com.apple.private.nehelper.privileged - - com.apple.networkd_privileged - diff --git a/Clients/dnssdutil/dnssdutil.c b/Clients/dnssdutil/dnssdutil.c index d56ed00..31aebed 100644 --- a/Clients/dnssdutil/dnssdutil.c +++ b/Clients/dnssdutil/dnssdutil.c @@ -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 #include #include +#include #include CF_RUNTIME_HEADER @@ -19,7 +31,8 @@ #include #include #include - #include + #include + #include #include #include #include @@ -27,6 +40,7 @@ #if( TARGET_OS_POSIX ) #include + #include #endif #if( !defined( DNSSDUTIL_INCLUDE_DNSCRYPT ) ) @@ -42,9 +56,13 @@ #endif #if( MDNSRESPONDER_PROJECT ) + #include #include + #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 //=========================================================================================================================== @@ -87,7 +105,7 @@ "\x12" "WakeOnResolve\0" \ "\x13" "BackgroundTrafficClass\0" \ "\x14" "IncludeAWDL\0" \ - "\x15" "Validate\0" \ + "\x15" "EnableDNSSEC\0" \ "\x16" "UnicastResponse\0" \ "\x17" "ValidateOptional\0" \ "\x18" "WakeOnlyService\0" \ @@ -130,9 +148,6 @@ #define kDefaultMDNSMessageID 0 #define kDefaultMDNSQueryFlags 0 -#define kQClassUnicastResponseBit ( 1U << 15 ) -#define kRRClassCacheFlushBit ( 1U << 15 ) - // Recommended Resource Record TTL values. See . #define kMDNSRecordTTL_Host 120 // TTL for resource records related to a host name, e.g., A, AAAA, SRV, etc. @@ -155,21 +170,38 @@ #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 . 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 . + +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:\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:\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" \ - " _._[.][.[.][.[.][...]]].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" + " _._[.][.[.][.[.][...]]].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" \ - " and .\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" + " and .\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 [.]., where 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 [.]., where 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. 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. 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., .local.,\n" \ - " -1.local., ..., -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---._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---._tcp.local.\n" \ - " TYPE: PTR\n" \ - " CLASS: IN\n" \ - " TTL: 4500\n" \ - " RDATA: ._t---._tcp.local.\n" \ - "\n" \ - " 2. For each i in [2, N], there is one PTR record defined as\n" \ - "\n" \ - " NAME: _t---._tcp.local.\n" \ - " TYPE: PTR\n" \ - " CLASS: IN\n" \ - " TTL: 4500\n" \ - " RDATA: \" ()._t---._tcp.local.\"\n" - -#define kMDNSReplierInfoText_SRV \ - "The replier's authoritative SRV records have names of the form ._t---._tcp.local.,\n" \ - "where L is an integer in [1, 65535], N is an integer in [1, N_max], and is or\n" \ - "\" ()\", 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: ._t---._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: .local.\n" \ - "\n" \ - " 2. For each i in [2, N], there is one SRV record defined as:\n" \ - "\n" \ - " NAME: \" ()._t---._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: -.local.\n" - -#define kMDNSReplierInfoText_TXT \ - "The replier's authoritative TXT records have names of the form ._t---._tcp.local.,\n" \ - "where L is an integer in [1, 65535], N is an integer in [1, N_max], and is or\n" \ - "\" ()\", 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: ._t---._tcp.local.\n" \ - " TYPE: TXT\n" \ - " CLASS: IN\n" \ - " TTL: 4500\n" \ - " RDLENGTH: L\n" \ - " RDATA: \n" \ - "\n" \ - " 2. For each i in [2, N], there is one TXT record:\n" \ - "\n" \ - " NAME: \" ()._t---._tcp.local.\"\n" \ - " TYPE: TXT\n" \ - " CLASS: IN\n" \ - " TTL: 4500\n" \ - " RDLENGTH: L\n" \ - " RDATA: \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. 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. 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., .local.,\n" + " -1.local., ..., -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---._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---._tcp.local.\n" + " TYPE: PTR\n" + " CLASS: IN\n" + " TTL: 4500\n" + " RDATA: ._t---._tcp.local.\n" + "\n" + " 2. For each i in [2, N], there is one PTR record defined as\n" + "\n" + " NAME: _t---._tcp.local.\n" + " TYPE: PTR\n" + " CLASS: IN\n" + " TTL: 4500\n" + " RDATA: \" ()._t---._tcp.local.\"\n"; + +static const char kMDNSReplierInfoText_SRV[] = + "The replier's authoritative SRV records have names of the form ._t---._tcp.local.,\n" + "where L is an integer in [1, 65535], N is an integer in [1, N_max], and is or\n" + "\" ()\", 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: ._t---._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: .local.\n" + "\n" + " 2. For each i in [2, N], there is one SRV record defined as:\n" + "\n" + " NAME: \" ()._t---._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: -.local.\n"; + +static const char kMDNSReplierInfoText_TXT[] = + "The replier's authoritative TXT records have names of the form ._t---._tcp.local.,\n" + "where L is an integer in [1, 65535], N is an integer in [1, N_max], and is or\n" + "\" ()\", 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: ._t---._tcp.local.\n" + " TYPE: TXT\n" + " CLASS: IN\n" + " TTL: 4500\n" + " RDLENGTH: L\n" + " RDATA: \n" + "\n" + " 2. For each i in [2, N], there is one TXT record:\n" + "\n" + " NAME: \" ()._t---._tcp.local.\"\n" + " TYPE: TXT\n" + " CLASS: IN\n" + " TTL: 4500\n" + " RDLENGTH: L\n" + " RDATA: \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: .local.\n" \ - " TYPE: A\n" \ - " CLASS: IN\n" \ - " TTL: 120\n" \ - " RDLENGTH: 4\n" \ - " RDATA: 0.0.1.\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: -.local.\n" \ - " TYPE: A\n" \ - " CLASS: IN\n" \ - " TTL: 120\n" \ - " RDLENGTH: 4\n" \ - " RDATA: 0...\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: .local.\n" \ - " TYPE: AAAA\n" \ - " CLASS: IN\n" \ - " TTL: 120\n" \ - " RDLENGTH: 16\n" \ - " RDATA: 2001:db8:2::1:\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: -.local.\n" \ - " TYPE: AAAA\n" \ - " CLASS: IN\n" \ - " TTL: 120\n" \ - " RDLENGTH: 16\n" \ - " RDATA: 2001:db8:2:::\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: \".local.\" if i = 1, otherwise \"-.local.\"\n" + " TYPE: A\n" + " CLASS: IN\n" + " TTL: 120\n" + " RDLENGTH: 4\n" + " RDATA: 0.<⌊i / 256⌋>..\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: .local.\n" + " TYPE: AAAA\n" + " CLASS: IN\n" + " TTL: 120\n" + " RDLENGTH: 16\n" + " RDATA: fe80::1:\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: -.local.\n" + " TYPE: AAAA\n" + " CLASS: IN\n" + " TTL: 120\n" + " RDLENGTH: 16\n" + " RDATA: 2001:db8:2:::\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 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 // 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-- + +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-". 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-". 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 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 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 . + +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( "" ); + } +} + +//=========================================================================================================================== + +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( "" ); + } +} + +//=========================================================================================================================== + +static const char * _DNSProxyTestModeToString( DNSProxyTestMode inMode ) +{ + switch( inMode ) + { + case kDNSProxyTestMode_Normal: return( "normal" ); + case kDNSProxyTestMode_ForceAAAASynthesis: return( "force-AAAA-synthesis" ); + default: return( "" ); + } +} + +//=========================================================================================================================== + +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 : + // + // 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, "\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 ); + } } //=========================================================================================================================== diff --git a/Clients/mDNSNetMonitor.VisualStudio/mDNSNetMonitor.vcxproj b/Clients/mDNSNetMonitor.VisualStudio/mDNSNetMonitor.vcxproj old mode 100755 new mode 100644 index 89a27a4..c2498e1 --- a/Clients/mDNSNetMonitor.VisualStudio/mDNSNetMonitor.vcxproj +++ b/Clients/mDNSNetMonitor.VisualStudio/mDNSNetMonitor.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -22,25 +30,41 @@ {AF35C285-528D-46A1-8A0E-47B0733DC718} mDNSNetMonitor Win32Proj + 10.0.18362.0 Application Unicode true + v142 Application Unicode true + v142 + + + Application + Unicode + true + v142 Application Unicode + v142 Application Unicode + v142 + + + Application + Unicode + v142 @@ -51,34 +75,45 @@ + + + + + + <_ProjectFileVersion>10.0.30319.1 $(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ true true + true $(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + $(Platform)\$(Configuration)\$(ProjectName)\ false false + false Disabled ../../mDNSWindows/SystemService;../../mDNSWindows;../../mDNSShared;../../mDNSCore;$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;C:/Program Files/Microsoft SDKs/Windows/v6.1/Include;%(AdditionalIncludeDirectories) - 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) - true + 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) EnableFastChecks MultiThreadedDebug @@ -105,7 +140,33 @@ Disabled ../../mDNSWindows/SystemService;../../mDNSWindows;../../mDNSShared;../../mDNSCore;$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;C:/Program Files/Microsoft SDKs/Windows/v6.1/Include;%(AdditionalIncludeDirectories) - 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) + 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) + EnableFastChecks + MultiThreadedDebug + + + Level3 + ProgramDatabase + + + ../../mDNSWindows;%(AdditionalIncludeDirectories) + + + /NXCOMPAT /DYNAMICBASE %(AdditionalOptions) + ws2_32.lib;iphlpapi.lib;crypt32.lib;netapi32.lib;powrprof.lib;%(AdditionalDependencies) + true + Console + RequireAdministrator + + + mDNSNetMonitor.manifest;%(AdditionalManifestFiles) + + + + + Disabled + ../../mDNSWindows/SystemService;../../mDNSWindows;../../mDNSShared;../../mDNSCore;$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;C:/Program Files/Microsoft SDKs/Windows/v6.1/Include;%(AdditionalIncludeDirectories) + 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) EnableFastChecks MultiThreadedDebug @@ -130,7 +191,7 @@ ../../mDNSWindows/SystemService;../../mDNSWindows;../../mDNSShared;../../mDNSCore;$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;C:/Program Files/Microsoft SDKs/Windows/v6.1/Include;%(AdditionalIncludeDirectories) - 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) + 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) MultiThreaded @@ -164,7 +225,40 @@ xcopy /I/Y "$(TargetPath)" ../../mDNSWindows/SystemService;../../mDNSWindows;../../mDNSShared;../../mDNSCore;$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;C:/Program Files/Microsoft SDKs/Windows/v6.1/Include;%(AdditionalIncludeDirectories) - 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) + 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) + MultiThreaded + + + Level3 + ProgramDatabase + + + ../../mDNSWindows;%(AdditionalIncludeDirectories) + + + /NXCOMPAT /DYNAMICBASE %(AdditionalOptions) + ws2_32.lib;iphlpapi.lib;crypt32.lib;netapi32.lib;powrprof.lib;%(AdditionalDependencies) + true + Console + true + true + RequireAdministrator + + + mDNSNetMonitor.manifest;%(AdditionalManifestFiles) + + + if not "%RC_XBS%" == "YES" goto END +if not exist "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)" mkdir "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)" +xcopy /I/Y "$(TargetPath)" "$(DSTROOT)\Program Files\Bonjour SDK\bin\$(Platform)" +:END + + + + + + ../../mDNSWindows/SystemService;../../mDNSWindows;../../mDNSShared;../../mDNSCore;$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;C:/Program Files/Microsoft SDKs/Windows/v6.1/Include;%(AdditionalIncludeDirectories) + 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) MultiThreaded @@ -195,8 +289,6 @@ xcopy /I/Y "$(TargetPath)" - - diff --git a/Clients/mDNSNetMonitor.VisualStudio/mDNSNetMonitor.vcxproj.filters b/Clients/mDNSNetMonitor.VisualStudio/mDNSNetMonitor.vcxproj.filters old mode 100755 new mode 100644 index b9a25be..1a95a4d --- a/Clients/mDNSNetMonitor.VisualStudio/mDNSNetMonitor.vcxproj.filters +++ b/Clients/mDNSNetMonitor.VisualStudio/mDNSNetMonitor.vcxproj.filters @@ -51,12 +51,6 @@ Source Files - - Source Files - - - Source Files - diff --git a/Clients/srputil/srputil-entitlements.plist b/Clients/srputil/srputil-entitlements.plist new file mode 100644 index 0000000..bdfa30d --- /dev/null +++ b/Clients/srputil/srputil-entitlements.plist @@ -0,0 +1,12 @@ + + + + + com.apple.srp-mdns-proxy.proxy + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/Clients/srputil/srputil.c b/Clients/srputil/srputil.c new file mode 100644 index 0000000..c59fb33 --- /dev/null +++ b/Clients/srputil/srputil.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 = ""; + 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: diff --git a/DSO/dso-transport.c b/DSO/dso-transport.c index ab0be62..562905b 100644 --- a/DSO/dso-transport.c +++ b/DSO/dso-transport.c @@ -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]; diff --git a/DSO/dso.c b/DSO/dso.c index eeef345..871d1d5 100644 --- a/DSO/dso.c +++ b/DSO/dso.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -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; } diff --git a/DSO/dso.h b/DSO/dso.h index 13ad08a..fc2b4e1 100644 --- 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); diff --git a/Makefile b/Makefile index ad4b4ca..69c06bd 100644 --- 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 index 0000000..1e7adf9 --- /dev/null +++ b/Platforms/ADK/Thread/Makefile @@ -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 index 0000000..2059195 --- /dev/null +++ b/Platforms/ADK/Thread/adk-mem-parse.py @@ -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)) diff --git a/ServiceRegistration/Makefile b/ServiceRegistration/Makefile index 34ba931..957377d 100644 --- a/ServiceRegistration/Makefile +++ b/ServiceRegistration/Makefile @@ -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 index 0000000..344def7 --- /dev/null +++ b/ServiceRegistration/config-parse.c @@ -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 // For printf() +#include // For malloc() +#include // For strrchr(), strcmp() +#include // For "struct tm" etc. +#include // For SIGINT, SIGTERM +#include +#include // For gethostbyname() +#include // For AF_INET, AF_INET6, etc. +#include // For IF_NAMESIZE +#include // For INADDR_NONE +#include // For SOL_TCP, TCP_NOTSENT_LOWAT +#include // For inet_addr() +#include +#include +#include +#include + +#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 index 0000000..69161df --- /dev/null +++ b/ServiceRegistration/config-parse.h @@ -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 diff --git a/ServiceRegistration/dns-msg.h b/ServiceRegistration/dns-msg.h index 93d5d96..6c3bc82 100644 --- a/ServiceRegistration/dns-msg.h +++ b/ServiceRegistration/dns-msg.h @@ -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. @@ -21,16 +21,22 @@ #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: diff --git a/ServiceRegistration/dnssd-proxy.c b/ServiceRegistration/dnssd-proxy.c index 589d1dd..4804878 100644 --- a/ServiceRegistration/dnssd-proxy.c +++ b/ServiceRegistration/dnssd-proxy.c @@ -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: * @@ -34,83 +34,165 @@ #include #include #include -#include +#include #include #include #include -#include #include #include #include +#include +#include +#include +#include #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 + { "nopush", 3, 3, interface_handler }, // nopush + { "udp-port", 2, 2, port_handler }, // udp-port + { "tcp-port", 2, 2, port_handler }, // tcp-port + { "tls-port", 2, 2, port_handler }, // tls-port + { "my-name", 2, 2, my_name_handler }, // my-name + { "tls-key", 2, 2, tls_key_handler }, // tls-key + { "tls-cert", 2, 2, tls_cert_handler }, // tls-cert + { "tls-cacert", 2, 2, tls_cacert_handler }, // tls-cacert + { "listen-addr", 2, 2, listen_addr_handler }, // listen-addr + { "publish-addr", 2, 2, publish_addr_handler } // publish-addr +}; +#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 index 0000000..b47f346 --- /dev/null +++ b/ServiceRegistration/dnssd-relay.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + { "nopush", 3, 3, interface_handler }, // nopush + { "udp-port", 2, 2, port_handler }, // udp-port + { "tcp-port", 2, 2, port_handler }, // tcp-port + { "tls-port", 2, 2, port_handler }, // tls-port + { "tls-key", 2, 2, tls_key_handler }, // tls-key + { "tls-cert", 2, 2, tls_cert_handler }, // tls-cert + { "tls-cacert", 2, 2, tls_cacert_handler }, // tls-cacert + { "listen-addr", 2, 2, listen_addr_handler }, // listen-addr +}; +#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: diff --git a/ServiceRegistration/fromwire.c b/ServiceRegistration/fromwire.c index 0152536..ba9e9df 100644 --- a/ServiceRegistration/fromwire.c +++ b/ServiceRegistration/fromwire.c @@ -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 #include #include -#include +#include #include #include #include @@ -39,6 +39,44 @@ 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 %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 %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, ":", rr->type); + snprintf(outbuf, sizeof(outbuf), ":", rr->type); + ADVANCE(obp, outbuf, sizeof(outbuf)); if (rr->data.unparsed.len == 0) { - fputs(" ", outfile); + snprintf(obp, avail, " "); + 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 index 0000000..949261d --- /dev/null +++ b/ServiceRegistration/hmac-macos.c @@ -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 +#include +#include +#include +#include +#include +#include + +#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 index 0000000..6f35d43 --- /dev/null +++ b/ServiceRegistration/hmac-mbedtls.c @@ -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 +#include +#include +#include +#include +#include +#include + +#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 index 0000000..76176f6 --- /dev/null +++ b/ServiceRegistration/hmac-openssl.c @@ -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 +#include +#include +#include +#include +#include +#include + +#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: diff --git a/ServiceRegistration/ioloop.c b/ServiceRegistration/ioloop.c index 3218e7b..9fccb18 100644 --- a/ServiceRegistration/ioloop.c +++ b/ServiceRegistration/ioloop.c @@ -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. @@ -18,29 +18,40 @@ */ #define __APPLE_USE_RFC_3542 +#define _GNU_SOURCE #include #include #include #include #include -#include +#include #include #include #include +#ifdef USE_KQUEUE #include +#endif +#include #include #include +#include +#include +#include + +#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 diff --git a/ServiceRegistration/ioloop.h b/ServiceRegistration/ioloop.h index 9d73d5f..448773d 100644 --- a/ServiceRegistration/ioloop.h +++ b/ServiceRegistration/ioloop.h @@ -1,4 +1,4 @@ -/* ioloop.c +/* ioloop.h * * Copyright (c) 2018-2019 Apple Computer, Inc. All rights reserved. * @@ -20,6 +20,17 @@ #ifndef __IOLOOP_H #define __IOLOOP_H +#ifdef IOLOOP_MACOS +#include +#include +#include +#include +#endif + +#include +#include +#include + #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 diff --git a/ServiceRegistration/keydump.c b/ServiceRegistration/keydump.c index eb39d6e..5fbc5a9 100644 --- a/ServiceRegistration/keydump.c +++ b/ServiceRegistration/keydump.c @@ -25,17 +25,18 @@ #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 index 0000000..79a25ed --- /dev/null +++ b/ServiceRegistration/log_srp.m @@ -0,0 +1,155 @@ +// +// log_srp.m +// log_srp +// + +#import +#import +#import +#import +#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 (ULA) or (LUA) or (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(@"", [(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(@"", (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(@"", 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(@"", [(NSObject *)value description])); + + data = (NSData *)value; + // NULL pointer is allowed. + require_action_quiet(data.bytes != nil, exit, a_str = AStr(@"")); + + // The passing data must have valid length. + require_action(data.length > 0 && data.length <= kDomainNameLengthMax, exit, + a_str = AStrWithFormat(@"", (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(@"", [(NSObject *)value description])); + + data = (NSData *)value; +#define MACAddressLen 6 + require_action(data.bytes != nil && data.length == MACAddressLen, exit, + a_str = AStrWithFormat(@"", (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(@"")); + + 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 index 0000000..27ca76d --- /dev/null +++ b/ServiceRegistration/macos-ioloop.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 : ""); + } + 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 : "", 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 : "", 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 : "", 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 : "", 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 #%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 #%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 #%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 #%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 index 0000000..2c2bd2f --- /dev/null +++ b/ServiceRegistration/posix.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#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 index 0000000..22a5bfd --- /dev/null +++ b/ServiceRegistration/ra-tester/ra-tester.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 --h "); + 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 index 0000000..dda17f5 --- /dev/null +++ b/ServiceRegistration/route.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef IOLOOP_MACOS +#include + +#include +#include +#include +#include +#include +#include + +#include +#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 (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, "", 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<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 : "", 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 index 0000000..84d2931 --- /dev/null +++ b/ServiceRegistration/route.h @@ -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 +#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 index 0000000..3c1ee30 --- /dev/null +++ b/ServiceRegistration/sign-macos.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#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: diff --git a/ServiceRegistration/sign-mbedtls.c b/ServiceRegistration/sign-mbedtls.c index 2455d99..99d6613 100644 --- a/ServiceRegistration/sign-mbedtls.c +++ b/ServiceRegistration/sign-mbedtls.c @@ -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. @@ -18,29 +18,48 @@ * * 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 +#ifdef THREAD_DEVKIT_ADK +#include +#include "HAPPlatformRandomNumber.h" +#else #include +#ifdef LINUX_GETENTROPY +#define _GNU_SOURCE +#include +#include +#else +#include +#endif // LINUX_GETENTROPY +#endif // THREAD_DEVKIT_ADK #include #include #include #include -#include -#include +#include #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 index 0000000..cee1ea9 --- /dev/null +++ b/ServiceRegistration/srp-api.h @@ -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 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 index 0000000..3e1bdcd --- /dev/null +++ b/ServiceRegistration/srp-client-entitlements.plist @@ -0,0 +1,12 @@ + + + + + application-identifier + com.apple.srp-client + com.apple.security.network.client + + com.apple.srp-mdns-proxy.proxy + + + diff --git a/ServiceRegistration/srp-client.c b/ServiceRegistration/srp-client.c new file mode 100644 index 0000000..917d574 --- /dev/null +++ b/ServiceRegistration/srp-client.c @@ -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 +#include +#include +#include +#include +#include +#ifdef THREAD_DEVKIT +#include "../mDNSShared/dns_sd.h" +#else +#include +#include +#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 : ""); + 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 = ¤t_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 : "", 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 : "", 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 ; 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 = + 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 = + 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 = + 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 = + // RDATA = + 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: diff --git a/ServiceRegistration/srp-crypto.h b/ServiceRegistration/srp-crypto.h index 0065dd9..70d8e8e 100644 --- a/ServiceRegistration/srp-crypto.h +++ b/ServiceRegistration/srp-crypto.h @@ -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. @@ -22,12 +22,72 @@ #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 +#include +// #include +#include +#include +#include +#ifndef OPEN_SOURCE +#include +#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 #include +#include #include #include #include @@ -35,53 +95,78 @@ typedef struct srp_key srp_key_t; #include #include +#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 index 0000000..e1b1490 --- /dev/null +++ b/ServiceRegistration/srp-dns-proxy.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 -k -t ... -u ...", 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(" is an IPv4 address or IPv6 address."); + ERROR(" is a UDP port number."); + ERROR(" is a file containing an HMAC-SHA256 key for authenticating updates to the auth server."); + ERROR(" is an IP address followed by a slash followed by the prefix width."); + ERROR(" 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 and a PTR exists and is 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: diff --git a/ServiceRegistration/srp-gw.c b/ServiceRegistration/srp-gw.c index add3086..9ec3733 100644 --- a/ServiceRegistration/srp-gw.c +++ b/ServiceRegistration/srp-gw.c @@ -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 @@ -46,11 +46,10 @@ #include #include #include -#include +#include #include #include #include -#include #include #include @@ -59,429 +58,947 @@ #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 -t ... -u ...", progname); + ERROR("usage: %s -s -k -t ... -u ...", 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(" is an IPv4 address or IPv6 address."); ERROR(" is a UDP port number."); + ERROR(" is a file containing an HMAC-SHA256 key for authenticating updates to the auth server."); ERROR(" is an IP address followed by a slash followed by the prefix width."); ERROR(" 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 and a PTR exists and is 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 index 0000000..5b6f950 --- /dev/null +++ b/ServiceRegistration/srp-gw.h @@ -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 index 0000000..70c2ce4 --- /dev/null +++ b/ServiceRegistration/srp-ioloop.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#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 " %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 ] [--client-count ] [--server
%%]\n" +#ifdef LIVE_TRANSACTION_CLEANUP + " [--live-transaction-cleanup ]\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 index 0000000..c3de424 --- /dev/null +++ b/ServiceRegistration/srp-mdns-proxy.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#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 : "", 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 ] [--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 index 0000000..c4a7d96 --- /dev/null +++ b/ServiceRegistration/srp-mdns-proxy.h @@ -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 index 0000000..c698a75 --- /dev/null +++ b/ServiceRegistration/srp-parse.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 0000000..f6ddc2f --- /dev/null +++ b/ServiceRegistration/srp-proxy.h @@ -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 index 3590dcf..0000000 --- a/ServiceRegistration/srp-simple.c +++ /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 -#include -#include -#include - -#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 ; 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 = - 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 = - 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 = - 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 = - // RDATA = - 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 index 0000000..33a98e9 --- /dev/null +++ b/ServiceRegistration/srp-thread.c @@ -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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +#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 ? "" : name, + regtype == NULL ? "" : regtype, domain == 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/mDNSMacOSX/DNSSECSupport.h b/ServiceRegistration/srp-thread.h similarity index 58% rename from mDNSMacOSX/DNSSECSupport.h rename to ServiceRegistration/srp-thread.h index eaaf36e..10b8588 100644 --- a/mDNSMacOSX/DNSSECSupport.h +++ b/ServiceRegistration/srp-thread.h @@ -1,6 +1,6 @@ -/* -*- Mode: C; tab-width: 4 -*- +/* srp-ioloop.c * - * Copyright (c) 2012-2013 Apple Inc. All rights reserved. + * 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. @@ -13,12 +13,20 @@ * 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. */ -#ifndef __DNSSEC_SUPPORT_H -#define __DNSSEC_SUPPORT_H +#include -extern mStatus DNSSECPlatformInit(mDNS *const m); -extern void UpdateTrustAnchors(mDNS *const m); +int srp_thread_init(otInstance* instance); +int srp_thread_shutdown(otInstance* instance); -#endif // __DNSSEC_SUPPORT_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-tls.h b/ServiceRegistration/srp-tls.h new file mode 100644 index 0000000..c7cb902 --- /dev/null +++ b/ServiceRegistration/srp-tls.h @@ -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 +#include +#include + +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: diff --git a/ServiceRegistration/srp.h b/ServiceRegistration/srp.h index de89d82..7b4e2d0 100644 --- a/ServiceRegistration/srp.h +++ b/ServiceRegistration/srp.h @@ -24,20 +24,473 @@ #include #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 + + // Apple device always has OS_LOG support. + #ifdef __APPLE__ + #define OS_LOG_ENABLED 1 + #include + + // 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 "" 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, 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, "", sizeof("") < sizeof(BUF_NAME) ? sizeof("") : 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, 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, "", sizeof("") < sizeof(BUF_NAME) ? sizeof("") : 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 index 0000000..aec1238 --- /dev/null +++ b/ServiceRegistration/tls-mbedtls.c @@ -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 +#include +#include +#include +#include +#include + +#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: diff --git a/ServiceRegistration/towire.c b/ServiceRegistration/towire.c index 1d82ef2..d1cb1cb 100644 --- a/ServiceRegistration/towire.c +++ b/ServiceRegistration/towire.c @@ -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. @@ -28,9 +28,12 @@ #include #include #include -#include -#include +#include +#ifndef THREAD_DEVKIT_ADK #include +#endif +#include + #include "srp.h" #include "dns-msg.h" #include "srp-crypto.h" @@ -39,61 +42,125 @@ #include #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 index 0000000..1b0fac6 --- /dev/null +++ b/ServiceRegistration/verify-macos.c @@ -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 +#include +#include +#include +#include +#include + +#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: diff --git a/ServiceRegistration/verify-mbedtls.c b/ServiceRegistration/verify-mbedtls.c index 58205d9..ed0634f 100644 --- a/ServiceRegistration/verify-mbedtls.c +++ b/ServiceRegistration/verify-mbedtls.c @@ -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 index 0000000..8a3e54b --- /dev/null +++ b/ServiceRegistration/wireutils.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#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 index aeca551..0000000 --- a/mDNSCore/CryptoAlg.c +++ /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 index c21507e..0000000 --- a/mDNSCore/CryptoAlg.h +++ /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 diff --git a/mDNSCore/DNSCommon.c b/mDNSCore/DNSCommon.c index 1e70883..f24495e 100644 --- a/mDNSCore/DNSCommon.c +++ b/mDNSCore/DNSCommon.c @@ -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. @@ -15,10 +15,13 @@ * 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<>", 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 . + + 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 diff --git a/mDNSCore/DNSCommon.h b/mDNSCore/DNSCommon.h index 1e422d7..48de85f 100644 --- a/mDNSCore/DNSCommon.h +++ b/mDNSCore/DNSCommon.h @@ -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 - diff --git a/mDNSCore/dnsproxy.c b/mDNSCore/dnsproxy.c index 120d2fc..58dafed 100644 --- a/mDNSCore/dnsproxy.c +++ b/mDNSCore/dnsproxy.c @@ -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. @@ -17,9 +17,18 @@ #include "dnsproxy.h" +#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64) +#include +#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 . + 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 . + 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 . + 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 diff --git a/mDNSCore/dnsproxy.h b/mDNSCore/dnsproxy.h index 6889e73..dcb7d55 100644 --- a/mDNSCore/dnsproxy.h +++ b/mDNSCore/dnsproxy.h @@ -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 index 0766582..0000000 --- a/mDNSCore/dnssec.c +++ /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 : -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> - 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 index c34ad67..0000000 --- a/mDNSCore/dnssec.h +++ /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 diff --git a/mDNSCore/mDNS.c b/mDNSCore/mDNS.c old mode 100755 new mode 100644 index 2fbea04..c32c915 --- a/mDNSCore/mDNS.c +++ b/mDNSCore/mDNS.c @@ -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. @@ -25,15 +25,21 @@ #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 +#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. + // 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. - // 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 . + 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. - // 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. + // 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 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; iDupSuppress[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 diff --git a/mDNSCore/mDNSDebug.h b/mDNSCore/mDNSDebug.h index be0a4c1..25e684d 100755 --- a/mDNSCore/mDNSDebug.h +++ b/mDNSCore/mDNSDebug.h @@ -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 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 '> 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 diff --git a/mDNSCore/mDNSEmbeddedAPI.h b/mDNSCore/mDNSEmbeddedAPI.h index 78d957b..27deefd 100755 --- a/mDNSCore/mDNSEmbeddedAPI.h +++ b/mDNSCore/mDNSEmbeddedAPI.h @@ -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 @@ -89,6 +89,10 @@ #include #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 @@ -99,6 +103,10 @@ #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 + // 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 index 8c782b5..0000000 --- a/mDNSCore/nsec.c +++ /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 index 198f57d..0000000 --- a/mDNSCore/nsec.h +++ /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 index 4bacd52..0000000 --- a/mDNSCore/nsec3.c +++ /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 index be32b80..0000000 --- a/mDNSCore/nsec3.h +++ /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 diff --git a/mDNSCore/uDNS.c b/mDNSCore/uDNS.c index 7a4d9e6..98d8ea0 100755 --- a/mDNSCore/uDNS.c +++ b/mDNSCore/uDNS.c @@ -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. @@ -29,6 +29,14 @@ #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) diff --git a/mDNSCore/uDNS.h b/mDNSCore/uDNS.h index ee83393..5561599 100755 --- a/mDNSCore/uDNS.h +++ b/mDNSCore/uDNS.h @@ -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. 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 diff --git a/mDNSMacOSX/ApplePlatformFeatures.h b/mDNSMacOSX/ApplePlatformFeatures.h index a4586af..e65e5a1 100644 --- a/mDNSMacOSX/ApplePlatformFeatures.h +++ b/mDNSMacOSX/ApplePlatformFeatures.h @@ -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. @@ -50,6 +50,14 @@ #define MDNSRESPONDER_SUPPORTS_APPLE_D2D 1 #endif +// Feature: DNS64 support for DNS proxy +// Radar: +// Enabled: Yes. + +#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_DNS_PROXY_DNS64) + #define MDNSRESPONDER_SUPPORTS_APPLE_DNS_PROXY_DNS64 1 +#endif + // Feature: DNS64 IPv6 synthesis. // Radar: // Enabled: Yes, but only for iOS and macOS, which support the DNS proxy network extension. @@ -134,6 +142,14 @@ #define MDNSRESPONDER_SUPPORTS_APPLE_PREALLOCATED_CACHE 0 #endif +// Feature: Use mdns_querier objects for DNS transports. +// Radar: +// Enabled: Yes. + +#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_QUERIER) + #define MDNSRESPONDER_SUPPORTS_APPLE_QUERIER 1 +#endif + // Feature: Randomized AWDL Hostname // Radar: // Enabled: Yes. @@ -159,22 +175,6 @@ #define MDNSRESPONDER_SUPPORTS_APPLE_SLOW_ACTIVATION 0 #endif -// Feature: Suspicious Reply Defense -// Radar: -// Enabled: Yes. - -#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_SUSPICIOUS_REPLY_DEFENSE) - #define MDNSRESPONDER_SUPPORTS_APPLE_SUSPICIOUS_REPLY_DEFENSE 1 -#endif - -// Feature: Symptoms Reporting -// Radar: -// 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: // Enabled: Yes. @@ -203,4 +203,95 @@ #endif #endif +// Feature: Support for Analytics For Cache +// Radar: +// 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: +// 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: +// 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: +// 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: +// 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: +// 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 index 5ed3dee..0000000 --- a/mDNSMacOSX/CryptoSupport.c +++ /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 // For Hash algorithms SHA1 etc. -#include // For Base32/Base64 encoding/decoding -#include // dispatch_data_create_with_transform -#include "CryptoAlg.h" -#include "CryptoSupport.h" -#include "dnssec.h" -#include "DNSSECSupport.h" - -#if TARGET_OS_IPHONE -#include // For RSA_SHA1 etc. verification -#else -#include -#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/D2D.c b/mDNSMacOSX/D2D.c index 4194d6f..4070f2e 100644 --- a/mDNSMacOSX/D2D.c +++ b/mDNSMacOSX/D2D.c @@ -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); } } diff --git a/mDNSMacOSX/D2D.h b/mDNSMacOSX/D2D.h index be008fd..11cb207 100644 --- a/mDNSMacOSX/D2D.h +++ b/mDNSMacOSX/D2D.h @@ -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. @@ -22,22 +22,22 @@ #include "dnssd_ipc.h" #include -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); diff --git a/mDNSMacOSX/DNS64.c b/mDNSMacOSX/DNS64.c index 3aa54c8..d6bf895 100644 --- a/mDNSMacOSX/DNS64.c +++ b/mDNSMacOSX/DNS64.c @@ -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 . - - 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); diff --git a/mDNSMacOSX/DNS64.h b/mDNSMacOSX/DNS64.h index dc07dcc..2e21613 100644 --- a/mDNSMacOSX/DNS64.h +++ b/mDNSMacOSX/DNS64.h @@ -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/CryptoSupport.h b/mDNSMacOSX/DNSHeuristics.h similarity index 65% rename from mDNSMacOSX/CryptoSupport.h rename to mDNSMacOSX/DNSHeuristics.h index 8625ba7..4ef3013 100644 --- a/mDNSMacOSX/CryptoSupport.h +++ b/mDNSMacOSX/DNSHeuristics.h @@ -1,6 +1,5 @@ -/* -*- Mode: C; tab-width: 4 -*- - * - * Copyright (c) 2011 Apple Inc. All rights reserved. +/* + * 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. @@ -15,9 +14,13 @@ * limitations under the License. */ -#ifndef __CRYPTO_SUPPORT_H -#define __CRYPTO_SUPPORT_H +#ifndef __DNSHeuristics_h +#define __DNSHeuristics_h + +#import + +void dns_heuristics_report_resolution_failure(NSURL *url, bool is_timeout); -extern mStatus DNSSECCryptoInit(mDNS *const m); +void dns_heuristics_report_resolution_success(void); -#endif // __CRYPTO_SUPPORT_H +#endif // __DNSHeuristics_h diff --git a/mDNSMacOSX/DNSHeuristics.m b/mDNSMacOSX/DNSHeuristics.m new file mode 100644 index 0000000..449d29d --- /dev/null +++ b/mDNSMacOSX/DNSHeuristics.m @@ -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 + +#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 +#import +#import +#import +#import +#import +#import +#import + +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 index 0000000..91b6196 --- /dev/null +++ b/mDNSMacOSX/DNSHeuristicsInternal.h @@ -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 + +#if (TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST) +#import +#import +#import +#import +#import +#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 */ diff --git a/mDNSMacOSX/DNSProxySupport.c b/mDNSMacOSX/DNSProxySupport.c index 5e9f005..1b66863 100644 --- a/mDNSMacOSX/DNSProxySupport.c +++ b/mDNSMacOSX/DNSProxySupport.c @@ -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 index acaff4e..0000000 --- a/mDNSMacOSX/DNSSECSupport.c +++ /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 // For Hash algorithms SHA1 etc. - -// Following are needed for fetching the root trust anchor dynamically -#include -#include -#include -#include -#include - -// 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 -// -// has two children: and -// has four children -// -// 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/FeatureFlags/mDNSResponder.plist b/mDNSMacOSX/FeatureFlags/mDNSResponder.plist new file mode 100644 index 0000000..1d7c9e1 --- /dev/null +++ b/mDNSMacOSX/FeatureFlags/mDNSResponder.plist @@ -0,0 +1,17 @@ + + + + + bonjour_privacy + + Enabled + + DisplayName + Require Entitlements For Bonjour + Description + Restrict bonjour browsing without proper entitlments + Radar + 59374417 + + + diff --git a/mDNSMacOSX/HTTPUtilities.h b/mDNSMacOSX/HTTPUtilities.h new file mode 100644 index 0000000..b024c8e --- /dev/null +++ b/mDNSMacOSX/HTTPUtilities.h @@ -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 +#include + +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 index 0000000..ec86d8a --- /dev/null +++ b/mDNSMacOSX/HTTPUtilities.m @@ -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 + +// _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 +#import +#import + +#import "mdns_symptoms.h" +#import "DNSMessage.h" +#import "HTTPUtilities.h" +#import +#import "DNSHeuristics.h" +#import +#import + +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 index 0000000..83ab7ee --- /dev/null +++ b/mDNSMacOSX/LoggingProfiles/AppleInternal/com.apple.mdns.plist @@ -0,0 +1,19 @@ + + + + + DEFAULT-OPTIONS + + Level + + Persist + Info + + + resolver + + Enable-Oversize-Messages + + + + 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 index 0000000..2deb6cb --- /dev/null +++ b/mDNSMacOSX/LoggingProfiles/AppleInternal/com.apple.srp-mdns-proxy.plist @@ -0,0 +1,14 @@ + + + + + DEFAULT-OPTIONS + + Level + + Persist + Debug + + + + diff --git a/mDNSMacOSX/LoggingProfiles/com.apple.mDNSResponder.plist b/mDNSMacOSX/LoggingProfiles/com.apple.mDNSResponder.plist index 1eba185..680d65e 100644 --- a/mDNSMacOSX/LoggingProfiles/com.apple.mDNSResponder.plist +++ b/mDNSMacOSX/LoggingProfiles/com.apple.mDNSResponder.plist @@ -1,5 +1,5 @@ - + DEFAULT-OPTIONS @@ -12,5 +12,10 @@ Inherit + Default + + Enable-Oversize-Messages + + diff --git a/mDNSMacOSX/LoggingProfiles/com.apple.srp-mdns-proxy.plist b/mDNSMacOSX/LoggingProfiles/com.apple.srp-mdns-proxy.plist new file mode 100644 index 0000000..1eba185 --- /dev/null +++ b/mDNSMacOSX/LoggingProfiles/com.apple.srp-mdns-proxy.plist @@ -0,0 +1,16 @@ + + + + + DEFAULT-OPTIONS + + Level + + Persist + Inherit + Enable + Inherit + + + + diff --git a/mDNSMacOSX/Private/advertising_proxy_services.c b/mDNSMacOSX/Private/advertising_proxy_services.c new file mode 100644 index 0000000..4a21fa8 --- /dev/null +++ b/mDNSMacOSX/Private/advertising_proxy_services.c @@ -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 +#include +#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 index 0000000..9fdd77f --- /dev/null +++ b/mDNSMacOSX/Private/advertising_proxy_services.h @@ -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 +#include + +#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 index 0000000..3493c16 --- /dev/null +++ b/mDNSMacOSX/Private/cti-services.c @@ -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 +#include +#include +#include +#include +#include +#include +#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 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 index 0000000..f632e6f --- /dev/null +++ b/mDNSMacOSX/Private/cti-services.h @@ -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 +#include +#include +#include +#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: diff --git a/mDNSMacOSX/Private/dns_services.c b/mDNSMacOSX/Private/dns_services.c index 69f2f02..e7a15a5 100644 --- a/mDNSMacOSX/Private/dns_services.c +++ b/mDNSMacOSX/Private/dns_services.c @@ -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 #include #include +#include #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); +} diff --git a/mDNSMacOSX/Private/dns_services.h b/mDNSMacOSX/Private/dns_services.h index 4c3544f..975cd59 100644 --- a/mDNSMacOSX/Private/dns_services.h +++ b/mDNSMacOSX/Private/dns_services.h @@ -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 index 0000000..00c8da0 --- /dev/null +++ b/mDNSMacOSX/QuerierSupport.c @@ -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 +#include +#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 index 0000000..6c0c7ae --- /dev/null +++ b/mDNSMacOSX/QuerierSupport.h @@ -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__ diff --git a/mDNSMacOSX/Scripts/bonjour-mcast-diagnose b/mDNSMacOSX/Scripts/bonjour-mcast-diagnose index af22676..eefffab 100755 --- a/mDNSMacOSX/Scripts/bonjour-mcast-diagnose +++ b/mDNSMacOSX/Scripts/bonjour-mcast-diagnose @@ -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." diff --git a/mDNSMacOSX/SymptomReporter.c b/mDNSMacOSX/SymptomReporter.c index 9cb08e5..7cd8a3e 100644 --- a/mDNSMacOSX/SymptomReporter.c +++ b/mDNSMacOSX/SymptomReporter.c @@ -16,6 +16,7 @@ #include "SymptomReporter.h" +#if MDNSRESPONDER_SUPPORTS(APPLE, SYMPTOMS) && !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER) #include #include #include @@ -175,3 +176,4 @@ mDNSexport mStatus SymptomReporterDNSServerUnreachable(DNSServer *s) exit: return err; } +#endif // MDNSRESPONDER_SUPPORTS(APPLE, SYMPTOMS) diff --git a/mDNSMacOSX/SymptomReporter.h b/mDNSMacOSX/SymptomReporter.h index 5cde328..75c3bdc 100644 --- a/mDNSMacOSX/SymptomReporter.h +++ b/mDNSMacOSX/SymptomReporter.h @@ -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 diff --git a/mDNSMacOSX/Tests/Unit Tests/CNameRecordTest.m b/mDNSMacOSX/Tests/Unit Tests/CNameRecordTest.m index f96d4ad..e5b5dc4 100644 --- a/mDNSMacOSX/Tests/Unit Tests/CNameRecordTest.m +++ b/mDNSMacOSX/Tests/Unit Tests/CNameRecordTest.m @@ -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); diff --git a/mDNSMacOSX/Tests/Unit Tests/CacheOrderTest.m b/mDNSMacOSX/Tests/Unit Tests/CacheOrderTest.m index facd79b..50ddaa0 100644 --- a/mDNSMacOSX/Tests/Unit Tests/CacheOrderTest.m +++ b/mDNSMacOSX/Tests/Unit Tests/CacheOrderTest.m @@ -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 index 0000000..8af2071 --- /dev/null +++ b/mDNSMacOSX/Tests/Unit Tests/DNSHeuristicsTest.m @@ -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 +#import + +@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 index 0000000..f1bd475 --- /dev/null +++ b/mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Crypto/CanonicalMethodsTest.m @@ -0,0 +1,79 @@ +// +// ValidationMethodsTest.m +// Tests +// +// Copyright (c) 2020 Apple Inc. All rights reserved. +// + +#import +#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 index 0000000..93dfdae --- /dev/null +++ b/mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Crypto/DigestCalculationTest.m @@ -0,0 +1,146 @@ +// +// DigestCalculationTest.m +// Tests +// +// Created by Joey Deng on 1/28/20. +// + +#import +#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 index 0000000..09b58a4 --- /dev/null +++ b/mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Crypto/NSEC3HashTest.m @@ -0,0 +1,137 @@ +// +// NSEC3HashTest.m +// Tests +// +// Created by Joey Deng on 1/29/20. +// + +#import +#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 index 0000000..1b35019 --- /dev/null +++ b/mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Utility/BaseNEncodingDecodingTest.m @@ -0,0 +1,88 @@ +// +// BaseNEncodingDecodingTest.m +// Tests +// +// Copyright (c) 2020 Apple Inc. All rights reserved. +// + +#import +#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 index 0000000..7454aa7 --- /dev/null +++ b/mDNSMacOSX/Tests/Unit Tests/DNSSEC Unit Tests/Utility/ListTMethodsTest.m @@ -0,0 +1,210 @@ +// +// ListTMethodsTest.m +// Tests +// +// Copyright (c) 2020 Apple Inc. All rights reserved. +// + +#import +#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) diff --git a/mDNSMacOSX/Tests/Unit Tests/HelperFunctionTest.m b/mDNSMacOSX/Tests/Unit Tests/HelperFunctionTest.m index 4497a9d..5b0a437 100644 --- a/mDNSMacOSX/Tests/Unit Tests/HelperFunctionTest.m +++ b/mDNSMacOSX/Tests/Unit Tests/HelperFunctionTest.m @@ -7,6 +7,7 @@ #import #include "unittest_common.h" +#include "helper.h" @interface HelperFunctionTest : XCTestCase @@ -55,4 +56,11 @@ } } +- (void)testHelperRequestBPF +{ + fprintf(stdout, "Start %s\n", __FUNCTION__); + mDNSRequestBPF(); + fprintf(stdout, "Completed %s\n", __FUNCTION__); +} + @end diff --git a/mDNSMacOSX/Tests/Unit Tests/LocalOnlyTimeoutTest.m b/mDNSMacOSX/Tests/Unit Tests/LocalOnlyTimeoutTest.m index 9e0e952..05fc201 100644 --- a/mDNSMacOSX/Tests/Unit Tests/LocalOnlyTimeoutTest.m +++ b/mDNSMacOSX/Tests/Unit Tests/LocalOnlyTimeoutTest.m @@ -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); diff --git a/mDNSMacOSX/Tests/Unit Tests/LocalOnlyWithInterfacesTest.m b/mDNSMacOSX/Tests/Unit Tests/LocalOnlyWithInterfacesTest.m index 07656c4..c115f9b 100644 --- a/mDNSMacOSX/Tests/Unit Tests/LocalOnlyWithInterfacesTest.m +++ b/mDNSMacOSX/Tests/Unit Tests/LocalOnlyWithInterfacesTest.m @@ -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); diff --git a/mDNSMacOSX/Tests/Unit Tests/PathEvaluationTest.m b/mDNSMacOSX/Tests/Unit Tests/PathEvaluationTest.m index 0371086..1b4cc9f 100644 --- a/mDNSMacOSX/Tests/Unit Tests/PathEvaluationTest.m +++ b/mDNSMacOSX/Tests/Unit Tests/PathEvaluationTest.m @@ -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]; @@ -73,10 +72,10 @@ [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 index 534bbb8..0000000 --- a/mDNSMacOSX/Tests/Unit Tests/SuspiciousReplyTest.m +++ /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 - -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, ¬Used); - 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, ¬Used); - 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 diff --git a/mDNSMacOSX/Tests/mDNSResponder.plist b/mDNSMacOSX/Tests/mDNSResponder.plist index 79dc174..4a282d1 100644 --- a/mDNSMacOSX/Tests/mDNSResponder.plist +++ b/mDNSMacOSX/Tests/mDNSResponder.plist @@ -1,5 +1,5 @@ - + Project @@ -78,6 +78,94 @@ mDNSResponder + + TestName + DNS Server Retry + Description + 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. + AsRoot + + RequiresWiFi + + Timeout + 900 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + rcodes + --format + json + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + Suspicious UDP Reply Defense + Description + Tests mDNSResponder'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. + AsRoot + + RequiresWiFi + + Timeout + 90 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + gaiperf + --suite + basic + --timeLimit + 250 + --format + json + --skipPathEval + --badUDPMode + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + TestName mDNS Discovery 1-1-1 @@ -752,9 +840,9 @@ TestName - mDNS Discovery w/Packet Drops 10 + mDNS Discovery w/Packet Drops 10 (IPv4) Description - 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. + 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. AsRoot RequiresWiFi @@ -780,7 +868,7 @@ 2 --countAAAA 2 - --ipv6 + --ipv4 --udrop 0.5 --mdrop @@ -813,9 +901,9 @@ TestName - mDNS Discovery w/Packet Drops 100 + mDNS Discovery w/Packet Drops 10 (IPv6) Description - 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. + 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. AsRoot RequiresWiFi @@ -832,11 +920,11 @@ --interface lo0 --instanceCount - 100 + 10 --txtSize 100 --browseTime - 18 + 16 --countA 2 --countAAAA @@ -874,26 +962,44 @@ TestName - DotLocal Queries + mDNS Discovery w/Packet Drops 100 (IPv4) Description - Tests DNS and mDNS queries for domain names in the local domain. + 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. AsRoot - + RequiresWiFi Timeout - 40 + 30 IgnoreOutput Command /usr/local/bin/dnssdutil test - dotlocal + mdnsdiscovery --interface lo0 + --instanceCount + 100 + --txtSize + 100 + --browseTime + 18 + --countA + 2 + --countAAAA + 2 + --ipv4 + --udrop + 0.5 + --mdrop + 0.5 + --maxDropCount + 3 --format json + --flushCache @@ -917,25 +1023,44 @@ TestName - Service Registration + mDNS Discovery w/Packet Drops 100 (IPv6) Description - Tests Bonjour service registration. + 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. AsRoot - - RequiresWiFi + RequiresWiFi + Timeout - 120 + 30 IgnoreOutput Command /usr/local/bin/dnssdutil test - registration + mdnsdiscovery + --interface + lo0 + --instanceCount + 100 + --txtSize + 100 + --browseTime + 18 + --countA + 2 + --countAAAA + 2 + --ipv6 + --udrop + 0.5 + --mdrop + 0.5 + --maxDropCount + 3 --format json - --bats + --flushCache @@ -959,24 +1084,40 @@ TestName - KeepAlive Record Registration + mDNS Discovery 1-1-1 (New GAI) Description - Tests KeepAlive record registrations. + Tests mDNS discovery and resolution of one service instance with one one-byte TXT record, one A record, and one AAAA record. AsRoot - + RequiresWiFi Timeout - 60 + 10 IgnoreOutput Command /usr/local/bin/dnssdutil test - keepalive + mdnsdiscovery + --interface + lo0 + --instanceCount + 1 + --txtSize + 1 + --browseTime + 3 + --countA + 1 + --countAAAA + 1 + --ipv4 + --ipv6 --format json + --flushCache + --useNewGAI @@ -1000,26 +1141,41 @@ TestName - Probe Conflicts + mDNS Discovery 1-1-1 (No Additionals, New GAI) Description - Tests various probe conflict scenarios, some of which are expected to result in service instance and record renames. + 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. AsRoot - + RequiresWiFi Timeout - 300 + 10 IgnoreOutput Command /usr/local/bin/dnssdutil test - probeconflicts + mdnsdiscovery --interface lo0 + --instanceCount + 1 + --txtSize + 1 + --browseTime + 3 + --countA + 1 + --countAAAA + 1 + --ipv4 + --ipv6 --format json + --noAdditionals + --flushCache + --useNewGAI @@ -1043,30 +1199,40 @@ TestName - TCP Fallback + mDNS Discovery 10-100-2 (New GAI) Description - Tests mDNSResponder's TCP fallback mechanism, which is triggered by UDP responses with invalid message IDs that would otherwise be acceptable. + Tests mDNS discovery and resolution of ten service instances with one 100-byte TXT record, two A records, and two AAAA records. AsRoot RequiresWiFi Timeout - 90 + 10 IgnoreOutput Command /usr/local/bin/dnssdutil test - gaiperf - --suite - basic - --timeLimit - 250 + mdnsdiscovery + --interface + lo0 + --instanceCount + 10 + --txtSize + 100 + --browseTime + 3 + --countA + 2 + --countAAAA + 2 + --ipv4 + --ipv6 --format json - --skipPathEval - --badUDPMode + --flushCache + --useNewGAI @@ -1090,24 +1256,41 @@ TestName - State Dump + mDNS Discovery 10-100-2 (No Additionals, New GAI) Description - 1. Tests whether the state dump can be triggered correctly, and whether the file (or stdout'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. + 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. AsRoot RequiresWiFi - + Timeout - 60 + 10 IgnoreOutput - + Command /usr/local/bin/dnssdutil - browseAll - && - /bin/sh - /AppleInternal/Tests/mDNSResponder/bats_test_state_dump.sh + test + mdnsdiscovery + --interface + lo0 + --instanceCount + 10 + --txtSize + 100 + --browseTime + 3 + --countA + 2 + --countAAAA + 2 + --ipv4 + --ipv6 + --format + json + --noAdditionals + --flushCache + --useNewGAI @@ -1131,21 +1314,40 @@ TestName - DNS Proxy + mDNS Discovery 100-500-2 (New GAI) Description - 1. Tests the DNS Proxy by doing a DNS UDP query. 2. Tests the DNS proxy by doing a DNS TCP query. + Tests mDNS discovery and resolution of 100 service instances with one 500-byte TXT record, two A records, and two AAAA records. AsRoot RequiresWiFi - + Timeout - 60 + 10 IgnoreOutput - + Command - /bin/sh - /AppleInternal/Tests/mDNSResponder/bats_test_proxy.sh + /usr/local/bin/dnssdutil + test + mdnsdiscovery + --interface + lo0 + --instanceCount + 100 + --txtSize + 500 + --browseTime + 5 + --countA + 2 + --countAAAA + 2 + --ipv4 + --ipv6 + --format + json + --flushCache + --useNewGAI @@ -1169,27 +1371,41 @@ TestName - Expensive/Constrained Interface + mDNS Discovery 100-500-2 (No Additionals, New GAI) Description - 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. + 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. AsRoot RequiresWiFi Timeout - 1200 + 10 IgnoreOutput - + Command /usr/local/bin/dnssdutil test - expensive_constrained_updates + mdnsdiscovery + --interface + lo0 + --instanceCount + 100 + --txtSize + 500 + --browseTime + 5 + --countA + 2 + --countAAAA + 2 + --ipv4 + --ipv6 + --noAdditionals + --format + json + --flushCache + --useNewGAI @@ -1213,24 +1429,96 @@ TestName - Fix Verification #1 + mDNS Discovery 1-1-1 (No Cache Flush, New GAI) Description - Fix Verification #1 + 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. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + mdnsdiscovery + --interface + lo0 + --instanceCount + 1 + --txtSize + 1 + --browseTime + 3 + --countA + 1 + --countAAAA + 1 + --ipv4 + --ipv6 + --format + json + --useNewGAI + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. AsRoot RequiresWiFi Timeout - 45 + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + mDNS Discovery 1-1-1 (No Cache Flush, No Additionals, New GAI) + Description + 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. + AsRoot + + RequiresWiFi + + Timeout + 10 IgnoreOutput Command /usr/local/bin/dnssdutil - verifyFix - earlyAWDL + test + mdnsdiscovery + --interface + lo0 + --instanceCount + 1 + --txtSize + 1 + --browseTime + 3 + --countA + 1 + --countAAAA + 1 + --ipv4 + --ipv6 --format json + --noAdditionals + --useNewGAI @@ -1254,51 +1542,1280 @@ TestName - XCTests + mDNS Discovery 10-100-2 (No Cache Flush, New GAI) Description - mDNSResponder XCTests - WorkingDirectory - /AppleInternal/XCTests/com.apple.mDNSResponder/ + 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. AsRoot RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + mdnsdiscovery + --interface + lo0 + --instanceCount + 10 + --txtSize + 100 + --browseTime + 3 + --countA + 2 + --countAAAA + 2 + --ipv4 + --ipv6 + --format + json + --useNewGAI + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + RequiresWiFi + Timeout - 20 - ShowSubtestResults + 10 + IgnoreOutput Command - BATS_XCTEST_CMD - -NSTreatUnknownArgumentsAsOpen - NO - -ApplePersistenceIgnoreState - YES - -XCTest - Self - Tests.xctest + /usr/bin/leaks + mDNSResponder TestName - PathEvaluationTest + mDNS Discovery 10-100-2 (No Cache Flush, No Additionals, New GAI) Description - PathEvaluationTest from Tests.xctest + 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. AsRoot RequiresWiFi Timeout - 5 + 10 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + mdnsdiscovery + --interface + lo0 + --instanceCount + 10 + --txtSize + 100 + --browseTime + 3 + --countA + 2 + --countAAAA + 2 + --ipv4 + --ipv6 + --format + json + --noAdditionals + --useNewGAI + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + mDNS Discovery 100-500-2 (No Cache Flush, New GAI) + Description + 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. + AsRoot + RequiresWiFi + + Timeout + 10 + IgnoreOutput + Command /usr/local/bin/dnssdutil test - xctest - -c - PathEvaluationTest + mdnsdiscovery + --interface + lo0 + --instanceCount + 100 + --txtSize + 500 + --browseTime + 5 + --countA + 2 + --countAAAA + 2 + --ipv4 + --ipv6 + --format + json + --useNewGAI + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + mDNS Discovery 100-500-2 (No Cache Flush, No Additionals, New GAI) + Description + 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. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + mdnsdiscovery + --interface + lo0 + --instanceCount + 100 + --txtSize + 500 + --browseTime + 5 + --countA + 2 + --countAAAA + 2 + --ipv4 + --ipv6 + --noAdditionals + --format + json + --useNewGAI + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + mDNS Discovery w/Packet Drops 10 (IPv4, New GAI) + Description + 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. + AsRoot + + RequiresWiFi + + Timeout + 30 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + mdnsdiscovery + --interface + lo0 + --instanceCount + 10 + --txtSize + 100 + --browseTime + 16 + --countA + 2 + --countAAAA + 2 + --ipv4 + --udrop + 0.5 + --mdrop + 0.5 + --maxDropCount + 3 + --format + json + --flushCache + --useNewGAI + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + mDNS Discovery w/Packet Drops 10 (IPv6, New GAI) + Description + 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. + AsRoot + + RequiresWiFi + + Timeout + 30 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + mdnsdiscovery + --interface + lo0 + --instanceCount + 10 + --txtSize + 100 + --browseTime + 16 + --countA + 2 + --countAAAA + 2 + --ipv6 + --udrop + 0.5 + --mdrop + 0.5 + --maxDropCount + 3 + --format + json + --flushCache + --useNewGAI + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + mDNS Discovery w/Packet Drops 100 (IPv4, New GAI) + Description + 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. + AsRoot + + RequiresWiFi + + Timeout + 30 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + mdnsdiscovery + --interface + lo0 + --instanceCount + 100 + --txtSize + 100 + --browseTime + 18 + --countA + 2 + --countAAAA + 2 + --ipv4 + --udrop + 0.5 + --mdrop + 0.5 + --maxDropCount + 3 + --format + json + --flushCache + --useNewGAI + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + mDNS Discovery w/Packet Drops 100 (IPv6, New GAI) + Description + 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. + AsRoot + + RequiresWiFi + + Timeout + 30 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + mdnsdiscovery + --interface + lo0 + --instanceCount + 100 + --txtSize + 100 + --browseTime + 18 + --countA + 2 + --countAAAA + 2 + --ipv6 + --udrop + 0.5 + --mdrop + 0.5 + --maxDropCount + 3 + --format + json + --flushCache + --useNewGAI + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + DotLocal Queries + Description + Tests DNS and mDNS queries for domain names in the local domain. + AsRoot + + RequiresWiFi + + Timeout + 40 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + dotlocal + --interface + lo0 + --format + json + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + Service Registration + Description + Tests Bonjour service registration. + AsRoot + + RequiresWiFi + + Timeout + 120 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + registration + --format + json + --bats + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + KeepAlive Record Registration + Description + Tests KeepAlive record registrations. + AsRoot + + RequiresWiFi + + Timeout + 60 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + keepalive + --format + json + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + Probe Conflicts (IPv4) + Description + Tests various probe conflict scenarios, some of which are expected to result in service instance and record renames. The probe conflicts occur via IPv4. + AsRoot + + RequiresWiFi + + Timeout + 300 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + probeconflicts + --interface + lo0 + --ipv4 + --format + json + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + Probe Conflicts (IPv6) + Description + Tests various probe conflict scenarios, some of which are expected to result in service instance and record renames. The probe conflicts occur via IPv6. + AsRoot + + RequiresWiFi + + Timeout + 300 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + probeconflicts + --interface + lo0 + --ipv6 + --format + json + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + Expensive/Constrained Interface + Description + 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. + AsRoot + + RequiresWiFi + + Timeout + 1200 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + expensive_constrained_updates + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + DNS Proxy + Description + Tests mDNSResponder'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'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. + AsRoot + + RequiresWiFi + + Timeout + 600 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + dnsproxy + --format + json + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + State Dump + Description + 1. Tests whether the state dump can be triggered correctly, and whether the file (or stdout'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. + AsRoot + + RequiresWiFi + + Timeout + 60 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + browseAll + && + /bin/sh + /AppleInternal/Tests/mDNSResponder/bats_test_state_dump.sh + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + Fix Verification #1 + Description + Fix Verification #1 + AsRoot + + RequiresWiFi + + Timeout + 45 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + verifyFix + earlyAWDL + --format + json + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + Multiple Stub Connections + Description + Verifies any issues with multiple simultanious connections from a client + AsRoot + + RequiresWiFi + + Timeout + 60 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + multiconnect + --connections + 100 + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + DNSSEC Test - Basic Validation + Description + Verifies if mDNSResponder could handle the basic DNSSEC validation + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + dnssec + -n + "basic validation" + + + + TestName + mDNSResponder Leaks + Description + Checks mDNSResponder for memory leaks. + AsRoot + + RequiresWiFi + + Timeout + 10 + IgnoreOutput + + Command + + /usr/bin/leaks + mDNSResponder + + + + TestName + Paragon (Daemon Score Card) + Description + Gathers performance metrics and performs daemon-related checks after a simple kickstart of mDNSResponder daemon + AsRoot + + RequiresWiFi + + Timeout + 60 + IgnoreOutput + + Command + + /usr/local/bin/perfcheck + daemon + -p + mDNSResponder + --perfdata + /tmp/scorecard-mDNSResponder.pdj + --xpc-trace + -s + 2 + -c + launchctl + kickstart + -kp + system/com.apple.mDNSResponder.reloaded + 2> + /tmp/perf_mDNSResponder + + + + TestName + LocalOnlyATimeoutTest + Description + LocalOnlyATimeoutTest from Tests.xctest + AsRoot + + RequiresWiFi + + Timeout + 5 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + xctest + -c + LocalOnlyATimeoutTest + + + + TestName + CNameRecordTest + Description + CNameRecordTest from Tests.xctest + AsRoot + + RequiresWiFi + + Timeout + 5 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + xctest + -c + CNameRecordTest + + + + TestName + mDNSCoreReceiveTest + Description + mDNSCoreReceiveTest from Tests.xctest + AsRoot + + RequiresWiFi + + Timeout + 5 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + xctest + -c + mDNSCoreReceiveTest + + + + TestName + ResourceRecordTest + Description + ResourceRecordTest from Tests.xctest + AsRoot + + RequiresWiFi + + Timeout + 5 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + xctest + -c + ResourceRecordTest + + + + TestName + DNSMessageTest + Description + DNSMessageTest from Tests.xctest + AsRoot + + RequiresWiFi + + Timeout + 5 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + xctest + -c + DNSMessageTest + + + + TestName + HelperFunctionTest + Description + HelperFunctionTest from Tests.xctest + AsRoot + + RequiresWiFi + + Timeout + 5 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + xctest + -c + HelperFunctionTest + + + + TestName + CacheOrderTest + Description + CacheOrderTest from Tests.xctest + AsRoot + + RequiresWiFi + + Timeout + 5 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + xctest + -c + CacheOrderTest + + + + TestName + LocalOnlyWithInterfacesTest + Description + LocalOnlyWithInterfacesTest from Tests.xctest + AsRoot + + RequiresWiFi + + Timeout + 5 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + xctest + -c + LocalOnlyWithInterfacesTest + + + + TestName + PathEvaluationTest + Description + PathEvaluationTest from Tests.xctest + AsRoot + + RequiresWiFi + + Timeout + 5 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + xctest + -c + PathEvaluationTest + + + + TestName + ListTMethodsTest + Description + ListTMethodsTest from Tests.xctest + AsRoot + + RequiresWiFi + + Timeout + 5 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + xctest + -c + ListTMethodsTest + + + + TestName + BaseNEncodingDecodingTest + Description + BaseNEncodingDecodingTest from Tests.xctest + AsRoot + + RequiresWiFi + + Timeout + 5 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + xctest + -c + BaseNEncodingDecodingTest + + + + TestName + CanonicalMethodsTest + Description + CanonicalMethodsTest from Tests.xctest + AsRoot + + RequiresWiFi + + Timeout + 5 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + xctest + -c + CanonicalMethodsTest + + + + TestName + DigestCalculationTest + Description + DigestCalculationTest from Tests.xctest + AsRoot + + RequiresWiFi + + Timeout + 5 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + xctest + -c + DigestCalculationTest + + + + TestName + NSEC3HashTest + Description + NSEC3HashTest from Tests.xctest + AsRoot + + RequiresWiFi + + Timeout + 5 + IgnoreOutput + + Command + + /usr/local/bin/dnssdutil + test + xctest + -c + NSEC3HashTest diff --git a/mDNSMacOSX/Tests/mDNSResponderTests-Entitlements.plist b/mDNSMacOSX/Tests/mDNSResponderTests-Entitlements.plist new file mode 100644 index 0000000..330560c --- /dev/null +++ b/mDNSMacOSX/Tests/mDNSResponderTests-Entitlements.plist @@ -0,0 +1,8 @@ + + + + + com.apple.wifi.manager-access + + + diff --git a/mDNSMacOSX/com.apple.srp-mdns-proxy.plist b/mDNSMacOSX/com.apple.srp-mdns-proxy.plist new file mode 100644 index 0000000..21b4a65 --- /dev/null +++ b/mDNSMacOSX/com.apple.srp-mdns-proxy.plist @@ -0,0 +1,38 @@ + + + + + Label + com.apple.srp-mdns-proxy + + InitGroups + + + GroupName + _mdnsresponder + + ProgramArguments + + /usr/libexec/srp-mdns-proxy + + + MachServices + + com.apple.srp-mdns-proxy.proxy + + + + POSIXSpawnType + Interactive + + EnablePressuredExit + + + LimitLoadToHardware + + features.fillmore + + + + + diff --git a/mDNSMacOSX/command_line_client_entitlements/dns-sd-entitlements.plist b/mDNSMacOSX/command_line_client_entitlements/dns-sd-entitlements.plist index 3823528..5b9ca70 100644 --- a/mDNSMacOSX/command_line_client_entitlements/dns-sd-entitlements.plist +++ b/mDNSMacOSX/command_line_client_entitlements/dns-sd-entitlements.plist @@ -4,6 +4,9 @@ com.apple.mDNSResponder.log_utility + com.apple.developer.networking.multicast.BYPASS + + com.apple.developer.on-demand-install-capable.BYPASS + - diff --git a/mDNSMacOSX/daemon.c b/mDNSMacOSX/daemon.c index 53dcead..b5754f1 100644 --- a/mDNSMacOSX/daemon.c +++ b/mDNSMacOSX/daemon.c @@ -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. @@ -53,6 +53,11 @@ #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 : ""); + free(desc); + return false; + }); + } + } +#else LogToFD(fd, "--------- DNS Servers(%d) ----------", CountOfUnicastDNSServers(&mDNSStorage)); if (!mDNSStorage.DNSServers) LogToFD(fd, ""); 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, ""); @@ -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 #include #include -#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 "" -#define DNSSD_REDACTED_ACTUAL_HOSTNAME_STR "" -#define DNSSD_REDACTED_IPv4_ADDRESS_STR "" -#define DNSSD_REDACTED_IPv6_ADDRESS_STR "" +#define DNSSD_REDACTED_HOSTNAME_STR "" +#define DNSSD_REDACTED_IPv4_ADDRESS_STR "" +#define DNSSD_REDACTED_IPv6_ADDRESS_STR "" -#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 : ""); + n = _dnssd_snprintf(&dst, end, "hostname: %s", hostname_str ? hostname_str : ""); 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 : "", - actual_hostname ? actual_hostname : "", addr ? addr : "", - (unsigned long)me->interface_index); + n = _dnssd_snprintf(&dst, end, "hostname: %s, address: %s, type: %s, ifindex: %lu", + hostname ? hostname : "", addr_str ? addr_str : "", + 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 index 0000000..32968bf --- /dev/null +++ b/mDNSMacOSX/dnssd_analytics.c @@ -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 +#include +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 index 0000000..8f40f44 --- /dev/null +++ b/mDNSMacOSX/dnssd_analytics.h @@ -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 index 0000000..cca0126 --- /dev/null +++ b/mDNSMacOSX/dnssd_descriptions.m @@ -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 + +#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 diff --git a/mDNSMacOSX/dnssd_object.h b/mDNSMacOSX/dnssd_object.h index 3a1c73d..816365b 100644 --- a/mDNSMacOSX/dnssd_object.h +++ b/mDNSMacOSX/dnssd_object.h @@ -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__ diff --git a/mDNSMacOSX/dnssd_object.m b/mDNSMacOSX/dnssd_object.m index 104dee3..8aed0c1 100644 --- a/mDNSMacOSX/dnssd_object.m +++ b/mDNSMacOSX/dnssd_object.m @@ -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, @@ -17,13 +17,10 @@ #import "dnssd_object.h" #import -#import #import -#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); diff --git a/mDNSMacOSX/dnssd_private.h b/mDNSMacOSX/dnssd_private.h index cfe87f9..961fdd0 100644 --- a/mDNSMacOSX/dnssd_private.h +++ b/mDNSMacOSX/dnssd_private.h @@ -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 +#include #include #include @@ -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 dnssd_getaddrinfo_set_delegate_uuid(). + * This function is an alternative to dnssd_getaddrinfo_set_delegate_audit_token(). * * 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 kDNSServiceErr_NotAuth error. * * This function is an alternative to dnssd_getaddrinfo_set_delegate_pid(). + * This function is an alternative to dnssd_getaddrinfo_set_delegate_audit_token(). * * 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 kDNSServiceErr_NotAuth error. + * + * This function is an alternative to dnssd_getaddrinfo_set_delegate_pid(). + * This function is an alternative to dnssd_getaddrinfo_set_delegate_uuid(). + * + * 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 dnssd_getaddrinfo_result_type_add 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 true if encrypted queries are required, otherwise, pass false. + * + * @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 + * kDNSServiceFlagsReturnIntermediates flag was set for the getaddrinfo object with + * dnssd_getaddrinfo_set_flags(). + * + * 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 true if the result came from cached DNS records, otherwise, returns + * false. + */ +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 dnssd_cname_array_get_count() 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__ diff --git a/mDNSMacOSX/dnssd_server.c b/mDNSMacOSX/dnssd_server.c index 6db523a..7b124bc 100644 --- a/mDNSMacOSX/dnssd_server.c +++ b/mDNSMacOSX/dnssd_server.c @@ -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. @@ -18,190 +18,265 @@ #include "ClientRequests.h" #include "dnssd_xpc.h" +#include "dnssd_svcb.h" +#include "dnssd_private.h" +#include "mdns_helpers.h" #include "mDNSMacOSX.h" +#include #include #include #include +#include #include +#include +#include #include #include -#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 #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: (" 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) diff --git a/mDNSMacOSX/dnssd_server.h b/mDNSMacOSX/dnssd_server.h index ff61736..75a4ae8 100644 --- a/mDNSMacOSX/dnssd_server.h +++ b/mDNSMacOSX/dnssd_server.h @@ -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, @@ -17,20 +17,23 @@ #ifndef __DNSSD_SERVER_H__ #define __DNSSD_SERVER_H__ -#include "mDNSEmbeddedAPI.h" +#include #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 index 0000000..8f76470 --- /dev/null +++ b/mDNSMacOSX/dnssd_svcb.c @@ -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 + +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 index 0000000..1573c03 --- /dev/null +++ b/mDNSMacOSX/dnssd_svcb.h @@ -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__ diff --git a/mDNSMacOSX/dnssd_xpc.c b/mDNSMacOSX/dnssd_xpc.c index c304c8b..f1b76ad 100644 --- a/mDNSMacOSX/dnssd_xpc.c +++ b/mDNSMacOSX/dnssd_xpc.c @@ -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, @@ -16,10 +16,8 @@ #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) diff --git a/mDNSMacOSX/dnssd_xpc.h b/mDNSMacOSX/dnssd_xpc.h index ed392ae..5447531 100644 --- a/mDNSMacOSX/dnssd_xpc.h +++ b/mDNSMacOSX/dnssd_xpc.h @@ -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 true if encrypted queries are required, otherwise, pass false. + * + * @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 index 0000000..fe80ebc --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2.c @@ -0,0 +1,403 @@ +// +// dnssec_v2.c +// mDNSResponder +// +// Copyright (c) 2020 Apple Inc. All rights reserved. +// + +#include // for require_* macro +#include // 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(¶m->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 index 0000000..7883182 --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2.h @@ -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 +#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 index 0000000..249aac5 --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2_client.c @@ -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 */); + +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 ? ¶meters->peer_audit_token : mDNSNULL, + parameters->has_delegate_audit_token ? ¶meters->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 ? ¶meters->peer_audit_token : mDNSNULL, + parameters->has_delegate_audit_token ? ¶meters->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 */) { + 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 */) { + 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 index 0000000..29aaa10 --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2_client.h @@ -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 +#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 index 0000000..ead61cd --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2_crypto.c @@ -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 +#include +#include +#include +#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, ¶ms.modulus, ¶ms.modulusLength, ¶ms.exponent, ¶ms.exponentLength); + key = SecKeyCreateRSAPublicKey(kCFAllocatorDefault, (const uint8_t *)¶ms, 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 index 0000000..0630edd --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2_crypto.h @@ -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 +#include +#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 index 0000000..f11b80e --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2_embedded.h @@ -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 index 0000000..c2e3a49 --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2_helper.c @@ -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 +#include + +#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 index 0000000..49e2274 --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2_helper.h @@ -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 index 0000000..6148863 --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2_log.h @@ -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 index 0000000..06397d9 --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2_retrieval.c @@ -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 // for strerror +#include // 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(¶meters->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 ? ¶ms->peer_audit_token : mDNSNULL, + params->has_delegate_audit_token ? ¶ms->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 ? ¶ms->peer_audit_token : mDNSNULL, + params->has_delegate_audit_token ? ¶ms->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 ? ¶ms->peer_audit_token : mDNSNULL, + params->has_delegate_audit_token ? ¶ms->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 ? ¶ms->peer_audit_token : mDNSNULL, + params->has_delegate_audit_token ? ¶ms->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 + 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 index 0000000..ecf225b --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2_retrieval.h @@ -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 +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 index 0000000..3c13d89 --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2_structs.c @@ -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 // for strerror +#include // 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(¶meters->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 index 0000000..b9915a4 --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2_structs.h @@ -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 ) +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 ) +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 ) +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 ) +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 ) +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 ) +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, 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, the list of dnssec_rr_t structure, each dnssec_rr_t represents one wildcard record. + list_t wildcard_rrsigs; // list_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, 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, 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, the list of dnssec_rr_t structure, each dnssec_rr_t represents one wildcard record. + list_t wildcard_rrsigs; // list_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, 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, 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, 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, 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, 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, the list of dnssec_original_t structures, each dnssec_original_t holds one response that user expects. + list_t rrsig_records; // list_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, 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, 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, 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, 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, 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, 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, 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; +}; + +#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, 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, 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 index 0000000..8453ce0 --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2_trust_anchor.c @@ -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 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 + 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 index 0000000..ffca59e --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2_trust_anchor.h @@ -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 +#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 index 0000000..f917512 --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2_validation.c @@ -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 // for strerror +#include // 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; type: zone_signing_key, list; type: key_signing_key, list; + // type: nsec, list_t; type: nsec3, list_t + 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 + response_type_t response_type); // list_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 + +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 + +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 + const list_t * const _Nonnull rrsig_covering_it, // list_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 + const list_t * const _Nonnull rrsig_covering_it, // list_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 */, + 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 */); + +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 */); + +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 + 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 + 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 + 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 + 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 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 + // 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 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 + // 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 + response_type_t response_type) { // list_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 + + 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 + + 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 + const list_t * const _Nonnull rrsig_covering_it, // list_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 + const list_t * const _Nonnull rrsig_covering_it, // list_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 or list_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_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_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 index 0000000..682f0ff --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/dnssec_v2_validation.h @@ -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 +#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 index 0000000..2ffdd3d --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/utilities/base_encoding/base_n.c @@ -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 +#include +#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 index 0000000..bb568ff --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/utilities/base_encoding/base_n.h @@ -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 +#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 index 0000000..96dc4e5 --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/utilities/list/list.c @@ -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 index 0000000..536a66d --- /dev/null +++ b/mDNSMacOSX/dnssec_v2/utilities/list/list.h @@ -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 +#include +#include // 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 diff --git a/mDNSMacOSX/helper-main.c b/mDNSMacOSX/helper-main.c index 25bb052..57d87df 100644 --- a/mDNSMacOSX/helper-main.c +++ b/mDNSMacOSX/helper-main.c @@ -14,8 +14,6 @@ * limitations under the License. */ -#define _FORTIFY_SOURCE 2 - #include #include #include diff --git a/mDNSMacOSX/helper-stubs.c b/mDNSMacOSX/helper-stubs.c index 4783ee5..fecd36b 100644 --- a/mDNSMacOSX/helper-stubs.c +++ b/mDNSMacOSX/helper-stubs.c @@ -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", diff --git a/mDNSMacOSX/helper.c b/mDNSMacOSX/helper.c index dae2cc4..5c21ffd 100644 --- a/mDNSMacOSX/helper.c +++ b/mDNSMacOSX/helper.c @@ -52,6 +52,8 @@ #include "helper.h" #include "helper-server.h" #include "P2PPacketFilter.h" +#include "setup_assistant_helper.h" +#include #include #include @@ -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(¬ify_count)); + if (buddy_state == buddy_state_in_process && + atomic_load(¬ify_count) == 0) + { + assistant_helper_notify_when_buddy_done(^{ + os_log_info(log_handle, "update_notification: buddy done notification (%u)", atomic_load(¬ify_count)); + update_notification(); + atomic_store(¬ify_count, 0); + }); + } + atomic_fetch_add(¬ify_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. diff --git a/mDNSMacOSX/mDNSMacOSX.c b/mDNSMacOSX/mDNSMacOSX.c index 3236966..b94371c 100644 --- a/mDNSMacOSX/mDNSMacOSX.c +++ b/mDNSMacOSX/mDNSMacOSX.c @@ -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. @@ -32,7 +32,13 @@ #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 +#endif #include #include // For va_list support @@ -85,14 +91,12 @@ #include -#if TARGET_OS_IPHONE +#if (TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST) #include // For WiFiManagerClientRef etc, declarations. #include #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 @@ -105,6 +109,10 @@ #include #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"). // 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; i1 ? "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 -#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 // 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 // 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 diff --git a/mDNSMacOSX/mDNSMacOSX.h b/mDNSMacOSX/mDNSMacOSX.h index b0a3b12..8c55ad5 100644 --- a/mDNSMacOSX/mDNSMacOSX.h +++ b/mDNSMacOSX/mDNSMacOSX.h @@ -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]; }; diff --git a/mDNSMacOSX/mDNSResponder-entitlements.plist b/mDNSMacOSX/mDNSResponder-entitlements.plist index 049f4fc..609f9d2 100644 --- a/mDNSMacOSX/mDNSResponder-entitlements.plist +++ b/mDNSMacOSX/mDNSResponder-entitlements.plist @@ -42,5 +42,11 @@ com.apple.wifip2pd + com.apple.bluetooth.system + + com.apple.private.networkextension.configuration + + com.apple.symptom_analytics.delegate_symptom + diff --git a/mDNSMacOSX/mDNSResponder.sb b/mDNSMacOSX/mDNSResponder.sb index 493e3a2..25921fd 100644 --- a/mDNSMacOSX/mDNSResponder.sb +++ b/mDNSMacOSX/mDNSResponder.sb @@ -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") @@ -68,19 +70,16 @@ (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")) @@ -127,6 +126,7 @@ (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") @@ -183,3 +183,8 @@ ; "/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")) + +; 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/"))) diff --git a/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj b/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj index 1aafbd1..d5b59fe 100644 --- a/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj +++ b/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 03067D6E0C83A39C0022BE1F /* PBXTargetDependency */, B7237FE62194D99200B113B1 /* PBXTargetDependency */, D4528FFE21F91263004D61BF /* PBXTargetDependency */, + BD7BFBFC236FDCB2000456BC /* PBXTargetDependency */, ); name = "Build Core"; productName = "Build Some"; @@ -47,12 +48,27 @@ 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 */, @@ -90,6 +106,7 @@ buildPhases = ( ); dependencies = ( + 89DC990623F72765009598E1 /* PBXTargetDependency */, B7237FDC2194D16900B113B1 /* PBXTargetDependency */, B7DB58C2215F04490054CD46 /* PBXTargetDependency */, B7DB58A0215EB61C0054CD46 /* PBXTargetDependency */, @@ -98,6 +115,17 @@ 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" */; @@ -118,6 +146,9 @@ ); dependencies = ( 2141DCFD123FFB7D0086D23E /* PBXTargetDependency */, + B7C90F602346D1A200AA89F1 /* PBXTargetDependency */, + B7A41B3C245E33B500FA6163 /* PBXTargetDependency */, + B7C90F682346D1B000AA89F1 /* PBXTargetDependency */, ); name = "Build All"; productName = "Build All"; @@ -128,7 +159,6 @@ 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 */; }; @@ -148,14 +178,6 @@ 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 */; }; @@ -167,17 +189,12 @@ 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 */; }; @@ -214,11 +231,58 @@ 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 */; }; @@ -232,10 +296,48 @@ 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, ); }; }; @@ -243,6 +345,10 @@ 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 */; }; @@ -256,13 +362,16 @@ 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 */; }; @@ -287,6 +396,8 @@ 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 */; }; @@ -299,6 +410,15 @@ 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 */; }; @@ -311,79 +431,154 @@ 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 */; }; @@ -426,17 +621,18 @@ 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 */; }; @@ -445,6 +641,13 @@ 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 */; }; @@ -458,7 +661,30 @@ 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 */; }; @@ -466,7 +692,6 @@ 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 */; }; @@ -566,6 +791,48 @@ 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 */; @@ -636,6 +903,27 @@ 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 */; @@ -671,6 +959,20 @@ 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 */; @@ -772,6 +1074,65 @@ 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; @@ -783,6 +1144,17 @@ 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; @@ -805,15 +1177,16 @@ 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 */ = { @@ -864,7 +1237,6 @@ 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; @@ -905,28 +1277,16 @@ 0C6FB90E1D775FE900DF6F51 /* mDNSNetMonitor.8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = mDNSNetMonitor.8; sourceTree = ""; }; 0C7C00491DD553490078BA89 /* unittest_common.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = unittest_common.c; path = ../unittests/unittest_common.c; sourceTree = ""; }; 0C7C004A1DD553490078BA89 /* unittest_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = unittest_common.h; path = ../unittests/unittest_common.h; sourceTree = ""; }; - 21070E5D16486B9000A69507 /* DNSSECSupport.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DNSSECSupport.c; sourceTree = ""; }; - 21070E5E16486B9000A69507 /* DNSSECSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNSSECSupport.h; sourceTree = ""; }; - 2124FA2B1471E98C0021D7BB /* nsec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nsec.h; path = ../mDNSCore/nsec.h; sourceTree = ""; }; - 2124FA2F1471E9B50021D7BB /* dnssec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dnssec.h; path = ../mDNSCore/dnssec.h; sourceTree = ""; }; - 2124FA321471E9DE0021D7BB /* nsec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = nsec.c; path = ../mDNSCore/nsec.c; sourceTree = ""; }; - 2127A47515C3C7B900A857FC /* nsec3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = nsec3.c; path = ../mDNSCore/nsec3.c; sourceTree = ""; }; - 2127A47615C3C7B900A857FC /* nsec3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nsec3.h; path = ../mDNSCore/nsec3.h; sourceTree = ""; }; - 213BDC6C147319F400000896 /* dnssec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dnssec.c; path = ../mDNSCore/dnssec.c; sourceTree = ""; }; 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 = ""; }; 213FB22D12028B53002B3A08 /* BonjourEvents-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "BonjourEvents-Info.plist"; sourceTree = ""; }; 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 = ""; }; + 216D9ACD1720C9F5008066E1 /* uDNSPathEvaluation.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = uDNSPathEvaluation.c; sourceTree = ""; }; 218E8E4F156D8C0300720DA0 /* dnsproxy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dnsproxy.c; path = ../mDNSCore/dnsproxy.c; sourceTree = ""; }; 218E8E50156D8C0300720DA0 /* dnsproxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dnsproxy.h; path = ../mDNSCore/dnsproxy.h; sourceTree = ""; }; 219D5541149ED645004464AE /* libxml2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = /usr/lib/libxml2.dylib; sourceTree = ""; }; - 21A57F4A145B2AE100939099 /* CryptoAlg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = CryptoAlg.c; path = ../mDNSCore/CryptoAlg.c; sourceTree = ""; }; - 21A57F4B145B2AE100939099 /* CryptoAlg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CryptoAlg.h; path = ../mDNSCore/CryptoAlg.h; sourceTree = ""; }; - 21A57F51145B2B1400939099 /* CryptoSupport.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CryptoSupport.c; sourceTree = ""; }; - 21A57F52145B2B1400939099 /* CryptoSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoSupport.h; sourceTree = ""; }; 21DED43415702C0F0060B6B9 /* DNSProxySupport.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DNSProxySupport.c; sourceTree = ""; }; 21F51DBD1B3540DB0070B05C /* com.apple.dnsextd.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.apple.dnsextd.plist; sourceTree = ""; }; 21F51DBE1B3541030070B05C /* com.apple.mDNSResponderHelper.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.apple.mDNSResponderHelper.plist; sourceTree = ""; }; @@ -956,13 +1316,25 @@ 4ADB5F230F6AB9F400B95BF3 /* helper-entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "helper-entitlements.plist"; sourceTree = ""; }; 4BD2B638134FE09F002B96D5 /* P2PPacketFilter.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = P2PPacketFilter.c; sourceTree = ""; }; 4BD2B639134FE09F002B96D5 /* P2PPacketFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = P2PPacketFilter.h; sourceTree = ""; }; + 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 = ""; }; + 5A9E8E5E23F4B99A003B4CAD /* DNSHeuristicsInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DNSHeuristicsInternal.h; sourceTree = ""; }; + 5A9E8E6323F4BE29003B4CAD /* mDNSResponderTests-Entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "mDNSResponderTests-Entitlements.plist"; sourceTree = ""; }; + 5AF23B2823F37309004AB237 /* DNSHeuristics.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DNSHeuristics.m; sourceTree = ""; }; + 5AF23B2A23F37316004AB237 /* DNSHeuristics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DNSHeuristics.h; sourceTree = ""; }; 654BE64F02B63B93000001D1 /* mDNSEmbeddedAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mDNSEmbeddedAPI.h; path = ../mDNSCore/mDNSEmbeddedAPI.h; sourceTree = ""; }; - 654BE65002B63B93000001D1 /* mDNSDebug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mDNSDebug.h; path = ../mDNSCore/mDNSDebug.h; sourceTree = ""; }; + 654BE65002B63B93000001D1 /* mDNSDebug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mDNSDebug.h; path = ../mDNSCore/mDNSDebug.h; sourceTree = ""; usesTabs = 0; }; 65713D46025A293200000109 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = /System/Library/Frameworks/SystemConfiguration.framework; sourceTree = ""; }; 6575FBE9022EAF5A00000109 /* mDNS.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.c; name = mDNS.c; path = ../mDNSCore/mDNS.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; 6575FBEB022EAF7200000109 /* mDNSMacOSX.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = mDNSMacOSX.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; 6575FBEC022EAF7200000109 /* daemon.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = daemon.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 787E8B72239485D200DC9D01 /* HTTPUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HTTPUtilities.h; sourceTree = ""; }; + 787E8B73239485D200DC9D01 /* HTTPUtilities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HTTPUtilities.m; sourceTree = ""; }; 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 = ""; }; + 78BD2BF723FC521B004A3D8B /* dnssd_svcb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dnssd_svcb.c; sourceTree = ""; }; + 78BD2BF823FC521B004A3D8B /* dnssd_svcb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dnssd_svcb.h; sourceTree = ""; }; 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; }; @@ -973,22 +1345,88 @@ 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 = ""; }; 84F4C08F188F04CF00D1E1DE /* dns_services.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dns_services.h; path = Private/dns_services.h; sourceTree = ""; }; + 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 = ""; }; + 891F2F872397EA79001EC153 /* srp-parse.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "srp-parse.c"; sourceTree = ""; }; + 891F2F882397EA79001EC153 /* srp-proxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "srp-proxy.h"; sourceTree = ""; }; + 891F2F892397EA79001EC153 /* srp-mdns-proxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "srp-mdns-proxy.h"; sourceTree = ""; }; + 891F2F8D2397EB30001EC153 /* macos-ioloop.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "macos-ioloop.c"; sourceTree = ""; }; + 891F2F8E2397EB30001EC153 /* sign-macos.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "sign-macos.c"; sourceTree = ""; }; + 891F2F8F2397EB30001EC153 /* towire.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = towire.c; sourceTree = ""; }; + 891F2F902397EB30001EC153 /* ioloop.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ioloop.h; sourceTree = ""; }; + 891F2F912397EB30001EC153 /* verify-macos.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "verify-macos.c"; sourceTree = ""; }; + 891F2F922397EB30001EC153 /* hmac-macos.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "hmac-macos.c"; sourceTree = ""; }; + 891F2F932397EB30001EC153 /* fromwire.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fromwire.c; sourceTree = ""; }; + 891F2F942397EB30001EC153 /* wireutils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = wireutils.c; sourceTree = ""; }; + 891F2F952397EB30001EC153 /* dns-msg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "dns-msg.h"; sourceTree = ""; }; + 891F2FA42397EC47001EC153 /* srp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = srp.h; sourceTree = ""; }; + 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 = ""; }; + 89458DA023A1AA5000C3978D /* srp-ioloop.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "srp-ioloop.c"; sourceTree = ""; }; 894A55D622C438AF008CDEA1 /* bats_test_proxy.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = bats_test_proxy.sh; sourceTree = ""; }; + 89511E4923C5FE3B00D603D4 /* advertising_proxy_services.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = advertising_proxy_services.h; path = ../mDNSMacOSX/Private/advertising_proxy_services.h; sourceTree = ""; }; + 89511E4A23C5FE3B00D603D4 /* advertising_proxy_services.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = advertising_proxy_services.c; path = ../mDNSMacOSX/Private/advertising_proxy_services.c; sourceTree = ""; }; + 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 = ""; }; + 895A278424D465FC00697AB1 /* DebugServices.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = DebugServices.c; path = ../mDNSShared/DebugServices.c; sourceTree = ""; }; + 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 = ""; }; 897729AF2202A5370018FAEB /* dso-transport.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "dso-transport.c"; path = "../DSO/dso-transport.c"; sourceTree = ""; }; 897729B02202A5370018FAEB /* dso-transport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "dso-transport.h"; path = "../DSO/dso-transport.h"; sourceTree = ""; }; 897729B12202A5370018FAEB /* dso.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dso.h; path = ../DSO/dso.h; sourceTree = ""; }; 897729B62202A5480018FAEB /* dnssd_clientshim.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dnssd_clientshim.c; path = ../mDNSShared/dnssd_clientshim.c; sourceTree = ""; }; + 8998717523C6977E00C3AF39 /* srputil.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srputil.c; path = ../Clients/srputil/srputil.c; sourceTree = ""; }; + 8998717623C697BE00C3AF39 /* srputil-entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "srputil-entitlements.plist"; path = "../Clients/srputil/srputil-entitlements.plist"; sourceTree = ""; }; + 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 = ""; }; + 89AFC1EE23F5E09500538084 /* config-parse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "config-parse.h"; sourceTree = ""; }; + 89AFC1EF23F5E09500538084 /* srp-crypto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "srp-crypto.h"; sourceTree = ""; }; + 89AFC1F023F5E09500538084 /* dnssd-proxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "dnssd-proxy.h"; sourceTree = ""; }; + 89AFC1F123F5E09500538084 /* srp-tls.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "srp-tls.h"; sourceTree = ""; }; + 89AFC1F223F5E09500538084 /* srp-gw.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "srp-gw.h"; sourceTree = ""; }; + 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 = ""; }; + 89C38ECA23C93B6600800A42 /* route.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = route.h; sourceTree = ""; }; + 89C38ECB23C93B6600800A42 /* route.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = route.c; sourceTree = ""; }; + 89D3B57D2463363900FA67EB /* com.apple.srp-mdns-proxy.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "com.apple.srp-mdns-proxy.plist"; sourceTree = ""; }; + 89D3B57F2463365B00FA67EB /* com.apple.srp-mdns-proxy.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "com.apple.srp-mdns-proxy.plist"; sourceTree = ""; }; + 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 = ""; }; + 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 = ""; }; B7016F511D5D0D2900107E7C /* Localizable.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = Localizable.strings; path = ../SettingsBundle/Localizable.strings; sourceTree = ""; }; B701E7031D9DD811008F3022 /* BonjourSCStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BonjourSCStore.m; path = ../SettingsBundle/BonjourSCStore.m; sourceTree = ""; }; B716801A1E8330B400459A35 /* entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = entitlements.plist; sourceTree = ""; }; + B72BC06123ABFD8C0021E0C9 /* bundle_utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = bundle_utilities.m; sourceTree = ""; }; + B72BC06223ABFD8C0021E0C9 /* bundle_utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bundle_utilities.h; sourceTree = ""; }; B72C96091D6236A500AD682A /* BonjourSCStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BonjourSCStore.h; path = ../SettingsBundle/BonjourSCStore.h; sourceTree = ""; }; B72D38B31ECB96ED00B10E39 /* Localizable.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; B7325FE61DA4737400663834 /* CNBrowseDomainsController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CNBrowseDomainsController.h; path = ../SettingsBundle/CNBrowseDomainsController.h; sourceTree = ""; }; B7325FE71DA4737400663834 /* CNBrowseDomainsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CNBrowseDomainsController.m; path = ../SettingsBundle/CNBrowseDomainsController.m; sourceTree = ""; }; 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 = ""; }; + B737059222CD65E400477EB9 /* dnssd_analytics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dnssd_analytics.h; sourceTree = ""; }; + B73C5CCD24B78EE70002050A /* setup_assistant_helper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = setup_assistant_helper.h; sourceTree = ""; }; + B73C5CCE24B78EE70002050A /* setup_assistant_helper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = setup_assistant_helper.m; sourceTree = ""; }; + 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 = ""; }; B7473E651EC3954400D31B9D /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -1040,27 +1478,24 @@ B799209D1DA6BA8E00C6E02B /* CNDomainBrowserPathUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CNDomainBrowserPathUtils.h; sourceTree = ""; }; B799209E1DA6BA8E00C6E02B /* CNDomainBrowserPathUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CNDomainBrowserPathUtils.m; sourceTree = ""; }; B79920A31DA6C49700C6E02B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = ../SettingsBundle/Assets.xcassets; sourceTree = ""; }; + 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 = ""; }; B79FA154211CF0AF00B7861E /* uDNS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = uDNS.h; sourceTree = ""; }; - B79FA155211CF0AF00B7861E /* nsec.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nsec.h; sourceTree = ""; }; B79FA156211CF0AF00B7861E /* DNSCommon.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = DNSCommon.c; sourceTree = ""; }; B79FA157211CF0AF00B7861E /* mDNSDebug.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mDNSDebug.h; sourceTree = ""; }; - B79FA158211CF0AF00B7861E /* dnssec.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec.c; sourceTree = ""; }; - B79FA159211CF0AF00B7861E /* CryptoAlg.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CryptoAlg.h; sourceTree = ""; }; - B79FA15A211CF0AF00B7861E /* nsec3.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = nsec3.h; sourceTree = ""; }; B79FA15B211CF0AF00B7861E /* Implementer Notes.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Implementer Notes.txt"; sourceTree = ""; }; - B79FA15C211CF0AF00B7861E /* nsec.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = nsec.c; sourceTree = ""; }; B79FA15D211CF0AF00B7861E /* uDNS.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = uDNS.c; sourceTree = ""; }; B79FA15E211CF0AF00B7861E /* dnsproxy.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnsproxy.c; sourceTree = ""; }; - B79FA15F211CF0AF00B7861E /* dnssec.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec.h; sourceTree = ""; }; - B79FA160211CF0AF00B7861E /* mDNS.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mDNS.c; sourceTree = ""; }; + B79FA160211CF0AF00B7861E /* mDNS.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mDNS.c; sourceTree = ""; usesTabs = 0; }; B79FA162211CF0AF00B7861E /* DNSCommon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DNSCommon.h; sourceTree = ""; }; - B79FA163211CF0AF00B7861E /* CryptoAlg.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = CryptoAlg.c; sourceTree = ""; }; B79FA164211CF0AF00B7861E /* mDNSEmbeddedAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mDNSEmbeddedAPI.h; sourceTree = ""; }; - B79FA165211CF0AF00B7861E /* nsec3.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = nsec3.c; sourceTree = ""; }; B79FA166211CF0AF00B7861E /* DNSDigest.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = DNSDigest.c; sourceTree = ""; }; B7A214081D1B29D6005F7DD9 /* CNDomainBrowserViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CNDomainBrowserViewController.h; sourceTree = ""; }; B7A214091D1B29D6005F7DD9 /* CNDomainBrowserViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CNDomainBrowserViewController.m; sourceTree = ""; }; + B7A5C33623986BBF00615DBD /* mDNSResponder-entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "mDNSResponder-entitlements.plist"; sourceTree = ""; }; + B7A5C33A2399A24C00615DBD /* mDNSResponder.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = mDNSResponder.plist; sourceTree = ""; }; B7A8618821274BFC00E81CC3 /* ResourceRecordTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ResourceRecordTest.m; sourceTree = ""; }; B7A8618A21274FA200E81CC3 /* mDNSCoreReceiveTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = mDNSCoreReceiveTest.m; sourceTree = ""; }; B7A861962127845700E81CC3 /* CNameRecordTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CNameRecordTest.m; sourceTree = ""; }; @@ -1072,6 +1507,9 @@ 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 = ""; }; + B7BAFB4B23A035140045705F /* system_utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = system_utilities.m; sourceTree = ""; }; + 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 = ""; }; B7D566A51E81D6A900E43008 /* BonjourPrefRemoteViewService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "BonjourPrefRemoteViewService-Info.plist"; sourceTree = ""; }; B7D566A71E81D6A900E43008 /* BonjourPrefRemoteViewService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BonjourPrefRemoteViewService.h; sourceTree = ""; }; @@ -1086,10 +1524,17 @@ B7D6CA691D1076C6005E24CF /* CNDomainBrowserView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CNDomainBrowserView.m; sourceTree = ""; }; 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 = ""; }; + B7E8092C23F70EF6002CB309 /* mdns_trust_checks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_trust_checks.h; sourceTree = ""; }; B7F1FEDC234F89C50081159C /* TestUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestUtils.h; sourceTree = ""; }; B7F1FEDD234F89C50081159C /* TestUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestUtils.m; sourceTree = ""; }; B7F1FEEA234FAF0F0081159C /* dnssdutil-entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "dnssdutil-entitlements.plist"; sourceTree = ""; }; + 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 = ""; }; + B7F9AB7A237F4F4300C2BEA2 /* mdns_trust.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdns_trust.h; sourceTree = ""; }; BD03E88C1AD31278005E8A81 /* SymptomReporter.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SymptomReporter.c; sourceTree = ""; }; + BD0FFC6E2502E78900B6DB73 /* mdns_managed_defaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdns_managed_defaults.h; sourceTree = ""; }; + BD0FFC6F2502E78A00B6DB73 /* mdns_managed_defaults.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdns_managed_defaults.c; sourceTree = ""; }; BD11266D21DB1AFE006115E6 /* dnssd_xpc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dnssd_xpc.h; sourceTree = ""; }; BD11266E21DB1AFE006115E6 /* dnssd_xpc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dnssd_xpc.c; sourceTree = ""; }; BD11266F21DB1AFE006115E6 /* dnssd_server.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dnssd_server.c; sourceTree = ""; }; @@ -1098,48 +1543,90 @@ BD11267621DB2A9A006115E6 /* dnssd_object.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = dnssd_object.m; sourceTree = ""; }; BD11267721DB2A9A006115E6 /* dnssd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dnssd.c; sourceTree = ""; }; BD11267B21DB2C7C006115E6 /* dnssd_object.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dnssd_object.h; sourceTree = ""; }; + BD12709124469F1C00BC84D8 /* DNSServerDNSSEC.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DNSServerDNSSEC.c; sourceTree = ""; usesTabs = 1; }; + BD12709224469F1C00BC84D8 /* DNSServerDNSSEC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNSServerDNSSEC.h; sourceTree = ""; usesTabs = 1; }; BD1628CD2168B02600020528 /* ClientRequests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ClientRequests.h; path = ../mDNSShared/ClientRequests.h; sourceTree = ""; }; - BD1628CE2168B02700020528 /* ClientRequests.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ClientRequests.c; path = ../mDNSShared/ClientRequests.c; sourceTree = ""; }; + BD1628CE2168B02700020528 /* ClientRequests.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ClientRequests.c; path = ../mDNSShared/ClientRequests.c; sourceTree = ""; usesTabs = 0; }; + BD1904EC235E759500F146D6 /* mdns_dns_service.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdns_dns_service.h; sourceTree = ""; }; + BD1904ED235E759500F146D6 /* mdns_dns_service.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdns_dns_service.c; sourceTree = ""; }; + BD1970B323F90E94001BA06F /* mdns_powerlog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdns_powerlog.h; sourceTree = ""; }; + BD1970B423F90E94001BA06F /* mdns_powerlog.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdns_powerlog.c; sourceTree = ""; }; BD28AE8E207B88F600F0B257 /* bonjour-mcast-diagnose */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "bonjour-mcast-diagnose"; sourceTree = ""; }; - BD2A15B5225ED2E500BEA50A /* mdns_object.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = mdns_object.m; sourceTree = ""; }; - BD2A15B6225ED2E500BEA50A /* mdns_object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_object.h; sourceTree = ""; }; - BD2A15B7225ED2E500BEA50A /* mdns.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mdns.c; sourceTree = ""; }; - BD2A15B8225ED2E500BEA50A /* mdns_private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_private.h; sourceTree = ""; }; 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 = ""; }; + BD4FC0D624A81643000B0B21 /* mdns_message.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mdns_message.c; sourceTree = ""; }; + BD52398123F3BBCD004AC878 /* mdns_set.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdns_set.c; sourceTree = ""; }; + BD52398223F3BBCD004AC878 /* mdns_set.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdns_set.h; sourceTree = ""; }; + 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 = ""; }; BD691B281ED2F43200E6F317 /* DNS64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DNS64.c; sourceTree = ""; }; BD691B291ED2F43200E6F317 /* DNS64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNS64.h; sourceTree = ""; }; + BD7BFBD9236D5342000456BC /* mdns_symptoms.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdns_symptoms.h; sourceTree = ""; }; + BD7BFBDA236D5342000456BC /* mdns_symptoms.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdns_symptoms.c; sourceTree = ""; }; + 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 = ""; }; 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 = ""; }; + BD97754B221D643500F68FFC /* dnssdutil.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dnssdutil.c; sourceTree = ""; usesTabs = 1; }; BD97754D221D64BF00F68FFC /* DNSMessage.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DNSMessage.c; sourceTree = ""; usesTabs = 1; }; BD97754E221D64BF00F68FFC /* DNSMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNSMessage.h; sourceTree = ""; }; BD98A796213A3EAE0002EC47 /* mDNSResponder.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = mDNSResponder.plist; sourceTree = ""; }; 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 = ""; }; + BDA37F1323471F6100B9266D /* mdns_base.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_base.h; sourceTree = ""; }; + BDA37F1423471F6100B9266D /* mdns_internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_internal.h; sourceTree = ""; }; + BDA37F1523471F6100B9266D /* mdns_resolver.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mdns_resolver.c; sourceTree = ""; }; + BDA37F1623471F6100B9266D /* mdns_address.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mdns_address.c; sourceTree = ""; }; + BDA37F1723471F6100B9266D /* mdns_object.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_object.h; sourceTree = ""; }; + BDA37F1823471F6100B9266D /* mdns_objects.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = mdns_objects.m; sourceTree = ""; }; + BDA37F1923471F6100B9266D /* mdns_helpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_helpers.h; sourceTree = ""; }; + BDA37F1A23471F6100B9266D /* mdns_interface_monitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_interface_monitor.h; sourceTree = ""; }; + BDA37F1B23471F6100B9266D /* mdns_resolver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_resolver.h; sourceTree = ""; }; + BDA37F1C23471F6100B9266D /* mdns_object.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mdns_object.c; sourceTree = ""; }; + BDA37F1D23471F6100B9266D /* mdns_address.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_address.h; sourceTree = ""; }; + BDA37F1E23471F6100B9266D /* mdns_private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_private.h; sourceTree = ""; }; + BDA37F1F23471F6100B9266D /* mdns_helpers.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = mdns_helpers.c; sourceTree = ""; }; + BDA37F2023471F6100B9266D /* mdns_objects.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mdns_objects.h; sourceTree = ""; }; BDA3F0871C48DB6D0054FB4B /* Metrics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Metrics.h; sourceTree = ""; }; BDA3F0881C48DB6D0054FB4B /* Metrics.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Metrics.m; sourceTree = ""; }; 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 = ""; }; + BDA9C35A2370304E00DC86BD /* QuerierSupport.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = QuerierSupport.c; sourceTree = ""; }; + BDA9C35C2370307600DC86BD /* QuerierSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QuerierSupport.h; sourceTree = ""; }; 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 = ""; }; BDB61846206ADDDF00AFF600 /* com.apple.mDNSResponder.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = com.apple.mDNSResponder.plist; sourceTree = ""; }; BDBF9B931ED74B8C001498A8 /* DNS64State.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNS64State.h; sourceTree = ""; }; 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 = ""; }; + 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 = ""; }; BDF2D02D216A25E800D0DBF5 /* ApplePlatformFeatures.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ApplePlatformFeatures.h; sourceTree = ""; }; + BDFE3FFB2408C58B00C77011 /* mdns_tlv.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdns_tlv.c; sourceTree = ""; }; + BDFE3FFC2408C58B00C77011 /* mdns_tlv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdns_tlv.h; sourceTree = ""; }; + 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 = ""; }; + 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 = ""; }; + BDFE400D2408F62E00C77011 /* mdns_xpc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdns_xpc.c; sourceTree = ""; }; + BDFE400E2408F62E00C77011 /* mdns_xpc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdns_xpc.h; sourceTree = ""; }; + 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 = ""; }; 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 = ""; usesTabs = 1; }; + D41DED3523DFC3C3009F9854 /* base_n.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = base_n.c; sourceTree = ""; usesTabs = 1; }; + D41DED3923E0CC40009F9854 /* BaseNEncodingDecodingTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BaseNEncodingDecodingTest.m; sourceTree = ""; }; + D41DED3E23E0F71D009F9854 /* DigestCalculationTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DigestCalculationTest.m; sourceTree = ""; }; + D41DED4223E21D65009F9854 /* NSEC3HashTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NSEC3HashTest.m; sourceTree = ""; }; D421B3DD22178BE700D35C20 /* system_utilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = system_utilities.h; sourceTree = ""; }; - D421B3DE22178BE700D35C20 /* system_utilities.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = system_utilities.c; sourceTree = ""; }; + D43B120E23A442CE00B8D941 /* dnssec_v2_crypto.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_crypto.h; sourceTree = ""; usesTabs = 1; }; + D43B120F23A442CE00B8D941 /* dnssec_v2_crypto.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec_v2_crypto.c; sourceTree = ""; usesTabs = 1; }; + D43B121523BEB1A200B8D941 /* CanonicalMethodsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CanonicalMethodsTest.m; sourceTree = ""; }; D448F7A7222DAA1F0069E1D2 /* bats_test_state_dump.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = bats_test_state_dump.sh; sourceTree = ""; }; - 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 = ""; }; D459F0D3222862BA0056AC5B /* HelperFunctionTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HelperFunctionTest.m; sourceTree = ""; }; D461F9482203A6B400A88910 /* xpc_services.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = xpc_services.h; sourceTree = ""; }; @@ -1147,6 +1634,11 @@ D461F94D2203A8AA00A88910 /* xpc_service_dns_proxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = xpc_service_dns_proxy.h; sourceTree = ""; }; D461F94E2203A8AA00A88910 /* xpc_service_dns_proxy.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = xpc_service_dns_proxy.c; sourceTree = ""; }; 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 = ""; usesTabs = 1; }; + D473A5102396F1F800D0F827 /* dnssec_v2_trust_anchor.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec_v2_trust_anchor.c; sourceTree = ""; usesTabs = 1; }; + D473A5192397176A00D0F827 /* dnssec_v2_client.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_client.h; sourceTree = ""; usesTabs = 1; }; + D473A51A2397176A00D0F827 /* dnssec_v2_client.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec_v2_client.c; sourceTree = ""; usesTabs = 1; }; + D473A51F2398895E00D0F827 /* dnssec_v2_log.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_log.h; sourceTree = ""; usesTabs = 1; }; D49ECA17220BBAC400655887 /* dns-sd-entitlements.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "dns-sd-entitlements.plist"; sourceTree = ""; }; D4BFF8D922B1C52100A0BA86 /* posix_utilities.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = posix_utilities.c; path = ../mDNSPosix/posix_utilities.c; sourceTree = ""; }; D4BFF8DA22B1C52100A0BA86 /* posix_utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = posix_utilities.h; path = ../mDNSPosix/posix_utilities.h; sourceTree = ""; }; @@ -1157,6 +1649,20 @@ 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 = ""; }; D4CFA7D221E7BA8F00F5AD0E /* mDNSFeatures.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mDNSFeatures.h; path = ../mDNSShared/mDNSFeatures.h; sourceTree = ""; }; + D4ED20B72360D16900632A59 /* list.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = list.h; sourceTree = ""; usesTabs = 1; }; + D4ED20B82360D16900632A59 /* list.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = list.c; sourceTree = ""; usesTabs = 1; }; + D4ED20BC236117F700632A59 /* dnssec_v2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2.h; sourceTree = ""; usesTabs = 1; }; + D4ED20BD236117F700632A59 /* dnssec_v2.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec_v2.c; sourceTree = ""; usesTabs = 1; }; + D4ED20CE23749DA900632A59 /* dnssec_v2_retrieval.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_retrieval.h; sourceTree = ""; usesTabs = 1; }; + D4ED20CF23749DA900632A59 /* dnssec_v2_retrieval.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec_v2_retrieval.c; sourceTree = ""; usesTabs = 1; }; + D4ED20D32374A03A00632A59 /* dnssec_v2_embedded.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_embedded.h; sourceTree = ""; usesTabs = 1; }; + D4ED20D52375F99700632A59 /* dnssec_v2_structs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_structs.h; sourceTree = ""; usesTabs = 1; }; + D4ED20D62375F99700632A59 /* dnssec_v2_structs.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec_v2_structs.c; sourceTree = ""; usesTabs = 1; }; + D4ED20DA237CC90200632A59 /* dnssec_v2_validation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_validation.h; sourceTree = ""; usesTabs = 1; }; + D4ED20DB237CC90200632A59 /* dnssec_v2_validation.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec_v2_validation.c; sourceTree = ""; usesTabs = 1; }; + D4ED20DF237DCD7000632A59 /* dnssec_v2_helper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dnssec_v2_helper.h; sourceTree = ""; usesTabs = 1; }; + D4ED20E0237DCD7000632A59 /* dnssec_v2_helper.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = dnssec_v2_helper.c; sourceTree = ""; usesTabs = 1; }; + D4F2AA43239ADFB10073E461 /* ListTMethodsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ListTMethodsTest.m; sourceTree = ""; }; 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; }; @@ -1176,6 +1682,7 @@ 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; }; @@ -1267,8 +1774,51 @@ 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; }; @@ -1283,6 +1833,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 892EF6F1246DD00000DB9EA1 /* AppKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1290,6 +1841,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 892EF6EF246DCE2600DB9EA1 /* SafariServices.framework in Frameworks */, B7A861A1212B3FF400E81CC3 /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1298,7 +1850,12 @@ 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; }; @@ -1306,14 +1863,14 @@ 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; }; @@ -1321,6 +1878,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 892EF6E9246DCD7600DB9EA1 /* CoreServices.framework in Frameworks */, B74F2B461E82FEAE0084960E /* Foundation.framework in Frameworks */, B74F2B471E82FEFE0084960E /* Security.framework in Frameworks */, ); @@ -1343,6 +1901,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + BD7BFBF1236FCF0E000456BC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B74ECA1924134C5F001DDFE8 /* CoreUtils.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; BD9BA74D1EAF90E400658CCF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1353,23 +1919,35 @@ 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; }; @@ -1416,6 +1994,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B74ECA1824134BDC001DDFE8 /* CoreUtils.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1450,6 +2029,9 @@ 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 */, @@ -1473,6 +2055,7 @@ 08FB7795FE84155DC02AAC07 /* mDNS Server Sources */ = { isa = PBXGroup; children = ( + 895A278424D465FC00697AB1 /* DebugServices.c */, 897729B62202A5480018FAEB /* dnssd_clientshim.c */, 897729AF2202A5370018FAEB /* dso-transport.c */, 897729B02202A5370018FAEB /* dso-transport.h */, @@ -1494,10 +2077,6 @@ 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 */, @@ -1519,12 +2098,10 @@ 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 */, @@ -1542,20 +2119,22 @@ 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 */, ); @@ -1647,6 +2226,12 @@ 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 = ""; @@ -1683,10 +2268,79 @@ FF1C919D07021D77001048AB /* dns-sd.1 */, FF1C919F07021E3F001048AB /* dns-sd.c */, FF5852100DD27BD300862BDF /* ClientCommon.c */, + 895E9DAC23C68D7300DA0FE0 /* srputil */, ); name = "Command-Line Clients"; sourceTree = ""; }; + 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 = ""; + }; + 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 = ""; + }; + 895E9DAC23C68D7300DA0FE0 /* srputil */ = { + isa = PBXGroup; + children = ( + 8998717623C697BE00C3AF39 /* srputil-entitlements.plist */, + 8998717523C6977E00C3AF39 /* srputil.c */, + ); + name = srputil; + sourceTree = ""; + }; + 89AFC1E423F5DE2B00538084 /* ra-tester */ = { + isa = PBXGroup; + children = ( + 89AFC1E523F5DE2B00538084 /* ra-tester.c */, + ); + path = "ra-tester"; + sourceTree = ""; + }; B7473E621EC3954400D31B9D /* Bonjour Safari Menu */ = { isa = PBXGroup; children = ( @@ -1741,6 +2395,7 @@ B74BF92F2322E99700E35354 /* Unit Tests */ = { isa = PBXGroup; children = ( + D4F2AA40239ADD940073E461 /* DNSSEC Unit Tests */, B74F16EF2114E49D00BEBE84 /* Info.plist */, B7A86198212B074500E81CC3 /* LocalOnlyTimeoutTest.m */, B7A861962127845700E81CC3 /* CNameRecordTest.m */, @@ -1750,6 +2405,7 @@ D459F0D3222862BA0056AC5B /* HelperFunctionTest.m */, B74BF92D2322E97400E35354 /* SuspiciousReplyTest.m */, B74BF930232701F600E35354 /* CacheOrderTest.m */, + 5A9E8E5C23F47817003B4CAD /* DNSHeuristicsTest.m */, B7E06CDF2329AA7B0021401F /* LocalOnlyWithInterfacesTest.m */, B77CAFCD234EADE5006706B4 /* PathEvaluationTest.m */, ); @@ -1778,6 +2434,7 @@ isa = PBXGroup; children = ( BD98A796213A3EAE0002EC47 /* mDNSResponder.plist */, + 5A9E8E6323F4BE29003B4CAD /* mDNSResponderTests-Entitlements.plist */, D448F7A6222DA98E0069E1D2 /* BATS Scripts */, 37DDE9241BA382280092AC61 /* Unit Test Utilities */, B74BF92F2322E99700E35354 /* Unit Tests */, @@ -1803,22 +2460,14 @@ 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; @@ -1837,6 +2486,14 @@ path = iOS; sourceTree = ""; }; + B7BAFB4723A025390045705F /* FeatureFlags */ = { + isa = PBXGroup; + children = ( + B7A5C33A2399A24C00615DBD /* mDNSResponder.plist */, + ); + path = FeatureFlags; + sourceTree = ""; + }; B7D566A41E81D6A900E43008 /* RemoteViewService */ = { isa = PBXGroup; children = ( @@ -1885,20 +2542,25 @@ 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 = ""; @@ -1916,11 +2578,13 @@ 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; @@ -1929,11 +2593,34 @@ 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 */, @@ -1941,18 +2628,63 @@ BD893CE6206C0EAF0055F9E7 /* CoreFoundation.framework */, BD893CE4206C0D980055F9E7 /* SystemConfiguration.framework */, 789036911F7AC1F90077A962 /* libnetwork.tbd */, + B7BAFB5A23A2A27F0045705F /* Network.framework */, BD9BA7571EAF929C00658CCF /* CoreUtils.framework */, B735A2EE21B8293900025BB0 /* Foundation.framework */, ); name = Frameworks; sourceTree = ""; }; + 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 = ""; + }; 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 = ""; @@ -1960,16 +2692,50 @@ 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 = ""; }; + D41DED3123DFBF3A009F9854 /* base_encoding */ = { + isa = PBXGroup; + children = ( + D41DED3423DFC3C3009F9854 /* base_n.h */, + D41DED3523DFC3C3009F9854 /* base_n.c */, + ); + path = base_encoding; + sourceTree = ""; + }; + D41DED3D23E0F6C2009F9854 /* Crypto */ = { + isa = PBXGroup; + children = ( + D43B121523BEB1A200B8D941 /* CanonicalMethodsTest.m */, + D41DED3E23E0F71D009F9854 /* DigestCalculationTest.m */, + D41DED4223E21D65009F9854 /* NSEC3HashTest.m */, + ); + path = Crypto; + sourceTree = ""; + }; + D41DED4023E0F72A009F9854 /* Utility */ = { + isa = PBXGroup; + children = ( + D4F2AA43239ADFB10073E461 /* ListTMethodsTest.m */, + D41DED3923E0CC40009F9854 /* BaseNEncodingDecodingTest.m */, + ); + path = Utility; + sourceTree = ""; + }; 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 = ""; @@ -1986,6 +2752,7 @@ D461F9472203A59200A88910 /* xpc_services */ = { isa = PBXGroup; children = ( + 89B0BF3723C55A4400245A42 /* xpc_client_advertising_proxy.h */, 848DA5D516547F7200D2E8B4 /* xpc_clients.h */, D461F9482203A6B400A88910 /* xpc_services.h */, D461F9492203A6B400A88910 /* xpc_services.c */, @@ -1999,6 +2766,15 @@ path = xpc_services; sourceTree = ""; }; + D473A51E239842C900D0F827 /* list */ = { + isa = PBXGroup; + children = ( + D4ED20B72360D16900632A59 /* list.h */, + D4ED20B82360D16900632A59 /* list.c */, + ); + path = list; + sourceTree = ""; + }; D49ECA15220BBA2D00655887 /* command_line_client_entitlements */ = { isa = PBXGroup; children = ( @@ -2016,6 +2792,50 @@ name = mDNSPosix; sourceTree = ""; }; + 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 = ""; + }; + D4ED20AC2360CCC800632A59 /* utilities */ = { + isa = PBXGroup; + children = ( + D41DED3123DFBF3A009F9854 /* base_encoding */, + D473A51E239842C900D0F827 /* list */, + ); + path = utilities; + sourceTree = ""; + }; + D4F2AA40239ADD940073E461 /* DNSSEC Unit Tests */ = { + isa = PBXGroup; + children = ( + D41DED4023E0F72A009F9854 /* Utility */, + D41DED3D23E0F6C2009F9854 /* Crypto */, + ); + path = "DNSSEC Unit Tests"; + sourceTree = ""; + }; DB2CC4420662DCE500335AB3 /* Java Support */ = { isa = PBXGroup; children = ( @@ -2071,6 +2891,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + BDEF597D24FDD15B001F8CB5 /* dnssd_clientstub_apple.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2078,6 +2899,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + BDEF597E24FDD15C001F8CB5 /* dnssd_clientstub_apple.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2085,6 +2907,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + BDEF597F24FDD15C001F8CB5 /* dnssd_clientstub_apple.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2094,6 +2917,7 @@ 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 */, ); @@ -2105,12 +2929,37 @@ 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; @@ -2131,56 +2980,107 @@ ); 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; }; @@ -2203,7 +3103,6 @@ buildActionMask = 2147483647; files = ( 2E35529F0C3A9E7600CA1CB7 /* helper.h in Headers */, - FFD52A9E1AF858DD00CAD3EC /* CryptoAlg.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2226,6 +3125,8 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + BDFE40052408D4BD00C77011 /* dnssd_clientstub_apple.h in Headers */, + BDFE40092408D4D800C77011 /* mdns_tlv.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2233,6 +3134,8 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + BDFE40062408D4BE00C77011 /* dnssd_clientstub_apple.h in Headers */, + BDFE400A2408D4D900C77011 /* mdns_tlv.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2242,6 +3145,9 @@ 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; }; @@ -2405,6 +3311,78 @@ 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" */; @@ -2545,6 +3523,23 @@ 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" */; @@ -2562,6 +3557,23 @@ 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" */; @@ -2570,11 +3582,12 @@ 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 */, @@ -2733,7 +3746,6 @@ FFB765800AEED9C700583A2C /* Headers */, FFB765810AEED9C700583A2C /* Sources */, FFB765820AEED9C700583A2C /* Frameworks */, - 21DE714D115831CB00DD4BD1 /* ShellScript */, ); buildRules = ( ); @@ -2758,6 +3770,26 @@ 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; }; @@ -2801,6 +3833,13 @@ B7DB589D215EB61C0054CD46 = { ProvisioningStyle = Automatic; }; + BD7BFBF2236FCF0E000456BC = { + CreatedOnToolsVersion = 11.2; + ProvisioningStyle = Automatic; + }; + BF0E381424882B650028D528 = { + ProvisioningStyle = Automatic; + }; D4CFA7CB21E7B95E00F5AD0E = { CreatedOnToolsVersion = 11.0; ProvisioningStyle = Automatic; @@ -2824,9 +3863,11 @@ 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 */, @@ -2859,6 +3900,12 @@ 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 */ @@ -2912,6 +3959,7 @@ 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 */, ); @@ -2969,19 +4017,6 @@ 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; @@ -3068,7 +4103,6 @@ 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; @@ -3108,6 +4142,7 @@ 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; }; @@ -3119,6 +4154,7 @@ 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; }; @@ -3130,6 +4166,7 @@ 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; }; @@ -3137,6 +4174,7 @@ 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 */, @@ -3147,10 +4185,71 @@ 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; }; @@ -3208,51 +4307,67 @@ 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; }; @@ -3285,26 +4400,63 @@ ); 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 */, @@ -3312,35 +4464,53 @@ 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; }; @@ -3368,7 +4538,6 @@ 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 */, @@ -3409,8 +4578,11 @@ 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; @@ -3420,8 +4592,11 @@ 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; @@ -3431,8 +4606,11 @@ 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; @@ -3485,6 +4663,36 @@ 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 */; @@ -3535,6 +4743,21 @@ 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 */; @@ -3560,6 +4783,16 @@ 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 */; @@ -3632,7 +4865,6 @@ 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; @@ -3678,7 +4910,6 @@ 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; @@ -3726,6 +4957,7 @@ 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; @@ -3743,6 +4975,7 @@ ); 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", @@ -3787,7 +5020,32 @@ "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 = ( @@ -3798,8 +5056,29 @@ 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", @@ -3810,18 +5089,29 @@ ); 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; @@ -3856,12 +5146,16 @@ ); 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; }; @@ -3900,7 +5194,6 @@ ); INSTALL_PATH = /usr/sbin; LIBRARY_SEARCH_PATHS = "\"${CONFIGURATION_TEMP_DIR}\""; - MACOSX_DEPLOYMENT_TARGET = 10.14; OTHER_CFLAGS = "-UAPPLE_OSX_mDNSResponder"; PRODUCT_NAME = dnsextd; }; @@ -3919,7 +5212,6 @@ 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; @@ -4010,6 +5302,7 @@ 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; @@ -4020,7 +5313,6 @@ 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)"; @@ -4039,7 +5331,6 @@ 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; @@ -4065,6 +5356,9 @@ "-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; @@ -4102,6 +5396,9 @@ "-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; @@ -4141,6 +5438,9 @@ "-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; @@ -4156,6 +5456,7 @@ 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; @@ -4188,7 +5489,10 @@ 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; @@ -4310,7 +5614,6 @@ 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; @@ -4354,7 +5657,6 @@ 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"; @@ -4451,12 +5753,16 @@ ); 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; }; @@ -4475,6 +5781,7 @@ 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; @@ -4507,7 +5814,7 @@ 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; @@ -4567,121 +5874,117 @@ }; 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; @@ -4690,207 +5993,288 @@ 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; @@ -4898,351 +6282,376 @@ 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; @@ -5253,20 +6662,17 @@ 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; @@ -5274,23 +6680,23 @@ 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; @@ -5301,20 +6707,17 @@ 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 = ( @@ -5327,200 +6730,1086 @@ 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; @@ -5536,10 +7825,13 @@ 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", @@ -5568,54 +7860,67 @@ }; 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; @@ -5631,10 +7936,14 @@ 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", @@ -5670,6 +7979,7 @@ 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; @@ -5685,6 +7995,7 @@ ); 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", @@ -5715,7 +8026,32 @@ "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 = ( @@ -5726,8 +8062,29 @@ 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", @@ -5738,18 +8095,29 @@ ); 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; @@ -5815,7 +8183,6 @@ ); INSTALL_PATH = /usr/sbin; LIBRARY_SEARCH_PATHS = "\"${CONFIGURATION_TEMP_DIR}\""; - MACOSX_DEPLOYMENT_TARGET = 10.14; OTHER_CFLAGS = "-UAPPLE_OSX_mDNSResponder"; PRODUCT_NAME = dnsextd; }; @@ -5834,7 +8201,6 @@ 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; @@ -5848,7 +8214,6 @@ 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; @@ -5889,12 +8254,12 @@ 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; }; @@ -5905,7 +8270,6 @@ 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; @@ -5951,13 +8315,13 @@ 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; }; @@ -5989,6 +8353,9 @@ "-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; @@ -6028,6 +8395,9 @@ "-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; @@ -6054,6 +8424,7 @@ 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; @@ -6064,7 +8435,6 @@ 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)"; @@ -6083,7 +8453,6 @@ 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; @@ -6109,6 +8478,9 @@ "-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; @@ -6229,6 +8601,51 @@ 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 = ( @@ -6337,6 +8754,24 @@ 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 = ( @@ -6346,6 +8781,15 @@ 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 = ( diff --git a/mDNSMacOSX/mDNSResponder.xcodeproj/xcshareddata/xcschemes/Build All.xcscheme b/mDNSMacOSX/mDNSResponder.xcodeproj/xcshareddata/xcschemes/Build All.xcscheme index 6bc511e..07c3d3c 100644 --- a/mDNSMacOSX/mDNSResponder.xcodeproj/xcshareddata/xcschemes/Build All.xcscheme +++ b/mDNSMacOSX/mDNSResponder.xcodeproj/xcshareddata/xcschemes/Build All.xcscheme @@ -29,8 +29,6 @@ shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + + + + @@ -40,17 +49,6 @@ - - - - - - - - -#import -#import - -//====================================================================================================================== -// 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 index 0000000..5b4840b --- /dev/null +++ b/mDNSMacOSX/mdns_objects/log_mdns.m @@ -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 +#import +#import + +#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.}.*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 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.} 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 index 0000000..e37c586 --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_address.c @@ -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 + +//====================================================================================================================== +// 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., "[]:" or "[]". + 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 = ""; + } 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 = ""; + } 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, ""); + 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 index 0000000..f31f171 --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_address.h @@ -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 + +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 + * . 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 index 0000000..4bbaa50 --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_base.h @@ -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 + +#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 index 0000000..61ad5b0 --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_dns_service.c @@ -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 +#include + +//====================================================================================================================== +// 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 : ""); + 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 "" + +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("", (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("", (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("", (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) ? "" : ""; + } + } else { + str = ""; + } + _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 : ""); + 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 index 0000000..4bc76ff --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_dns_service.h @@ -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 +#include +#include +#include +#include + +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 + * mdns_dns_service_manager_activate(). + */ +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 + * mdns_dns_service_manager_activate()() or + * mdns_dns_service_manager_invalidate(). + * + * The event handler will be invoked on the dispatch queue specified by + * mdns_dns_service_manager_create() with event mdns_event_error when a fatal error + * occurs, with event mdns_event_invalidated when the interface monitor has been invalidated, and + * with mdns_event_update when there are pending DNS service updates. + * + * After an mdns_event_invalidated 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 . + * + * @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 mdns_dns_service_manager_deregister_custom_service(). + */ +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 mdns_dns_service_manager_register_custom_service() 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 + * mdns_event_invalidated 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 mdns_dns_service_manager_apply_dns_config() or + * mdns_dns_service_manager_apply_pending_updates() 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 mdns_dns_service_manager_apply_dns_config() or + * mdns_dns_service_manager_apply_pending_updates() 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 mdns_dns_service_manager_apply_dns_config() or + * mdns_dns_service_manager_apply_pending_updates() 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 + * mdns_dns_service_manager_register_custom_service() 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 mdns_dns_service_manager_apply_dns_config() or + * mdns_dns_service_manager_apply_pending_updates() 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 mdns_event_update 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 mdns_dns_service_manager_apply_dns_config()) 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 index 0000000..79921be --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_helpers.c @@ -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 +#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, "", 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, "", strbuf); + } + default: { + return kTypeErr; + } + } +} diff --git a/mDNSMacOSX/mdns_objects/mdns_helpers.h b/mDNSMacOSX/mdns_objects/mdns_helpers.h new file mode 100644 index 0000000..b39ca6a --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_helpers.h @@ -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 +#include + +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.c b/mDNSMacOSX/mdns_objects/mdns_interface_monitor.c similarity index 64% rename from mDNSMacOSX/mdns.c rename to mDNSMacOSX/mdns_objects/mdns_interface_monitor.c index 504b8d9..57352bc 100644 --- a/mDNSMacOSX/mdns.c +++ b/mDNSMacOSX/mdns_objects/mdns_interface_monitor.c @@ -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. @@ -14,9 +14,10 @@ * limitations under the License. */ -#include "mdns_private.h" - -#include "mdns_object.h" +#include "mdns_internal.h" +#include "mdns_interface_monitor.h" +#include "mdns_helpers.h" +#include "mdns_objects.h" #include #include @@ -25,92 +26,7 @@ #include //====================================================================================================================== -// 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 +// MARK: - Interface Monitor Kind Definition struct mdns_interface_monitor_s { struct mdns_object_s base; // Object base. @@ -153,24 +69,9 @@ _mdns_get_interface_flags_from_nw_path(nw_path_t path, mdns_interface_flags_t cu 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 @@ -208,119 +109,11 @@ _mdns_nwi_state_mutex_queue(void) //====================================================================================================================== -#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 +// MARK: - Interface Monitor Public Methods mdns_interface_monitor_t mdns_interface_monitor_create(uint32_t interface_index) @@ -495,21 +288,21 @@ mdns_interface_monitor_is_clat46(mdns_interface_monitor_t me) } //====================================================================================================================== -// MARK: - mdns_interface_monitor Private Methods + +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; -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) { @@ -521,17 +314,25 @@ _mdns_interface_monitor_copy_description(mdns_interface_monitor_t me, const bool *dst = '\0'; if (debug) { - n = _mdns_snprintf_add(&dst, lim, "mdns_%s (%p): ", me->base.kind->name, me); + 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); + 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); + n = mdns_snprintf_add(&dst, lim, "%s%s", separator, flag_desc->desc); require_quiet(n >= 0, exit); separator = ", "; } @@ -691,10 +492,8 @@ _mdns_get_interface_flags_from_nw_path(nw_path_t path, mdns_interface_flags_t cu 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; - } + if (nw_path_is_constrained(path)) { + flags |= mdns_interface_flag_constrained; } return flags; } @@ -702,29 +501,29 @@ _mdns_get_interface_flags_from_nw_path(nw_path_t path, mdns_interface_flags_t cu //====================================================================================================================== // 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 +#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 *ifname, mdns_interface_flags_t current_flags) +_mdns_get_interface_flags_from_nwi_state(const char * const ifname, const mdns_interface_flags_t current_flags) { - __block nwi_ifstate_flags ifstate_flags = 0; + __block mdns_interface_flags_t flags = current_flags; 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); - } + 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; } }); - 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; } @@ -778,30 +577,3 @@ _mdns_nwi_state_update(void) } } } - -//====================================================================================================================== -// 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_private.h b/mDNSMacOSX/mdns_objects/mdns_interface_monitor.h similarity index 69% rename from mDNSMacOSX/mdns_private.h rename to mDNSMacOSX/mdns_objects/mdns_interface_monitor.h index 6000d9d..4c82582 100644 --- a/mDNSMacOSX/mdns_private.h +++ b/mDNSMacOSX/mdns_objects/mdns_interface_monitor.h @@ -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. @@ -14,95 +14,23 @@ * limitations under the License. */ -#ifndef __MDNS_PRIVATE_H__ -#define __MDNS_PRIVATE_H__ +#ifndef __MDNS_INTERFACE_MONITOR_H__ +#define __MDNS_INTERFACE_MONITOR_H__ -#include -#include -#include -#include - -#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 +#include "mdns_base.h" +#include "mdns_object.h" - MDNS_DECL(object); -#endif +#include -// mdns Object Declarations +#include +#include 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 +MDNS_ASSUME_NONNULL_BEGIN __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. @@ -176,43 +104,6 @@ mdns_interface_monitor_invalidate(mdns_interface_monitor_t monitor); 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 mdns_event_error event. This argument should be ignored for all other - * types of events. - * - * @discussion - * After an mdns_event_invalidated 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. @@ -240,9 +131,9 @@ mdns_interface_monitor_set_event_handler(mdns_interface_monitor_t monitor, mdns_ * 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. + * 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, @@ -251,6 +142,7 @@ OS_CLOSED_OPTIONS(mdns_interface_flags, uint32_t, 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) ); @@ -305,8 +197,8 @@ mdns_interface_monitor_get_interface_index(mdns_interface_monitor_t 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. + * 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); @@ -319,8 +211,8 @@ mdns_interface_monitor_has_ipv4_connectivity(mdns_interface_monitor_t 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. + * 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); @@ -367,8 +259,24 @@ mdns_interface_monitor_is_constrained(mdns_interface_monitor_t monitor); 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 -OS_ASSUME_NONNULL_END +MDNS_ASSUME_NONNULL_END + +#define mdns_interface_monitor_forget(X) mdns_forget_with_invalidation(X, interface_monitor) -#endif // __MDNS_PRIVATE_H__ +#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 index 0000000..d034b2d --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_internal.h @@ -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 index 0000000..5fedabe --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_managed_defaults.c @@ -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 + +//====================================================================================================================== +// 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 index 0000000..5578db2 --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_managed_defaults.h @@ -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 +#include +#include + +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 index 0000000..235613e --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_message.c @@ -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 + +//====================================================================================================================== +// 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 // + +#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 index 0000000..b2dffc0 --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_message.h @@ -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 +#include + +#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 . + * + * 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 . + * + * 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 . + * + * 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 . + * + * 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 index 0000000..80ff2f2 --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_object.c @@ -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 + +//====================================================================================================================== +// 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 index 0000000..f93f48d --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_object.h @@ -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 + +/*! + * @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 mdns_event_error event. This argument should be ignored for all + * other types of events. + * + * @discussion + * After an mdns_event_invalidated 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 index 0000000..610eb1c --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_objects.h @@ -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 + +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 index 0000000..a9e923b --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_objects.m @@ -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 +#import +#import + +//====================================================================================================================== +// 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 index 0000000..b06e86e --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_powerlog.c @@ -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 +#include +#include +#include +#include +#include + +//====================================================================================================================== +// 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 . + 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 index 0000000..141750c --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_powerlog.h @@ -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 +#include + +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 index 0000000..9111455 --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_private.h @@ -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 index 0000000..b1e7b37 --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_resolver.c @@ -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 +#include +#include +#include + +//====================================================================================================================== +// 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 . + .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 . + .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 . + .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 . + .stream_only = true, + .needs_edns0_padding = true, + .needs_zero_ids = true, // See . + .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 : ""); + 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, "", 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, "", 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 = ""; + } + 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(¶ms_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 ). + // 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 : ""; + } else { + qname = qname_str; + } + } else { + qname = ""; + } + } else { + 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(¶ms); + } + 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 ""; + } +} + +//====================================================================================================================== + +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 index 0000000..59dac3a --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_resolver.h @@ -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 +#include +#include + +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 ""; + } +} + +__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 ""; + } +} + +#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 : + * + * 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 : + * + * 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 ""; +} +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 index 0000000..bd427a1 --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_set.c @@ -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 + +//====================================================================================================================== +// 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 index 0000000..d5f97a2 --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_set.h @@ -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 + +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 index 0000000..08e9730 --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_symptoms.c @@ -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 +#include + +#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 index 0000000..562a062 --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_symptoms.h @@ -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 +#include // audit_token_t +#include + +// 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 index 0000000..49c1aba --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_tlv.c @@ -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 + +//====================================================================================================================== +// 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 index 0000000..802a0c2 --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_tlv.h @@ -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 +#include + +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 index 0000000..313bb7a --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_trust.c @@ -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 +#include + +//====================================================================================================================== +// 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 index 0000000..452b7fe --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_trust.h @@ -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 + +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 ""; + } +} + +//====================================================================================================================== +// 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 ""; + } +} + +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 index 0000000..69fdccb --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_trust_checks.h @@ -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 + +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 ""; + } +} + +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 ""; + } +} + +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 index 0000000..ec830a9 --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_trust_checks.m @@ -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 +#import +#import +#else +#import +#import +#endif + +#import +#import +#import +#import +#import +#import +#import +#import + +#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 . + + 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 *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 index 0000000..ddca5db --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_xpc.c @@ -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 +#include + +//====================================================================================================================== +// 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 index 0000000..0465315 --- /dev/null +++ b/mDNSMacOSX/mdns_objects/mdns_xpc.h @@ -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 +#include + +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/ra-tester-entitlements.plist b/mDNSMacOSX/ra-tester-entitlements.plist new file mode 100644 index 0000000..e0706b6 --- /dev/null +++ b/mDNSMacOSX/ra-tester-entitlements.plist @@ -0,0 +1,16 @@ + + + + + com.apple.networkd_privileged + + com.apple.private.network.reserved-port + + com.apple.private.network.socket-delegate + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/mDNSMacOSX/srp-mdns-proxy-entitlements.plist b/mDNSMacOSX/srp-mdns-proxy-entitlements.plist new file mode 100644 index 0000000..2035b53 --- /dev/null +++ b/mDNSMacOSX/srp-mdns-proxy-entitlements.plist @@ -0,0 +1,67 @@ + + + + + seatbelt-profiles + + temporary-sandbox + + platform-application + + + com.apple.security.network.client + + + com.apple.security.network.server + + + com.apple.networkd_privileged + + + com.apple.private.network.reserved-port + + + com.apple.private.wpantund.service.xpc + + + com.apple.private.fillmore.service.add + + + com.apple.private.fillmore.service.remove + + + com.apple.private.fillmore.prefix.modification + + + com.apple.security.exception.mach-lookup.global-name + + com.apple.wpantund.xpc + com.apple.network.IPConfiguration + + + com.apple.security.exception.sysctl.read-write + + net.inet6.ip6.forwarding + + + com.apple.SystemConfiguration.SCDynamicStore-write-access + + + com.apple.security.exception.shared-preference.read-write + + com.apple.srp-mdns-proxy.preferences + + + com.apple.security.exception.files.absolute-path.read-write + + /private/var/preferences/SystemConfiguration/preferences.plist + + + com.apple.security.exception.files.absolute-path.read-only + + /usr/libexec + /dev/fd + + + + diff --git a/mDNSMacOSX/srp-mdns-proxy.plist b/mDNSMacOSX/srp-mdns-proxy.plist new file mode 100644 index 0000000..323abe2 --- /dev/null +++ b/mDNSMacOSX/srp-mdns-proxy.plist @@ -0,0 +1,10 @@ + + + + + CFBundleIdentifier + com.apple.srp-mdns-proxy + CFBundleName + DNS-SD Service Registration Protocol Proxy for Multicast DNS + + diff --git a/mDNSMacOSX/uDNSPathEvaluation.c b/mDNSMacOSX/uDNSPathEvaluation.c new file mode 100644 index 0000000..643730b --- /dev/null +++ b/mDNSMacOSX/uDNSPathEvaluation.c @@ -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 +#include + +#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(¶meters); + + 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(¶meters); + _nw_forget(&path); + _nw_forget(&evaluator); + q->BlockedByPolicy = isBlocked; +} diff --git a/mDNSMacOSX/uDNSPathEvalulation.c b/mDNSMacOSX/uDNSPathEvalulation.c deleted file mode 100644 index 9923446..0000000 --- a/mDNSMacOSX/uDNSPathEvalulation.c +++ /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 - -#if __has_include() - #include -#else - #include - #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 index 0000000..2cd8643 --- /dev/null +++ b/mDNSMacOSX/utilities/bundle_utilities.h @@ -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 + +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 index 0000000..348bd09 --- /dev/null +++ b/mDNSMacOSX/utilities/bundle_utilities.m @@ -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 +#import + +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 index 0000000..63c35fb --- /dev/null +++ b/mDNSMacOSX/utilities/setup_assistant_helper.h @@ -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 +#include + +__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 ""; + } +} + +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 index 0000000..192180a --- /dev/null +++ b/mDNSMacOSX/utilities/setup_assistant_helper.m @@ -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 + +#if TARGET_OS_OSX + +#import +#import + +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 index 834e8b2..0000000 --- a/mDNSMacOSX/utilities/system_utilities.c +++ /dev/null @@ -1,15 +0,0 @@ -// -// system_utilities.c -// mDNSResponder -// -// Copyright (c) 2019 Apple Inc. All rights reserved. -// - -#include // 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); -} diff --git a/mDNSMacOSX/utilities/system_utilities.h b/mDNSMacOSX/utilities/system_utilities.h index 789be42..e1417c0 100644 --- a/mDNSMacOSX/utilities/system_utilities.h +++ b/mDNSMacOSX/utilities/system_utilities.h @@ -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 +#include +#include + +__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 index 0000000..35a907c --- /dev/null +++ b/mDNSMacOSX/utilities/system_utilities.m @@ -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_has_internal_diagnostics +#import + +#if TARGET_OS_OSX +#import +#import +#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 index 0000000..f73af2f --- /dev/null +++ b/mDNSMacOSX/xpc_services/xpc_client_advertising_proxy.h @@ -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: diff --git a/mDNSMacOSX/xpc_services/xpc_client_dns_proxy.h b/mDNSMacOSX/xpc_services/xpc_client_dns_proxy.h index 1676299..aafbcd8 100644 --- a/mDNSMacOSX/xpc_services/xpc_client_dns_proxy.h +++ b/mDNSMacOSX/xpc_services/xpc_client_dns_proxy.h @@ -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 */ diff --git a/mDNSMacOSX/xpc_services/xpc_clients.h b/mDNSMacOSX/xpc_services/xpc_clients.h index 7a12746..047530d 100644 --- a/mDNSMacOSX/xpc_services/xpc_clients.h +++ b/mDNSMacOSX/xpc_services/xpc_clients.h @@ -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" diff --git a/mDNSMacOSX/xpc_services/xpc_service_dns_proxy.c b/mDNSMacOSX/xpc_services/xpc_service_dns_proxy.c index 47e837a..9fee867 100644 --- a/mDNSMacOSX/xpc_services/xpc_service_dns_proxy.c +++ b/mDNSMacOSX/xpc_services/xpc_service_dns_proxy.c @@ -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"); diff --git a/mDNSMacOSX/xpc_services/xpc_service_log_utility.c b/mDNSMacOSX/xpc_services/xpc_service_log_utility.c index 47348cb..6342de7 100644 --- a/mDNSMacOSX/xpc_services/xpc_service_log_utility.c +++ b/mDNSMacOSX/xpc_services/xpc_service_log_utility.c @@ -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, @@ -15,171 +15,196 @@ */ #include -#include // opendir -#include // stat +#include // opendir +#include // stat #include #include -#include // require, require_action +#include // 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 . 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 , 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 , 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; } diff --git a/mDNSMacOSX/xpc_services/xpc_service_log_utility.h b/mDNSMacOSX/xpc_services/xpc_service_log_utility.h index 5dd9201..2f23ff0 100644 --- a/mDNSMacOSX/xpc_services/xpc_service_log_utility.h +++ b/mDNSMacOSX/xpc_services/xpc_service_log_utility.h @@ -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 */ diff --git a/mDNSPosix/Makefile b/mDNSPosix/Makefile index 16477ce..5837722 100755 --- a/mDNSPosix/Makefile +++ b/mDNSPosix/Makefile @@ -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 diff --git a/mDNSPosix/mDNSPosix.c b/mDNSPosix/mDNSPosix.c index 9793195..d1bec5f 100755 --- a/mDNSPosix/mDNSPosix.c +++ b/mDNSPosix/mDNSPosix.c @@ -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 #include @@ -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) { diff --git a/mDNSResponder.sln b/mDNSResponder.sln old mode 100755 new mode 100644 index c93f95a..f1884eb --- a/mDNSResponder.sln +++ b/mDNSResponder.sln @@ -1,5 +1,7 @@ -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29911.84 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DLL", "mDNSWindows\DLL\dnssd.vcxproj", "{AB581101-18F0-46F6-B56A-83A6B1EA657E}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mDNSResponder", "mDNSWindows\SystemService\Service.vcxproj", "{C1D98254-BA27-4427-A3BE-A68CA2CC5F69}" @@ -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} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Java", "mDNSWindows\Java\Java.vcxproj", "{9CE2568A-3170-41C6-9F20-A0188A9EC114}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JavaSamples", "Clients\Java\JavaSamples.vcxproj", "{A987A0C1-344F-475C-869C-F082EB11EEBA}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DLLStub", "mDNSWindows\DLLStub\DLLStub.vcxproj", "{3A2B6325-3053-4236-84BD-AA9BE2E323E5}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DLLX", "mDNSWindows\DLLX\DLLX.vcxproj", "{78FBFCC5-2873-4AE2-9114-A08082F71124}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DNSServiceBrowser.NET", "Clients\DNSServiceBrowser.NET\DNSServiceBrowser.NET.csproj", "{DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}" -EndProject -Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "DNSServiceBrowser.VB", "Clients\DNSServiceBrowser.VB\DNSServiceBrowser.VB.vbproj", "{FB79E297-5703-435C-A829-51AA51CD71C2}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mDNSNetMonitor", "Clients\mDNSNetMonitor.VisualStudio\mDNSNetMonitor.vcxproj", "{AF35C285-528D-46A1-8A0E-47B0733DC718}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ControlPanelLocRes", "mDNSWindows\ControlPanel\ControlPanelLocRes.vcxproj", "{4490229E-025A-478F-A2CF-51154DA83E39}" @@ -63,320 +57,291 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ControlPanel", "mDNSWindows {4490229E-025A-478F-A2CF-51154DA83E39} = {4490229E-025A-478F-A2CF-51154DA83E39} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FirefoxExtension", "Clients\FirefoxExtension\FirefoxExtension.vcxproj", "{7826EA27-D4CC-4FAA-AD23-DF813823227B}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU + Debug|ARM64 = Debug|ARM64 Debug|Mixed Platforms = Debug|Mixed Platforms Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU + Release|ARM64 = Release|ARM64 Release|Mixed Platforms = Release|Mixed Platforms Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|Any CPU.ActiveCfg = Debug|x64 + {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|ARM64.Build.0 = Debug|ARM64 {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|Mixed Platforms.Build.0 = Debug|x64 {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|Win32.ActiveCfg = Debug|Win32 {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|Win32.Build.0 = Debug|Win32 {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|x64.ActiveCfg = Debug|x64 {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Debug|x64.Build.0 = Debug|x64 - {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|Any CPU.ActiveCfg = Release|x64 + {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|ARM64.ActiveCfg = Release|ARM64 + {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|ARM64.Build.0 = Release|ARM64 {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|Mixed Platforms.ActiveCfg = Release|x64 {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|Mixed Platforms.Build.0 = Release|x64 {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|Win32.ActiveCfg = Release|Win32 {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|Win32.Build.0 = Release|Win32 {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|x64.ActiveCfg = Release|x64 {AB581101-18F0-46F6-B56A-83A6B1EA657E}.Release|x64.Build.0 = Release|x64 - {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|Any CPU.ActiveCfg = Debug|x64 + {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|ARM64.Build.0 = Debug|ARM64 {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|Mixed Platforms.Build.0 = Debug|x64 {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|Win32.ActiveCfg = Debug|Win32 {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|Win32.Build.0 = Debug|Win32 {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|x64.ActiveCfg = Debug|x64 {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Debug|x64.Build.0 = Debug|x64 - {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|Any CPU.ActiveCfg = Release|x64 + {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|ARM64.ActiveCfg = Release|ARM64 + {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|ARM64.Build.0 = Release|ARM64 {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|Mixed Platforms.ActiveCfg = Release|x64 {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|Mixed Platforms.Build.0 = Release|x64 {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|Win32.ActiveCfg = Release|Win32 {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|Win32.Build.0 = Release|Win32 {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|x64.ActiveCfg = Release|x64 {C1D98254-BA27-4427-A3BE-A68CA2CC5F69}.Release|x64.Build.0 = Release|x64 - {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|Any CPU.ActiveCfg = Debug|x64 + {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|ARM64.Build.0 = Debug|ARM64 {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|Mixed Platforms.Build.0 = Debug|x64 {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|Win32.ActiveCfg = Debug|Win32 {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|Win32.Build.0 = Debug|Win32 {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|x64.ActiveCfg = Debug|x64 {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Debug|x64.Build.0 = Debug|x64 - {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|Any CPU.ActiveCfg = Release|x64 + {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|ARM64.ActiveCfg = Release|ARM64 + {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|ARM64.Build.0 = Release|ARM64 {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|Mixed Platforms.ActiveCfg = Release|x64 {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|Mixed Platforms.Build.0 = Release|x64 {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|Win32.ActiveCfg = Release|Win32 {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|Win32.Build.0 = Release|Win32 {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|x64.ActiveCfg = Release|x64 {208B3A9F-1CA0-4D1D-9D6C-C61616F94705}.Release|x64.Build.0 = Release|x64 - {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|Any CPU.ActiveCfg = Debug|x64 + {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|ARM64.Build.0 = Debug|ARM64 {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|Mixed Platforms.Build.0 = Debug|x64 {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|Win32.ActiveCfg = Debug|Win32 {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|Win32.Build.0 = Debug|Win32 {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|x64.ActiveCfg = Debug|x64 {F4F15529-F0EB-402F-8662-73C5797EE557}.Debug|x64.Build.0 = Debug|x64 - {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|Any CPU.ActiveCfg = Release|x64 + {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|ARM64.ActiveCfg = Release|ARM64 + {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|ARM64.Build.0 = Release|ARM64 {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|Mixed Platforms.ActiveCfg = Release|x64 {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|Mixed Platforms.Build.0 = Release|x64 {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|Win32.ActiveCfg = Release|Win32 {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|Win32.Build.0 = Release|Win32 {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|x64.ActiveCfg = Release|x64 {F4F15529-F0EB-402F-8662-73C5797EE557}.Release|x64.Build.0 = Release|x64 - {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|Any CPU.ActiveCfg = Debug|x64 + {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|ARM64.Build.0 = Debug|ARM64 {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|Mixed Platforms.Build.0 = Debug|x64 {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|Win32.ActiveCfg = Debug|Win32 {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|Win32.Build.0 = Debug|Win32 {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|x64.ActiveCfg = Debug|x64 {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Debug|x64.Build.0 = Debug|x64 - {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|Any CPU.ActiveCfg = Release|x64 + {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|ARM64.ActiveCfg = Release|ARM64 + {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|ARM64.Build.0 = Release|ARM64 {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|Mixed Platforms.ActiveCfg = Release|x64 {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|Mixed Platforms.Build.0 = Release|x64 {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|Win32.ActiveCfg = Release|Win32 {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|Win32.Build.0 = Release|Win32 {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|x64.ActiveCfg = Release|x64 {BB8AC1B5-6587-4163-BDC6-788B157705CA}.Release|x64.Build.0 = Release|x64 - {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|Any CPU.ActiveCfg = Debug|x64 + {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|ARM64.Build.0 = Debug|ARM64 {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|Mixed Platforms.Build.0 = Debug|x64 {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|Win32.ActiveCfg = Debug|Win32 {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|Win32.Build.0 = Debug|Win32 {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|x64.ActiveCfg = Debug|x64 {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Debug|x64.Build.0 = Debug|x64 - {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|Any CPU.ActiveCfg = Release|x64 + {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|ARM64.ActiveCfg = Release|ARM64 + {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|ARM64.Build.0 = Release|ARM64 {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|Mixed Platforms.ActiveCfg = Release|x64 {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|Mixed Platforms.Build.0 = Release|x64 {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|Win32.ActiveCfg = Release|Win32 {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|Win32.Build.0 = Release|Win32 {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|x64.ActiveCfg = Release|x64 {B1D2CDA2-CC8F-45D5-A694-2EE45B0308CF}.Release|x64.Build.0 = Release|x64 - {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|Any CPU.ActiveCfg = Debug|x64 + {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|ARM64.Build.0 = Debug|ARM64 {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|Mixed Platforms.Build.0 = Debug|x64 {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|Win32.ActiveCfg = Debug|Win32 {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|Win32.Build.0 = Debug|Win32 {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|x64.ActiveCfg = Debug|x64 {967F5375-0176-43D3-ADA3-22EE25551C37}.Debug|x64.Build.0 = Debug|x64 - {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|Any CPU.ActiveCfg = Release|x64 + {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|ARM64.ActiveCfg = Release|ARM64 + {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|ARM64.Build.0 = Release|ARM64 {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|Mixed Platforms.ActiveCfg = Release|x64 {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|Mixed Platforms.Build.0 = Release|x64 {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|Win32.ActiveCfg = Release|Win32 {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|Win32.Build.0 = Release|Win32 {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|x64.ActiveCfg = Release|x64 {967F5375-0176-43D3-ADA3-22EE25551C37}.Release|x64.Build.0 = Release|x64 - {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|Any CPU.ActiveCfg = Debug|x64 + {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|ARM64.Build.0 = Debug|ARM64 {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|Mixed Platforms.Build.0 = Debug|x64 {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|Win32.ActiveCfg = Debug|Win32 {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|Win32.Build.0 = Debug|Win32 {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|x64.ActiveCfg = Debug|x64 {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Debug|x64.Build.0 = Debug|x64 - {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|Any CPU.ActiveCfg = Release|x64 + {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|ARM64.ActiveCfg = Release|ARM64 + {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|ARM64.Build.0 = Release|ARM64 {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|Mixed Platforms.ActiveCfg = Release|x64 {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|Mixed Platforms.Build.0 = Release|x64 {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|Win32.ActiveCfg = Release|Win32 {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|Win32.Build.0 = Release|Win32 {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|x64.ActiveCfg = Release|x64 {CFCCB176-6CAA-472B-B0A2-90511C8E2E52}.Release|x64.Build.0 = Release|x64 - {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|Any CPU.ActiveCfg = Debug|x64 + {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|ARM64.Build.0 = Debug|ARM64 {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|Mixed Platforms.Build.0 = Debug|x64 {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|Win32.ActiveCfg = Debug|Win32 {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|Win32.Build.0 = Debug|Win32 {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|x64.ActiveCfg = Debug|x64 {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Debug|x64.Build.0 = Debug|x64 - {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|Any CPU.ActiveCfg = Release|x64 + {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|ARM64.ActiveCfg = Release|ARM64 + {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|ARM64.Build.0 = Release|ARM64 {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|Mixed Platforms.ActiveCfg = Release|x64 {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|Mixed Platforms.Build.0 = Release|x64 {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|Win32.ActiveCfg = Release|Win32 {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|Win32.Build.0 = Release|Win32 {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|x64.ActiveCfg = Release|x64 {1643427B-F226-4AD6-B413-97DA64D5C6B4}.Release|x64.Build.0 = Release|x64 - {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|Any CPU.ActiveCfg = Debug|x64 + {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|ARM64.Build.0 = Debug|ARM64 {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|Mixed Platforms.Build.0 = Debug|x64 {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|Win32.ActiveCfg = Debug|Win32 {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|Win32.Build.0 = Debug|Win32 {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|x64.ActiveCfg = Debug|x64 {871B1492-B4A4-4B57-9237-FA798484D7D7}.Debug|x64.Build.0 = Debug|x64 - {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|Any CPU.ActiveCfg = Release|x64 + {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|ARM64.ActiveCfg = Release|ARM64 + {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|ARM64.Build.0 = Release|ARM64 {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|Mixed Platforms.ActiveCfg = Release|x64 {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|Mixed Platforms.Build.0 = Release|x64 {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|Win32.ActiveCfg = Release|Win32 {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|Win32.Build.0 = Release|Win32 {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|x64.ActiveCfg = Release|x64 {871B1492-B4A4-4B57-9237-FA798484D7D7}.Release|x64.Build.0 = Release|x64 - {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|Any CPU.ActiveCfg = Debug|x64 + {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|ARM64.Build.0 = Debug|ARM64 {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|Mixed Platforms.Build.0 = Debug|x64 {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|Win32.ActiveCfg = Debug|Win32 {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|Win32.Build.0 = Debug|Win32 {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|x64.ActiveCfg = Debug|x64 {AA230639-E115-4A44-AA5A-44A61235BA50}.Debug|x64.Build.0 = Debug|x64 - {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|Any CPU.ActiveCfg = Release|x64 + {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|ARM64.ActiveCfg = Release|ARM64 + {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|ARM64.Build.0 = Release|ARM64 {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|Mixed Platforms.ActiveCfg = Release|x64 {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|Mixed Platforms.Build.0 = Release|x64 {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|Win32.ActiveCfg = Release|Win32 {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|Win32.Build.0 = Release|Win32 {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|x64.ActiveCfg = Release|x64 {AA230639-E115-4A44-AA5A-44A61235BA50}.Release|x64.Build.0 = Release|x64 - {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Debug|Any CPU.ActiveCfg = Debug|x64 - {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 - {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Debug|Mixed Platforms.Build.0 = Debug|x64 - {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Debug|Win32.ActiveCfg = Debug|Win32 - {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Debug|Win32.Build.0 = Debug|Win32 - {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Debug|x64.ActiveCfg = Debug|x64 - {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Debug|x64.Build.0 = Debug|x64 - {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Release|Any CPU.ActiveCfg = Release|x64 - {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Release|Mixed Platforms.ActiveCfg = Release|x64 - {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Release|Mixed Platforms.Build.0 = Release|x64 - {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Release|Win32.ActiveCfg = Release|Win32 - {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Release|Win32.Build.0 = Release|Win32 - {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Release|x64.ActiveCfg = Release|x64 - {9CE2568A-3170-41C6-9F20-A0188A9EC114}.Release|x64.Build.0 = Release|x64 - {A987A0C1-344F-475C-869C-F082EB11EEBA}.Debug|Any CPU.ActiveCfg = Debug|x64 - {A987A0C1-344F-475C-869C-F082EB11EEBA}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 - {A987A0C1-344F-475C-869C-F082EB11EEBA}.Debug|Mixed Platforms.Build.0 = Debug|x64 - {A987A0C1-344F-475C-869C-F082EB11EEBA}.Debug|Win32.ActiveCfg = Debug|Win32 - {A987A0C1-344F-475C-869C-F082EB11EEBA}.Debug|Win32.Build.0 = Debug|Win32 - {A987A0C1-344F-475C-869C-F082EB11EEBA}.Debug|x64.ActiveCfg = Debug|x64 - {A987A0C1-344F-475C-869C-F082EB11EEBA}.Release|Any CPU.ActiveCfg = Release|x64 - {A987A0C1-344F-475C-869C-F082EB11EEBA}.Release|Mixed Platforms.ActiveCfg = Release|x64 - {A987A0C1-344F-475C-869C-F082EB11EEBA}.Release|Mixed Platforms.Build.0 = Release|x64 - {A987A0C1-344F-475C-869C-F082EB11EEBA}.Release|Win32.ActiveCfg = Release|Win32 - {A987A0C1-344F-475C-869C-F082EB11EEBA}.Release|Win32.Build.0 = Release|Win32 - {A987A0C1-344F-475C-869C-F082EB11EEBA}.Release|x64.ActiveCfg = Release|x64 - {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|Any CPU.ActiveCfg = Debug|x64 + {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|ARM64.Build.0 = Debug|ARM64 {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|Mixed Platforms.Build.0 = Debug|x64 {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|Win32.ActiveCfg = Debug|Win32 {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|Win32.Build.0 = Debug|Win32 {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|x64.ActiveCfg = Debug|x64 {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Debug|x64.Build.0 = Debug|x64 - {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|Any CPU.ActiveCfg = Release|x64 + {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|ARM64.ActiveCfg = Release|ARM64 + {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|ARM64.Build.0 = Release|ARM64 {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|Mixed Platforms.ActiveCfg = Release|x64 {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|Mixed Platforms.Build.0 = Release|x64 {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|Win32.ActiveCfg = Release|Win32 {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|Win32.Build.0 = Release|Win32 {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|x64.ActiveCfg = Release|x64 {3A2B6325-3053-4236-84BD-AA9BE2E323E5}.Release|x64.Build.0 = Release|x64 - {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|Any CPU.ActiveCfg = Debug|x64 + {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|ARM64.Build.0 = Debug|ARM64 {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|Mixed Platforms.Build.0 = Debug|x64 {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|Win32.ActiveCfg = Debug|Win32 {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|Win32.Build.0 = Debug|Win32 {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|x64.ActiveCfg = Debug|x64 {78FBFCC5-2873-4AE2-9114-A08082F71124}.Debug|x64.Build.0 = Debug|x64 - {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|Any CPU.ActiveCfg = Release|x64 + {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|ARM64.ActiveCfg = Release|ARM64 + {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|ARM64.Build.0 = Release|ARM64 {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|Mixed Platforms.ActiveCfg = Release|x64 {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|Mixed Platforms.Build.0 = Release|x64 {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|Win32.ActiveCfg = Release|Win32 {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|Win32.Build.0 = Release|Win32 {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|x64.ActiveCfg = Release|x64 {78FBFCC5-2873-4AE2-9114-A08082F71124}.Release|x64.Build.0 = Release|x64 - {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Debug|Win32.ActiveCfg = Debug|Any CPU - {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Debug|x64.ActiveCfg = Debug|Any CPU - {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Release|Any CPU.Build.0 = Release|Any CPU - {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Release|Win32.ActiveCfg = Release|Any CPU - {DE8DB97E-37A3-43ED-9A5E-CCC5F6DE9CB4}.Release|x64.ActiveCfg = Release|Any CPU - {FB79E297-5703-435C-A829-51AA51CD71C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FB79E297-5703-435C-A829-51AA51CD71C2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FB79E297-5703-435C-A829-51AA51CD71C2}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {FB79E297-5703-435C-A829-51AA51CD71C2}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {FB79E297-5703-435C-A829-51AA51CD71C2}.Debug|Win32.ActiveCfg = Debug|Any CPU - {FB79E297-5703-435C-A829-51AA51CD71C2}.Debug|x64.ActiveCfg = Debug|Any CPU - {FB79E297-5703-435C-A829-51AA51CD71C2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FB79E297-5703-435C-A829-51AA51CD71C2}.Release|Any CPU.Build.0 = Release|Any CPU - {FB79E297-5703-435C-A829-51AA51CD71C2}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {FB79E297-5703-435C-A829-51AA51CD71C2}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {FB79E297-5703-435C-A829-51AA51CD71C2}.Release|Win32.ActiveCfg = Release|Any CPU - {FB79E297-5703-435C-A829-51AA51CD71C2}.Release|x64.ActiveCfg = Release|Any CPU - {AF35C285-528D-46A1-8A0E-47B0733DC718}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {AF35C285-528D-46A1-8A0E-47B0733DC718}.Debug|ARM64.ActiveCfg = Debug|ARM64 {AF35C285-528D-46A1-8A0E-47B0733DC718}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 {AF35C285-528D-46A1-8A0E-47B0733DC718}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {AF35C285-528D-46A1-8A0E-47B0733DC718}.Debug|Win32.ActiveCfg = Debug|Win32 {AF35C285-528D-46A1-8A0E-47B0733DC718}.Debug|Win32.Build.0 = Debug|Win32 {AF35C285-528D-46A1-8A0E-47B0733DC718}.Debug|x64.ActiveCfg = Debug|x64 - {AF35C285-528D-46A1-8A0E-47B0733DC718}.Release|Any CPU.ActiveCfg = Release|Win32 + {AF35C285-528D-46A1-8A0E-47B0733DC718}.Release|ARM64.ActiveCfg = Release|ARM64 {AF35C285-528D-46A1-8A0E-47B0733DC718}.Release|Mixed Platforms.ActiveCfg = Release|Win32 {AF35C285-528D-46A1-8A0E-47B0733DC718}.Release|Mixed Platforms.Build.0 = Release|Win32 {AF35C285-528D-46A1-8A0E-47B0733DC718}.Release|Win32.ActiveCfg = Release|Win32 {AF35C285-528D-46A1-8A0E-47B0733DC718}.Release|Win32.Build.0 = Release|Win32 {AF35C285-528D-46A1-8A0E-47B0733DC718}.Release|x64.ActiveCfg = Release|Win32 - {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|Any CPU.ActiveCfg = Debug|x64 + {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|ARM64.Build.0 = Debug|ARM64 {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|Mixed Platforms.Build.0 = Debug|x64 {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|Win32.ActiveCfg = Debug|Win32 {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|Win32.Build.0 = Debug|Win32 {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|x64.ActiveCfg = Debug|x64 {4490229E-025A-478F-A2CF-51154DA83E39}.Debug|x64.Build.0 = Debug|x64 - {4490229E-025A-478F-A2CF-51154DA83E39}.Release|Any CPU.ActiveCfg = Release|x64 + {4490229E-025A-478F-A2CF-51154DA83E39}.Release|ARM64.ActiveCfg = Release|ARM64 + {4490229E-025A-478F-A2CF-51154DA83E39}.Release|ARM64.Build.0 = Release|ARM64 {4490229E-025A-478F-A2CF-51154DA83E39}.Release|Mixed Platforms.ActiveCfg = Release|x64 {4490229E-025A-478F-A2CF-51154DA83E39}.Release|Mixed Platforms.Build.0 = Release|x64 {4490229E-025A-478F-A2CF-51154DA83E39}.Release|Win32.ActiveCfg = Release|Win32 {4490229E-025A-478F-A2CF-51154DA83E39}.Release|Win32.Build.0 = Release|Win32 {4490229E-025A-478F-A2CF-51154DA83E39}.Release|x64.ActiveCfg = Release|x64 {4490229E-025A-478F-A2CF-51154DA83E39}.Release|x64.Build.0 = Release|x64 - {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|Any CPU.ActiveCfg = Debug|x64 + {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|ARM64.Build.0 = Debug|ARM64 {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|Mixed Platforms.Build.0 = Debug|x64 {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|Win32.ActiveCfg = Debug|Win32 {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|Win32.Build.0 = Debug|Win32 {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|x64.ActiveCfg = Debug|x64 {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Debug|x64.Build.0 = Debug|x64 - {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|Any CPU.ActiveCfg = Release|x64 + {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|ARM64.ActiveCfg = Release|ARM64 + {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|ARM64.Build.0 = Release|ARM64 {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|Mixed Platforms.ActiveCfg = Release|x64 {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|Mixed Platforms.Build.0 = Release|x64 {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|Win32.ActiveCfg = Release|Win32 {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|Win32.Build.0 = Release|Win32 {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|x64.ActiveCfg = Release|x64 {5254AA9C-3D2E-4539-86D9-5EB0F4151215}.Release|x64.Build.0 = Release|x64 - {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|Any CPU.ActiveCfg = Debug|x64 + {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|ARM64.Build.0 = Debug|ARM64 {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|Mixed Platforms.Build.0 = Debug|x64 {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|Win32.ActiveCfg = Debug|Win32 {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|Win32.Build.0 = Debug|Win32 {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|x64.ActiveCfg = Debug|x64 {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Debug|x64.Build.0 = Debug|x64 - {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|Any CPU.ActiveCfg = Release|x64 + {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|ARM64.ActiveCfg = Release|ARM64 + {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|ARM64.Build.0 = Release|ARM64 {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|Mixed Platforms.ActiveCfg = Release|x64 {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|Mixed Platforms.Build.0 = Release|x64 {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|Win32.ActiveCfg = Release|Win32 {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|Win32.Build.0 = Release|Win32 {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|x64.ActiveCfg = Release|x64 {0DF09484-B4C2-4AB4-9FC0-7B091ADEAFEB}.Release|x64.Build.0 = Release|x64 - {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 - {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Debug|Mixed Platforms.Build.0 = Debug|Win32 - {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Debug|Win32.ActiveCfg = Debug|Win32 - {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Debug|Win32.Build.0 = Debug|Win32 - {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Debug|x64.ActiveCfg = Debug|Win32 - {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Release|Any CPU.ActiveCfg = Release|Win32 - {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Release|Mixed Platforms.ActiveCfg = Release|Win32 - {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Release|Mixed Platforms.Build.0 = Release|Win32 - {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Release|Win32.ActiveCfg = Release|Win32 - {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Release|Win32.Build.0 = Release|Win32 - {7826EA27-D4CC-4FAA-AD23-DF813823227B}.Release|x64.ActiveCfg = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3AB8E02C-A6A4-4530-908D-882B34FC7BA4} + EndGlobalSection EndGlobal diff --git a/mDNSShared/ClientRequests.c b/mDNSShared/ClientRequests.c index 6c139f4..4ea8101 100644 --- a/mDNSShared/ClientRequests.c +++ b/mDNSShared/ClientRequests.c @@ -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. @@ -19,6 +19,10 @@ #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; diff --git a/mDNSShared/ClientRequests.h b/mDNSShared/ClientRequests.h index 82f8d77..c3a42f4 100644 --- a/mDNSShared/ClientRequests.h +++ b/mDNSShared/ClientRequests.h @@ -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 diff --git a/mDNSShared/CommonServices.h b/mDNSShared/CommonServices.h index 55353bc..096fc7b 100644 --- a/mDNSShared/CommonServices.h +++ b/mDNSShared/CommonServices.h @@ -238,6 +238,11 @@ extern "C" { #elif ( defined( _MSC_VER ) ) + #if ( _MSC_VER >= 1900 ) + #include + #include + #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. diff --git a/mDNSShared/PlatformCommon.c b/mDNSShared/PlatformCommon.c index f3b2aab..6b7f838 100644 --- a/mDNSShared/PlatformCommon.c +++ b/mDNSShared/PlatformCommon.c @@ -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 diff --git a/mDNSShared/dns_sd.h b/mDNSShared/dns_sd.h index e1080ff..d6d1bc8 100644 --- a/mDNSShared/dns_sd.h +++ b/mDNSShared/dns_sd.h @@ -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) */ diff --git a/mDNSShared/dns_sd_internal.h b/mDNSShared/dns_sd_internal.h index 22931f2..a3755a1 100644 --- a/mDNSShared/dns_sd_internal.h +++ b/mDNSShared/dns_sd_internal.h @@ -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 diff --git a/mDNSShared/dns_sd_private.h b/mDNSShared/dns_sd_private.h index 19de052..1c4baab 100644 --- a/mDNSShared/dns_sd_private.h +++ b/mDNSShared/dns_sd_private.h @@ -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 +#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 diff --git a/mDNSShared/dnssd_clientshim.c b/mDNSShared/dnssd_clientshim.c index d3e9a44..3c0371e 100644 --- a/mDNSShared/dnssd_clientshim.c +++ b/mDNSShared/dnssd_clientshim.c @@ -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; diff --git a/mDNSShared/dnssd_clientstub.c b/mDNSShared/dnssd_clientstub.c index a5111a2..074db5a 100644 --- a/mDNSShared/dnssd_clientstub.c +++ b/mDNSShared/dnssd_clientstub.c @@ -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: @@ -35,7 +35,14 @@ #include #include #include -#include "dns_sd_internal.h" +#include "dns_sd_private.h" +#include "dnssd_clientstub_apple.h" +#include +#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 +#endif + #if defined(_WIN32) // 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 index 0000000..8f5efe6 --- /dev/null +++ b/mDNSShared/dnssd_clientstub_apple.c @@ -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 +#include +#include + +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 index 0000000..1330dcf --- /dev/null +++ b/mDNSShared/dnssd_clientstub_apple.h @@ -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 + +#define kDNSServiceDefaultsKey_RequirePrivacy "require_privacy" +#define kDNSServiceDefaultsKey_ResolverConfigPListData "resolver_config_plist_data" + +xpc_object_t +DNSServiceGetRetainedResolverDefaults(void); diff --git a/mDNSShared/dnssd_ipc.c b/mDNSShared/dnssd_ipc.c index 625c88e..a149678 100644 --- a/mDNSShared/dnssd_ipc.c +++ b/mDNSShared/dnssd_ipc.c @@ -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); diff --git a/mDNSShared/dnssd_ipc.h b/mDNSShared/dnssd_ipc.h index 96466a9..4d9090c 100644 --- a/mDNSShared/dnssd_ipc.h +++ b/mDNSShared/dnssd_ipc.h @@ -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 diff --git a/mDNSShared/uds_daemon.c b/mDNSShared/uds_daemon.c index 1982982..834d4db 100644 --- a/mDNSShared/uds_daemon.c +++ b/mDNSShared/uds_daemon.c @@ -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. @@ -59,6 +59,31 @@ #include "BLE.h" #endif +#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT) +#include "mDNSMacOSX.h" +#include +#endif + +#if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN) +#include +#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 s", not "Zero or more 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) : "", 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) : "", 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, ¶ms->fqdn, request->flags)) + { + request->u.resolve.external_advertise = mDNStrue; + LogInfo("handle_resolve_request: calling external_start_resolving_service()"); + external_start_resolving_service(params->InterfaceID, ¶ms->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(¶ms.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, ¶ms.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, ¶ms.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, ¶ms); + } + else + { + err = _handle_resolve_request_start(request, ¶ms); } +#else + err = _handle_resolve_request_start(request, ¶ms); +#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, ¶ms); + } + else + { + err = _handle_queryrecord_request_start(request, ¶ms); + } +#else + err = _handle_queryrecord_request_start(request, ¶ms); +#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, ¶ms); + } + else + { + err = _handle_addrinfo_request_start(request, ¶ms); + } +#else + err = _handle_addrinfo_request_start(request, ¶ms); +#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, ""); - } - 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]; }; diff --git a/mDNSShared/uds_daemon.h b/mDNSShared/uds_daemon.h index 99ec904..d921057 100644 --- a/mDNSShared/uds_daemon.h +++ b/mDNSShared/uds_daemon.h @@ -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); diff --git a/unittests/unittest_common.c b/unittests/unittest_common.c index 66216c4..f507e7a 100644 --- a/unittests/unittest_common.c +++ b/unittests/unittest_common.c @@ -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); diff --git a/unittests/unittest_common.h b/unittests/unittest_common.h index 5755a75..2f9547e 100644 --- a/unittests/unittest_common.h +++ b/unittests/unittest_common.h @@ -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); -- 2.45.2