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
|