blob: 31998dba77266bd6bf428be5a7f3e39b1569e872 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// 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 <utility>
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
#include "base/optional.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/tick_clock.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/quic/platform/api/quic_hostname_utils.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 kNetworkIsolationKey[] = "isolation";
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 base::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, NetworkIsolationKey>>*
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, NetworkIsolationKey> index(
*canonical_suffix, server_info_key.network_isolation_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) {
DCHECK(dict->is_dict());
dict->SetIntKey(kPortKey, alternative_service.port);
if (!alternative_service.host.empty()) {
dict->SetStringKey(kHostKey, alternative_service.host);
}
dict->SetStringKey(kProtocolKey,
NextProtoToString(alternative_service.protocol));
}
// Fails in the case of NetworkIsolationKeys that can't be persisted to disk,
// like unique origins.
bool TryAddBrokenAlternativeServiceFieldsToDictionaryValue(
const BrokenAlternativeService& broken_alt_service,
base::Value* dict) {
DCHECK(dict->is_dict());
base::Value network_isolation_key_value;
if (!broken_alt_service.network_isolation_key.ToValue(
&network_isolation_key_value)) {
return false;
}
dict->SetKey(kNetworkIsolationKey, std::move(network_isolation_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 representing a dictionary, and whether
// NetworkIsolationKeys are enabled for HttpServerProperties, and extracts the
// NetworkIsolationKey stored with the |kNetworkIsolationKey| in the dictionary,
// and writes it to |out_network_isolation_key|. Returns false if unable to load
// a NetworkIsolationKey, or the NetworkIsolationKey is non-empty, but
// |use_network_isolation_key| is false.
bool GetNetworkIsolationKeyFromDict(
const base::Value& dict,
bool use_network_isolation_key,
NetworkIsolationKey* out_network_isolation_key) {
DCHECK(dict.is_dict());
const base::Value* network_isolation_key_value =
dict.FindKey(kNetworkIsolationKey);
NetworkIsolationKey network_isolation_key;
if (!network_isolation_key_value ||
!NetworkIsolationKey::FromValue(*network_isolation_key_value,
&network_isolation_key)) {
return false;
}
// Fail if NetworkIsolationKeys are disabled, but the entry has a non-empty
// NetworkIsolationKey.
if (!use_network_isolation_key && !network_isolation_key.IsEmpty())
return false;
*out_network_isolation_key = std::move(network_isolation_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* http_server_properties_dict =
pref_delegate_->GetServerProperties();
// If there are no preferences set, do nothing.
if (!http_server_properties_dict || !http_server_properties_dict->is_dict())
return;
net_log_.AddEvent(NetLogEventType::HTTP_SERVER_PROPERTIES_UPDATE_CACHE,
[&] { return http_server_properties_dict->Clone(); });
base::Optional<int> maybe_version_number =
http_server_properties_dict->FindIntKey(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 MRU order. |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* servers_list =
http_server_properties_dict->FindListKey(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_isolation_key = base::FeatureList::IsEnabled(
features::kPartitionHttpServerPropertiesByNetworkIsolationKey);
// Iterate servers list in reverse MRU order so that entries are inserted
// into |spdy_servers_map|, |alternative_service_map|, and
// |server_network_stats_map| from oldest to newest.
for (auto it = servers_list->GetList().end();
it != servers_list->GetList().begin();) {
--it;
if (!it->is_dict()) {
DVLOG(1) << "Malformed http_server_properties for servers dictionary.";
continue;
}
AddServerData(*it, server_info_map->get(), use_network_isolation_key);
}
AddToQuicServerInfoMap(*http_server_properties_dict,
use_network_isolation_key,
quic_server_info_map->get());
// Read list containing broken and recently-broken alternative services, if
// it exists.
const base::Value* broken_alt_svc_list =
http_server_properties_dict->FindListKey(kBrokenAlternativeServicesKey);
if (broken_alt_svc_list) {
*broken_alternative_service_list =
std::make_unique<BrokenAlternativeServiceList>();
*recently_broken_alternative_services =
std::make_unique<RecentlyBrokenAlternativeServices>(
kMaxRecentlyBrokenAlternativeServiceEntries);
// Iterate list in reverse-MRU order
for (auto it = broken_alt_svc_list->GetList().end();
it != broken_alt_svc_list->GetList().begin();) {
--it;
if (!it->is_dict()) {
DVLOG(1) << "Malformed broken alterantive service entry.";
continue;
}
AddToBrokenAlternativeServices(
*it, use_network_isolation_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& broken_alt_svc_entry_dict,
bool use_network_isolation_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;
}
NetworkIsolationKey network_isolation_key;
if (!GetNetworkIsolationKeyFromDict(broken_alt_svc_entry_dict,
use_network_isolation_key,
&network_isolation_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.FindKey(kBrokenCountKey)) {
base::Optional<int> broken_count =
broken_alt_svc_entry_dict.FindIntKey(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_isolation_key,
use_network_isolation_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.FindKey(kBrokenUntilKey)) {
const std::string* expiration_string =
broken_alt_svc_entry_dict.FindStringKey(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_isolation_key,
use_network_isolation_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& server_dict,
HttpServerProperties::ServerInfoMap* server_info_map,
bool use_network_isolation_key) {
// Get server's scheme/host/pair.
const std::string* server_str = server_dict.FindStringKey(kServerKey);
NetworkIsolationKey network_isolation_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_isolation_key,
&network_isolation_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.FindBoolKey(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_isolation_key,
use_network_isolation_key),
std::move(server_info));
}
}
bool HttpServerPropertiesManager::ParseAlternativeServiceDict(
const base::Value& dict,
bool host_optional,
const std::string& parsing_under,
AlternativeService* alternative_service) {
// Protocol is mandatory.
const std::string* protocol_str = dict.FindStringKey(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.FindKey(kHostKey)) {
hostp = dict.FindStringKey(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.
base::Optional<int> maybe_port = dict.FindIntKey(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,
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.FindKey(kExpirationKey)) {
alternative_service_info->set_expiration(base::Time::Now() +
base::TimeDelta::FromDays(1));
} else {
const std::string* expiration_string = dict.FindStringKey(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.FindKey(kAdvertisedAlpnsKey)) {
const base::Value* versions_list = dict.FindListKey(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->GetList()) {
std::string version_string;
if (!value.GetAsString(&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& server_pref_dict,
HttpServerProperties::ServerInfo* server_info) {
DCHECK(!server_info->alternative_services.has_value());
const base::Value* alternative_service_list =
server_pref_dict.FindListKey(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->GetList()) {
if (!alternative_service_list_item.is_dict())
return false;
AlternativeServiceInfo alternative_service_info;
if (!ParseAlternativeServiceInfoDictOfServer(alternative_service_list_item,
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& http_server_properties_dict,
IPAddress* last_local_address_when_quic_worked) {
const base::Value* supports_quic_dict =
http_server_properties_dict.FindDictKey(kSupportsQuicKey);
if (!supports_quic_dict) {
return;
}
const base::Value* used_quic = supports_quic_dict->FindKey(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->FindStringKey(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& server_pref_dict,
HttpServerProperties::ServerInfo* server_info) {
DCHECK(!server_info->server_network_stats.has_value());
const base::Value* server_network_stats_dict =
server_pref_dict.FindDictKey(kNetworkStatsKey);
if (!server_network_stats_dict) {
return;
}
base::Optional<int> maybe_srtt =
server_network_stats_dict->FindIntKey(kSrttKey);
if (!maybe_srtt.has_value()) {
DVLOG(1) << "Malformed ServerNetworkStats for server: "
<< server.Serialize();
return;
}
ServerNetworkStats server_network_stats;
server_network_stats.srtt =
base::TimeDelta::FromMicroseconds(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& http_server_properties_dict,
bool use_network_isolation_key,
HttpServerProperties::QuicServerInfoMap* quic_server_info_map) {
const base::Value* quic_server_info_list =
http_server_properties_dict.FindListKey(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->GetList()) {
if (!quic_server_info_value.is_dict())
continue;
const std::string* quic_server_id_str =
quic_server_info_value.FindStringKey(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;
}
NetworkIsolationKey network_isolation_key;
if (!GetNetworkIsolationKeyFromDict(quic_server_info_value,
use_network_isolation_key,
&network_isolation_key)) {
DVLOG(1) << "Malformed http_server_properties quic server dict: "
<< *quic_server_id_str;
continue;
}
const std::string* quic_server_info =
quic_server_info_value.FindStringKey(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_isolation_key, use_network_isolation_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, NetworkIsolationKey>>
persisted_canonical_suffix_set;
const base::Time now = base::Time::Now();
base::Value http_server_properties_dict(base::Value::Type::DICTIONARY);
// Convert |server_info_map| to a dictionary Value and add it to
// |http_server_properties_dict|.
base::Value servers_list(base::Value::Type::LIST);
for (auto map_it = server_info_map.rbegin(); map_it != server_info_map.rend();
++map_it) {
const HttpServerProperties::ServerInfoMapKey key = map_it->first;
const HttpServerProperties::ServerInfo& server_info = map_it->second;
// If can't convert the NetworkIsolationKey to a value, don't save to disk.
// Generally happens because the key is for a unique origin.
base::Value network_isolation_key_value;
if (!key.network_isolation_key.ToValue(&network_isolation_key_value))
continue;
base::Value server_dict(base::Value::Type::DICTIONARY);
bool supports_spdy = server_info.supports_spdy.value_or(false);
if (supports_spdy)
server_dict.SetBoolKey(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.DictEmpty())
continue;
server_dict.SetStringKey(kServerKey, key.server.Serialize());
server_dict.SetKey(kNetworkIsolationKey,
std::move(network_isolation_key_value));
servers_list.Append(std::move(server_dict));
}
http_server_properties_dict.SetKey(kServersKey, std::move(servers_list));
http_server_properties_dict.SetIntKey(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);
pref_delegate_->SetServerProperties(http_server_properties_dict,
std::move(callback));
net_log_.AddEvent(NetLogEventType::HTTP_SERVER_PROPERTIES_UPDATE_PREFS,
[&] { return http_server_properties_dict.Clone(); });
}
void HttpServerPropertiesManager::SaveAlternativeServiceToServerPrefs(
const AlternativeServiceInfoVector& alternative_service_info_vector,
base::Value* server_pref_dict) {
if (alternative_service_info_vector.empty()) {
return;
}
base::Value alternative_service_list(base::Value::Type::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 alternative_service_dict(base::Value::Type::DICTIONARY);
AddAlternativeServiceFieldsToDictionaryValue(alternative_service,
&alternative_service_dict);
// JSON cannot store int64_t, so expiration is converted to a string.
alternative_service_dict.SetStringKey(
kExpirationKey,
base::NumberToString(
alternative_service_info.expiration().ToInternalValue()));
base::Value advertised_versions_list(base::Value::Type::LIST);
for (const auto& version : alternative_service_info.advertised_versions()) {
advertised_versions_list.Append(quic::AlpnForVersion(version));
}
alternative_service_dict.SetKey(kAdvertisedAlpnsKey,
std::move(advertised_versions_list));
alternative_service_list.Append(std::move(alternative_service_dict));
}
if (alternative_service_list.GetList().size() == 0)
return;
server_pref_dict->SetKey(kAlternativeServiceKey,
std::move(alternative_service_list));
}
void HttpServerPropertiesManager::SaveLastLocalAddressWhenQuicWorkedToPrefs(
const IPAddress& last_local_address_when_quic_worked,
base::Value* http_server_properties_dict) {
if (!last_local_address_when_quic_worked.IsValid())
return;
base::Value supports_quic_dict(base::Value::Type::DICTIONARY);
supports_quic_dict.SetBoolKey(kUsedQuicKey, true);
supports_quic_dict.SetStringKey(
kAddressKey, last_local_address_when_quic_worked.ToString());
http_server_properties_dict->SetKey(kSupportsQuicKey,
std::move(supports_quic_dict));
}
void HttpServerPropertiesManager::SaveNetworkStatsToServerPrefs(
const ServerNetworkStats& server_network_stats,
base::Value* server_pref_dict) {
base::Value server_network_stats_dict(base::Value::Type::DICTIONARY);
// Becasue JSON doesn't support int64_t, persist int64_t as a string.
server_network_stats_dict.SetIntKey(
kSrttKey, static_cast<int>(server_network_stats.srtt.InMicroseconds()));
// TODO(rtenneti): When QUIC starts using bandwidth_estimate, then persist
// bandwidth_estimate.
server_pref_dict->SetKey(kNetworkStatsKey,
std::move(server_network_stats_dict));
}
void HttpServerPropertiesManager::SaveQuicServerInfoMapToServerPrefs(
const HttpServerProperties::QuicServerInfoMap& quic_server_info_map,
base::Value* http_server_properties_dict) {
if (quic_server_info_map.empty())
return;
base::Value quic_servers_list(base::Value::Type::LIST);
for (auto it = quic_server_info_map.rbegin();
it != quic_server_info_map.rend(); ++it) {
const HttpServerProperties::QuicServerInfoMapKey& key = it->first;
base::Value network_isolation_key_value;
// Don't save entries with ephemeral NIKs.
if (!key.network_isolation_key.ToValue(&network_isolation_key_value))
continue;
base::Value quic_server_pref_dict(base::Value::Type::DICTIONARY);
quic_server_pref_dict.SetStringKey(kQuicServerIdKey,
QuicServerIdToString(key.server_id));
quic_server_pref_dict.SetKey(kNetworkIsolationKey,
std::move(network_isolation_key_value));
quic_server_pref_dict.SetStringKey(kServerInfoKey, it->second);
quic_servers_list.Append(std::move(quic_server_pref_dict));
}
http_server_properties_dict->SetKey(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* http_server_properties_dict) {
if (broken_alternative_service_list.empty() &&
recently_broken_alternative_services.empty()) {
return;
}
// JSON list will be in MRU order according to
// |recently_broken_alternative_services|.
base::Value json_list(base::Value::Type::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 (auto it = recently_broken_alternative_services.rbegin();
it != recently_broken_alternative_services.rend(); ++it) {
const BrokenAlternativeService& broken_alt_service = it->first;
int broken_count = it->second;
base::Value entry_dict(base::Value::Type::DICTIONARY);
if (!TryAddBrokenAlternativeServiceFieldsToDictionaryValue(
broken_alt_service, &entry_dict)) {
continue;
}
entry_dict.SetKey(kBrokenCountKey, base::Value(broken_count));
json_list_index_map[broken_alt_service] = json_list.GetList().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.GetList()[json_list_index];
DCHECK(entry_dict.is_dict());
DCHECK(!entry_dict.FindKey(kBrokenUntilKey));
entry_dict.SetKey(kBrokenUntilKey,
base::Value(base::NumberToString(expiration_int64)));
} else {
base::Value entry_dict(base::Value::Type::DICTIONARY);
if (!TryAddBrokenAlternativeServiceFieldsToDictionaryValue(
broken_alt_service, &entry_dict)) {
continue;
}
entry_dict.SetKey(kBrokenUntilKey,
base::Value(base::NumberToString(expiration_int64)));
json_list.Append(std::move(entry_dict));
}
}
}
// This can happen if all the entries are for NetworkIsolationKeys for opaque
// origins, which isn't exactly common, but can theoretically happen.
if (json_list.GetList().empty())
return;
http_server_properties_dict->SetKey(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