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. `empty_source` is 160 // Source to assume if `results` is empty of any results from which Source 161 // can be read. 162 Entry(const std::set<std::unique_ptr<HostResolverInternalResult>>& results, 163 base::Time now, 164 base::TimeTicks now_ticks, 165 Source empty_source = SOURCE_UNKNOWN); 166 167 Entry(const Entry& entry); 168 Entry(Entry&& entry); 169 ~Entry(); 170 171 Entry& operator=(const Entry& entry); 172 Entry& operator=(Entry&& entry); 173 174 bool operator==(const Entry& other) const { 175 return ContentsEqual(other) && 176 std::tie(source_, pinning_, ttl_, expires_, network_changes_, 177 total_hits_, stale_hits_) == 178 std::tie(other.source_, other.pinning_, other.ttl_, 179 other.expires_, other.network_changes_, 180 other.total_hits_, other.stale_hits_); 181 } 182 ContentsEqual(const Entry & other)183 bool ContentsEqual(const Entry& other) const { 184 return std::tie(error_, ip_endpoints_, endpoint_metadatas_, aliases_, 185 text_records_, hostnames_, https_record_compatibility_, 186 canonical_names_) == 187 std::tie( 188 other.error_, other.ip_endpoints_, other.endpoint_metadatas_, 189 other.aliases_, other.text_records_, other.hostnames_, 190 other.https_record_compatibility_, other.canonical_names_); 191 } 192 error()193 int error() const { return error_; } did_complete()194 bool did_complete() const { 195 return error_ != ERR_NETWORK_CHANGED && 196 error_ != ERR_HOST_RESOLVER_QUEUE_TOO_LARGE; 197 } set_error(int error)198 void set_error(int error) { error_ = error; } 199 std::vector<HostResolverEndpointResult> GetEndpoints() const; ip_endpoints()200 const std::vector<IPEndPoint>& ip_endpoints() const { 201 return ip_endpoints_; 202 } set_ip_endpoints(std::vector<IPEndPoint> ip_endpoints)203 void set_ip_endpoints(std::vector<IPEndPoint> ip_endpoints) { 204 ip_endpoints_ = std::move(ip_endpoints); 205 } 206 std::vector<ConnectionEndpointMetadata> GetMetadatas() const; ClearMetadatas()207 void ClearMetadatas() { endpoint_metadatas_.clear(); } aliases()208 const std::set<std::string>& aliases() const { return aliases_; } set_aliases(std::set<std::string> aliases)209 void set_aliases(std::set<std::string> aliases) { 210 aliases_ = std::move(aliases); 211 } text_records()212 const std::vector<std::string>& text_records() const { 213 return text_records_; 214 } set_text_records(std::vector<std::string> text_records)215 void set_text_records(std::vector<std::string> text_records) { 216 text_records_ = std::move(text_records); 217 } hostnames()218 const std::vector<HostPortPair>& hostnames() const { return hostnames_; } set_hostnames(std::vector<HostPortPair> hostnames)219 void set_hostnames(std::vector<HostPortPair> hostnames) { 220 hostnames_ = std::move(hostnames); 221 } https_record_compatibility()222 const std::vector<bool>& https_record_compatibility() const { 223 return https_record_compatibility_; 224 } set_https_record_compatibility(std::vector<bool> https_record_compatibility)225 void set_https_record_compatibility( 226 std::vector<bool> https_record_compatibility) { 227 https_record_compatibility_ = std::move(https_record_compatibility); 228 } pinning()229 std::optional<bool> pinning() const { return pinning_; } set_pinning(std::optional<bool> pinning)230 void set_pinning(std::optional<bool> pinning) { pinning_ = pinning; } 231 canonical_names()232 const std::set<std::string>& canonical_names() const { 233 return canonical_names_; 234 } set_canonical_names(std::set<std::string> canonical_names)235 void set_canonical_names(std::set<std::string> canonical_names) { 236 canonical_names_ = std::move(canonical_names); 237 } 238 source()239 Source source() const { return source_; } has_ttl()240 bool has_ttl() const { return ttl_ >= base::TimeDelta(); } ttl()241 base::TimeDelta ttl() const { return ttl_; } 242 std::optional<base::TimeDelta> GetOptionalTtl() const; set_ttl(base::TimeDelta ttl)243 void set_ttl(base::TimeDelta ttl) { ttl_ = ttl; } 244 expires()245 base::TimeTicks expires() const { return expires_; } 246 247 // Public for the net-internals UI. network_changes()248 int network_changes() const { return network_changes_; } 249 250 // Merge |front| and |back|, representing results from multiple transactions 251 // for the same overall host resolution query. 252 // 253 // Merges lists, placing elements from |front| before elements from |back|. 254 // Further, dedupes legacy address lists and moves IPv6 addresses before 255 // IPv4 addresses (maintaining stable order otherwise). 256 // 257 // Fields that cannot be merged take precedence from |front|. 258 static Entry MergeEntries(Entry front, Entry back); 259 260 // Creates a value representation of the entry for use with NetLog. 261 base::Value NetLogParams() const; 262 263 // Creates a copy of |this| with the port of all address and hostname values 264 // set to |port| if the current port is 0. Preserves any non-zero ports. 265 HostCache::Entry CopyWithDefaultPort(uint16_t port) const; 266 267 static std::optional<base::TimeDelta> TtlFromInternalResults( 268 const std::set<std::unique_ptr<HostResolverInternalResult>>& results, 269 base::Time now, 270 base::TimeTicks now_ticks); 271 272 private: 273 using HttpsRecordPriority = uint16_t; 274 275 friend class HostCache; 276 277 static constexpr base::TimeDelta kUnknownTtl = base::Seconds(-1); 278 279 Entry(const Entry& entry, 280 base::TimeTicks now, 281 base::TimeDelta ttl, 282 int network_changes); 283 284 Entry(int error, 285 std::vector<IPEndPoint> ip_endpoints, 286 std::multimap<HttpsRecordPriority, ConnectionEndpointMetadata> 287 endpoint_metadatas, 288 std::set<std::string> aliases, 289 std::vector<std::string>&& text_results, 290 std::vector<HostPortPair>&& hostnames, 291 std::vector<bool>&& https_record_compatibility, 292 Source source, 293 base::TimeTicks expires, 294 int network_changes); 295 296 void PrepareForCacheInsertion(); 297 SetResult(std::multimap<HttpsRecordPriority,ConnectionEndpointMetadata> endpoint_metadatas)298 void SetResult( 299 std::multimap<HttpsRecordPriority, ConnectionEndpointMetadata> 300 endpoint_metadatas) { 301 endpoint_metadatas_ = std::move(endpoint_metadatas); 302 } SetResult(std::vector<std::string> text_records)303 void SetResult(std::vector<std::string> text_records) { 304 text_records_ = std::move(text_records); 305 } SetResult(std::vector<HostPortPair> hostnames)306 void SetResult(std::vector<HostPortPair> hostnames) { 307 hostnames_ = std::move(hostnames); 308 } SetResult(std::vector<bool> https_record_compatibility)309 void SetResult(std::vector<bool> https_record_compatibility) { 310 https_record_compatibility_ = std::move(https_record_compatibility); 311 } 312 total_hits()313 int total_hits() const { return total_hits_; } stale_hits()314 int stale_hits() const { return stale_hits_; } 315 316 bool IsStale(base::TimeTicks now, int network_changes) const; 317 void CountHit(bool hit_is_stale); 318 void GetStaleness(base::TimeTicks now, 319 int network_changes, 320 EntryStaleness* out) const; 321 322 base::Value::Dict GetAsValue(bool include_staleness) const; 323 324 // The resolve results for this entry. 325 int error_ = ERR_FAILED; 326 std::vector<IPEndPoint> ip_endpoints_; 327 std::multimap<HttpsRecordPriority, ConnectionEndpointMetadata> 328 endpoint_metadatas_; 329 std::set<std::string> aliases_; 330 std::vector<std::string> text_records_; 331 std::vector<HostPortPair> hostnames_; 332 333 // Bool of whether each HTTPS record received is compatible 334 // (draft-ietf-dnsop-svcb-https-08#section-8), considering alias records to 335 // always be compatible. 336 // 337 // This field may be reused for experimental query types to record 338 // successfully received records of that experimental type. 339 // 340 // For either usage, cleared before inserting in cache. 341 std::vector<bool> https_record_compatibility_; 342 343 // Where results were obtained (e.g. DNS lookup, hosts file, etc). 344 Source source_ = SOURCE_UNKNOWN; 345 // If true, this entry cannot be evicted from the cache until after the next 346 // network change. When an Entry is replaced by one whose pinning flag 347 // is not set, HostCache will copy this flag to the replacement. 348 // If this flag is null, HostCache will set it to false for simplicity. 349 // Note: This flag is not yet used, and should be removed if the proposals 350 // for followup queries after insecure/expired bootstrap are abandoned (see 351 // TODO(crbug.com/40178456) in HostResolverManager). 352 std::optional<bool> pinning_; 353 354 // The final name at the end of the alias chain that was the record name for 355 // the A/AAAA records. 356 std::set<std::string> canonical_names_; 357 358 // TTL obtained from the nameserver. Negative if unknown. 359 base::TimeDelta ttl_ = kUnknownTtl; 360 361 base::TimeTicks expires_; 362 // Copied from the cache's network_changes_ when the entry is set; can 363 // later be compared to it to see if the entry was received on the current 364 // network. 365 int network_changes_ = -1; 366 // Use clamped math to cap hit counts at INT_MAX. 367 base::ClampedNumeric<int> total_hits_ = 0; 368 base::ClampedNumeric<int> stale_hits_ = 0; 369 }; 370 371 // Interface for interacting with persistent storage, to be provided by the 372 // embedder. Does not include support for writes that must happen immediately. 373 class PersistenceDelegate { 374 public: 375 // Calling ScheduleWrite() signals that data has changed and should be 376 // written to persistent storage. The write might be delayed. 377 virtual void ScheduleWrite() = 0; 378 }; 379 380 using EntryMap = std::map<Key, Entry>; 381 382 // The two ways to serialize the cache to a value. 383 enum class SerializationType { 384 // Entries with transient NetworkAnonymizationKeys are not serialized, and 385 // RestoreFromListValue() can load the returned value. 386 kRestorable, 387 // Entries with transient NetworkAnonymizationKeys are serialized, and 388 // RestoreFromListValue() cannot load the returned value, since the debug 389 // serialization of NetworkAnonymizationKeys is used instead of the 390 // deserializable representation. 391 kDebug, 392 }; 393 394 // A HostCache::EntryStaleness representing a non-stale (fresh) cache entry. 395 static const HostCache::EntryStaleness kNotStale; 396 397 // Constructs a HostCache that stores up to |max_entries|. 398 explicit HostCache(size_t max_entries); 399 400 HostCache(const HostCache&) = delete; 401 HostCache& operator=(const HostCache&) = delete; 402 403 ~HostCache(); 404 405 // Returns a pointer to the matching (key, entry) pair, which is valid at time 406 // |now|. If |ignore_secure| is true, ignores the secure field in |key| when 407 // looking for a match. If there is no matching entry, returns NULL. 408 const std::pair<const Key, Entry>* Lookup(const Key& key, 409 base::TimeTicks now, 410 bool ignore_secure = false); 411 412 // Returns a pointer to the matching (key, entry) pair, whether it is valid or 413 // stale at time |now|. Fills in |stale_out| with information about how stale 414 // it is. If |ignore_secure| is true, ignores the secure field in |key| when 415 // looking for a match. If there is no matching entry, returns NULL. 416 const std::pair<const Key, Entry>* LookupStale(const Key& key, 417 base::TimeTicks now, 418 EntryStaleness* stale_out, 419 bool ignore_secure = false); 420 421 // Overwrites or creates an entry for |key|. 422 // |entry| is the value to set, |now| is the current time 423 // |ttl| is the "time to live". 424 void Set(const Key& key, 425 const Entry& entry, 426 base::TimeTicks now, 427 base::TimeDelta ttl); 428 429 // Checks whether an entry exists for `hostname`. 430 // If so, returns the matching key and writes the source (e.g. DNS, HOSTS 431 // file, etc.) to `source_out` and the staleness to `stale_out` (if they are 432 // not null). If no entry exists, returns nullptr. 433 // 434 // For testing use only and not very performant. Production code should only 435 // do lookups by precise Key. 436 const HostCache::Key* GetMatchingKeyForTesting( 437 std::string_view hostname, 438 HostCache::Entry::Source* source_out = nullptr, 439 HostCache::EntryStaleness* stale_out = nullptr) const; 440 441 // Marks all entries as stale on account of a network change. 442 void Invalidate(); 443 444 void set_persistence_delegate(PersistenceDelegate* delegate); 445 set_tick_clock_for_testing(const base::TickClock * tick_clock)446 void set_tick_clock_for_testing(const base::TickClock* tick_clock) { 447 tick_clock_ = tick_clock; 448 } 449 450 // Empties the cache. 451 void clear(); 452 453 // Clears hosts matching |host_filter| from the cache. 454 void ClearForHosts( 455 const base::RepeatingCallback<bool(const std::string&)>& host_filter); 456 457 // Fills the provided base::Value with the contents of the cache for 458 // serialization. `entry_list` must be non-null list, and will be cleared 459 // before adding the cache contents. 460 void GetList(base::Value::List& entry_list, 461 bool include_staleness, 462 SerializationType serialization_type) const; 463 // Takes a base::Value list representing cache entries and stores them in the 464 // cache, skipping any that already have entries. Returns true on success, 465 // false on failure. 466 bool RestoreFromListValue(const base::Value::List& old_cache); 467 // Returns the number of entries that were restored in the last call to 468 // RestoreFromListValue(). last_restore_size()469 size_t last_restore_size() const { return restore_size_; } 470 471 // Returns the number of entries in the cache. 472 size_t size() const; 473 474 // Following are used by net_internals UI. 475 size_t max_entries() const; network_changes()476 int network_changes() const { return network_changes_; } entries()477 const EntryMap& entries() const { return entries_; } 478 479 private: 480 FRIEND_TEST_ALL_PREFIXES(HostCacheTest, NoCache); 481 482 enum SetOutcome : int; 483 484 // The result of cache lookup. 485 // 486 // These values are persisted to logs. Entries should not be renumbered and 487 // numeric values should never be reused. 488 // 489 // LINT.IfChange(LookupOutcome) 490 enum class LookupOutcome { 491 kLookupMissAbsent = 0, 492 kLookupMissStale = 1, 493 kLookupHitValid = 2, 494 kLookupHitStale = 3, 495 kMaxValue = kLookupHitStale 496 }; 497 // LINT.ThenChange(//tools/metrics/histograms/metadata/net/enums.xml:LookupOutcome) 498 499 // The reason why an entry was erased. 500 // 501 // These values are persisted to logs. Entries should not be renumbered and 502 // numeric values should never be reused. 503 // 504 // LINT.IfChange(EraseReason) 505 enum class EraseReason { 506 kEraseEvict = 0, 507 kEraseClear = 1, 508 kEraseDestruct = 2, 509 kMaxValue = kEraseDestruct 510 }; 511 // LINT.ThenChange(//tools/metrics/histograms/metadata/net/enums.xml:EraseReason) 512 513 // Returns the result that is least stale, based on the number of network 514 // changes since the result was cached. If the results are equally stale, 515 // prefers a securely retrieved result. Returns nullptr if both results are 516 // nullptr. 517 static std::pair<const HostCache::Key, HostCache::Entry>* 518 GetLessStaleMoreSecureResult( 519 base::TimeTicks now, 520 std::pair<const HostCache::Key, HostCache::Entry>* result1, 521 std::pair<const HostCache::Key, HostCache::Entry>* result2); 522 523 // Returns matching key and entry from cache and nullptr if no match. Ignores 524 // the secure field in |initial_key| if |ignore_secure| is true. 525 std::pair<const Key, Entry>* LookupInternalIgnoringFields( 526 const Key& initial_key, 527 base::TimeTicks now, 528 bool ignore_secure); 529 530 // Returns matching key and entry from cache and nullptr if no match. An exact 531 // match for |key| is required. 532 std::pair<const Key, Entry>* LookupInternal(const Key& key); 533 534 // Record cache lookup metrics for the `entry`. 535 void RecordLookup(LookupOutcome outcome, 536 base::TimeTicks now, 537 const Key& key, 538 const Entry* entry); 539 540 // Record cache erase metrics for the `entry`. 541 void RecordErase(EraseReason reason, 542 base::TimeTicks now, 543 const Key& key, 544 const Entry& entry); 545 546 // Record cache erase metrics for all entries. 547 void RecordEraseAll(EraseReason reason, base::TimeTicks now); 548 549 // Returns true if this HostCache can contain no entries. caching_is_disabled()550 bool caching_is_disabled() const { return max_entries_ == 0; } 551 552 // Returns true if an entry was removed. 553 bool EvictOneEntry(base::TimeTicks now); 554 // Helper to check if an Entry is currently pinned in the cache. 555 bool HasActivePin(const Entry& entry); 556 // Helper to insert an Entry into the cache. 557 void AddEntry(const Key& key, Entry&& entry); 558 559 // Map from hostname (presumably in lowercase canonicalized format) to 560 // a resolved result entry. 561 EntryMap entries_; 562 size_t max_entries_; 563 int network_changes_ = 0; 564 // Number of cache entries that were restored in the last call to 565 // RestoreFromListValue(). Used in histograms. 566 size_t restore_size_ = 0; 567 568 raw_ptr<PersistenceDelegate> delegate_ = nullptr; 569 // Shared tick clock, overridden for testing. 570 raw_ptr<const base::TickClock> tick_clock_; 571 572 THREAD_CHECKER(thread_checker_); 573 }; 574 575 } // namespace net 576 577 // Debug logging support 578 std::ostream& operator<<(std::ostream& out, 579 const net::HostCache::EntryStaleness& s); 580 581 #endif // NET_DNS_HOST_CACHE_H_ 582