| // Copyright 2014 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/http/http_server_properties_manager.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/containers/adapters.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/time/tick_clock.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "net/base/features.h" |
| #include "net/base/host_port_pair.h" |
| #include "net/base/ip_address.h" |
| #include "net/base/port_util.h" |
| #include "net/base/privacy_mode.h" |
| #include "net/http/http_server_properties.h" |
| #include "net/third_party/quiche/src/quiche/quic/platform/api/quic_hostname_utils.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "url/gurl.h" |
| #include "url/scheme_host_port.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| // "version" 0 indicates, http_server_properties doesn't have "version" |
| // property. |
| const int kMissingVersion = 0; |
| |
| // The version number of persisted http_server_properties. |
| const int kVersionNumber = 5; |
| |
| // Persist at most 200 currently-broken alternative services to disk. |
| const int kMaxBrokenAlternativeServicesToPersist = 200; |
| |
| const char kServerKey[] = "server"; |
| const char kQuicServerIdKey[] = "server_id"; |
| const char kNetworkAnonymizationKey[] = "anonymization"; |
| const char kVersionKey[] = "version"; |
| const char kServersKey[] = "servers"; |
| const char kSupportsSpdyKey[] = "supports_spdy"; |
| const char kSupportsQuicKey[] = "supports_quic"; |
| const char kQuicServers[] = "quic_servers"; |
| const char kServerInfoKey[] = "server_info"; |
| const char kUsedQuicKey[] = "used_quic"; |
| const char kAddressKey[] = "address"; |
| const char kAlternativeServiceKey[] = "alternative_service"; |
| const char kProtocolKey[] = "protocol_str"; |
| const char kHostKey[] = "host"; |
| const char kPortKey[] = "port"; |
| const char kExpirationKey[] = "expiration"; |
| const char kAdvertisedAlpnsKey[] = "advertised_alpns"; |
| const char kNetworkStatsKey[] = "network_stats"; |
| const char kSrttKey[] = "srtt"; |
| const char kBrokenAlternativeServicesKey[] = "broken_alternative_services"; |
| const char kBrokenUntilKey[] = "broken_until"; |
| const char kBrokenCountKey[] = "broken_count"; |
| |
| // Utility method to return only those AlternativeServiceInfos that should be |
| // persisted to disk. In particular, removes expired and invalid alternative |
| // services. Also checks if an alternative service for the same canonical suffix |
| // has already been saved, and if so, returns an empty list. |
| AlternativeServiceInfoVector GetAlternativeServiceToPersist( |
| const absl::optional<AlternativeServiceInfoVector>& alternative_services, |
| const HttpServerProperties::ServerInfoMapKey& server_info_key, |
| base::Time now, |
| const HttpServerPropertiesManager::GetCannonicalSuffix& |
| get_canonical_suffix, |
| std::set<std::pair<std::string, NetworkAnonymizationKey>>* |
| persisted_canonical_suffix_set) { |
| if (!alternative_services) |
| return AlternativeServiceInfoVector(); |
| // Separate out valid, non-expired AlternativeServiceInfo entries. |
| AlternativeServiceInfoVector notbroken_alternative_service_info_vector; |
| for (const auto& alternative_service_info : alternative_services.value()) { |
| if (alternative_service_info.expiration() < now || |
| !IsAlternateProtocolValid( |
| alternative_service_info.alternative_service().protocol)) { |
| continue; |
| } |
| notbroken_alternative_service_info_vector.push_back( |
| alternative_service_info); |
| } |
| if (notbroken_alternative_service_info_vector.empty()) |
| return notbroken_alternative_service_info_vector; |
| const std::string* canonical_suffix = |
| get_canonical_suffix.Run(server_info_key.server.host()); |
| if (canonical_suffix) { |
| // Don't save if have already saved information associated with the same |
| // canonical suffix. |
| std::pair<std::string, NetworkAnonymizationKey> index( |
| *canonical_suffix, server_info_key.network_anonymization_key); |
| if (persisted_canonical_suffix_set->find(index) != |
| persisted_canonical_suffix_set->end()) { |
| return AlternativeServiceInfoVector(); |
| } |
| persisted_canonical_suffix_set->emplace(std::move(index)); |
| } |
| return notbroken_alternative_service_info_vector; |
| } |
| |
| void AddAlternativeServiceFieldsToDictionaryValue( |
| const AlternativeService& alternative_service, |
| base::Value::Dict& dict) { |
| dict.Set(kPortKey, alternative_service.port); |
| if (!alternative_service.host.empty()) { |
| dict.Set(kHostKey, alternative_service.host); |
| } |
| dict.Set(kProtocolKey, NextProtoToString(alternative_service.protocol)); |
| } |
| |
| // Fails in the case of NetworkAnonymizationKeys that can't be persisted to |
| // disk, like unique origins. |
| bool TryAddBrokenAlternativeServiceFieldsToDictionaryValue( |
| const BrokenAlternativeService& broken_alt_service, |
| base::Value::Dict& dict) { |
| base::Value network_anonymization_key_value; |
| if (!broken_alt_service.network_anonymization_key.ToValue( |
| &network_anonymization_key_value)) { |
| return false; |
| } |
| |
| dict.Set(kNetworkAnonymizationKey, |
| std::move(network_anonymization_key_value)); |
| AddAlternativeServiceFieldsToDictionaryValue( |
| broken_alt_service.alternative_service, dict); |
| return true; |
| } |
| |
| quic::QuicServerId QuicServerIdFromString(const std::string& str) { |
| GURL url(str); |
| if (!url.is_valid()) { |
| return quic::QuicServerId(); |
| } |
| HostPortPair host_port_pair = HostPortPair::FromURL(url); |
| return quic::QuicServerId(host_port_pair.host(), host_port_pair.port(), |
| url.path_piece() == "/private" |
| ? PRIVACY_MODE_ENABLED |
| : PRIVACY_MODE_DISABLED); |
| } |
| |
| std::string QuicServerIdToString(const quic::QuicServerId& server_id) { |
| HostPortPair host_port_pair(server_id.host(), server_id.port()); |
| return "https://" + host_port_pair.ToString() + |
| (server_id.privacy_mode_enabled() ? "/private" : ""); |
| } |
| |
| // Takes in a base::Value::Dict, and whether NetworkIsolationKeys are enabled |
| // for HttpServerProperties, and extracts the NetworkAnonymizationKey stored |
| // with the |kNetworkAnonymizationKey| in the dictionary, and writes it to |
| // |out_network_anonymization_key|. Returns false if unable to load a |
| // NetworkAnonymizationKey, or the NetworkAnonymizationKey is non-empty, but |
| // |use_network_anonymization_key| is false. |
| bool GetNetworkIsolationKeyFromDict( |
| const base::Value::Dict& dict, |
| bool use_network_anonymization_key, |
| NetworkAnonymizationKey* out_network_anonymization_key) { |
| const base::Value* network_anonymization_key_value = |
| dict.Find(kNetworkAnonymizationKey); |
| NetworkAnonymizationKey network_anonymization_key; |
| if (!network_anonymization_key_value || |
| !NetworkAnonymizationKey::FromValue(*network_anonymization_key_value, |
| &network_anonymization_key)) { |
| return false; |
| } |
| |
| // Fail if NetworkIsolationKeys are disabled, but the entry has a non-empty |
| // NetworkAnonymizationKey. |
| if (!use_network_anonymization_key && !network_anonymization_key.IsEmpty()) |
| return false; |
| |
| *out_network_anonymization_key = std::move(network_anonymization_key); |
| return true; |
| } |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // HttpServerPropertiesManager |
| |
| HttpServerPropertiesManager::HttpServerPropertiesManager( |
| std::unique_ptr<HttpServerProperties::PrefDelegate> pref_delegate, |
| OnPrefsLoadedCallback on_prefs_loaded_callback, |
| size_t max_server_configs_stored_in_properties, |
| NetLog* net_log, |
| const base::TickClock* clock) |
| : pref_delegate_(std::move(pref_delegate)), |
| on_prefs_loaded_callback_(std::move(on_prefs_loaded_callback)), |
| max_server_configs_stored_in_properties_( |
| max_server_configs_stored_in_properties), |
| clock_(clock), |
| net_log_( |
| NetLogWithSource::Make(net_log, |
| NetLogSourceType::HTTP_SERVER_PROPERTIES)) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(pref_delegate_); |
| DCHECK(on_prefs_loaded_callback_); |
| DCHECK(clock_); |
| |
| pref_delegate_->WaitForPrefLoad( |
| base::BindOnce(&HttpServerPropertiesManager::OnHttpServerPropertiesLoaded, |
| pref_load_weak_ptr_factory_.GetWeakPtr())); |
| net_log_.BeginEvent(NetLogEventType::HTTP_SERVER_PROPERTIES_INITIALIZATION); |
| } |
| |
| HttpServerPropertiesManager::~HttpServerPropertiesManager() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| void HttpServerPropertiesManager::ReadPrefs( |
| std::unique_ptr<HttpServerProperties::ServerInfoMap>* server_info_map, |
| IPAddress* last_local_address_when_quic_worked, |
| std::unique_ptr<HttpServerProperties::QuicServerInfoMap>* |
| quic_server_info_map, |
| std::unique_ptr<BrokenAlternativeServiceList>* |
| broken_alternative_service_list, |
| std::unique_ptr<RecentlyBrokenAlternativeServices>* |
| recently_broken_alternative_services) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| net_log_.EndEvent(NetLogEventType::HTTP_SERVER_PROPERTIES_INITIALIZATION); |
| |
| const base::Value::Dict& http_server_properties_dict = |
| pref_delegate_->GetServerProperties(); |
| |
| net_log_.AddEvent(NetLogEventType::HTTP_SERVER_PROPERTIES_UPDATE_CACHE, |
| [&] { return http_server_properties_dict.Clone(); }); |
| absl::optional<int> maybe_version_number = |
| http_server_properties_dict.FindInt(kVersionKey); |
| if (!maybe_version_number.has_value() || |
| *maybe_version_number != kVersionNumber) { |
| DVLOG(1) << "Missing or unsupported. Clearing all properties. " |
| << maybe_version_number.value_or(kMissingVersion); |
| return; |
| } |
| |
| // For Version 5, data is stored in the following format. |
| // `servers` are saved in LRU order (least-recently-used item is in the |
| // front). `servers` are in the format flattened representation of |
| // (scheme/host/port) where port might be ignored if is default with scheme. |
| // |
| // "http_server_properties": { |
| // "servers": [ |
| // {"https://yt3.ggpht.com" : {...}}, |
| // {"http://0.client-channel.google.com:443" : {...}}, |
| // {"http://0-edge-chat.facebook.com" : {...}}, |
| // ... |
| // ], ... |
| // }, |
| const base::Value::List* servers_list = |
| http_server_properties_dict.FindList(kServersKey); |
| if (!servers_list) { |
| DVLOG(1) << "Malformed http_server_properties for servers list."; |
| return; |
| } |
| |
| ReadLastLocalAddressWhenQuicWorked(http_server_properties_dict, |
| last_local_address_when_quic_worked); |
| |
| *server_info_map = std::make_unique<HttpServerProperties::ServerInfoMap>(); |
| *quic_server_info_map = |
| std::make_unique<HttpServerProperties::QuicServerInfoMap>( |
| max_server_configs_stored_in_properties_); |
| |
| bool use_network_anonymization_key = |
| NetworkAnonymizationKey::IsPartitioningEnabled(); |
| |
| // Iterate `servers_list` (least-recently-used item is in the front) so that |
| // entries are inserted into `server_info_map` from oldest to newest. |
| for (const auto& server_dict_value : *servers_list) { |
| if (!server_dict_value.is_dict()) { |
| DVLOG(1) << "Malformed http_server_properties for servers dictionary."; |
| continue; |
| } |
| AddServerData(server_dict_value.GetDict(), server_info_map->get(), |
| use_network_anonymization_key); |
| } |
| |
| AddToQuicServerInfoMap(http_server_properties_dict, |
| use_network_anonymization_key, |
| quic_server_info_map->get()); |
| |
| // Read list containing broken and recently-broken alternative services, if |
| // it exists. |
| const base::Value::List* broken_alt_svc_list = |
| http_server_properties_dict.FindList(kBrokenAlternativeServicesKey); |
| if (broken_alt_svc_list) { |
| *broken_alternative_service_list = |
| std::make_unique<BrokenAlternativeServiceList>(); |
| *recently_broken_alternative_services = |
| std::make_unique<RecentlyBrokenAlternativeServices>( |
| kMaxRecentlyBrokenAlternativeServiceEntries); |
| |
| // Iterate `broken_alt_svc_list` (least-recently-used item is in the front) |
| // so that entries are inserted into `recently_broken_alternative_services` |
| // from oldest to newest. |
| for (const auto& broken_alt_svc_entry_dict_value : *broken_alt_svc_list) { |
| if (!broken_alt_svc_entry_dict_value.is_dict()) { |
| DVLOG(1) << "Malformed broken alterantive service entry."; |
| continue; |
| } |
| AddToBrokenAlternativeServices( |
| broken_alt_svc_entry_dict_value.GetDict(), |
| use_network_anonymization_key, broken_alternative_service_list->get(), |
| recently_broken_alternative_services->get()); |
| } |
| } |
| |
| // Set the properties loaded from prefs on |http_server_properties_impl_|. |
| |
| // TODO(mmenke): Rename this once more information is stored in this map. |
| UMA_HISTOGRAM_COUNTS_1M("Net.HttpServerProperties.CountOfServers", |
| (*server_info_map)->size()); |
| |
| UMA_HISTOGRAM_COUNTS_1000("Net.CountOfQuicServerInfos", |
| (*quic_server_info_map)->size()); |
| |
| if (*recently_broken_alternative_services) { |
| DCHECK(*broken_alternative_service_list); |
| |
| UMA_HISTOGRAM_COUNTS_1000("Net.CountOfBrokenAlternativeServices", |
| (*broken_alternative_service_list)->size()); |
| UMA_HISTOGRAM_COUNTS_1000("Net.CountOfRecentlyBrokenAlternativeServices", |
| (*recently_broken_alternative_services)->size()); |
| } |
| } |
| |
| void HttpServerPropertiesManager::AddToBrokenAlternativeServices( |
| const base::Value::Dict& broken_alt_svc_entry_dict, |
| bool use_network_anonymization_key, |
| BrokenAlternativeServiceList* broken_alternative_service_list, |
| RecentlyBrokenAlternativeServices* recently_broken_alternative_services) { |
| AlternativeService alt_service; |
| if (!ParseAlternativeServiceDict(broken_alt_svc_entry_dict, false, |
| "broken alternative services", |
| &alt_service)) { |
| return; |
| } |
| |
| NetworkAnonymizationKey network_anonymization_key; |
| if (!GetNetworkIsolationKeyFromDict(broken_alt_svc_entry_dict, |
| use_network_anonymization_key, |
| &network_anonymization_key)) { |
| return; |
| } |
| |
| // Each entry must contain either broken-count and/or broken-until fields. |
| bool contains_broken_count_or_broken_until = false; |
| |
| // Read broken-count and add an entry for |alt_service| into |
| // |recently_broken_alternative_services|. |
| if (broken_alt_svc_entry_dict.Find(kBrokenCountKey)) { |
| absl::optional<int> broken_count = |
| broken_alt_svc_entry_dict.FindInt(kBrokenCountKey); |
| if (!broken_count.has_value()) { |
| DVLOG(1) << "Recently broken alternative service has malformed " |
| << "broken-count."; |
| return; |
| } |
| if (broken_count.value() < 0) { |
| DVLOG(1) << "Broken alternative service has negative broken-count."; |
| return; |
| } |
| recently_broken_alternative_services->Put( |
| BrokenAlternativeService(alt_service, network_anonymization_key, |
| use_network_anonymization_key), |
| broken_count.value()); |
| contains_broken_count_or_broken_until = true; |
| } |
| |
| // Read broken-until and add an entry for |alt_service| in |
| // |broken_alternative_service_list|. |
| if (broken_alt_svc_entry_dict.Find(kBrokenUntilKey)) { |
| const std::string* expiration_string = |
| broken_alt_svc_entry_dict.FindString(kBrokenUntilKey); |
| int64_t expiration_int64; |
| if (!expiration_string || |
| !base::StringToInt64(*expiration_string, &expiration_int64)) { |
| DVLOG(1) << "Broken alternative service has malformed broken-until " |
| << "string."; |
| return; |
| } |
| |
| time_t expiration_time_t = static_cast<time_t>(expiration_int64); |
| // Convert expiration from time_t to Time to TimeTicks |
| base::TimeTicks expiration_time_ticks = |
| clock_->NowTicks() + |
| (base::Time::FromTimeT(expiration_time_t) - base::Time::Now()); |
| broken_alternative_service_list->push_back(std::make_pair( |
| BrokenAlternativeService(alt_service, network_anonymization_key, |
| use_network_anonymization_key), |
| expiration_time_ticks)); |
| contains_broken_count_or_broken_until = true; |
| } |
| |
| if (!contains_broken_count_or_broken_until) { |
| DVLOG(1) << "Broken alternative service has neither broken-count nor " |
| << "broken-until specified."; |
| } |
| } |
| |
| void HttpServerPropertiesManager::AddServerData( |
| const base::Value::Dict& server_dict, |
| HttpServerProperties::ServerInfoMap* server_info_map, |
| bool use_network_anonymization_key) { |
| // Get server's scheme/host/pair. |
| const std::string* server_str = server_dict.FindString(kServerKey); |
| NetworkAnonymizationKey network_anonymization_key; |
| // Can't load entry if server name missing, or if the network isolation key is |
| // missing or invalid. |
| if (!server_str || !GetNetworkIsolationKeyFromDict( |
| server_dict, use_network_anonymization_key, |
| &network_anonymization_key)) { |
| return; |
| } |
| |
| url::SchemeHostPort spdy_server((GURL(*server_str))); |
| if (spdy_server.host().empty()) { |
| DVLOG(1) << "Malformed http_server_properties for server: " << server_str; |
| return; |
| } |
| |
| HttpServerProperties::ServerInfo server_info; |
| |
| server_info.supports_spdy = server_dict.FindBool(kSupportsSpdyKey); |
| |
| if (ParseAlternativeServiceInfo(spdy_server, server_dict, &server_info)) |
| ParseNetworkStats(spdy_server, server_dict, &server_info); |
| |
| if (!server_info.empty()) { |
| server_info_map->Put(HttpServerProperties::ServerInfoMapKey( |
| std::move(spdy_server), network_anonymization_key, |
| use_network_anonymization_key), |
| std::move(server_info)); |
| } |
| } |
| |
| bool HttpServerPropertiesManager::ParseAlternativeServiceDict( |
| const base::Value::Dict& dict, |
| bool host_optional, |
| const std::string& parsing_under, |
| AlternativeService* alternative_service) { |
| // Protocol is mandatory. |
| const std::string* protocol_str = dict.FindString(kProtocolKey); |
| if (!protocol_str) { |
| DVLOG(1) << "Malformed alternative service protocol string under: " |
| << parsing_under; |
| return false; |
| } |
| NextProto protocol = NextProtoFromString(*protocol_str); |
| if (!IsAlternateProtocolValid(protocol)) { |
| DVLOG(1) << "Invalid alternative service protocol string \"" << protocol_str |
| << "\" under: " << parsing_under; |
| return false; |
| } |
| alternative_service->protocol = protocol; |
| |
| // If host is optional, it defaults to "". |
| std::string host = ""; |
| const std::string* hostp = nullptr; |
| if (dict.Find(kHostKey)) { |
| hostp = dict.FindString(kHostKey); |
| if (!hostp) { |
| DVLOG(1) << "Malformed alternative service host string under: " |
| << parsing_under; |
| return false; |
| } |
| host = *hostp; |
| } else if (!host_optional) { |
| DVLOG(1) << "alternative service missing host string under: " |
| << parsing_under; |
| return false; |
| } |
| alternative_service->host = host; |
| |
| // Port is mandatory. |
| absl::optional<int> maybe_port = dict.FindInt(kPortKey); |
| if (!maybe_port.has_value() || !IsPortValid(maybe_port.value())) { |
| DVLOG(1) << "Malformed alternative service port under: " << parsing_under; |
| return false; |
| } |
| alternative_service->port = static_cast<uint32_t>(maybe_port.value()); |
| |
| return true; |
| } |
| |
| bool HttpServerPropertiesManager::ParseAlternativeServiceInfoDictOfServer( |
| const base::Value::Dict& dict, |
| const std::string& server_str, |
| AlternativeServiceInfo* alternative_service_info) { |
| AlternativeService alternative_service; |
| if (!ParseAlternativeServiceDict(dict, true, "server " + server_str, |
| &alternative_service)) { |
| return false; |
| } |
| alternative_service_info->set_alternative_service(alternative_service); |
| |
| // Expiration is optional, defaults to one day. |
| if (!dict.Find(kExpirationKey)) { |
| alternative_service_info->set_expiration(base::Time::Now() + base::Days(1)); |
| } else { |
| const std::string* expiration_string = dict.FindString(kExpirationKey); |
| if (expiration_string) { |
| int64_t expiration_int64 = 0; |
| if (!base::StringToInt64(*expiration_string, &expiration_int64)) { |
| DVLOG(1) << "Malformed alternative service expiration for server: " |
| << server_str; |
| return false; |
| } |
| alternative_service_info->set_expiration( |
| base::Time::FromInternalValue(expiration_int64)); |
| } else { |
| DVLOG(1) << "Malformed alternative service expiration for server: " |
| << server_str; |
| return false; |
| } |
| } |
| |
| // Advertised versions list is optional. |
| if (dict.Find(kAdvertisedAlpnsKey)) { |
| const base::Value::List* versions_list = dict.FindList(kAdvertisedAlpnsKey); |
| if (!versions_list) { |
| DVLOG(1) << "Malformed alternative service advertised versions list for " |
| << "server: " << server_str; |
| return false; |
| } |
| quic::ParsedQuicVersionVector advertised_versions; |
| for (const auto& value : *versions_list) { |
| const std::string* version_string = value.GetIfString(); |
| if (!version_string) { |
| DVLOG(1) << "Malformed alternative service version for server: " |
| << server_str; |
| return false; |
| } |
| quic::ParsedQuicVersion version = |
| quic::ParseQuicVersionString(*version_string); |
| if (version != quic::ParsedQuicVersion::Unsupported()) { |
| advertised_versions.push_back(version); |
| } |
| } |
| alternative_service_info->set_advertised_versions(advertised_versions); |
| } |
| |
| return true; |
| } |
| |
| bool HttpServerPropertiesManager::ParseAlternativeServiceInfo( |
| const url::SchemeHostPort& server, |
| const base::Value::Dict& server_pref_dict, |
| HttpServerProperties::ServerInfo* server_info) { |
| DCHECK(!server_info->alternative_services.has_value()); |
| const base::Value::List* alternative_service_list = |
| server_pref_dict.FindList(kAlternativeServiceKey); |
| if (!alternative_service_list) { |
| return true; |
| } |
| if (server.scheme() != "https") { |
| return false; |
| } |
| |
| AlternativeServiceInfoVector alternative_service_info_vector; |
| for (const auto& alternative_service_list_item : *alternative_service_list) { |
| if (!alternative_service_list_item.is_dict()) |
| return false; |
| AlternativeServiceInfo alternative_service_info; |
| if (!ParseAlternativeServiceInfoDictOfServer( |
| alternative_service_list_item.GetDict(), server.Serialize(), |
| &alternative_service_info)) { |
| return false; |
| } |
| if (base::Time::Now() < alternative_service_info.expiration()) { |
| alternative_service_info_vector.push_back(alternative_service_info); |
| } |
| } |
| |
| if (alternative_service_info_vector.empty()) { |
| return false; |
| } |
| |
| server_info->alternative_services = alternative_service_info_vector; |
| return true; |
| } |
| |
| void HttpServerPropertiesManager::ReadLastLocalAddressWhenQuicWorked( |
| const base::Value::Dict& http_server_properties_dict, |
| IPAddress* last_local_address_when_quic_worked) { |
| const base::Value::Dict* supports_quic_dict = |
| http_server_properties_dict.FindDict(kSupportsQuicKey); |
| if (!supports_quic_dict) { |
| return; |
| } |
| const base::Value* used_quic = supports_quic_dict->Find(kUsedQuicKey); |
| if (!used_quic || !used_quic->is_bool()) { |
| DVLOG(1) << "Malformed SupportsQuic"; |
| return; |
| } |
| if (!used_quic->GetBool()) |
| return; |
| |
| const std::string* address = supports_quic_dict->FindString(kAddressKey); |
| if (!address || |
| !last_local_address_when_quic_worked->AssignFromIPLiteral(*address)) { |
| DVLOG(1) << "Malformed SupportsQuic"; |
| } |
| } |
| |
| void HttpServerPropertiesManager::ParseNetworkStats( |
| const url::SchemeHostPort& server, |
| const base::Value::Dict& server_pref_dict, |
| HttpServerProperties::ServerInfo* server_info) { |
| DCHECK(!server_info->server_network_stats.has_value()); |
| const base::Value::Dict* server_network_stats_dict = |
| server_pref_dict.FindDict(kNetworkStatsKey); |
| if (!server_network_stats_dict) { |
| return; |
| } |
| absl::optional<int> maybe_srtt = server_network_stats_dict->FindInt(kSrttKey); |
| if (!maybe_srtt.has_value()) { |
| DVLOG(1) << "Malformed ServerNetworkStats for server: " |
| << server.Serialize(); |
| return; |
| } |
| ServerNetworkStats server_network_stats; |
| server_network_stats.srtt = base::Microseconds(maybe_srtt.value()); |
| // TODO(rtenneti): When QUIC starts using bandwidth_estimate, then persist |
| // bandwidth_estimate. |
| server_info->server_network_stats = server_network_stats; |
| } |
| |
| void HttpServerPropertiesManager::AddToQuicServerInfoMap( |
| const base::Value::Dict& http_server_properties_dict, |
| bool use_network_anonymization_key, |
| HttpServerProperties::QuicServerInfoMap* quic_server_info_map) { |
| const base::Value::List* quic_server_info_list = |
| http_server_properties_dict.FindList(kQuicServers); |
| if (!quic_server_info_list) { |
| DVLOG(1) << "Malformed http_server_properties for quic_servers."; |
| return; |
| } |
| |
| for (const auto& quic_server_info_value : *quic_server_info_list) { |
| const base::Value::Dict* quic_server_info_dict = |
| quic_server_info_value.GetIfDict(); |
| if (!quic_server_info_dict) |
| continue; |
| |
| const std::string* quic_server_id_str = |
| quic_server_info_dict->FindString(kQuicServerIdKey); |
| if (!quic_server_id_str || quic_server_id_str->empty()) |
| continue; |
| |
| quic::QuicServerId quic_server_id = |
| QuicServerIdFromString(*quic_server_id_str); |
| if (quic_server_id.host().empty()) { |
| DVLOG(1) << "Malformed http_server_properties for quic server: " |
| << quic_server_id_str; |
| continue; |
| } |
| |
| NetworkAnonymizationKey network_anonymization_key; |
| if (!GetNetworkIsolationKeyFromDict(*quic_server_info_dict, |
| use_network_anonymization_key, |
| &network_anonymization_key)) { |
| DVLOG(1) << "Malformed http_server_properties quic server dict: " |
| << *quic_server_id_str; |
| continue; |
| } |
| |
| const std::string* quic_server_info = |
| quic_server_info_dict->FindString(kServerInfoKey); |
| if (!quic_server_info) { |
| DVLOG(1) << "Malformed http_server_properties quic server info: " |
| << *quic_server_id_str; |
| continue; |
| } |
| quic_server_info_map->Put(HttpServerProperties::QuicServerInfoMapKey( |
| quic_server_id, network_anonymization_key, |
| use_network_anonymization_key), |
| *quic_server_info); |
| } |
| } |
| |
| void HttpServerPropertiesManager::WriteToPrefs( |
| const HttpServerProperties::ServerInfoMap& server_info_map, |
| const GetCannonicalSuffix& get_canonical_suffix, |
| const IPAddress& last_local_address_when_quic_worked, |
| const HttpServerProperties::QuicServerInfoMap& quic_server_info_map, |
| const BrokenAlternativeServiceList& broken_alternative_service_list, |
| const RecentlyBrokenAlternativeServices& |
| recently_broken_alternative_services, |
| base::OnceClosure callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // If loading prefs hasn't completed, don't call it, since this will overwrite |
| // existing prefs. |
| on_prefs_loaded_callback_.Reset(); |
| |
| std::set<std::pair<std::string, NetworkAnonymizationKey>> |
| persisted_canonical_suffix_set; |
| const base::Time now = base::Time::Now(); |
| base::Value::Dict http_server_properties_dict; |
| |
| // Convert |server_info_map| to a list Value and add it to |
| // |http_server_properties_dict|. |
| base::Value::List servers_list; |
| for (const auto& [key, server_info] : server_info_map) { |
| // If can't convert the NetworkAnonymizationKey to a value, don't save to |
| // disk. Generally happens because the key is for a unique origin. |
| base::Value network_anonymization_key_value; |
| if (!key.network_anonymization_key.ToValue( |
| &network_anonymization_key_value)) { |
| continue; |
| } |
| |
| base::Value::Dict server_dict; |
| |
| bool supports_spdy = server_info.supports_spdy.value_or(false); |
| if (supports_spdy) |
| server_dict.Set(kSupportsSpdyKey, supports_spdy); |
| |
| AlternativeServiceInfoVector alternative_services = |
| GetAlternativeServiceToPersist(server_info.alternative_services, key, |
| now, get_canonical_suffix, |
| &persisted_canonical_suffix_set); |
| if (!alternative_services.empty()) |
| SaveAlternativeServiceToServerPrefs(alternative_services, server_dict); |
| |
| if (server_info.server_network_stats) { |
| SaveNetworkStatsToServerPrefs(*server_info.server_network_stats, |
| server_dict); |
| } |
| |
| // Don't add empty entries. This can happen if, for example, all alternative |
| // services are empty, or |supports_spdy| is set to false, and all other |
| // fields are not set. |
| if (server_dict.empty()) |
| continue; |
| server_dict.Set(kServerKey, key.server.Serialize()); |
| server_dict.Set(kNetworkAnonymizationKey, |
| std::move(network_anonymization_key_value)); |
| servers_list.Append(std::move(server_dict)); |
| } |
| // Reverse `servers_list`. The least recently used item will be in the front. |
| std::reverse(servers_list.begin(), servers_list.end()); |
| |
| http_server_properties_dict.Set(kServersKey, std::move(servers_list)); |
| |
| http_server_properties_dict.Set(kVersionKey, kVersionNumber); |
| |
| SaveLastLocalAddressWhenQuicWorkedToPrefs(last_local_address_when_quic_worked, |
| http_server_properties_dict); |
| |
| SaveQuicServerInfoMapToServerPrefs(quic_server_info_map, |
| http_server_properties_dict); |
| |
| SaveBrokenAlternativeServicesToPrefs( |
| broken_alternative_service_list, kMaxBrokenAlternativeServicesToPersist, |
| recently_broken_alternative_services, http_server_properties_dict); |
| |
| net_log_.AddEvent(NetLogEventType::HTTP_SERVER_PROPERTIES_UPDATE_PREFS, |
| [&] { return std::move(http_server_properties_dict); }); |
| |
| pref_delegate_->SetServerProperties(std::move(http_server_properties_dict), |
| std::move(callback)); |
| } |
| |
| void HttpServerPropertiesManager::SaveAlternativeServiceToServerPrefs( |
| const AlternativeServiceInfoVector& alternative_service_info_vector, |
| base::Value::Dict& server_pref_dict) { |
| if (alternative_service_info_vector.empty()) { |
| return; |
| } |
| base::Value::List alternative_service_list; |
| for (const AlternativeServiceInfo& alternative_service_info : |
| alternative_service_info_vector) { |
| const AlternativeService& alternative_service = |
| alternative_service_info.alternative_service(); |
| DCHECK(IsAlternateProtocolValid(alternative_service.protocol)); |
| base::Value::Dict alternative_service_dict; |
| AddAlternativeServiceFieldsToDictionaryValue(alternative_service, |
| alternative_service_dict); |
| // JSON cannot store int64_t, so expiration is converted to a string. |
| alternative_service_dict.Set( |
| kExpirationKey, |
| base::NumberToString( |
| alternative_service_info.expiration().ToInternalValue())); |
| base::Value::List advertised_versions_list; |
| for (const auto& version : alternative_service_info.advertised_versions()) { |
| advertised_versions_list.Append(quic::AlpnForVersion(version)); |
| } |
| alternative_service_dict.Set(kAdvertisedAlpnsKey, |
| std::move(advertised_versions_list)); |
| alternative_service_list.Append(std::move(alternative_service_dict)); |
| } |
| if (alternative_service_list.size() == 0) |
| return; |
| server_pref_dict.Set(kAlternativeServiceKey, |
| std::move(alternative_service_list)); |
| } |
| |
| void HttpServerPropertiesManager::SaveLastLocalAddressWhenQuicWorkedToPrefs( |
| const IPAddress& last_local_address_when_quic_worked, |
| base::Value::Dict& http_server_properties_dict) { |
| if (!last_local_address_when_quic_worked.IsValid()) |
| return; |
| |
| base::Value::Dict supports_quic_dict; |
| supports_quic_dict.Set(kUsedQuicKey, true); |
| supports_quic_dict.Set(kAddressKey, |
| last_local_address_when_quic_worked.ToString()); |
| http_server_properties_dict.Set(kSupportsQuicKey, |
| std::move(supports_quic_dict)); |
| } |
| |
| void HttpServerPropertiesManager::SaveNetworkStatsToServerPrefs( |
| const ServerNetworkStats& server_network_stats, |
| base::Value::Dict& server_pref_dict) { |
| base::Value::Dict server_network_stats_dict; |
| // Because JSON doesn't support int64_t, persist int64_t as a string. |
| server_network_stats_dict.Set( |
| kSrttKey, static_cast<int>(server_network_stats.srtt.InMicroseconds())); |
| // TODO(rtenneti): When QUIC starts using bandwidth_estimate, then persist |
| // bandwidth_estimate. |
| server_pref_dict.Set(kNetworkStatsKey, std::move(server_network_stats_dict)); |
| } |
| |
| void HttpServerPropertiesManager::SaveQuicServerInfoMapToServerPrefs( |
| const HttpServerProperties::QuicServerInfoMap& quic_server_info_map, |
| base::Value::Dict& http_server_properties_dict) { |
| if (quic_server_info_map.empty()) |
| return; |
| base::Value::List quic_servers_list; |
| for (const auto& [key, server_info] : base::Reversed(quic_server_info_map)) { |
| base::Value network_anonymization_key_value; |
| // Don't save entries with ephemeral NIKs. |
| if (!key.network_anonymization_key.ToValue( |
| &network_anonymization_key_value)) { |
| continue; |
| } |
| |
| base::Value::Dict quic_server_pref_dict; |
| quic_server_pref_dict.Set(kQuicServerIdKey, |
| QuicServerIdToString(key.server_id)); |
| quic_server_pref_dict.Set(kNetworkAnonymizationKey, |
| std::move(network_anonymization_key_value)); |
| quic_server_pref_dict.Set(kServerInfoKey, server_info); |
| |
| quic_servers_list.Append(std::move(quic_server_pref_dict)); |
| } |
| http_server_properties_dict.Set(kQuicServers, std::move(quic_servers_list)); |
| } |
| |
| void HttpServerPropertiesManager::SaveBrokenAlternativeServicesToPrefs( |
| const BrokenAlternativeServiceList& broken_alternative_service_list, |
| size_t max_broken_alternative_services, |
| const RecentlyBrokenAlternativeServices& |
| recently_broken_alternative_services, |
| base::Value::Dict& http_server_properties_dict) { |
| if (broken_alternative_service_list.empty() && |
| recently_broken_alternative_services.empty()) { |
| return; |
| } |
| |
| // JSON list will be in LRU order (least-recently-used item is in the front) |
| // according to `recently_broken_alternative_services`. |
| base::Value::List json_list; |
| |
| // Maps recently-broken alternative services to the index where it's stored |
| // in |json_list|. |
| std::map<BrokenAlternativeService, size_t> json_list_index_map; |
| |
| if (!recently_broken_alternative_services.empty()) { |
| for (const auto& [broken_alt_service, broken_count] : |
| base::Reversed(recently_broken_alternative_services)) { |
| base::Value::Dict entry_dict; |
| if (!TryAddBrokenAlternativeServiceFieldsToDictionaryValue( |
| broken_alt_service, entry_dict)) { |
| continue; |
| } |
| entry_dict.Set(kBrokenCountKey, broken_count); |
| json_list_index_map[broken_alt_service] = json_list.size(); |
| json_list.Append(std::move(entry_dict)); |
| } |
| } |
| |
| if (!broken_alternative_service_list.empty()) { |
| // Add expiration time info from |broken_alternative_service_list| to |
| // the JSON list. |
| size_t count = 0; |
| for (auto it = broken_alternative_service_list.begin(); |
| it != broken_alternative_service_list.end() && |
| count < max_broken_alternative_services; |
| ++it, ++count) { |
| const BrokenAlternativeService& broken_alt_service = it->first; |
| base::TimeTicks expiration_time_ticks = it->second; |
| // Convert expiration from TimeTicks to Time to time_t |
| time_t expiration_time_t = |
| (base::Time::Now() + (expiration_time_ticks - clock_->NowTicks())) |
| .ToTimeT(); |
| int64_t expiration_int64 = static_cast<int64_t>(expiration_time_t); |
| |
| auto index_map_it = json_list_index_map.find(broken_alt_service); |
| if (index_map_it != json_list_index_map.end()) { |
| size_t json_list_index = index_map_it->second; |
| base::Value& entry_dict = json_list[json_list_index]; |
| DCHECK(entry_dict.is_dict()); |
| DCHECK(!entry_dict.GetDict().Find(kBrokenUntilKey)); |
| entry_dict.GetDict().Set(kBrokenUntilKey, |
| base::NumberToString(expiration_int64)); |
| } else { |
| base::Value::Dict entry_dict; |
| if (!TryAddBrokenAlternativeServiceFieldsToDictionaryValue( |
| broken_alt_service, entry_dict)) { |
| continue; |
| } |
| entry_dict.Set(kBrokenUntilKey, base::NumberToString(expiration_int64)); |
| json_list.Append(std::move(entry_dict)); |
| } |
| } |
| } |
| |
| // This can happen if all the entries are for NetworkAnonymizationKeys for |
| // opaque origins, which isn't exactly common, but can theoretically happen. |
| if (json_list.empty()) |
| return; |
| |
| http_server_properties_dict.Set(kBrokenAlternativeServicesKey, |
| std::move(json_list)); |
| } |
| |
| void HttpServerPropertiesManager::OnHttpServerPropertiesLoaded() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // If prefs have already been written, nothing to do. |
| if (!on_prefs_loaded_callback_) |
| return; |
| |
| std::unique_ptr<HttpServerProperties::ServerInfoMap> server_info_map; |
| IPAddress last_local_address_when_quic_worked; |
| std::unique_ptr<HttpServerProperties::QuicServerInfoMap> quic_server_info_map; |
| std::unique_ptr<BrokenAlternativeServiceList> broken_alternative_service_list; |
| std::unique_ptr<RecentlyBrokenAlternativeServices> |
| recently_broken_alternative_services; |
| |
| ReadPrefs(&server_info_map, &last_local_address_when_quic_worked, |
| &quic_server_info_map, &broken_alternative_service_list, |
| &recently_broken_alternative_services); |
| |
| std::move(on_prefs_loaded_callback_) |
| .Run(std::move(server_info_map), last_local_address_when_quic_worked, |
| std::move(quic_server_info_map), |
| std::move(broken_alternative_service_list), |
| std::move(recently_broken_alternative_services)); |
| } |
| |
| } // namespace net |