From d3ecdef920c616a3ebc935c19e8e34f71a18d1b6 Mon Sep 17 00:00:00 2001 From: Jose Ulises Nino Rivera Date: Wed, 30 Sep 2020 08:07:12 -0700 Subject: [PATCH] dns: add dns resolver implementation based on Apple APIs (#13074) Signed-off-by: Jose Nino --- api/envoy/api/v2/cluster.proto | 6 + api/envoy/config/bootstrap/v2/bootstrap.proto | 3 + api/envoy/config/bootstrap/v3/bootstrap.proto | 3 + .../config/bootstrap/v4alpha/bootstrap.proto | 3 + api/envoy/config/cluster/v3/cluster.proto | 6 + .../config/cluster/v4alpha/cluster.proto | 6 + .../dynamic_forward_proxy/v3/dns_cache.proto | 3 + ci/mac_ci_steps.sh | 7 +- ci/run_clang_tidy.sh | 2 +- docs/root/version_history/current.rst | 2 + .../envoy/api/v2/cluster.proto | 6 + .../envoy/config/bootstrap/v2/bootstrap.proto | 3 + .../envoy/config/bootstrap/v3/bootstrap.proto | 3 + .../config/bootstrap/v4alpha/bootstrap.proto | 3 + .../envoy/config/cluster/v3/cluster.proto | 6 + .../config/cluster/v4alpha/cluster.proto | 6 + .../dynamic_forward_proxy/v3/dns_cache.proto | 3 + source/common/event/BUILD | 7 +- source/common/event/dispatcher_impl.cc | 22 ++ source/common/network/BUILD | 21 ++ source/common/network/apple_dns_impl.cc | 325 ++++++++++++++++++ source/common/network/apple_dns_impl.h | 108 ++++++ source/common/runtime/runtime_features.cc | 1 + test/common/network/BUILD | 37 ++ test/common/network/apple_dns_impl_test.cc | 199 +++++++++++ tools/spelling/spelling_dictionary.txt | 2 + 26 files changed, 788 insertions(+), 5 deletions(-) create mode 100644 source/common/network/apple_dns_impl.cc create mode 100644 source/common/network/apple_dns_impl.h create mode 100644 test/common/network/apple_dns_impl_test.cc diff --git a/api/envoy/api/v2/cluster.proto b/api/envoy/api/v2/cluster.proto index d1a50fbdb91e..182afc8c521b 100644 --- a/api/envoy/api/v2/cluster.proto +++ b/api/envoy/api/v2/cluster.proto @@ -677,10 +677,16 @@ message Cluster { // :ref:`STRICT_DNS` // and :ref:`LOGICAL_DNS` // this setting is ignored. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple's API only allows overriding DNS resolvers via system settings. repeated core.Address dns_resolvers = 18; // [#next-major-version: Reconcile DNS options in a single message.] // Always use TCP queries instead of UDP queries for DNS lookups. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple' API only uses UDP for DNS resolution. bool use_tcp_for_dns_lookups = 45; // If specified, outlier detection will be enabled for this upstream cluster. diff --git a/api/envoy/config/bootstrap/v2/bootstrap.proto b/api/envoy/config/bootstrap/v2/bootstrap.proto index da88dce786ae..30c276f24276 100644 --- a/api/envoy/config/bootstrap/v2/bootstrap.proto +++ b/api/envoy/config/bootstrap/v2/bootstrap.proto @@ -169,6 +169,9 @@ message Bootstrap { // when :ref:`dns_resolvers ` and // :ref:`use_tcp_for_dns_lookups ` are // specified. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple' API only uses UDP for DNS resolution. bool use_tcp_for_dns_lookups = 20; } diff --git a/api/envoy/config/bootstrap/v3/bootstrap.proto b/api/envoy/config/bootstrap/v3/bootstrap.proto index 651ab978781d..a9a0290b297c 100644 --- a/api/envoy/config/bootstrap/v3/bootstrap.proto +++ b/api/envoy/config/bootstrap/v3/bootstrap.proto @@ -234,6 +234,9 @@ message Bootstrap { // when :ref:`dns_resolvers ` and // :ref:`use_tcp_for_dns_lookups ` are // specified. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple' API only uses UDP for DNS resolution. bool use_tcp_for_dns_lookups = 20; // Specifies optional bootstrap extensions to be instantiated at startup time. diff --git a/api/envoy/config/bootstrap/v4alpha/bootstrap.proto b/api/envoy/config/bootstrap/v4alpha/bootstrap.proto index 0cbfb6598e45..ef10dead9706 100644 --- a/api/envoy/config/bootstrap/v4alpha/bootstrap.proto +++ b/api/envoy/config/bootstrap/v4alpha/bootstrap.proto @@ -220,6 +220,9 @@ message Bootstrap { // when :ref:`dns_resolvers ` and // :ref:`use_tcp_for_dns_lookups ` are // specified. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple' API only uses UDP for DNS resolution. bool use_tcp_for_dns_lookups = 20; // Specifies optional bootstrap extensions to be instantiated at startup time. diff --git a/api/envoy/config/cluster/v3/cluster.proto b/api/envoy/config/cluster/v3/cluster.proto index 376fb1250e35..10edef375f25 100644 --- a/api/envoy/config/cluster/v3/cluster.proto +++ b/api/envoy/config/cluster/v3/cluster.proto @@ -829,10 +829,16 @@ message Cluster { // :ref:`STRICT_DNS` // and :ref:`LOGICAL_DNS` // this setting is ignored. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple's API only allows overriding DNS resolvers via system settings. repeated core.v3.Address dns_resolvers = 18; // [#next-major-version: Reconcile DNS options in a single message.] // Always use TCP queries instead of UDP queries for DNS lookups. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple' API only uses UDP for DNS resolution. bool use_tcp_for_dns_lookups = 45; // If specified, outlier detection will be enabled for this upstream cluster. diff --git a/api/envoy/config/cluster/v4alpha/cluster.proto b/api/envoy/config/cluster/v4alpha/cluster.proto index 2025bb530479..35117aba77fd 100644 --- a/api/envoy/config/cluster/v4alpha/cluster.proto +++ b/api/envoy/config/cluster/v4alpha/cluster.proto @@ -839,10 +839,16 @@ message Cluster { // :ref:`STRICT_DNS` // and :ref:`LOGICAL_DNS` // this setting is ignored. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple's API only allows overriding DNS resolvers via system settings. repeated core.v4alpha.Address dns_resolvers = 18; // [#next-major-version: Reconcile DNS options in a single message.] // Always use TCP queries instead of UDP queries for DNS lookups. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple' API only uses UDP for DNS resolution. bool use_tcp_for_dns_lookups = 45; // If specified, outlier detection will be enabled for this upstream cluster. diff --git a/api/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto b/api/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto index d084cbac99de..5579cc16bd97 100644 --- a/api/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto +++ b/api/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto @@ -98,5 +98,8 @@ message DnsCacheConfig { // [#next-major-version: Reconcile DNS options in a single message.] // Always use TCP queries instead of UDP queries for DNS lookups. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple' API only uses UDP for DNS resolution. bool use_tcp_for_dns_lookups = 8; } diff --git a/ci/mac_ci_steps.sh b/ci/mac_ci_steps.sh index 41d21c0d6f57..5ebaba83ce95 100755 --- a/ci/mac_ci_steps.sh +++ b/ci/mac_ci_steps.sh @@ -36,10 +36,13 @@ BAZEL_BUILD_OPTIONS=( if [[ $# -gt 0 ]]; then TEST_TARGETS=$* else - TEST_TARGETS=//test/integration/... + TEST_TARGETS='//test/integration/...' fi if [[ "$TEST_TARGETS" == "//test/..." || "$TEST_TARGETS" == "//test/integration/..." ]]; then bazel build "${BAZEL_BUILD_OPTIONS[@]}" //source/exe:envoy-static fi -bazel test "${BAZEL_BUILD_OPTIONS[@]}" ${TEST_TARGETS} +bazel test "${BAZEL_BUILD_OPTIONS[@]}" "${TEST_TARGETS}" + +# Additionally run macOS specific test suites +bazel test "${BAZEL_BUILD_OPTIONS[@]}" //test/common/network:apple_dns_impl_test diff --git a/ci/run_clang_tidy.sh b/ci/run_clang_tidy.sh index 18aaa1988732..3e3c97961f8b 100755 --- a/ci/run_clang_tidy.sh +++ b/ci/run_clang_tidy.sh @@ -37,7 +37,7 @@ function exclude_win32_impl() { # Do not run clang-tidy against macOS impl # TODO: We should run clang-tidy against macOS impl for completeness function exclude_macos_impl() { - grep -v source/common/filesystem/kqueue/ + grep -v source/common/filesystem/kqueue/ | grep -v source/common/network/apple_dns_impl | grep -v test/common/network/apple_dns_impl_test } # Do not run incremental clang-tidy on check_format testdata files. diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 9d4b7912bed2..e3484e053d78 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -6,6 +6,7 @@ Incompatible Behavior Changes *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* * build: added visibility rules for upstream. If these cause visibility related breakage, see notes in //BUILD. +* dns: ``envoy.restart_features.use_apple_api_for_dns_lookups`` is on by default. This flag only affects Apple platforms (macOS, iOS). It is incompatible to have the runtime flag set to true at the same time as specifying the ``use_tcp_for_dns_lookups`` option or custom dns resolvers. Doing so will cause failure. * watchdog: added two guarddogs, breaking the aggregated stats for the single guarddog system. The aggregated stats for the guarddogs will have the following prefixes: `main_thread` and `workers`. Concretely, anything monitoring `server.watchdog_miss` and `server.watchdog_mega_miss` will need to be updated. Minor Behavior Changes @@ -82,6 +83,7 @@ New Features * build: enable building envoy :ref:`arm64 images ` by buildx tool in x86 CI platform. * cluster: added new :ref:`connection_pool_per_downstream_connection ` flag, which enable creation of a new connection pool for each downstream connection. * decompressor filter: reports compressed and uncompressed bytes in trailers. +* dns: added support for doing DNS resolution using Apple's DnsService APIs in Apple platforms (macOS, iOS). This feature is ON by default, and is only configurable via the ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime key. Note that this value is latched during server startup and changing the runtime key is a no-op during the lifetime of the process. * dns_filter: added support for answering :ref:`service record` queries. * dynamic_forward_proxy: added :ref:`use_tcp_for_dns_lookups` option to use TCP for DNS lookups in order to match the DNS options for :ref:`Clusters`. * ext_authz filter: added support for emitting dynamic metadata for both :ref:`HTTP ` and :ref:`network ` filters. diff --git a/generated_api_shadow/envoy/api/v2/cluster.proto b/generated_api_shadow/envoy/api/v2/cluster.proto index d1a50fbdb91e..182afc8c521b 100644 --- a/generated_api_shadow/envoy/api/v2/cluster.proto +++ b/generated_api_shadow/envoy/api/v2/cluster.proto @@ -677,10 +677,16 @@ message Cluster { // :ref:`STRICT_DNS` // and :ref:`LOGICAL_DNS` // this setting is ignored. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple's API only allows overriding DNS resolvers via system settings. repeated core.Address dns_resolvers = 18; // [#next-major-version: Reconcile DNS options in a single message.] // Always use TCP queries instead of UDP queries for DNS lookups. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple' API only uses UDP for DNS resolution. bool use_tcp_for_dns_lookups = 45; // If specified, outlier detection will be enabled for this upstream cluster. diff --git a/generated_api_shadow/envoy/config/bootstrap/v2/bootstrap.proto b/generated_api_shadow/envoy/config/bootstrap/v2/bootstrap.proto index da88dce786ae..30c276f24276 100644 --- a/generated_api_shadow/envoy/config/bootstrap/v2/bootstrap.proto +++ b/generated_api_shadow/envoy/config/bootstrap/v2/bootstrap.proto @@ -169,6 +169,9 @@ message Bootstrap { // when :ref:`dns_resolvers ` and // :ref:`use_tcp_for_dns_lookups ` are // specified. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple' API only uses UDP for DNS resolution. bool use_tcp_for_dns_lookups = 20; } diff --git a/generated_api_shadow/envoy/config/bootstrap/v3/bootstrap.proto b/generated_api_shadow/envoy/config/bootstrap/v3/bootstrap.proto index d7e6ff2bba2a..cce2dceb72d8 100644 --- a/generated_api_shadow/envoy/config/bootstrap/v3/bootstrap.proto +++ b/generated_api_shadow/envoy/config/bootstrap/v3/bootstrap.proto @@ -232,6 +232,9 @@ message Bootstrap { // when :ref:`dns_resolvers ` and // :ref:`use_tcp_for_dns_lookups ` are // specified. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple' API only uses UDP for DNS resolution. bool use_tcp_for_dns_lookups = 20; // Specifies optional bootstrap extensions to be instantiated at startup time. diff --git a/generated_api_shadow/envoy/config/bootstrap/v4alpha/bootstrap.proto b/generated_api_shadow/envoy/config/bootstrap/v4alpha/bootstrap.proto index ea900a0685e4..57f37e5ff6ee 100644 --- a/generated_api_shadow/envoy/config/bootstrap/v4alpha/bootstrap.proto +++ b/generated_api_shadow/envoy/config/bootstrap/v4alpha/bootstrap.proto @@ -233,6 +233,9 @@ message Bootstrap { // when :ref:`dns_resolvers ` and // :ref:`use_tcp_for_dns_lookups ` are // specified. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple' API only uses UDP for DNS resolution. bool use_tcp_for_dns_lookups = 20; // Specifies optional bootstrap extensions to be instantiated at startup time. diff --git a/generated_api_shadow/envoy/config/cluster/v3/cluster.proto b/generated_api_shadow/envoy/config/cluster/v3/cluster.proto index acf42f5140eb..481266cba383 100644 --- a/generated_api_shadow/envoy/config/cluster/v3/cluster.proto +++ b/generated_api_shadow/envoy/config/cluster/v3/cluster.proto @@ -827,10 +827,16 @@ message Cluster { // :ref:`STRICT_DNS` // and :ref:`LOGICAL_DNS` // this setting is ignored. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple's API only allows overriding DNS resolvers via system settings. repeated core.v3.Address dns_resolvers = 18; // [#next-major-version: Reconcile DNS options in a single message.] // Always use TCP queries instead of UDP queries for DNS lookups. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple' API only uses UDP for DNS resolution. bool use_tcp_for_dns_lookups = 45; // If specified, outlier detection will be enabled for this upstream cluster. diff --git a/generated_api_shadow/envoy/config/cluster/v4alpha/cluster.proto b/generated_api_shadow/envoy/config/cluster/v4alpha/cluster.proto index dd482b99a242..d0883e9c4629 100644 --- a/generated_api_shadow/envoy/config/cluster/v4alpha/cluster.proto +++ b/generated_api_shadow/envoy/config/cluster/v4alpha/cluster.proto @@ -839,10 +839,16 @@ message Cluster { // :ref:`STRICT_DNS` // and :ref:`LOGICAL_DNS` // this setting is ignored. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple's API only allows overriding DNS resolvers via system settings. repeated core.v4alpha.Address dns_resolvers = 18; // [#next-major-version: Reconcile DNS options in a single message.] // Always use TCP queries instead of UDP queries for DNS lookups. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple' API only uses UDP for DNS resolution. bool use_tcp_for_dns_lookups = 45; // If specified, outlier detection will be enabled for this upstream cluster. diff --git a/generated_api_shadow/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto b/generated_api_shadow/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto index d084cbac99de..5579cc16bd97 100644 --- a/generated_api_shadow/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto +++ b/generated_api_shadow/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto @@ -98,5 +98,8 @@ message DnsCacheConfig { // [#next-major-version: Reconcile DNS options in a single message.] // Always use TCP queries instead of UDP queries for DNS lookups. + // Setting this value causes failure if the + // ``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime value is true during + // server startup. Apple' API only uses UDP for DNS resolution. bool use_tcp_for_dns_lookups = 8; } diff --git a/source/common/event/BUILD b/source/common/event/BUILD index cf0ded8373e9..30dbf8de3339 100644 --- a/source/common/event/BUILD +++ b/source/common/event/BUILD @@ -30,11 +30,14 @@ envoy_cc_library( "//source/common/common:assert_lib", "//source/common/common:thread_lib", "//source/common/filesystem:watcher_lib", - "//source/common/network:connection_lib", "//source/common/network:dns_lib", + "//source/common/network:connection_lib", "//source/common/network:listener_lib", "//source/common/runtime:runtime_features_lib", - ], + ] + select({ + "//bazel:apple": ["//source/common/network:apple_dns_lib"], + "//conditions:default": [], + }), ) envoy_cc_library( diff --git a/source/common/event/dispatcher_impl.cc b/source/common/event/dispatcher_impl.cc index 3ce0a54fdfb0..612aef3b7c41 100644 --- a/source/common/event/dispatcher_impl.cc +++ b/source/common/event/dispatcher_impl.cc @@ -22,6 +22,7 @@ #include "common/network/dns_impl.h" #include "common/network/tcp_listener_impl.h" #include "common/network/udp_listener_impl.h" +#include "common/runtime/runtime_features.h" #include "event2/event.h" @@ -29,6 +30,10 @@ #include "common/signal/signal_action.h" #endif +#ifdef __APPLE__ +#include "common/network/apple_dns_impl.h" +#endif + namespace Envoy { namespace Event { @@ -121,6 +126,23 @@ Network::DnsResolverSharedPtr DispatcherImpl::createDnsResolver( const std::vector& resolvers, const bool use_tcp_for_dns_lookups) { ASSERT(isThreadSafe()); +#ifdef __APPLE__ + static bool use_apple_api_for_dns_lookups = + Runtime::runtimeFeatureEnabled("envoy.restart_features.use_apple_api_for_dns_lookups"); + if (use_apple_api_for_dns_lookups) { + RELEASE_ASSERT( + resolvers.empty(), + "defining custom resolvers is not possible when using Apple APIs for DNS resolution. " + "Apple's API only allows overriding DNS resolvers via system settings. Delete resolvers " + "config or disable the envoy.restart_features.use_apple_api_for_dns_lookups runtime " + "feature."); + RELEASE_ASSERT(!use_tcp_for_dns_lookups, + "using TCP for DNS lookups is not possible when using Apple APIs for DNS " + "resolution. Apple' API only uses UDP for DNS resolution. Use UDP or disable " + "the envoy.restart_features.use_apple_api_for_dns_lookups runtime feature."); + return Network::DnsResolverSharedPtr{new Network::AppleDnsResolverImpl(*this)}; + } +#endif return Network::DnsResolverSharedPtr{ new Network::DnsResolverImpl(*this, resolvers, use_tcp_for_dns_lookups)}; } diff --git a/source/common/network/BUILD b/source/common/network/BUILD index 6def5e024a4c..94d2d2051932 100644 --- a/source/common/network/BUILD +++ b/source/common/network/BUILD @@ -91,6 +91,27 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "apple_dns_lib", + srcs = select({ + "//bazel:apple": ["apple_dns_impl.cc"], + "//conditions:default": [], + }), + hdrs = select({ + "//bazel:apple": ["apple_dns_impl.h"], + "//conditions:default": [], + }), + deps = [ + ":address_lib", + ":utility_lib", + "//include/envoy/event:dispatcher_interface", + "//include/envoy/event:file_event_interface", + "//include/envoy/network:dns_interface", + "//source/common/common:assert_lib", + "//source/common/common:linked_object", + ], +) + envoy_cc_library( name = "dns_lib", srcs = ["dns_impl.cc"], diff --git a/source/common/network/apple_dns_impl.cc b/source/common/network/apple_dns_impl.cc new file mode 100644 index 000000000000..05e430076056 --- /dev/null +++ b/source/common/network/apple_dns_impl.cc @@ -0,0 +1,325 @@ +#include "common/network/apple_dns_impl.h" + +#include + +#include +#include +#include +#include +#include + +#include "envoy/common/platform.h" + +#include "common/common/assert.h" +#include "common/common/fmt.h" +#include "common/network/address_impl.h" +#include "common/network/utility.h" + +#include "absl/strings/str_join.h" + +namespace Envoy { +namespace Network { + +AppleDnsResolverImpl::AppleDnsResolverImpl(Event::Dispatcher& dispatcher) + : dispatcher_(dispatcher) { + ENVOY_LOG(debug, "Constructing DNS resolver"); + initializeMainSdRef(); +} + +AppleDnsResolverImpl::~AppleDnsResolverImpl() { + ENVOY_LOG(debug, "Destructing DNS resolver"); + deallocateMainSdRef(); +} + +void AppleDnsResolverImpl::deallocateMainSdRef() { + ENVOY_LOG(debug, "DNSServiceRefDeallocate main sd ref"); + // dns_sd.h says: + // If the reference's underlying socket is used in a run loop or select() call, it should + // be removed BEFORE DNSServiceRefDeallocate() is called, as this function closes the + // reference's socket. + sd_ref_event_.reset(); + DNSServiceRefDeallocate(main_sd_ref_); +} + +void AppleDnsResolverImpl::initializeMainSdRef() { + // This implementation uses a shared connection for three main reasons: + // 1. Efficiency of concurrent resolutions by sharing the same underlying UDS to the DNS + // server. + // 2. An error on a connection to the DNS server is good indication that other connections, + // even if not shared, would not succeed. So it is better to share one connection and + // promptly cancel all outstanding queries, rather than individually wait for all + // connections to error out. + // 3. It follows the precedent set in dns_impl with the c-ares library, for consistency of + // style, performance, and expectations between the two implementations. + // However, using a shared connection brings some complexities detailed in the inline comments + // for kDNSServiceFlagsShareConnection in dns_sd.h, and copied (and edited) in this implementation + // where relevant. + auto error = DNSServiceCreateConnection(&main_sd_ref_); + RELEASE_ASSERT(!error, "error in DNSServiceCreateConnection"); + + auto fd = DNSServiceRefSockFD(main_sd_ref_); + RELEASE_ASSERT(fd != -1, "error in DNSServiceRefSockFD"); + ENVOY_LOG(debug, "DNS resolver has fd={}", fd); + + sd_ref_event_ = dispatcher_.createFileEvent( + fd, + // note: Event::FileTriggerType::Level is used here to closely resemble the c-ares + // implementation in dns_impl.cc. + [this](uint32_t events) { onEventCallback(events); }, Event::FileTriggerType::Level, + Event::FileReadyType::Read); + sd_ref_event_->setEnabled(Event::FileReadyType::Read); +} + +void AppleDnsResolverImpl::onEventCallback(uint32_t events) { + ENVOY_LOG(debug, "DNS resolver file event"); + ASSERT(events & Event::FileReadyType::Read); + DNSServiceProcessResult(main_sd_ref_); +} + +ActiveDnsQuery* AppleDnsResolverImpl::resolve(const std::string& dns_name, + DnsLookupFamily dns_lookup_family, + ResolveCb callback) { + ENVOY_LOG(debug, "DNS resolver resolve={}", dns_name); + std::unique_ptr pending_resolution( + new PendingResolution(*this, callback, dispatcher_, main_sd_ref_, dns_name)); + + DNSServiceErrorType error = pending_resolution->dnsServiceGetAddrInfo(dns_lookup_family); + if (error != kDNSServiceErr_NoError) { + ENVOY_LOG(warn, "DNS resolver error in dnsServiceGetAddrInfo for {}", dns_name); + return nullptr; + } + + // If the query was synchronously resolved, there is no need to return the query. + if (pending_resolution->synchronously_completed_) { + return nullptr; + } + + pending_resolution->owned_ = true; + return pending_resolution.release(); +} + +void AppleDnsResolverImpl::addPendingQuery(PendingResolution* query) { + ASSERT(queries_with_pending_cb_.count(query) == 0); + queries_with_pending_cb_.insert(query); +} + +void AppleDnsResolverImpl::removePendingQuery(PendingResolution* query) { + auto erased = queries_with_pending_cb_.erase(query); + ASSERT(erased == 1); +} + +void AppleDnsResolverImpl::flushPendingQueries(const bool with_error) { + ENVOY_LOG(debug, "DNS Resolver flushing {} queries", queries_with_pending_cb_.size()); + for (std::set::iterator it = queries_with_pending_cb_.begin(); + it != queries_with_pending_cb_.end(); ++it) { + auto query = *it; + try { + ASSERT(query->pending_cb_); + query->callback_(query->pending_cb_->status_, std::move(query->pending_cb_->responses_)); + } catch (const std::exception& e) { + ENVOY_LOG(warn, "std::exception in DNSService callback: {}", e.what()); + throw EnvoyException(e.what()); + } catch (...) { + ENVOY_LOG(warn, "Unknown exception in DNSService callback"); + throw EnvoyException("unknown"); + } + + if (query->owned_) { + ENVOY_LOG(debug, "Resolution for {} completed (async)", query->dns_name_); + delete *it; + } else { + ENVOY_LOG(debug, "Resolution for {} completed (synchronously)", query->dns_name_); + query->synchronously_completed_ = true; + } + } + + // Purge the contents so no one tries to delete them again. + queries_with_pending_cb_.clear(); + + if (with_error) { + // The main sd ref is destroyed here because a callback with an error is good indication that + // the connection to the DNS server is faulty and needs to be torn down. + // + // Deallocation of the MainSdRef __has__ to happen __after__ flushing queries. Flushing queries + // de-allocates individual refs, so deallocating the main ref ahead would cause deallocation of + // invalid individual refs per dns_sd.h + deallocateMainSdRef(); + initializeMainSdRef(); + } +} + +AppleDnsResolverImpl::PendingResolution::~PendingResolution() { + ENVOY_LOG(debug, "Destroying PendingResolution for {}", dns_name_); + DNSServiceRefDeallocate(individual_sd_ref_); +} + +void AppleDnsResolverImpl::PendingResolution::cancel() { + ENVOY_LOG(debug, "Cancelling PendingResolution for {}", dns_name_); + ASSERT(owned_); + if (pending_cb_) { + /* (taken and edited from dns_sd.h) + * Canceling operations and kDNSServiceFlagsMoreComing + * Whenever you cancel any operation for which you had deferred [resolution] + * because of a kDNSServiceFlagsMoreComing flag, you should [flush]. This is because, after + * cancelling the operation, you can no longer wait for a callback *without* MoreComing set, to + * tell you [to flush] (the operation has been canceled, so there will be no more callbacks). + * + * [FURTHER] An implication of the collective + * kDNSServiceFlagsMoreComing flag for shared connections is that this + * guideline applies more broadly -- any time you cancel an operation on + * a shared connection, you should perform all deferred updates for all + * operations sharing that connection. This is because the MoreComing flag + * might have been referring to events coming for the operation you canceled, + * which will now not be coming because the operation has been canceled. + */ + // First, get rid of the current query, because if it is canceled, its callback should not be + // executed during the subsequent flush. + parent_.removePendingQuery(this); + // Then, flush all other queries. + parent_.flushPendingQueries(false /* with_error */); + } + // Because the query is self-owned, delete now. + delete this; +} + +void AppleDnsResolverImpl::PendingResolution::onDNSServiceGetAddrInfoReply( + DNSServiceFlags flags, uint32_t interface_index, DNSServiceErrorType error_code, + const char* hostname, const struct sockaddr* address, uint32_t ttl) { + ENVOY_LOG(debug, + "DNS for {} resolved with: flags={}[MoreComing={}, Add={}], interface_index={}, " + "error_code={}, hostname={}", + dns_name_, flags, flags & kDNSServiceFlagsMoreComing ? "yes" : "no", + flags & kDNSServiceFlagsAdd ? "yes" : "no", interface_index, error_code, hostname); + ASSERT(interface_index == 0); + + // Generic error handling. + if (error_code != kDNSServiceErr_NoError) { + // TODO(junr03): consider creating stats for known error types (timeout, refused connection, + // etc.). Currently a bit challenging because there is no scope access wired through. Current + // query gets a failure status + if (!pending_cb_) { + ENVOY_LOG(warn, "[Error path] Adding to queries pending callback"); + pending_cb_ = {ResolutionStatus::Failure, {}}; + parent_.addPendingQuery(this); + } else { + ENVOY_LOG(warn, "[Error path] Changing status for query already pending flush"); + pending_cb_->status_ = ResolutionStatus::Failure; + } + + ENVOY_LOG(warn, "[Error path] DNS Resolver flushing queries pending callback"); + parent_.flushPendingQueries(true /* with_error */); + // Note: Nothing can follow this call to flushPendingQueries due to deletion of this + // object upon resolution. + return; + } + + // Only add this address to the list if kDNSServiceFlagsAdd is set. Callback targets are purely + // additive. + if (flags & kDNSServiceFlagsAdd) { + auto dns_response = buildDnsResponse(address, ttl); + ENVOY_LOG(debug, "Address to add address={}, ttl={}", + dns_response.address_->ip()->addressAsString(), ttl); + + if (!pending_cb_) { + ENVOY_LOG(debug, "Adding to queries pending callback"); + pending_cb_ = {ResolutionStatus::Success, {dns_response}}; + parent_.addPendingQuery(this); + } else { + ENVOY_LOG(debug, "New address for query already pending flush"); + pending_cb_->responses_.push_back(dns_response); + } + } + + if (!(flags & kDNSServiceFlagsMoreComing)) { + /* (taken and edited from dns_sd.h) + * Collective kDNSServiceFlagsMoreComing flag: + * When [DNSServiceGetAddrInfoReply] are invoked using a shared DNSServiceRef, the + * kDNSServiceFlagsMoreComing flag applies collectively to *all* active + * operations sharing the same [main_sd_ref]. If the MoreComing flag is + * set it means that there are more results queued on this parent DNSServiceRef, + * but not necessarily more results for this particular callback function. + * The implication of this for client programmers is that when a callback + * is invoked with the MoreComing flag set, the code should update its + * internal data structures with the new result (as is done above when calling + * parent_.addPendingQuery(this))...Then, later when a callback is eventually invoked with the + * MoreComing flag not set, the code should update *all* [pending queries] related to that + * shared parent DNSServiceRef that need updating (i.e that have had DNSServiceGetAddrInfoReply + * called on them since the last flush), not just the [queries] related to the particular + * callback that happened to be the last one to be invoked. + */ + ENVOY_LOG(debug, "DNS Resolver flushing queries pending callback"); + parent_.flushPendingQueries(false /* with_error */); + // Note: Nothing can follow this call to flushPendingQueries due to deletion of this + // object upon resolution. + return; + } +} + +DNSServiceErrorType +AppleDnsResolverImpl::PendingResolution::dnsServiceGetAddrInfo(DnsLookupFamily dns_lookup_family) { + DNSServiceProtocol protocol; + switch (dns_lookup_family) { + case DnsLookupFamily::V4Only: + protocol = kDNSServiceProtocol_IPv4; + break; + case DnsLookupFamily::V6Only: + protocol = kDNSServiceProtocol_IPv6; + break; + case DnsLookupFamily::Auto: + protocol = kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6; + break; + } + + // TODO: explore caching: there are caching flags in the dns_sd.h flags, allow expired answers + // from the cache? + // TODO: explore validation via DNSSEC? + return DNSServiceGetAddrInfo( + &individual_sd_ref_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, protocol, + dns_name_.c_str(), + /* + * About Thread Safety (taken from inline documentation there): + * The dns_sd.h API does not presuppose any particular threading model, and consequently + * does no locking internally (which would require linking with a specific threading library). + * If the client concurrently, from multiple threads (or contexts), calls API routines using + * the same DNSServiceRef, it is the client's responsibility to provide mutual exclusion for + * that DNSServiceRef. + */ + + // Therefore, much like the c-ares implementation All calls and callbacks to the API need to + // happen on the thread that owns the creating dispatcher. This is the case as callbacks are + // driven by processing bytes in onEventCallback which run on the passed in dispatcher's event + // loop. + [](DNSServiceRef, DNSServiceFlags flags, uint32_t interface_index, + DNSServiceErrorType error_code, const char* hostname, const struct sockaddr* address, + uint32_t ttl, void* context) { + static_cast(context)->onDNSServiceGetAddrInfoReply( + flags, interface_index, error_code, hostname, address, ttl); + }, + this); +} + +DnsResponse +AppleDnsResolverImpl::PendingResolution::buildDnsResponse(const struct sockaddr* address, + uint32_t ttl) { + switch (address->sa_family) { + case AF_INET: + sockaddr_in address_in; + memset(&address_in, 0, sizeof(address_in)); + address_in.sin_family = AF_INET; + address_in.sin_port = 0; + address_in.sin_addr = reinterpret_cast(address)->sin_addr; + return {std::make_shared(&address_in), std::chrono::seconds(ttl)}; + case AF_INET6: + sockaddr_in6 address_in6; + memset(&address_in6, 0, sizeof(address_in6)); + address_in6.sin6_family = AF_INET6; + address_in6.sin6_port = 0; + address_in6.sin6_addr = reinterpret_cast(address)->sin6_addr; + return {std::make_shared(address_in6), std::chrono::seconds(ttl)}; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } +} + +} // namespace Network +} // namespace Envoy diff --git a/source/common/network/apple_dns_impl.h b/source/common/network/apple_dns_impl.h new file mode 100644 index 000000000000..ff805ce796c4 --- /dev/null +++ b/source/common/network/apple_dns_impl.h @@ -0,0 +1,108 @@ +#pragma once + +#include + +#include +#include + +#include "envoy/common/platform.h" +#include "envoy/event/dispatcher.h" +#include "envoy/event/file_event.h" +#include "envoy/network/dns.h" + +#include "common/common/linked_object.h" +#include "common/common/logger.h" +#include "common/common/utility.h" + +#include "absl/container/node_hash_map.h" + +namespace Envoy { +namespace Network { + +/** + * Implementation of DnsResolver that uses Apple dns_sd.h APIs. All calls and callbacks are assumed + * to happen on the thread that owns the creating dispatcher. + */ +class AppleDnsResolverImpl : public DnsResolver, protected Logger::Loggable { +public: + AppleDnsResolverImpl(Event::Dispatcher& dispatcher); + ~AppleDnsResolverImpl() override; + + // Network::DnsResolver + ActiveDnsQuery* resolve(const std::string& dns_name, DnsLookupFamily dns_lookup_family, + ResolveCb callback) override; + +private: + struct PendingResolution : public ActiveDnsQuery { + PendingResolution(AppleDnsResolverImpl& parent, ResolveCb callback, + Event::Dispatcher& dispatcher, DNSServiceRef sd_ref, + const std::string& dns_name) + : parent_(parent), callback_(callback), dispatcher_(dispatcher), + /* (taken and edited from dns_sd.h): + * For efficiency, clients that perform many concurrent operations may want to use a + * single Unix Domain Socket connection with the background daemon, instead of having a + * separate connection for each independent operation. To use this mode, clients first + * call DNSServiceCreateConnection(&SharedRef) to initialize the main DNSServiceRef. + * For each subsequent operation that is to share that same connection, the client copies + * the SharedRef, and then passes the address of that copy, setting the ShareConnection + * flag to tell the library that this DNSServiceRef is not a typical uninitialized + * DNSServiceRef; it's a copy of an existing DNSServiceRef whose connection information + * should be reused. + */ + individual_sd_ref_(sd_ref), dns_name_(dns_name) {} + ~PendingResolution(); + + // Network::ActiveDnsQuery + void cancel() override; + + static DnsResponse buildDnsResponse(const struct sockaddr* address, uint32_t ttl); + // Wrapper for the API call. + DNSServiceErrorType dnsServiceGetAddrInfo(DnsLookupFamily dns_lookup_family); + // Wrapper for the API callback. + void onDNSServiceGetAddrInfoReply(DNSServiceFlags flags, uint32_t interface_index, + DNSServiceErrorType error_code, const char* hostname, + const struct sockaddr* address, uint32_t ttl); + + // Small wrapping struct to accumulate addresses from firings of the + // onDNSServiceGetAddrInfoReply callback. + struct FinalResponse { + ResolutionStatus status_; + std::list responses_; + }; + + AppleDnsResolverImpl& parent_; + // Caller supplied callback to invoke on query completion or error. + const ResolveCb callback_; + // Dispatcher to post any callback_ exceptions to. + Event::Dispatcher& dispatcher_; + DNSServiceRef individual_sd_ref_; + const std::string dns_name_; + bool synchronously_completed_{}; + bool owned_{}; + // DNSServiceGetAddrInfo fires one callback DNSServiceGetAddrInfoReply callback per IP address, + // and informs via flags if more IP addresses are incoming. Therefore, these addresses need to + // be accumulated before firing callback_. + absl::optional pending_cb_{}; + }; + + void initializeMainSdRef(); + void deallocateMainSdRef(); + void onEventCallback(uint32_t events); + void addPendingQuery(PendingResolution* query); + void removePendingQuery(PendingResolution* query); + void flushPendingQueries(const bool with_error); + + Event::Dispatcher& dispatcher_; + DNSServiceRef main_sd_ref_; + Event::FileEventPtr sd_ref_event_; + // When using a shared sd ref via DNSServiceCreateConnection, the DNSServiceGetAddrInfoReply + // callback with the kDNSServiceFlagsMoreComing flag might refer to addresses for various + // PendingResolutions. Therefore, the resolver needs to have a container of queries pending + // calling their own callback_s until a DNSServiceGetAddrInfoReply is called with + // kDNSServiceFlagsMoreComing not set or an error status is received in + // DNSServiceGetAddrInfoReply. + std::set queries_with_pending_cb_; +}; + +} // namespace Network +} // namespace Envoy diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 173286c1489b..56a7eb8189cb 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -90,6 +90,7 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.strict_1xx_and_204_response_headers", "envoy.reloadable_features.tls_use_io_handle_bio", "envoy.reloadable_features.unify_grpc_handling", + "envoy.restart_features.use_apple_api_for_dns_lookups", }; // This is a section for officially sanctioned runtime features which are too diff --git a/test/common/network/BUILD b/test/common/network/BUILD index a053e00cfb8d..d173d8195e7b 100644 --- a/test/common/network/BUILD +++ b/test/common/network/BUILD @@ -95,9 +95,46 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "apple_dns_impl_test", + srcs = select({ + "//bazel:apple": ["apple_dns_impl_test.cc"], + "//conditions:default": [], + }), + deps = [ + "//include/envoy/event:dispatcher_interface", + "//include/envoy/network:address_interface", + "//include/envoy/network:dns_interface", + "//source/common/buffer:buffer_lib", + "//source/common/event:dispatcher_includes", + "//source/common/event:dispatcher_lib", + "//source/common/network:address_lib", + "//source/common/network:filter_lib", + "//source/common/network:listen_socket_lib", + "//source/common/stats:stats_lib", + "//source/common/stream_info:stream_info_lib", + "//test/mocks/network:network_mocks", + "//test/test_common:environment_lib", + "//test/test_common:network_utility_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "//test/mocks/local_info:local_info_mocks", + "//test/mocks/protobuf:protobuf_mocks", + "//test/mocks/runtime:runtime_mocks", + "//test/mocks/thread_local:thread_local_mocks", + ] + select({ + "//bazel:apple": ["//source/common/network:dns_lib"], + "//conditions:default": [], + }), +) + envoy_cc_test( name = "dns_impl_test", srcs = ["dns_impl_test.cc"], + args = [ + # Used in createDnsResolver to force creation of DnsResolverImpl when running test on macOS. + "--runtime-feature-disable-for-tests=envoy.restart_features.use_apple_api_for_dns_lookups", + ], deps = [ "//include/envoy/event:dispatcher_interface", "//include/envoy/network:address_interface", diff --git a/test/common/network/apple_dns_impl_test.cc b/test/common/network/apple_dns_impl_test.cc new file mode 100644 index 000000000000..fb0a20bb546d --- /dev/null +++ b/test/common/network/apple_dns_impl_test.cc @@ -0,0 +1,199 @@ +#include +#include +#include +#include + +#include "envoy/common/platform.h" +#include "envoy/config/core/v3/address.pb.h" +#include "envoy/event/dispatcher.h" +#include "envoy/network/address.h" +#include "envoy/network/dns.h" + +#include "common/buffer/buffer_impl.h" +#include "common/common/utility.h" +#include "common/event/dispatcher_impl.h" +#include "common/network/address_impl.h" +#include "common/network/apple_dns_impl.h" +#include "common/network/filter_impl.h" +#include "common/network/listen_socket_impl.h" +#include "common/network/utility.h" +#include "common/runtime/runtime_impl.h" +#include "common/stream_info/stream_info_impl.h" + +#include "test/mocks/common.h" +#include "test/mocks/local_info/mocks.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/protobuf/mocks.h" +#include "test/mocks/runtime/mocks.h" +#include "test/mocks/thread_local/mocks.h" +#include "test/test_common/environment.h" +#include "test/test_common/network_utility.h" +#include "test/test_common/printers.h" +#include "test/test_common/utility.h" + +#include "absl/container/fixed_array.h" +#include "absl/container/node_hash_map.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::Contains; +using testing::InSequence; +using testing::IsSupersetOf; +using testing::NiceMock; +using testing::Not; +using testing::Return; + +namespace Envoy { +namespace Network { +namespace { + +// Note: this test suite is, unfortunately, not hermetic. Apple's APIs do not allow overriding the +// IP address used for resolution via API calls (only in system settings), and worse +// yet does not allow overriding the port number used _at all_. Therefore, the tests do not use a +// test DNS server like in dns_impl_test, and thus affords less flexibility in testing scenarios: no +// concurrent requests, no expressive error responses, etc. Further experiments could be done in +// order to create a test connection that is reachable locally (potentially by binding port 53 -- +// default for DNS). However, @junr03's initial attempts were not successful. +class AppleDnsImplTest : public testing::Test { +public: + AppleDnsImplTest() + : api_(Api::createApiForTest()), dispatcher_(api_->allocateDispatcher("test_thread")) {} + + void SetUp() override { resolver_ = dispatcher_->createDnsResolver({}, false); } + + ActiveDnsQuery* resolveWithExpectations(const std::string& address, + const DnsLookupFamily lookup_family, + const DnsResolver::ResolutionStatus expected_status, + const bool expected_results) { + return resolver_->resolve( + address, lookup_family, + [=](DnsResolver::ResolutionStatus status, std::list&& results) -> void { + EXPECT_EQ(expected_status, status); + if (expected_results) { + EXPECT_FALSE(results.empty()); + for (const auto& result : results) { + if (lookup_family == DnsLookupFamily::V4Only) { + EXPECT_NE(nullptr, result.address_->ip()->ipv4()); + } else if (lookup_family == DnsLookupFamily::V6Only) { + EXPECT_NE(nullptr, result.address_->ip()->ipv6()); + } + } + } + dispatcher_->exit(); + }); + } + + ActiveDnsQuery* resolveWithUnreferencedParameters(const std::string& address, + const DnsLookupFamily lookup_family, + bool expected_to_execute) { + return resolver_->resolve(address, lookup_family, + [expected_to_execute](DnsResolver::ResolutionStatus status, + std::list&& results) -> void { + if (!expected_to_execute) { + FAIL(); + } + UNREFERENCED_PARAMETER(status); + UNREFERENCED_PARAMETER(results); + }); + } + + template + ActiveDnsQuery* resolveWithException(const std::string& address, + const DnsLookupFamily lookup_family, T exception_object) { + return resolver_->resolve(address, lookup_family, + [exception_object](DnsResolver::ResolutionStatus status, + std::list&& results) -> void { + UNREFERENCED_PARAMETER(status); + UNREFERENCED_PARAMETER(results); + throw exception_object; + }); + } + +protected: + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; + DnsResolverSharedPtr resolver_; +}; + +TEST_F(AppleDnsImplTest, InvalidConfigOptions) { + EXPECT_DEATH( + dispatcher_->createDnsResolver({}, true), + "using TCP for DNS lookups is not possible when using Apple APIs for DNS resolution"); + EXPECT_DEATH( + dispatcher_->createDnsResolver({nullptr}, false), + "defining custom resolvers is not possible when using Apple APIs for DNS resolution"); +} + +// Validate that when AppleDnsResolverImpl is destructed with outstanding requests, +// that we don't invoke any callbacks if the query was cancelled. This is a regression test from +// development, where segfaults were encountered due to callback invocations on +// destruction. +TEST_F(AppleDnsImplTest, DestructPending) { + ActiveDnsQuery* query = resolveWithUnreferencedParameters("", DnsLookupFamily::V4Only, 0); + ASSERT_NE(nullptr, query); + query->cancel(); +} + +TEST_F(AppleDnsImplTest, LocalLookup) { + EXPECT_NE(nullptr, resolveWithExpectations("localhost", DnsLookupFamily::Auto, + DnsResolver::ResolutionStatus::Success, true)); +} + +TEST_F(AppleDnsImplTest, DnsIpAddressVersion) { + EXPECT_NE(nullptr, resolveWithExpectations("google.com", DnsLookupFamily::Auto, + DnsResolver::ResolutionStatus::Success, true)); + dispatcher_->run(Event::Dispatcher::RunType::Block); + + EXPECT_NE(nullptr, resolveWithExpectations("google.com", DnsLookupFamily::V4Only, + DnsResolver::ResolutionStatus::Success, true)); + dispatcher_->run(Event::Dispatcher::RunType::Block); + + EXPECT_NE(nullptr, resolveWithExpectations("google.com", DnsLookupFamily::V6Only, + DnsResolver::ResolutionStatus::Success, true)); + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + +TEST_F(AppleDnsImplTest, CallbackException) { + EXPECT_NE(nullptr, resolveWithException("1.2.3.4", DnsLookupFamily::V4Only, + EnvoyException("Envoy exception"))); + EXPECT_THROW_WITH_MESSAGE(dispatcher_->run(Event::Dispatcher::RunType::Block), EnvoyException, + "Envoy exception"); +} + +TEST_F(AppleDnsImplTest, CallbackException2) { + EXPECT_NE(nullptr, resolveWithException("1.2.3.4", DnsLookupFamily::V4Only, + std::runtime_error("runtime error"))); + EXPECT_THROW_WITH_MESSAGE(dispatcher_->run(Event::Dispatcher::RunType::Block), EnvoyException, + "runtime error"); +} + +TEST_F(AppleDnsImplTest, CallbackException3) { + EXPECT_NE(nullptr, + resolveWithException("1.2.3.4", DnsLookupFamily::V4Only, std::string())); + EXPECT_THROW_WITH_MESSAGE(dispatcher_->run(Event::Dispatcher::RunType::Block), EnvoyException, + "unknown"); +} + +// Validate working of cancellation provided by ActiveDnsQuery return. +TEST_F(AppleDnsImplTest, Cancel) { + ActiveDnsQuery* query = + resolveWithUnreferencedParameters("some.domain", DnsLookupFamily::Auto, false); + + EXPECT_NE(nullptr, resolveWithExpectations("google.com", DnsLookupFamily::Auto, + DnsResolver::ResolutionStatus::Success, true)); + + ASSERT_NE(nullptr, query); + query->cancel(); + + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + +TEST_F(AppleDnsImplTest, Timeout) { + EXPECT_NE(nullptr, resolveWithExpectations("some.domain", DnsLookupFamily::V6Only, + DnsResolver::ResolutionStatus::Failure, false)); + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + +} // namespace +} // namespace Network +} // namespace Envoy diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 12cf11d0bf2b..f27c193d401f 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -71,6 +71,7 @@ DFATAL DGRAM DLOG DNS +DNSSEC DQUOTE DRYs DS @@ -1018,6 +1019,7 @@ sched schedulable schemas scopekey +sd secp sendmsg sendmmsg