naiveproxy/src/net/http/http_server_properties_manager.cc
2024-01-19 10:08:33 +08:00

960 lines
38 KiB
C++

// 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_|.
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 http_server_properties_dict.Clone(); });
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