xref: /aosp_15_r20/external/cronet/net/dns/host_cache.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef NET_DNS_HOST_CACHE_H_
6 #define NET_DNS_HOST_CACHE_H_
7 
8 #include <stddef.h>
9 
10 #include <functional>
11 #include <map>
12 #include <memory>
13 #include <optional>
14 #include <ostream>
15 #include <set>
16 #include <string>
17 #include <string_view>
18 #include <tuple>
19 #include <utility>
20 #include <vector>
21 
22 #include "base/check.h"
23 #include "base/gtest_prod_util.h"
24 #include "base/memory/raw_ptr.h"
25 #include "base/numerics/clamped_math.h"
26 #include "base/threading/thread_checker.h"
27 #include "base/time/time.h"
28 #include "base/types/optional_util.h"
29 #include "base/values.h"
30 #include "net/base/address_family.h"
31 #include "net/base/connection_endpoint_metadata.h"
32 #include "net/base/expiring_cache.h"
33 #include "net/base/host_port_pair.h"
34 #include "net/base/ip_endpoint.h"
35 #include "net/base/net_errors.h"
36 #include "net/base/net_export.h"
37 #include "net/base/network_anonymization_key.h"
38 #include "net/dns/public/dns_query_type.h"
39 #include "net/dns/public/host_resolver_results.h"
40 #include "net/dns/public/host_resolver_source.h"
41 #include "net/log/net_log_capture_mode.h"
42 #include "third_party/abseil-cpp/absl/types/variant.h"
43 #include "url/scheme_host_port.h"
44 
45 namespace base {
46 class TickClock;
47 }  // namespace base
48 
49 namespace net {
50 
51 class HostResolverInternalResult;
52 
53 // Cache used by HostResolver to map hostnames to their resolved result.
54 class NET_EXPORT HostCache {
55  public:
56   struct NET_EXPORT Key {
57     // Hostnames in `host` must not be IP literals. IP literals should be
58     // resolved directly to the IP address and not be stored/queried in
59     // HostCache.
60     Key(absl::variant<url::SchemeHostPort, std::string> host,
61         DnsQueryType dns_query_type,
62         HostResolverFlags host_resolver_flags,
63         HostResolverSource host_resolver_source,
64         const NetworkAnonymizationKey& network_anonymization_key);
65     Key();
66     Key(const Key& key);
67     Key(Key&& key);
68     ~Key();
69 
70     // This is a helper used in comparing keys. The order of comparisons of
71     // `Key` fields is arbitrary, but the tuple is constructed with
72     // `dns_query_type` and `host_resolver_flags` before `host` under the
73     // assumption that integer comparisons are faster than string comparisons.
GetTupleKey74     static auto GetTuple(const Key* key) {
75       return std::tie(key->dns_query_type, key->host_resolver_flags, key->host,
76                       key->host_resolver_source, key->network_anonymization_key,
77                       key->secure);
78     }
79 
80     bool operator==(const Key& other) const {
81       return GetTuple(this) == GetTuple(&other);
82     }
83 
84     bool operator!=(const Key& other) const {
85       return GetTuple(this) != GetTuple(&other);
86     }
87 
88     bool operator<(const Key& other) const {
89       return GetTuple(this) < GetTuple(&other);
90     }
91 
92     absl::variant<url::SchemeHostPort, std::string> host;
93     DnsQueryType dns_query_type = DnsQueryType::UNSPECIFIED;
94     HostResolverFlags host_resolver_flags = 0;
95     HostResolverSource host_resolver_source = HostResolverSource::ANY;
96     NetworkAnonymizationKey network_anonymization_key;
97     bool secure = false;
98   };
99 
100   struct NET_EXPORT EntryStaleness {
101     // Time since the entry's TTL has expired. Negative if not expired.
102     base::TimeDelta expired_by;
103 
104     // Number of network changes since this result was cached.
105     int network_changes;
106 
107     // Number of hits to the cache entry while stale (expired or past-network).
108     int stale_hits;
109 
is_staleEntryStaleness110     bool is_stale() const {
111       return network_changes > 0 || expired_by >= base::TimeDelta();
112     }
113   };
114 
115   // Stores the latest address list that was looked up for a hostname.
116   class NET_EXPORT Entry {
117    public:
118     enum Source : int {
119       // Address list was obtained from an unknown source.
120       SOURCE_UNKNOWN,
121       // Address list was obtained via a DNS lookup.
122       SOURCE_DNS,
123       // Address list was obtained by searching a HOSTS file.
124       SOURCE_HOSTS,
125       // Address list was a preset from the DnsConfig.
126       SOURCE_CONFIG,
127     };
128 
129     // |ttl=std::nullopt| for unknown TTL.
130     template <typename T>
Entry(int error,T && results,Source source,std::optional<base::TimeDelta> ttl)131     Entry(int error,
132           T&& results,
133           Source source,
134           std::optional<base::TimeDelta> ttl)
135         : error_(error),
136           source_(source),
137           ttl_(ttl ? ttl.value() : kUnknownTtl) {
138       DCHECK(!ttl || ttl.value() >= base::TimeDelta());
139       SetResult(std::forward<T>(results));
140     }
141 
142     // Use when |ttl| is unknown.
143     template <typename T>
Entry(int error,T && results,Source source)144     Entry(int error, T&& results, Source source)
145         : Entry(error, std::forward<T>(results), source, std::nullopt) {}
146 
147     // Use for address entries.
148     Entry(int error,
149           std::vector<IPEndPoint> ip_endpoints,
150           std::set<std::string> aliases,
151           Source source,
152           std::optional<base::TimeDelta> ttl = std::nullopt);
153 
154     // For errors with no |results|.
155     Entry(int error,
156           Source source,
157           std::optional<base::TimeDelta> ttl = std::nullopt);
158 
159     // Adaptor to construct from HostResolverInternalResults. Only supports
160     // results extracted from a single DnsTransaction. `empty_source` is Source
161     // to assume if `results` is empty of any results from which Source can be
162     // read.
163     Entry(const std::set<std::unique_ptr<HostResolverInternalResult>>& results,
164           base::Time now,
165           base::TimeTicks now_ticks,
166           Source empty_source = SOURCE_UNKNOWN);
167 
168     Entry(const Entry& entry);
169     Entry(Entry&& entry);
170     ~Entry();
171 
172     Entry& operator=(const Entry& entry);
173     Entry& operator=(Entry&& entry);
174 
175     bool operator==(const Entry& other) const {
176       return ContentsEqual(other) &&
177              std::tie(source_, pinning_, ttl_, expires_, network_changes_,
178                       total_hits_, stale_hits_) ==
179                  std::tie(other.source_, other.pinning_, other.ttl_,
180                           other.expires_, other.network_changes_,
181                           other.total_hits_, other.stale_hits_);
182     }
183 
ContentsEqual(const Entry & other)184     bool ContentsEqual(const Entry& other) const {
185       return std::tie(error_, ip_endpoints_, endpoint_metadatas_, aliases_,
186                       text_records_, hostnames_, https_record_compatibility_,
187                       canonical_names_) ==
188              std::tie(
189                  other.error_, other.ip_endpoints_, other.endpoint_metadatas_,
190                  other.aliases_, other.text_records_, other.hostnames_,
191                  other.https_record_compatibility_, other.canonical_names_);
192     }
193 
error()194     int error() const { return error_; }
did_complete()195     bool did_complete() const {
196       return error_ != ERR_NETWORK_CHANGED &&
197              error_ != ERR_HOST_RESOLVER_QUEUE_TOO_LARGE;
198     }
set_error(int error)199     void set_error(int error) { error_ = error; }
200     std::vector<HostResolverEndpointResult> GetEndpoints() const;
ip_endpoints()201     const std::vector<IPEndPoint>& ip_endpoints() const {
202       return ip_endpoints_;
203     }
set_ip_endpoints(std::vector<IPEndPoint> ip_endpoints)204     void set_ip_endpoints(std::vector<IPEndPoint> ip_endpoints) {
205       ip_endpoints_ = std::move(ip_endpoints);
206     }
207     std::vector<ConnectionEndpointMetadata> GetMetadatas() const;
ClearMetadatas()208     void ClearMetadatas() { endpoint_metadatas_.clear(); }
aliases()209     const std::set<std::string>& aliases() const { return aliases_; }
set_aliases(std::set<std::string> aliases)210     void set_aliases(std::set<std::string> aliases) {
211       aliases_ = std::move(aliases);
212     }
text_records()213     const std::vector<std::string>& text_records() const {
214       return text_records_;
215     }
set_text_records(std::vector<std::string> text_records)216     void set_text_records(std::vector<std::string> text_records) {
217       text_records_ = std::move(text_records);
218     }
hostnames()219     const std::vector<HostPortPair>& hostnames() const { return hostnames_; }
set_hostnames(std::vector<HostPortPair> hostnames)220     void set_hostnames(std::vector<HostPortPair> hostnames) {
221       hostnames_ = std::move(hostnames);
222     }
https_record_compatibility()223     const std::vector<bool>& https_record_compatibility() const {
224       return https_record_compatibility_;
225     }
set_https_record_compatibility(std::vector<bool> https_record_compatibility)226     void set_https_record_compatibility(
227         std::vector<bool> https_record_compatibility) {
228       https_record_compatibility_ = std::move(https_record_compatibility);
229     }
pinning()230     std::optional<bool> pinning() const { return pinning_; }
set_pinning(std::optional<bool> pinning)231     void set_pinning(std::optional<bool> pinning) { pinning_ = pinning; }
232 
canonical_names()233     const std::set<std::string>& canonical_names() const {
234       return canonical_names_;
235     }
set_canonical_names(std::set<std::string> canonical_names)236     void set_canonical_names(std::set<std::string> canonical_names) {
237       canonical_names_ = std::move(canonical_names);
238     }
239 
source()240     Source source() const { return source_; }
has_ttl()241     bool has_ttl() const { return ttl_ >= base::TimeDelta(); }
ttl()242     base::TimeDelta ttl() const { return ttl_; }
243     std::optional<base::TimeDelta> GetOptionalTtl() const;
set_ttl(base::TimeDelta ttl)244     void set_ttl(base::TimeDelta ttl) { ttl_ = ttl; }
245 
expires()246     base::TimeTicks expires() const { return expires_; }
247 
248     // Public for the net-internals UI.
network_changes()249     int network_changes() const { return network_changes_; }
250 
251     // Merge |front| and |back|, representing results from multiple transactions
252     // for the same overall host resolution query.
253     //
254     // Merges lists, placing elements from |front| before elements from |back|.
255     // Further, dedupes legacy address lists and moves IPv6 addresses before
256     // IPv4 addresses (maintaining stable order otherwise).
257     //
258     // Fields that cannot be merged take precedence from |front|.
259     static Entry MergeEntries(Entry front, Entry back);
260 
261     // Creates a value representation of the entry for use with NetLog.
262     base::Value NetLogParams() const;
263 
264     // Creates a copy of |this| with the port of all address and hostname values
265     // set to |port| if the current port is 0. Preserves any non-zero ports.
266     HostCache::Entry CopyWithDefaultPort(uint16_t port) const;
267 
268     static std::optional<base::TimeDelta> TtlFromInternalResults(
269         const std::set<std::unique_ptr<HostResolverInternalResult>>& results,
270         base::Time now,
271         base::TimeTicks now_ticks);
272 
273    private:
274     using HttpsRecordPriority = uint16_t;
275 
276     friend class HostCache;
277 
278     static constexpr base::TimeDelta kUnknownTtl = base::Seconds(-1);
279 
280     Entry(const Entry& entry,
281           base::TimeTicks now,
282           base::TimeDelta ttl,
283           int network_changes);
284 
285     Entry(int error,
286           std::vector<IPEndPoint> ip_endpoints,
287           std::multimap<HttpsRecordPriority, ConnectionEndpointMetadata>
288               endpoint_metadatas,
289           std::set<std::string> aliases,
290           std::vector<std::string>&& text_results,
291           std::vector<HostPortPair>&& hostnames,
292           std::vector<bool>&& https_record_compatibility,
293           Source source,
294           base::TimeTicks expires,
295           int network_changes);
296 
297     void PrepareForCacheInsertion();
298 
SetResult(std::multimap<HttpsRecordPriority,ConnectionEndpointMetadata> endpoint_metadatas)299     void SetResult(
300         std::multimap<HttpsRecordPriority, ConnectionEndpointMetadata>
301             endpoint_metadatas) {
302       endpoint_metadatas_ = std::move(endpoint_metadatas);
303     }
SetResult(std::vector<std::string> text_records)304     void SetResult(std::vector<std::string> text_records) {
305       text_records_ = std::move(text_records);
306     }
SetResult(std::vector<HostPortPair> hostnames)307     void SetResult(std::vector<HostPortPair> hostnames) {
308       hostnames_ = std::move(hostnames);
309     }
SetResult(std::vector<bool> https_record_compatibility)310     void SetResult(std::vector<bool> https_record_compatibility) {
311       https_record_compatibility_ = std::move(https_record_compatibility);
312     }
313 
total_hits()314     int total_hits() const { return total_hits_; }
stale_hits()315     int stale_hits() const { return stale_hits_; }
316 
317     bool IsStale(base::TimeTicks now, int network_changes) const;
318     void CountHit(bool hit_is_stale);
319     void GetStaleness(base::TimeTicks now,
320                       int network_changes,
321                       EntryStaleness* out) const;
322 
323     base::Value::Dict GetAsValue(bool include_staleness) const;
324 
325     // The resolve results for this entry.
326     int error_ = ERR_FAILED;
327     std::vector<IPEndPoint> ip_endpoints_;
328     std::multimap<HttpsRecordPriority, ConnectionEndpointMetadata>
329         endpoint_metadatas_;
330     std::set<std::string> aliases_;
331     std::vector<std::string> text_records_;
332     std::vector<HostPortPair> hostnames_;
333 
334     // Bool of whether each HTTPS record received is compatible
335     // (draft-ietf-dnsop-svcb-https-08#section-8), considering alias records to
336     // always be compatible.
337     //
338     // This field may be reused for experimental query types to record
339     // successfully received records of that experimental type.
340     //
341     // For either usage, cleared before inserting in cache.
342     std::vector<bool> https_record_compatibility_;
343 
344     // Where results were obtained (e.g. DNS lookup, hosts file, etc).
345     Source source_ = SOURCE_UNKNOWN;
346     // If true, this entry cannot be evicted from the cache until after the next
347     // network change.  When an Entry is replaced by one whose pinning flag
348     // is not set, HostCache will copy this flag to the replacement.
349     // If this flag is null, HostCache will set it to false for simplicity.
350     // Note: This flag is not yet used, and should be removed if the proposals
351     // for followup queries after insecure/expired bootstrap are abandoned (see
352     // TODO(crbug.com/1200908) in HostResolverManager).
353     std::optional<bool> pinning_;
354 
355     // The final name at the end of the alias chain that was the record name for
356     // the A/AAAA records.
357     std::set<std::string> canonical_names_;
358 
359     // TTL obtained from the nameserver. Negative if unknown.
360     base::TimeDelta ttl_ = kUnknownTtl;
361 
362     base::TimeTicks expires_;
363     // Copied from the cache's network_changes_ when the entry is set; can
364     // later be compared to it to see if the entry was received on the current
365     // network.
366     int network_changes_ = -1;
367     // Use clamped math to cap hit counts at INT_MAX.
368     base::ClampedNumeric<int> total_hits_ = 0;
369     base::ClampedNumeric<int> stale_hits_ = 0;
370   };
371 
372   // Interface for interacting with persistent storage, to be provided by the
373   // embedder. Does not include support for writes that must happen immediately.
374   class PersistenceDelegate {
375    public:
376     // Calling ScheduleWrite() signals that data has changed and should be
377     // written to persistent storage. The write might be delayed.
378     virtual void ScheduleWrite() = 0;
379   };
380 
381   using EntryMap = std::map<Key, Entry>;
382 
383   // The two ways to serialize the cache to a value.
384   enum class SerializationType {
385     // Entries with transient NetworkAnonymizationKeys are not serialized, and
386     // RestoreFromListValue() can load the returned value.
387     kRestorable,
388     // Entries with transient NetworkAnonymizationKeys are serialized, and
389     // RestoreFromListValue() cannot load the returned value, since the debug
390     // serialization of NetworkAnonymizationKeys is used instead of the
391     // deserializable representation.
392     kDebug,
393   };
394 
395   // A HostCache::EntryStaleness representing a non-stale (fresh) cache entry.
396   static const HostCache::EntryStaleness kNotStale;
397 
398   // Constructs a HostCache that stores up to |max_entries|.
399   explicit HostCache(size_t max_entries);
400 
401   HostCache(const HostCache&) = delete;
402   HostCache& operator=(const HostCache&) = delete;
403 
404   ~HostCache();
405 
406   // Returns a pointer to the matching (key, entry) pair, which is valid at time
407   // |now|. If |ignore_secure| is true, ignores the secure field in |key| when
408   // looking for a match. If there is no matching entry, returns NULL.
409   const std::pair<const Key, Entry>* Lookup(const Key& key,
410                                             base::TimeTicks now,
411                                             bool ignore_secure = false);
412 
413   // Returns a pointer to the matching (key, entry) pair, whether it is valid or
414   // stale at time |now|. Fills in |stale_out| with information about how stale
415   // it is. If |ignore_secure| is true, ignores the secure field in |key| when
416   // looking for a match. If there is no matching entry, returns NULL.
417   const std::pair<const Key, Entry>* LookupStale(const Key& key,
418                                                  base::TimeTicks now,
419                                                  EntryStaleness* stale_out,
420                                                  bool ignore_secure = false);
421 
422   // Overwrites or creates an entry for |key|.
423   // |entry| is the value to set, |now| is the current time
424   // |ttl| is the "time to live".
425   void Set(const Key& key,
426            const Entry& entry,
427            base::TimeTicks now,
428            base::TimeDelta ttl);
429 
430   // Checks whether an entry exists for `hostname`.
431   // If so, returns the matching key and writes the source (e.g. DNS, HOSTS
432   // file, etc.) to `source_out` and the staleness to `stale_out` (if they are
433   // not null). If no entry exists, returns nullptr.
434   //
435   // For testing use only and not very performant. Production code should only
436   // do lookups by precise Key.
437   const HostCache::Key* GetMatchingKeyForTesting(
438       std::string_view hostname,
439       HostCache::Entry::Source* source_out = nullptr,
440       HostCache::EntryStaleness* stale_out = nullptr) const;
441 
442   // Marks all entries as stale on account of a network change.
443   void Invalidate();
444 
445   void set_persistence_delegate(PersistenceDelegate* delegate);
446 
set_tick_clock_for_testing(const base::TickClock * tick_clock)447   void set_tick_clock_for_testing(const base::TickClock* tick_clock) {
448     tick_clock_ = tick_clock;
449   }
450 
451   // Empties the cache.
452   void clear();
453 
454   // Clears hosts matching |host_filter| from the cache.
455   void ClearForHosts(
456       const base::RepeatingCallback<bool(const std::string&)>& host_filter);
457 
458   // Fills the provided base::Value with the contents of the cache for
459   // serialization. `entry_list` must be non-null list, and will be cleared
460   // before adding the cache contents.
461   void GetList(base::Value::List& entry_list,
462                bool include_staleness,
463                SerializationType serialization_type) const;
464   // Takes a base::Value list representing cache entries and stores them in the
465   // cache, skipping any that already have entries. Returns true on success,
466   // false on failure.
467   bool RestoreFromListValue(const base::Value::List& old_cache);
468   // Returns the number of entries that were restored in the last call to
469   // RestoreFromListValue().
last_restore_size()470   size_t last_restore_size() const { return restore_size_; }
471 
472   // Returns the number of entries in the cache.
473   size_t size() const;
474 
475   // Following are used by net_internals UI.
476   size_t max_entries() const;
network_changes()477   int network_changes() const { return network_changes_; }
entries()478   const EntryMap& entries() const { return entries_; }
479 
480  private:
481   FRIEND_TEST_ALL_PREFIXES(HostCacheTest, NoCache);
482 
483   enum SetOutcome : int;
484   enum LookupOutcome : int;
485   enum EraseReason : int;
486 
487   // Returns the result that is least stale, based on the number of network
488   // changes since the result was cached. If the results are equally stale,
489   // prefers a securely retrieved result. Returns nullptr if both results are
490   // nullptr.
491   static std::pair<const HostCache::Key, HostCache::Entry>*
492   GetLessStaleMoreSecureResult(
493       base::TimeTicks now,
494       std::pair<const HostCache::Key, HostCache::Entry>* result1,
495       std::pair<const HostCache::Key, HostCache::Entry>* result2);
496 
497   // Returns matching key and entry from cache and nullptr if no match. Ignores
498   // the secure field in |initial_key| if |ignore_secure| is true.
499   std::pair<const Key, Entry>* LookupInternalIgnoringFields(
500       const Key& initial_key,
501       base::TimeTicks now,
502       bool ignore_secure);
503 
504   // Returns matching key and entry from cache and nullptr if no match. An exact
505   // match for |key| is required.
506   std::pair<const Key, Entry>* LookupInternal(const Key& key);
507 
508   // Returns true if this HostCache can contain no entries.
caching_is_disabled()509   bool caching_is_disabled() const { return max_entries_ == 0; }
510 
511   // Returns true if an entry was removed.
512   bool EvictOneEntry(base::TimeTicks now);
513   // Helper to check if an Entry is currently pinned in the cache.
514   bool HasActivePin(const Entry& entry);
515   // Helper to insert an Entry into the cache.
516   void AddEntry(const Key& key, Entry&& entry);
517 
518   // Map from hostname (presumably in lowercase canonicalized format) to
519   // a resolved result entry.
520   EntryMap entries_;
521   size_t max_entries_;
522   int network_changes_ = 0;
523   // Number of cache entries that were restored in the last call to
524   // RestoreFromListValue(). Used in histograms.
525   size_t restore_size_ = 0;
526 
527   raw_ptr<PersistenceDelegate> delegate_ = nullptr;
528   // Shared tick clock, overridden for testing.
529   raw_ptr<const base::TickClock> tick_clock_;
530 
531   THREAD_CHECKER(thread_checker_);
532 };
533 
534 }  // namespace net
535 
536 // Debug logging support
537 std::ostream& operator<<(std::ostream& out,
538                          const net::HostCache::EntryStaleness& s);
539 
540 #endif  // NET_DNS_HOST_CACHE_H_
541