Skip to content

Commit

Permalink
Create HostResolverCache
Browse files Browse the repository at this point in the history
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
Show file tree
Hide file tree
Showing 4 changed files with 1,278 additions and 0 deletions.
3 changes: 3 additions & 0 deletions net/dns/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ source_set("dns") {
"dns_util.h",
"host_cache.cc",
"host_resolver.cc",
"host_resolver_cache.cc",
"host_resolver_cache.h",
"host_resolver_internal_result.cc",
"host_resolver_internal_result.h",
"host_resolver_manager.cc",
Expand Down Expand Up @@ -410,6 +412,7 @@ source_set("tests") {
"dns_udp_tracker_unittest.cc",
"dns_util_unittest.cc",
"host_cache_unittest.cc",
"host_resolver_cache_unittest.cc",
"host_resolver_internal_result_unittest.cc",
"host_resolver_manager_unittest.cc",
"https_record_rdata_unittest.cc",
Expand Down
162 changes: 162 additions & 0 deletions net/dns/host_resolver_cache.cc
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
132 changes: 132 additions & 0 deletions net/dns/host_resolver_cache.h
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_

0 comments on commit da551f0

Please sign in to comment.