-
Notifications
You must be signed in to change notification settings - Fork 6.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is the new HostCache replacement that is primarily designed for the purpose of supporting Happy Eyeballs v2 and expanded HTTPS support by caching data much more individually (mostly at the level of DNS record sets) instead of caching the entire results for a HostResolverRequest as a package. Design features: * Cache type is the newer HostResolverInternalResult type, which was explicitly designed to allow the separated caching needed here. * Better support around making some of the lookup criteria allow wildcard lookups, and in the case of DnsQueryType, allow wildcard stored values that can match any lookup. This avoids a lot of the old complexity and special casing that previously required multiple cache lookups for cases like secure and insecure cached values, and doing this better meets our caching needs as we move into functionality like separately-cached aliases that might need to match multiple query types. To implement this, only the never-wildcard criteria (domain name and anonymization key) are stored in the map key, and the lookup code then does a linear scan of the key matches for more specific matches of the wildcardable criteria. Should be more than performant since most lookups will only do that linear scan over 3 entries for A/AAAA/HTTPS results. * Entries are looked up via raw domain name, e.g. "foo.example" or "_1111._https.foo.example" rather than the previously-used std::variant<url::SchemeHostPort, std::string>. This is much more sensible and simple now that we're moving away from request-wide cache entries because the individual entries can use their slightly different actual domain names, and this will all help with doing cache lookups for HTTPS-based followup queries. Just implementing the basic lookup and set functionality for now, along with time-based entry expiration. Will add a bunch more necessary functionality in subsequent CLs, including: * Cache size limit. * Stale lookups. * Invalidation for network changes. * Cache serialization for logging and Cronet cache persistence. Will also need to add the code to use this cache and base::Features to switch out this cache for the old cache, along with a whole lot of metrics for the old and new cache to support the transition. Bug: 1448654 Change-Id: If31fb0785ff9c803d9477a8caca54a4d34c46f4d Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4544648 Reviewed-by: Tsuyoshi Horo <horo@chromium.org> Commit-Queue: Eric Orth <ericorth@chromium.org> Cr-Commit-Position: refs/heads/main@{#1152051}
- Loading branch information
Eric Orth
authored and
Chromium LUCI CQ
committed
Jun 1, 2023
1 parent
ea1b801
commit da551f0
Showing
4 changed files
with
1,278 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
// Copyright 2023 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#include "net/dns/host_resolver_cache.h" | ||
|
||
#include <memory> | ||
#include <string> | ||
#include <utility> | ||
#include <vector> | ||
|
||
#include "base/strings/string_piece.h" | ||
#include "base/time/clock.h" | ||
#include "net/base/network_anonymization_key.h" | ||
#include "net/dns/host_resolver_internal_result.h" | ||
#include "net/dns/public/dns_query_type.h" | ||
#include "net/dns/public/host_resolver_source.h" | ||
#include "third_party/abseil-cpp/absl/types/optional.h" | ||
#include "url/third_party/mozilla/url_parse.h" | ||
#include "url/url_canon.h" | ||
#include "url/url_canon_stdstring.h" | ||
|
||
namespace net { | ||
|
||
HostResolverCache::HostResolverCache(const base::Clock& clock, | ||
const base::TickClock& tick_clock) | ||
: clock_(clock), tick_clock_(tick_clock) {} | ||
|
||
HostResolverCache::~HostResolverCache() = default; | ||
|
||
HostResolverCache::HostResolverCache(HostResolverCache&&) = default; | ||
|
||
HostResolverCache& HostResolverCache::operator=(HostResolverCache&&) = default; | ||
|
||
const HostResolverInternalResult* HostResolverCache::Lookup( | ||
base::StringPiece domain_name, | ||
const NetworkAnonymizationKey& network_anonymization_key, | ||
DnsQueryType query_type, | ||
HostResolverSource source, | ||
absl::optional<bool> secure) const { | ||
std::vector<EntryMap::const_iterator> candidates = LookupInternal( | ||
domain_name, network_anonymization_key, query_type, source, secure); | ||
|
||
// Get the most secure, last-matching (which is first in the vector returned | ||
// by LookupInternal()) non-expired result. | ||
base::TimeTicks now_ticks = tick_clock_->NowTicks(); | ||
base::Time now = clock_->Now(); | ||
HostResolverInternalResult* most_secure_result = nullptr; | ||
for (const EntryMap::const_iterator& candidate : candidates) { | ||
DCHECK(candidate->second.result->timed_expiration().has_value()); | ||
|
||
if (candidate->second.result->expiration().has_value()) { | ||
if (candidate->second.result->expiration() <= now_ticks) { | ||
continue; | ||
} | ||
} else if (candidate->second.result->timed_expiration() <= now) { | ||
continue; | ||
} | ||
|
||
// If the candidate is secure, or all results are insecure, no need to check | ||
// any more. | ||
if (candidate->second.secure || !secure.value_or(true)) { | ||
return candidate->second.result.get(); | ||
} else if (most_secure_result == nullptr) { | ||
most_secure_result = candidate->second.result.get(); | ||
} | ||
} | ||
|
||
return most_secure_result; | ||
} | ||
|
||
void HostResolverCache::Set( | ||
std::unique_ptr<HostResolverInternalResult> result, | ||
const NetworkAnonymizationKey& network_anonymization_key, | ||
HostResolverSource source, | ||
bool secure) { | ||
// Result must have at least a timed expiration to be a cacheable result. | ||
DCHECK(result->timed_expiration().has_value()); | ||
|
||
std::vector<EntryMap::const_iterator> matches = | ||
LookupInternal(result->domain_name(), network_anonymization_key, | ||
result->query_type(), source, secure); | ||
|
||
for (const EntryMap::const_iterator& match : matches) { | ||
entries_.erase(match); | ||
} | ||
|
||
std::string domain_name = result->domain_name(); | ||
entries_.emplace(Key(std::move(domain_name), network_anonymization_key), | ||
Entry(std::move(result), source, secure)); | ||
} | ||
|
||
HostResolverCache::Entry::Entry( | ||
std::unique_ptr<HostResolverInternalResult> result, | ||
HostResolverSource source, | ||
bool secure) | ||
: result(std::move(result)), source(source), secure(secure) {} | ||
|
||
HostResolverCache::Entry::~Entry() = default; | ||
|
||
HostResolverCache::Entry::Entry(Entry&&) = default; | ||
|
||
HostResolverCache::Entry& HostResolverCache::Entry::operator=(Entry&&) = | ||
default; | ||
|
||
std::vector<HostResolverCache::EntryMap::const_iterator> | ||
HostResolverCache::LookupInternal( | ||
base::StringPiece domain_name, | ||
const NetworkAnonymizationKey& network_anonymization_key, | ||
DnsQueryType query_type, | ||
HostResolverSource source, | ||
absl::optional<bool> secure) const { | ||
auto matches = std::vector<EntryMap::const_iterator>(); | ||
|
||
if (entries_.empty()) { | ||
return matches; | ||
} | ||
|
||
std::string canonicalized; | ||
url::StdStringCanonOutput output(&canonicalized); | ||
url::CanonHostInfo host_info; | ||
|
||
url::CanonicalizeHostVerbose(domain_name.data(), | ||
url::Component(0, domain_name.size()), &output, | ||
&host_info); | ||
|
||
// For performance, when canonicalization can't canonicalize, minimize string | ||
// copies and just reuse the input StringPiece. This optimization prevents | ||
// easily reusing a MaybeCanoncalize util with similar code. | ||
base::StringPiece lookup_name = domain_name; | ||
if (host_info.family == url::CanonHostInfo::Family::NEUTRAL) { | ||
output.Complete(); | ||
lookup_name = canonicalized; | ||
} | ||
|
||
auto range = | ||
entries_.equal_range(KeyRef{lookup_name, network_anonymization_key}); | ||
if (range.first == entries_.cend() || range.second == entries_.cbegin()) { | ||
return matches; | ||
} | ||
|
||
// Iterate in reverse order to return most-recently-added entry first. | ||
auto it = --range.second; | ||
while (true) { | ||
if ((query_type == DnsQueryType::UNSPECIFIED || | ||
it->second.result->query_type() == DnsQueryType::UNSPECIFIED || | ||
query_type == it->second.result->query_type()) && | ||
(source == HostResolverSource::ANY || source == it->second.source) && | ||
(!secure.has_value() || secure.value() == it->second.secure)) { | ||
matches.push_back(it); | ||
} | ||
|
||
if (it == range.first) { | ||
break; | ||
} | ||
--it; | ||
} | ||
|
||
return matches; | ||
} | ||
|
||
} // namespace net |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// Copyright 2023 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#ifndef NET_DNS_HOST_RESOLVER_CACHE_H_ | ||
#define NET_DNS_HOST_RESOLVER_CACHE_H_ | ||
|
||
#include <map> | ||
#include <memory> | ||
#include <string> | ||
#include <tuple> | ||
#include <vector> | ||
|
||
#include "base/memory/raw_ref.h" | ||
#include "base/strings/string_piece.h" | ||
#include "base/time/clock.h" | ||
#include "base/time/default_clock.h" | ||
#include "base/time/default_tick_clock.h" | ||
#include "net/base/net_export.h" | ||
#include "net/base/network_anonymization_key.h" | ||
#include "net/dns/public/dns_query_type.h" | ||
#include "net/dns/public/host_resolver_source.h" | ||
#include "third_party/abseil-cpp/absl/types/optional.h" | ||
|
||
namespace net { | ||
|
||
class HostResolverInternalResult; | ||
|
||
// Cache used by HostResolverManager to save previously resolved information. | ||
class NET_EXPORT HostResolverCache final { | ||
public: | ||
explicit HostResolverCache( | ||
const base::Clock& clock = *base::DefaultClock::GetInstance(), | ||
const base::TickClock& tick_clock = | ||
*base::DefaultTickClock::GetInstance()); | ||
~HostResolverCache(); | ||
|
||
// Move-only. | ||
HostResolverCache(HostResolverCache&&); | ||
HostResolverCache& operator=(HostResolverCache&&); | ||
|
||
// Lookup an active (non-stale) cached result matching the given criteria. If | ||
// `query_type` is `DnsQueryType::UNSPECIFIED`, `source` is | ||
// `HostResolverSource::ANY`, or `secure` is `absl::nullopt`, it is a wildcard | ||
// that can match for any cached parameter of that type. In cases where a | ||
// wildcard lookup leads to multiple matching results, only the most recently | ||
// set result will be returned. Additionally, if a cached result has | ||
// `DnsQueryType::UNSPECIFIED`, it will match for any argument of | ||
// `query_type`. | ||
// | ||
// Returns nullptr on cache miss (no active result matches the given | ||
// criteria). | ||
const HostResolverInternalResult* Lookup( | ||
base::StringPiece domain_name, | ||
const NetworkAnonymizationKey& network_anonymization_key, | ||
DnsQueryType query_type = DnsQueryType::UNSPECIFIED, | ||
HostResolverSource source = HostResolverSource::ANY, | ||
absl::optional<bool> secure = absl::nullopt) const; | ||
|
||
// Sets the result into the cache, replacing any previous result entries that | ||
// would match the same criteria, even if a previous entry would have matched | ||
// more criteria than the new one, e.g. if the previous entry used a wildcard | ||
// `DnsQueryType::UNSPECIFIED`. | ||
void Set(std::unique_ptr<HostResolverInternalResult> result, | ||
const NetworkAnonymizationKey& network_anonymization_key, | ||
HostResolverSource source, | ||
bool secure); | ||
|
||
private: | ||
struct Key { | ||
std::string domain_name; | ||
NetworkAnonymizationKey network_anonymization_key; | ||
}; | ||
|
||
struct KeyRef { | ||
base::StringPiece domain_name; | ||
const NetworkAnonymizationKey& network_anonymization_key; | ||
}; | ||
|
||
// Allow comparing Key to KeyRef to allow refs for entry lookup. | ||
struct KeyComparator { | ||
using is_transparent = void; | ||
|
||
bool operator()(const Key& lhs, const Key& rhs) const { | ||
return std::tie(lhs.domain_name, lhs.network_anonymization_key) < | ||
std::tie(rhs.domain_name, rhs.network_anonymization_key); | ||
} | ||
|
||
bool operator()(const Key& lhs, const KeyRef& rhs) const { | ||
return std::tie(lhs.domain_name, lhs.network_anonymization_key) < | ||
std::tie(rhs.domain_name, rhs.network_anonymization_key); | ||
} | ||
|
||
bool operator()(const KeyRef& lhs, const Key& rhs) const { | ||
return std::tie(lhs.domain_name, lhs.network_anonymization_key) < | ||
std::tie(rhs.domain_name, rhs.network_anonymization_key); | ||
} | ||
}; | ||
|
||
struct Entry { | ||
Entry(std::unique_ptr<HostResolverInternalResult> result, | ||
HostResolverSource source, | ||
bool secure); | ||
~Entry(); | ||
|
||
Entry(Entry&&); | ||
Entry& operator=(Entry&&); | ||
|
||
std::unique_ptr<HostResolverInternalResult> result; | ||
HostResolverSource source; | ||
bool secure; | ||
}; | ||
|
||
using EntryMap = std::multimap<Key, Entry, KeyComparator>; | ||
|
||
// Get all matching results, from most to least recently added. | ||
std::vector<EntryMap::const_iterator> LookupInternal( | ||
base::StringPiece domain_name, | ||
const NetworkAnonymizationKey& network_anonymization_key, | ||
DnsQueryType query_type, | ||
HostResolverSource source, | ||
absl::optional<bool> secure) const; | ||
|
||
EntryMap entries_; | ||
|
||
raw_ref<const base::Clock> clock_; | ||
raw_ref<const base::TickClock> tick_clock_; | ||
}; | ||
|
||
} // namespace net | ||
|
||
#endif // NET_DNS_HOST_RESOLVER_CACHE_H_ |
Oops, something went wrong.