LCOV - code coverage report
Current view: top level - source/extensions/common/dynamic_forward_proxy - dns_cache_impl.cc (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 17 431 3.9 %
Date: 2024-01-05 06:35:25 Functions: 3 42 7.1 %

          Line data    Source code
       1             : #include "source/extensions/common/dynamic_forward_proxy/dns_cache_impl.h"
       2             : 
       3             : #include "envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.pb.h"
       4             : 
       5             : #include "source/common/common/dns_utils.h"
       6             : #include "source/common/common/stl_helpers.h"
       7             : #include "source/common/config/utility.h"
       8             : #include "source/common/http/utility.h"
       9             : #include "source/common/network/dns_resolver/dns_factory_util.h"
      10             : #include "source/common/network/resolver_impl.h"
      11             : #include "source/common/network/utility.h"
      12             : #include "source/common/runtime/runtime_features.h"
      13             : 
      14             : namespace Envoy {
      15             : namespace Extensions {
      16             : namespace Common {
      17             : namespace DynamicForwardProxy {
      18             : 
      19             : absl::StatusOr<std::shared_ptr<DnsCacheImpl>> DnsCacheImpl::createDnsCacheImpl(
      20             :     Server::Configuration::GenericFactoryContext& context,
      21           5 :     const envoy::extensions::common::dynamic_forward_proxy::v3::DnsCacheConfig& config) {
      22           5 :   const uint32_t max_hosts = PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_hosts, 1024);
      23           5 :   if (static_cast<size_t>(config.preresolve_hostnames().size()) > max_hosts) {
      24           0 :     return absl::InvalidArgumentError(fmt::format(
      25           0 :         "DNS Cache [{}] configured with preresolve_hostnames={} larger than max_hosts={}",
      26           0 :         config.name(), config.preresolve_hostnames().size(), max_hosts));
      27           0 :   }
      28             : 
      29           5 :   return std::shared_ptr<DnsCacheImpl>(new DnsCacheImpl(context, config));
      30           5 : }
      31             : 
      32             : DnsCacheImpl::DnsCacheImpl(
      33             :     Server::Configuration::GenericFactoryContext& context,
      34             :     const envoy::extensions::common::dynamic_forward_proxy::v3::DnsCacheConfig& config)
      35             :     : main_thread_dispatcher_(context.serverFactoryContext().mainThreadDispatcher()),
      36             :       config_(config), random_generator_(context.serverFactoryContext().api().randomGenerator()),
      37             :       dns_lookup_family_(DnsUtils::getDnsLookupFamilyFromEnum(config.dns_lookup_family())),
      38             :       resolver_(selectDnsResolver(config, main_thread_dispatcher_, context.serverFactoryContext())),
      39             :       tls_slot_(context.serverFactoryContext().threadLocal()),
      40             :       scope_(context.scope().createScope(fmt::format("dns_cache.{}.", config.name()))),
      41             :       stats_(generateDnsCacheStats(*scope_)),
      42             :       resource_manager_(*scope_, context.serverFactoryContext().runtime(), config.name(),
      43             :                         config.dns_cache_circuit_breaker()),
      44             :       refresh_interval_(PROTOBUF_GET_MS_OR_DEFAULT(config, dns_refresh_rate, 60000)),
      45             :       min_refresh_interval_(PROTOBUF_GET_MS_OR_DEFAULT(config, dns_min_refresh_rate, 5000)),
      46             :       timeout_interval_(PROTOBUF_GET_MS_OR_DEFAULT(config, dns_query_timeout, 5000)),
      47             :       file_system_(context.serverFactoryContext().api().fileSystem()),
      48             :       validation_visitor_(context.messageValidationVisitor()),
      49             :       host_ttl_(PROTOBUF_GET_MS_OR_DEFAULT(config, host_ttl, 300000)),
      50           5 :       max_hosts_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_hosts, 1024)) {
      51           5 :   tls_slot_.set([&](Event::Dispatcher&) { return std::make_shared<ThreadLocalHostInfo>(*this); });
      52             : 
      53           5 :   loadCacheEntries(config);
      54             : 
      55             :   // Preresolved hostnames are resolved without a read lock on primary hosts because it is done
      56             :   // during object construction.
      57           5 :   for (const auto& hostname : config.preresolve_hostnames()) {
      58             :     // No need to get a resolution handle on this resolution as the only outcome needed is for the
      59             :     // cache to load an entry. Further if this particular resolution fails all the is lost is the
      60             :     // potential optimization of having the entry be preresolved the first time a true consumer of
      61             :     // this DNS cache asks for it.
      62           0 :     const std::string host =
      63           0 :         (Runtime::runtimeFeatureEnabled(
      64           0 :             "envoy.reloadable_features.normalize_host_for_preresolve_dfp_dns"))
      65           0 :             ? DnsHostInfo::normalizeHostForDfp(hostname.address(), hostname.port_value())
      66           0 :             : hostname.address();
      67           0 :     ENVOY_LOG(debug, "DNS pre-resolve starting for host {}", host);
      68           0 :     startCacheLoad(host, hostname.port_value(), false);
      69           0 :   }
      70           5 : }
      71             : 
      72           0 : DnsCacheImpl::~DnsCacheImpl() {
      73           0 :   for (const auto& primary_host : primary_hosts_) {
      74           0 :     if (primary_host.second->active_query_ != nullptr) {
      75           0 :       primary_host.second->active_query_->cancel(
      76           0 :           Network::ActiveDnsQuery::CancelReason::QueryAbandoned);
      77           0 :     }
      78           0 :   }
      79             : 
      80           0 :   for (auto update_callbacks : update_callbacks_) {
      81           0 :     update_callbacks->cancel();
      82           0 :   }
      83           0 : }
      84             : 
      85             : Network::DnsResolverSharedPtr DnsCacheImpl::selectDnsResolver(
      86             :     const envoy::extensions::common::dynamic_forward_proxy::v3::DnsCacheConfig& config,
      87             :     Event::Dispatcher& main_thread_dispatcher,
      88           5 :     Server::Configuration::CommonFactoryContext& context) {
      89           5 :   envoy::config::core::v3::TypedExtensionConfig typed_dns_resolver_config;
      90           5 :   Network::DnsResolverFactory& dns_resolver_factory =
      91           5 :       Network::createDnsResolverFactoryFromProto(config, typed_dns_resolver_config);
      92           5 :   return dns_resolver_factory.createDnsResolver(main_thread_dispatcher, context.api(),
      93           5 :                                                 typed_dns_resolver_config);
      94           5 : }
      95             : 
      96           0 : DnsCacheStats DnsCacheImpl::generateDnsCacheStats(Stats::Scope& scope) {
      97           0 :   return {ALL_DNS_CACHE_STATS(POOL_COUNTER(scope), POOL_GAUGE(scope))};
      98           0 : }
      99             : 
     100             : DnsCacheImpl::LoadDnsCacheEntryResult
     101             : DnsCacheImpl::loadDnsCacheEntry(absl::string_view raw_host, uint16_t default_port,
     102           0 :                                 bool is_proxy_lookup, LoadDnsCacheEntryCallbacks& callbacks) {
     103           0 :   std::string host = DnsHostInfo::normalizeHostForDfp(raw_host, default_port);
     104             : 
     105           0 :   ENVOY_LOG(debug, "thread local lookup for host '{}' {}", host,
     106           0 :             is_proxy_lookup ? "proxy mode " : "");
     107           0 :   ThreadLocalHostInfo& tls_host_info = *tls_slot_;
     108             : 
     109           0 :   auto [is_overflow, host_info] = [&]() {
     110           0 :     absl::ReaderMutexLock read_lock{&primary_hosts_lock_};
     111           0 :     auto tls_host = primary_hosts_.find(host);
     112           0 :     return std::make_tuple(
     113           0 :         primary_hosts_.size() >= max_hosts_,
     114           0 :         (tls_host != primary_hosts_.end() && tls_host->second->host_info_->firstResolveComplete())
     115           0 :             ? absl::optional<DnsHostInfoSharedPtr>(tls_host->second->host_info_)
     116           0 :             : absl::nullopt);
     117           0 :   }();
     118             : 
     119           0 :   if (host_info) {
     120           0 :     ENVOY_LOG(debug, "cache hit for host '{}'", host);
     121           0 :     return {LoadDnsCacheEntryStatus::InCache, nullptr, host_info};
     122           0 :   } else if (is_overflow) {
     123           0 :     ENVOY_LOG(debug, "DNS cache overflow for host '{}'", host);
     124           0 :     stats_.host_overflow_.inc();
     125           0 :     return {LoadDnsCacheEntryStatus::Overflow, nullptr, absl::nullopt};
     126           0 :   } else {
     127           0 :     ENVOY_LOG(debug, "cache miss for host '{}', posting to main thread", host);
     128           0 :     main_thread_dispatcher_.post([this, host = std::string(host), default_port, is_proxy_lookup]() {
     129           0 :       startCacheLoad(host, default_port, is_proxy_lookup);
     130           0 :     });
     131           0 :     return {LoadDnsCacheEntryStatus::Loading,
     132           0 :             std::make_unique<LoadDnsCacheEntryHandleImpl>(tls_host_info.pending_resolutions_, host,
     133           0 :                                                           callbacks),
     134           0 :             absl::nullopt};
     135           0 :   }
     136           0 : }
     137             : 
     138           0 : Upstream::ResourceAutoIncDecPtr DnsCacheImpl::canCreateDnsRequest() {
     139           0 :   auto& current_pending_requests = resource_manager_.pendingRequests();
     140           0 :   if (!current_pending_requests.canCreate()) {
     141           0 :     stats_.dns_rq_pending_overflow_.inc();
     142           0 :     return nullptr;
     143           0 :   }
     144           0 :   return std::make_unique<Upstream::ResourceAutoIncDec>(current_pending_requests);
     145           0 : }
     146             : 
     147           0 : void DnsCacheImpl::iterateHostMap(IterateHostMapCb iterate_callback) {
     148           0 :   absl::ReaderMutexLock reader_lock{&primary_hosts_lock_};
     149           0 :   for (const auto& host : primary_hosts_) {
     150             :     // Only include hosts that have ever resolved to an address.
     151           0 :     if (host.second->host_info_->address() != nullptr) {
     152           0 :       iterate_callback(host.first, host.second->host_info_);
     153           0 :     }
     154           0 :   }
     155           0 : }
     156             : 
     157           0 : absl::optional<const DnsHostInfoSharedPtr> DnsCacheImpl::getHost(absl::string_view host_name) {
     158             :   // Find a host with the given name.
     159           0 :   const auto host_info = [&]() -> const DnsHostInfoSharedPtr {
     160           0 :     absl::ReaderMutexLock reader_lock{&primary_hosts_lock_};
     161           0 :     auto it = primary_hosts_.find(host_name);
     162           0 :     return it != primary_hosts_.end() ? it->second->host_info_ : nullptr;
     163           0 :   }();
     164             : 
     165             :   // Only include hosts that have ever resolved to an address.
     166           0 :   if (!host_info || host_info->address() == nullptr) {
     167           0 :     return {};
     168           0 :   } else {
     169           0 :     return host_info;
     170           0 :   }
     171           0 : }
     172             : 
     173             : DnsCacheImpl::AddUpdateCallbacksHandlePtr
     174           0 : DnsCacheImpl::addUpdateCallbacks(UpdateCallbacks& callbacks) {
     175           0 :   return std::make_unique<AddUpdateCallbacksHandleImpl>(update_callbacks_, callbacks);
     176           0 : }
     177             : 
     178             : void DnsCacheImpl::startCacheLoad(const std::string& host, uint16_t default_port,
     179           0 :                                   bool is_proxy_lookup) {
     180           0 :   ASSERT(main_thread_dispatcher_.isThreadSafe());
     181             : 
     182             :   // It's possible for multiple requests to race trying to start a resolution. If a host is
     183             :   // already in the map it's either in the process of being resolved or the resolution is already
     184             :   // heading out to the worker threads. Either way the pending resolution will be completed.
     185             : 
     186             :   // Functions like this one that modify primary_hosts_ are only called in the main thread so we
     187             :   // know it is safe to use the PrimaryHostInfo pointers outside of the lock.
     188           0 :   auto* primary_host = [&]() {
     189           0 :     absl::ReaderMutexLock reader_lock{&primary_hosts_lock_};
     190           0 :     auto host_it = primary_hosts_.find(host);
     191           0 :     return host_it != primary_hosts_.end() ? host_it->second.get() : nullptr;
     192           0 :   }();
     193             : 
     194           0 :   if (primary_host) {
     195           0 :     ENVOY_LOG(debug, "main thread resolve for host '{}' skipped. Entry present", host);
     196           0 :     return;
     197           0 :   }
     198             : 
     199           0 :   primary_host = createHost(host, default_port);
     200             :   // If the DNS request was simply to create a host endpoint in a Dynamic Forward Proxy cluster,
     201             :   // fast fail the look-up as the address is not needed.
     202           0 :   if (is_proxy_lookup) {
     203           0 :     finishResolve(host, Network::DnsResolver::ResolutionStatus::Success, {}, {}, true);
     204           0 :   } else {
     205           0 :     startResolve(host, *primary_host);
     206           0 :   }
     207           0 : }
     208             : 
     209             : DnsCacheImpl::PrimaryHostInfo* DnsCacheImpl::createHost(const std::string& host,
     210           0 :                                                         uint16_t default_port) {
     211           0 :   const auto host_attributes = Http::Utility::parseAuthority(host);
     212             :   // TODO(mattklein123): Right now, the same host with different ports will become two
     213             :   // independent primary hosts with independent DNS resolutions. I'm not sure how much this will
     214             :   // matter, but we could consider collapsing these down and sharing the underlying DNS resolution.
     215           0 :   {
     216           0 :     absl::WriterMutexLock writer_lock{&primary_hosts_lock_};
     217           0 :     return primary_hosts_
     218             :         // try_emplace() is used here for direct argument forwarding.
     219           0 :         .try_emplace(host,
     220           0 :                      std::make_unique<PrimaryHostInfo>(
     221           0 :                          *this, std::string(host_attributes.host_),
     222           0 :                          host_attributes.port_.value_or(default_port),
     223           0 :                          host_attributes.is_ip_address_, [this, host]() { onReResolve(host); },
     224           0 :                          [this, host]() { onResolveTimeout(host); }))
     225           0 :         .first->second.get();
     226           0 :   }
     227           0 : }
     228             : 
     229           0 : DnsCacheImpl::PrimaryHostInfo& DnsCacheImpl::getPrimaryHost(const std::string& host) {
     230             :   // Functions modify primary_hosts_ are only called in the main thread so we
     231             :   // know it is safe to use the PrimaryHostInfo pointers outside of the lock.
     232           0 :   ASSERT(main_thread_dispatcher_.isThreadSafe());
     233           0 :   absl::ReaderMutexLock reader_lock{&primary_hosts_lock_};
     234           0 :   const auto primary_host_it = primary_hosts_.find(host);
     235           0 :   ASSERT(primary_host_it != primary_hosts_.end());
     236           0 :   return *(primary_host_it->second);
     237           0 : }
     238             : 
     239           0 : void DnsCacheImpl::onResolveTimeout(const std::string& host) {
     240           0 :   ASSERT(main_thread_dispatcher_.isThreadSafe());
     241             : 
     242           0 :   auto& primary_host = getPrimaryHost(host);
     243           0 :   ENVOY_LOG_EVENT(debug, "dns_cache_resolve_timeout", "host='{}' resolution timeout", host);
     244           0 :   stats_.dns_query_timeout_.inc();
     245           0 :   primary_host.active_query_->cancel(Network::ActiveDnsQuery::CancelReason::Timeout);
     246           0 :   finishResolve(host, Network::DnsResolver::ResolutionStatus::Failure, {});
     247           0 : }
     248             : 
     249           0 : void DnsCacheImpl::onReResolve(const std::string& host) {
     250           0 :   ASSERT(main_thread_dispatcher_.isThreadSafe());
     251             :   // If we need to erase the host, hold onto the PrimaryHostInfo object that owns this callback.
     252             :   // This is defined at function scope so that it is only erased on function exit to avoid
     253             :   // use-after-free issues
     254           0 :   PrimaryHostInfoPtr host_to_erase;
     255             : 
     256           0 :   auto& primary_host = getPrimaryHost(host);
     257           0 :   const std::chrono::steady_clock::duration now_duration =
     258           0 :       main_thread_dispatcher_.timeSource().monotonicTime().time_since_epoch();
     259           0 :   auto last_used_time = primary_host.host_info_->lastUsedTime();
     260           0 :   ENVOY_LOG(debug, "host='{}' TTL check: now={} last_used={} TTL {}", host, now_duration.count(),
     261           0 :             last_used_time.count(), host_ttl_.count());
     262           0 :   if ((now_duration - last_used_time) > host_ttl_) {
     263           0 :     ENVOY_LOG(debug, "host='{}' TTL expired, removing", host);
     264             :     // If the host has no address then that means that the DnsCacheImpl has never
     265             :     // runAddUpdateCallbacks for this host, and thus the callback targets are not aware of it.
     266             :     // Therefore, runRemoveCallbacks should only be ran if the host's address != nullptr.
     267           0 :     if (primary_host.host_info_->address()) {
     268           0 :       runRemoveCallbacks(host);
     269           0 :     }
     270           0 :     {
     271           0 :       removeCacheEntry(host);
     272           0 :       absl::WriterMutexLock writer_lock{&primary_hosts_lock_};
     273           0 :       auto host_it = primary_hosts_.find(host);
     274           0 :       ASSERT(host_it != primary_hosts_.end());
     275           0 :       host_to_erase = std::move(host_it->second);
     276           0 :       primary_hosts_.erase(host_it);
     277           0 :     }
     278           0 :     notifyThreads(host, primary_host.host_info_);
     279           0 :   } else {
     280           0 :     startResolve(host, primary_host);
     281           0 :   }
     282           0 : }
     283             : 
     284           0 : void DnsCacheImpl::forceRefreshHosts() {
     285           0 :   ENVOY_LOG(debug, "beginning DNS cache force refresh");
     286             :   // Tell the underlying resolver to reset itself since we likely just went through a network
     287             :   // transition and parameters may have changed.
     288           0 :   resolver_->resetNetworking();
     289             : 
     290           0 :   absl::ReaderMutexLock reader_lock{&primary_hosts_lock_};
     291           0 :   for (auto& primary_host : primary_hosts_) {
     292             :     // Avoid holding the lock for longer than necessary by just triggering the refresh timer for
     293             :     // each host IFF the host is not already refreshing. Cancellation is assumed to be cheap for
     294             :     // resolvers.
     295           0 :     if (primary_host.second->active_query_ != nullptr) {
     296           0 :       primary_host.second->active_query_->cancel(
     297           0 :           Network::ActiveDnsQuery::CancelReason::QueryAbandoned);
     298           0 :       primary_host.second->active_query_ = nullptr;
     299           0 :       primary_host.second->timeout_timer_->disableTimer();
     300           0 :     }
     301             : 
     302           0 :     ASSERT(!primary_host.second->timeout_timer_->enabled());
     303           0 :     primary_host.second->refresh_timer_->enableTimer(std::chrono::milliseconds(0), nullptr);
     304           0 :     ENVOY_LOG_EVENT(debug, "force_refresh_host", "force refreshing host='{}'", primary_host.first);
     305           0 :   }
     306           0 : }
     307             : 
     308           0 : void DnsCacheImpl::startResolve(const std::string& host, PrimaryHostInfo& host_info) {
     309           0 :   ENVOY_LOG(debug, "starting main thread resolve for host='{}' dns='{}' port='{}' timeout='{}'",
     310           0 :             host, host_info.host_info_->resolvedHost(), host_info.port_, timeout_interval_.count());
     311           0 :   ASSERT(host_info.active_query_ == nullptr);
     312             : 
     313           0 :   stats_.dns_query_attempt_.inc();
     314             : 
     315           0 :   host_info.timeout_timer_->enableTimer(timeout_interval_, nullptr);
     316           0 :   host_info.active_query_ =
     317           0 :       resolver_->resolve(host_info.host_info_->resolvedHost(), dns_lookup_family_,
     318           0 :                          [this, host](Network::DnsResolver::ResolutionStatus status,
     319           0 :                                       std::list<Network::DnsResponse>&& response) {
     320           0 :                            finishResolve(host, status, std::move(response));
     321           0 :                          });
     322           0 : }
     323             : 
     324             : void DnsCacheImpl::finishResolve(const std::string& host,
     325             :                                  Network::DnsResolver::ResolutionStatus status,
     326             :                                  std::list<Network::DnsResponse>&& response,
     327             :                                  absl::optional<MonotonicTime> resolution_time,
     328           0 :                                  bool is_proxy_lookup) {
     329           0 :   ASSERT(main_thread_dispatcher_.isThreadSafe());
     330           0 :   ENVOY_LOG_EVENT(debug, "dns_cache_finish_resolve",
     331           0 :                   "main thread resolve complete for host '{}': {}", host,
     332           0 :                   accumulateToString<Network::DnsResponse>(response, [](const auto& dns_response) {
     333           0 :                     return dns_response.addrInfo().address_->asString();
     334           0 :                   }));
     335           0 :   const bool from_cache = resolution_time.has_value();
     336             : 
     337             :   // Functions like this one that modify primary_hosts_ are only called in the main thread so we
     338             :   // know it is safe to use the PrimaryHostInfo pointers outside of the lock.
     339           0 :   auto* primary_host_info = [&]() {
     340           0 :     absl::ReaderMutexLock reader_lock{&primary_hosts_lock_};
     341           0 :     const auto primary_host_it = primary_hosts_.find(host);
     342           0 :     ASSERT(primary_host_it != primary_hosts_.end());
     343           0 :     return primary_host_it->second.get();
     344           0 :   }();
     345             : 
     346           0 :   bool first_resolve = false;
     347             : 
     348           0 :   if (!from_cache) {
     349           0 :     first_resolve = !primary_host_info->host_info_->firstResolveComplete();
     350           0 :     primary_host_info->timeout_timer_->disableTimer();
     351           0 :     primary_host_info->active_query_ = nullptr;
     352             : 
     353           0 :     if (status == Network::DnsResolver::ResolutionStatus::Failure) {
     354           0 :       stats_.dns_query_failure_.inc();
     355           0 :     } else {
     356           0 :       stats_.dns_query_success_.inc();
     357           0 :     }
     358           0 :   }
     359             : 
     360             :   // If the DNS resolver successfully resolved with an empty response list, the dns cache does not
     361             :   // update. This ensures that a potentially previously resolved address does not stabilize back to
     362             :   // 0 hosts.
     363           0 :   const auto new_address =
     364           0 :       !response.empty() ? Network::Utility::getAddressWithPort(
     365           0 :                               *(response.front().addrInfo().address_), primary_host_info->port_)
     366           0 :                         : nullptr;
     367           0 :   auto address_list = DnsUtils::generateAddressList(response, primary_host_info->port_);
     368             : 
     369             :   // Only the change the address if:
     370             :   // 1) The new address is valid &&
     371             :   // 2a) The host doesn't yet have an address ||
     372             :   // 2b) The host has a changed address.
     373             :   //
     374             :   // This means that once a host gets an address it will stick even in the case of a subsequent
     375             :   // resolution failure.
     376           0 :   bool address_changed = false;
     377           0 :   auto current_address = primary_host_info->host_info_->address();
     378             : 
     379           0 :   if (!resolution_time.has_value()) {
     380           0 :     resolution_time = main_thread_dispatcher_.timeSource().monotonicTime();
     381           0 :   }
     382           0 :   std::chrono::seconds dns_ttl =
     383           0 :       std::chrono::duration_cast<std::chrono::seconds>(refresh_interval_);
     384           0 :   if (new_address) {
     385             :     // Update the cache entry and staleness any time the ttl changes.
     386           0 :     if (!from_cache) {
     387           0 :       addCacheEntry(host, new_address, address_list, response.front().addrInfo().ttl_);
     388           0 :     }
     389             :     // Arbitrarily cap DNS re-resolution at min_refresh_interval_ to avoid constant DNS queries.
     390           0 :     dns_ttl = std::max<std::chrono::seconds>(
     391           0 :         std::chrono::duration_cast<std::chrono::seconds>(min_refresh_interval_),
     392           0 :         response.front().addrInfo().ttl_);
     393           0 :     primary_host_info->host_info_->updateStale(resolution_time.value(), dns_ttl);
     394           0 :   }
     395             : 
     396           0 :   bool changed_to_non_null_address =
     397           0 :       (new_address != nullptr &&
     398           0 :        (current_address == nullptr || *current_address != *new_address ||
     399           0 :         DnsUtils::listChanged(address_list, primary_host_info->host_info_->addressList())));
     400             :   // If this was a proxy lookup it's OK to send a null address resolution as
     401             :   // long as this isn't a transition from non-null to null address.
     402           0 :   bool proxying_and_didnt_unresolve = is_proxy_lookup && !current_address;
     403             : 
     404           0 :   if (changed_to_non_null_address || proxying_and_didnt_unresolve) {
     405           0 :     ENVOY_LOG_EVENT(debug, "dns_cache_update_address",
     406           0 :                     "host '{}' address has changed from {} to {}", host,
     407           0 :                     current_address ? current_address->asStringView() : "<empty>",
     408           0 :                     new_address ? new_address->asStringView() : "<empty>");
     409           0 :     primary_host_info->host_info_->setAddresses(new_address, std::move(address_list));
     410             : 
     411           0 :     runAddUpdateCallbacks(host, primary_host_info->host_info_);
     412           0 :     if (Runtime::runtimeFeatureEnabled(
     413           0 :             "envoy.reloadable_features.dns_cache_set_first_resolve_complete")) {
     414           0 :       primary_host_info->host_info_->setFirstResolveComplete();
     415           0 :     }
     416           0 :     address_changed = true;
     417           0 :     stats_.host_address_changed_.inc();
     418           0 :   }
     419             : 
     420           0 :   if (first_resolve) {
     421           0 :     primary_host_info->host_info_->setFirstResolveComplete();
     422           0 :   }
     423           0 :   if (first_resolve || (address_changed && !primary_host_info->host_info_->isStale())) {
     424           0 :     notifyThreads(host, primary_host_info->host_info_);
     425           0 :   }
     426             : 
     427           0 :   runResolutionCompleteCallbacks(host, primary_host_info->host_info_, status);
     428             : 
     429             :   // Kick off the refresh timer.
     430           0 :   if (status == Network::DnsResolver::ResolutionStatus::Success) {
     431           0 :     primary_host_info->failure_backoff_strategy_->reset(
     432           0 :         std::chrono::duration_cast<std::chrono::milliseconds>(dns_ttl).count());
     433           0 :     primary_host_info->refresh_timer_->enableTimer(dns_ttl);
     434           0 :     ENVOY_LOG(debug, "DNS refresh rate reset for host '{}', refresh rate {} ms", host,
     435           0 :               dns_ttl.count() * 1000);
     436           0 :   } else {
     437           0 :     const uint64_t refresh_interval = primary_host_info->failure_backoff_strategy_->nextBackOffMs();
     438           0 :     primary_host_info->refresh_timer_->enableTimer(std::chrono::milliseconds(refresh_interval));
     439           0 :     ENVOY_LOG(debug, "DNS refresh rate reset for host '{}', (failure) refresh rate {} ms", host,
     440           0 :               refresh_interval);
     441           0 :   }
     442           0 : }
     443             : 
     444             : void DnsCacheImpl::runAddUpdateCallbacks(const std::string& host,
     445           0 :                                          const DnsHostInfoSharedPtr& host_info) {
     446           0 :   for (auto* callbacks : update_callbacks_) {
     447           0 :     callbacks->callbacks_.onDnsHostAddOrUpdate(host, host_info);
     448           0 :   }
     449           0 : }
     450             : 
     451             : void DnsCacheImpl::runResolutionCompleteCallbacks(const std::string& host,
     452             :                                                   const DnsHostInfoSharedPtr& host_info,
     453           0 :                                                   Network::DnsResolver::ResolutionStatus status) {
     454           0 :   for (auto* callbacks : update_callbacks_) {
     455           0 :     callbacks->callbacks_.onDnsResolutionComplete(host, host_info, status);
     456           0 :   }
     457           0 : }
     458             : 
     459           0 : void DnsCacheImpl::runRemoveCallbacks(const std::string& host) {
     460           0 :   for (auto* callbacks : update_callbacks_) {
     461           0 :     callbacks->callbacks_.onDnsHostRemove(host);
     462           0 :   }
     463           0 : }
     464             : 
     465             : void DnsCacheImpl::notifyThreads(const std::string& host,
     466           0 :                                  const DnsHostInfoImplSharedPtr& resolved_info) {
     467           0 :   auto shared_info = std::make_shared<HostMapUpdateInfo>(host, resolved_info);
     468           0 :   tls_slot_.runOnAllThreads([shared_info](OptRef<ThreadLocalHostInfo> local_host_info) {
     469           0 :     local_host_info->onHostMapUpdate(shared_info);
     470           0 :   });
     471           0 : }
     472             : 
     473           0 : DnsCacheImpl::ThreadLocalHostInfo::~ThreadLocalHostInfo() {
     474             :   // Make sure we cancel any handles that still exist.
     475           0 :   for (const auto& per_host_list : pending_resolutions_) {
     476           0 :     for (auto pending_resolution : per_host_list.second) {
     477           0 :       pending_resolution->cancel();
     478           0 :     }
     479           0 :   }
     480           0 : }
     481             : 
     482             : void DnsCacheImpl::ThreadLocalHostInfo::onHostMapUpdate(
     483           0 :     const HostMapUpdateInfoSharedPtr& resolved_host) {
     484           0 :   auto host_it = pending_resolutions_.find(resolved_host->host_);
     485           0 :   if (host_it != pending_resolutions_.end()) {
     486           0 :     for (auto* resolution : host_it->second) {
     487           0 :       auto& callbacks = resolution->callbacks_;
     488           0 :       resolution->cancel();
     489           0 :       callbacks.onLoadDnsCacheComplete(resolved_host->info_);
     490           0 :     }
     491           0 :     pending_resolutions_.erase(host_it);
     492           0 :   }
     493           0 : }
     494             : 
     495             : DnsCacheImpl::PrimaryHostInfo::PrimaryHostInfo(DnsCacheImpl& parent,
     496             :                                                absl::string_view host_to_resolve, uint16_t port,
     497             :                                                bool is_ip_address,
     498             :                                                const Event::TimerCb& refresh_timer_cb,
     499             :                                                const Event::TimerCb& timeout_timer_cb)
     500             :     : parent_(parent), port_(port),
     501             :       refresh_timer_(parent.main_thread_dispatcher_.createTimer(refresh_timer_cb)),
     502             :       timeout_timer_(parent.main_thread_dispatcher_.createTimer(timeout_timer_cb)),
     503             :       host_info_(std::make_shared<DnsHostInfoImpl>(parent.main_thread_dispatcher_.timeSource(),
     504             :                                                    host_to_resolve, is_ip_address)),
     505             :       failure_backoff_strategy_(
     506             :           Config::Utility::prepareDnsRefreshStrategy<
     507             :               envoy::extensions::common::dynamic_forward_proxy::v3::DnsCacheConfig>(
     508           0 :               parent_.config_, parent_.refresh_interval_.count(), parent_.random_generator_)) {
     509           0 :   parent_.stats_.host_added_.inc();
     510           0 :   parent_.stats_.num_hosts_.inc();
     511           0 : }
     512             : 
     513           0 : DnsCacheImpl::PrimaryHostInfo::~PrimaryHostInfo() {
     514           0 :   parent_.stats_.host_removed_.inc();
     515           0 :   parent_.stats_.num_hosts_.dec();
     516           0 : }
     517             : 
     518             : void DnsCacheImpl::addCacheEntry(
     519             :     const std::string& host, const Network::Address::InstanceConstSharedPtr& address,
     520             :     const std::vector<Network::Address::InstanceConstSharedPtr>& address_list,
     521           0 :     const std::chrono::seconds ttl) {
     522           0 :   if (!key_value_store_) {
     523           0 :     return;
     524           0 :   }
     525           0 :   MonotonicTime now = main_thread_dispatcher_.timeSource().monotonicTime();
     526           0 :   uint64_t seconds_since_epoch =
     527           0 :       std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
     528           0 :   std::string value;
     529           0 :   if (address_list.empty()) {
     530           0 :     value = absl::StrCat(address->asString(), "|", ttl.count(), "|", seconds_since_epoch);
     531           0 :   } else {
     532           0 :     value = absl::StrJoin(address_list, "\n", [&](std::string* out, const auto& addr) {
     533           0 :       absl::StrAppend(out, addr->asString(), "|", ttl.count(), "|", seconds_since_epoch);
     534           0 :     });
     535           0 :   }
     536           0 :   key_value_store_->addOrUpdate(host, value, absl::nullopt);
     537           0 : }
     538             : 
     539           0 : void DnsCacheImpl::removeCacheEntry(const std::string& host) {
     540           0 :   if (!key_value_store_) {
     541           0 :     return;
     542           0 :   }
     543           0 :   key_value_store_->remove(host);
     544           0 : }
     545             : 
     546             : absl::optional<Network::DnsResponse>
     547           0 : DnsCacheImpl::parseValue(absl::string_view value, absl::optional<MonotonicTime>& resolution_time) {
     548           0 :   Network::Address::InstanceConstSharedPtr address;
     549           0 :   const auto parts = StringUtil::splitToken(value, "|");
     550           0 :   std::chrono::seconds ttl(0);
     551           0 :   if (parts.size() != 3) {
     552           0 :     ENVOY_LOG(warn, "Incorrect number of tokens in the cache line");
     553           0 :     return {};
     554           0 :   }
     555           0 :   address = Network::Utility::parseInternetAddressAndPortNoThrow(std::string(parts[0]));
     556           0 :   if (address == nullptr) {
     557           0 :     ENVOY_LOG(warn, "{} is not a valid address", parts[0]);
     558           0 :   }
     559           0 :   uint64_t ttl_int;
     560           0 :   if (absl::SimpleAtoi(parts[1], &ttl_int) && ttl_int != 0) {
     561           0 :     ttl = std::chrono::seconds(ttl_int);
     562           0 :   } else {
     563           0 :     ENVOY_LOG(warn, "{} is not a valid ttl", parts[1]);
     564           0 :   }
     565           0 :   uint64_t epoch_int;
     566           0 :   if (absl::SimpleAtoi(parts[2], &epoch_int)) {
     567           0 :     MonotonicTime now = main_thread_dispatcher_.timeSource().monotonicTime();
     568           0 :     const std::chrono::seconds seconds_since_epoch =
     569           0 :         std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch());
     570           0 :     resolution_time = main_thread_dispatcher_.timeSource().monotonicTime() -
     571           0 :                       (seconds_since_epoch - std::chrono::seconds(epoch_int));
     572           0 :   }
     573           0 :   if (address == nullptr || ttl == std::chrono::seconds(0) || !resolution_time.has_value()) {
     574           0 :     ENVOY_LOG(warn, "Unable to parse cache line '{}'", value);
     575           0 :     return {};
     576           0 :   }
     577           0 :   return Network::DnsResponse(address, ttl);
     578           0 : }
     579             : 
     580             : void DnsCacheImpl::loadCacheEntries(
     581           0 :     const envoy::extensions::common::dynamic_forward_proxy::v3::DnsCacheConfig& config) {
     582           0 :   if (!config.has_key_value_config()) {
     583           0 :     return;
     584           0 :   }
     585           0 :   auto& factory =
     586           0 :       Config::Utility::getAndCheckFactory<KeyValueStoreFactory>(config.key_value_config().config());
     587           0 :   key_value_store_ = factory.createStore(config.key_value_config(), validation_visitor_,
     588           0 :                                          main_thread_dispatcher_, file_system_);
     589           0 :   KeyValueStore::ConstIterateCb load = [this](const std::string& key, const std::string& value) {
     590           0 :     absl::optional<MonotonicTime> resolution_time;
     591           0 :     std::list<Network::DnsResponse> responses;
     592           0 :     const auto addresses = StringUtil::splitToken(value, "\n");
     593           0 :     for (absl::string_view address_line : addresses) {
     594           0 :       absl::optional<Network::DnsResponse> response = parseValue(address_line, resolution_time);
     595           0 :       if (!response.has_value()) {
     596           0 :         return KeyValueStore::Iterate::Break;
     597           0 :       }
     598           0 :       responses.emplace_back(response.value());
     599           0 :     }
     600           0 :     if (responses.empty()) {
     601           0 :       return KeyValueStore::Iterate::Break;
     602           0 :     }
     603           0 :     createHost(key, responses.front().addrInfo().address_->ip()->port());
     604           0 :     ENVOY_LOG_EVENT(
     605           0 :         debug, "dns_cache_load_finished", "persistent dns cache load complete for host '{}': {}",
     606           0 :         key, accumulateToString<Network::DnsResponse>(responses, [](const auto& dns_response) {
     607           0 :           return dns_response.addrInfo().address_->asString();
     608           0 :         }));
     609           0 :     finishResolve(key, Network::DnsResolver::ResolutionStatus::Success, std::move(responses),
     610           0 :                   resolution_time);
     611           0 :     stats_.cache_load_.inc();
     612           0 :     return KeyValueStore::Iterate::Continue;
     613           0 :   };
     614           0 :   key_value_store_->iterate(load);
     615           0 : }
     616             : 
     617             : } // namespace DynamicForwardProxy
     618             : } // namespace Common
     619             : } // namespace Extensions
     620             : } // namespace Envoy

Generated by: LCOV version 1.15