| // 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 "components/cronet/url_request_context_config.h" |
| |
| #include <utility> |
| |
| #include "base/json/json_reader.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_split.h" |
| #include "base/values.h" |
| #include "components/cronet/stale_host_resolver.h" |
| #include "net/base/address_family.h" |
| #include "net/cert/caching_cert_verifier.h" |
| #include "net/cert/cert_verifier.h" |
| #include "net/cert/cert_verify_proc.h" |
| #include "net/cert/ct_policy_enforcer.h" |
| #include "net/cert/ct_policy_status.h" |
| #include "net/cert/do_nothing_ct_verifier.h" |
| #include "net/cert/multi_threaded_cert_verifier.h" |
| #include "net/dns/host_resolver.h" |
| #include "net/dns/mapped_host_resolver.h" |
| #include "net/http/http_network_session.h" |
| #include "net/http/http_server_properties.h" |
| #include "net/nqe/network_quality_estimator_params.h" |
| #include "net/quic/chromium/quic_utils_chromium.h" |
| #include "net/quic/core/quic_packets.h" |
| #include "net/socket/ssl_client_socket.h" |
| #include "net/url_request/url_request_context_builder.h" |
| |
| namespace cronet { |
| |
| namespace { |
| |
| // Name of disk cache directory. |
| const char kDiskCacheDirectoryName[] = "disk_cache"; |
| // TODO(xunjieli): Refactor constants in io_thread.cc. |
| const char kQuicFieldTrialName[] = "QUIC"; |
| const char kQuicConnectionOptions[] = "connection_options"; |
| const char kQuicStoreServerConfigsInProperties[] = |
| "store_server_configs_in_properties"; |
| const char kQuicMaxServerConfigsStoredInProperties[] = |
| "max_server_configs_stored_in_properties"; |
| const char kQuicIdleConnectionTimeoutSeconds[] = |
| "idle_connection_timeout_seconds"; |
| const char kQuicCloseSessionsOnIpChange[] = "close_sessions_on_ip_change"; |
| const char kQuicMigrateSessionsOnNetworkChange[] = |
| "migrate_sessions_on_network_change"; |
| const char kQuicUserAgentId[] = "user_agent_id"; |
| const char kQuicMigrateSessionsEarly[] = "migrate_sessions_early"; |
| const char kQuicDisableBidirectionalStreams[] = |
| "quic_disable_bidirectional_streams"; |
| const char kQuicRaceCertVerification[] = "race_cert_verification"; |
| |
| // AsyncDNS experiment dictionary name. |
| const char kAsyncDnsFieldTrialName[] = "AsyncDNS"; |
| // Name of boolean to enable AsyncDNS experiment. |
| const char kAsyncDnsEnable[] = "enable"; |
| |
| // Stale DNS (StaleHostResolver) experiment dictionary name. |
| const char kStaleDnsFieldTrialName[] = "StaleDNS"; |
| // Name of boolean to enable stale DNS experiment. |
| const char kStaleDnsEnable[] = "enable"; |
| // Name of integer delay in milliseconds before a stale DNS result will be |
| // used. |
| const char kStaleDnsDelayMs[] = "delay_ms"; |
| // Name of integer maximum age (past expiration) in milliseconds of a stale DNS |
| // result that will be used, or 0 for no limit. |
| const char kStaleDnsMaxExpiredTimeMs[] = "max_expired_time_ms"; |
| // Name of integer maximum times each stale DNS result can be used, or 0 for no |
| // limit. |
| const char kStaleDnsMaxStaleUses[] = "max_stale_uses"; |
| // Name of boolean to allow stale DNS results from other networks to be used on |
| // the current network. |
| const char kStaleDnsAllowOtherNetwork[] = "allow_other_network"; |
| // Name of boolean to enable persisting the DNS cache to disk. |
| const char kStaleDnsPersist[] = "persist_to_disk"; |
| // Name of integer minimum time in milliseconds between writes to disk for DNS |
| // cache persistence. Default value is one minute. Only relevant if |
| // "persist_to_disk" is true. |
| const char kStaleDnsPersistTimer[] = "persist_delay_ms"; |
| |
| // Rules to override DNS resolution. Intended for testing. |
| // See explanation of format in net/dns/mapped_host_resolver.h. |
| const char kHostResolverRulesFieldTrialName[] = "HostResolverRules"; |
| const char kHostResolverRules[] = "host_resolver_rules"; |
| |
| // NetworkQualityEstimator (NQE) experiment dictionary name. |
| const char kNetworkQualityEstimatorFieldTrialName[] = "NetworkQualityEstimator"; |
| // Name of the boolean to enable reading of the persistent prefs in NQE. |
| const char kNQEPersistentCacheReadingEnabled[] = |
| "persistent_cache_reading_enabled"; |
| |
| // Disable IPv6 when on WiFi. This is a workaround for a known issue on certain |
| // Android phones, and should not be necessary when not on one of those devices. |
| // See https://crbug.com/696569 for details. |
| const char kDisableIPv6OnWifi[] = "disable_ipv6_on_wifi"; |
| |
| const char kSSLKeyLogFile[] = "ssl_key_log_file"; |
| |
| // A CTPolicyEnforcer that accepts all certificates. |
| class DoNothingCTPolicyEnforcer : public net::CTPolicyEnforcer { |
| public: |
| DoNothingCTPolicyEnforcer() = default; |
| ~DoNothingCTPolicyEnforcer() override = default; |
| |
| net::ct::CertPolicyCompliance DoesConformToCertPolicy( |
| net::X509Certificate* cert, |
| const net::SCTList& verified_scts, |
| const net::NetLogWithSource& net_log) override { |
| return net::ct::CertPolicyCompliance::CERT_POLICY_COMPLIES_VIA_SCTS; |
| } |
| }; |
| |
| } // namespace |
| |
| URLRequestContextConfig::QuicHint::QuicHint(const std::string& host, |
| int port, |
| int alternate_port) |
| : host(host), port(port), alternate_port(alternate_port) {} |
| |
| URLRequestContextConfig::QuicHint::~QuicHint() {} |
| |
| URLRequestContextConfig::Pkp::Pkp(const std::string& host, |
| bool include_subdomains, |
| const base::Time& expiration_date) |
| : host(host), |
| include_subdomains(include_subdomains), |
| expiration_date(expiration_date) {} |
| |
| URLRequestContextConfig::Pkp::~Pkp() {} |
| |
| URLRequestContextConfig::URLRequestContextConfig( |
| bool enable_quic, |
| const std::string& quic_user_agent_id, |
| bool enable_spdy, |
| bool enable_sdch, |
| bool enable_brotli, |
| HttpCacheType http_cache, |
| int http_cache_max_size, |
| bool load_disable_cache, |
| const std::string& storage_path, |
| const std::string& user_agent, |
| const std::string& experimental_options, |
| std::unique_ptr<net::CertVerifier> mock_cert_verifier, |
| bool enable_network_quality_estimator, |
| bool bypass_public_key_pinning_for_local_trust_anchors, |
| const std::string& cert_verifier_data) |
| : enable_quic(enable_quic), |
| quic_user_agent_id(quic_user_agent_id), |
| enable_spdy(enable_spdy), |
| enable_sdch(enable_sdch), |
| enable_brotli(enable_brotli), |
| http_cache(http_cache), |
| http_cache_max_size(http_cache_max_size), |
| load_disable_cache(load_disable_cache), |
| storage_path(storage_path), |
| user_agent(user_agent), |
| mock_cert_verifier(std::move(mock_cert_verifier)), |
| enable_network_quality_estimator(enable_network_quality_estimator), |
| bypass_public_key_pinning_for_local_trust_anchors( |
| bypass_public_key_pinning_for_local_trust_anchors), |
| cert_verifier_data(cert_verifier_data), |
| nqe_persistent_caching_enabled(false), |
| experimental_options(experimental_options) {} |
| |
| URLRequestContextConfig::~URLRequestContextConfig() {} |
| |
| void URLRequestContextConfig::ParseAndSetExperimentalOptions( |
| net::URLRequestContextBuilder* context_builder, |
| net::HttpNetworkSession::Params* session_params, |
| net::NetLog* net_log, |
| const scoped_refptr<base::SequencedTaskRunner>& file_task_runner) { |
| if (experimental_options.empty()) |
| return; |
| |
| DCHECK(net_log); |
| |
| DVLOG(1) << "Experimental Options:" << experimental_options; |
| std::unique_ptr<base::Value> options = |
| base::JSONReader::Read(experimental_options); |
| |
| if (!options) { |
| DCHECK(false) << "Parsing experimental options failed: " |
| << experimental_options; |
| return; |
| } |
| |
| std::unique_ptr<base::DictionaryValue> dict = |
| base::DictionaryValue::From(std::move(options)); |
| |
| if (!dict) { |
| DCHECK(false) << "Experimental options string is not a dictionary: " |
| << experimental_options; |
| return; |
| } |
| |
| bool async_dns_enable = false; |
| bool stale_dns_enable = false; |
| bool host_resolver_rules_enable = false; |
| bool disable_ipv6_on_wifi = false; |
| |
| effective_experimental_options = dict->CreateDeepCopy(); |
| StaleHostResolver::StaleOptions stale_dns_options; |
| std::string host_resolver_rules_string; |
| |
| for (base::DictionaryValue::Iterator it(*dict.get()); !it.IsAtEnd(); |
| it.Advance()) { |
| if (it.key() == kQuicFieldTrialName) { |
| const base::DictionaryValue* quic_args = nullptr; |
| if (!it.value().GetAsDictionary(&quic_args)) { |
| LOG(ERROR) << "Quic config params \"" << it.value() |
| << "\" is not a dictionary value"; |
| effective_experimental_options->Remove(it.key(), nullptr); |
| continue; |
| } |
| std::string quic_connection_options; |
| if (quic_args->GetString(kQuicConnectionOptions, |
| &quic_connection_options)) { |
| session_params->quic_connection_options = |
| net::ParseQuicConnectionOptions(quic_connection_options); |
| } |
| |
| // TODO(rtenneti): Delete this option after apps stop using it. |
| // Added this for backward compatibility. |
| bool quic_store_server_configs_in_properties = false; |
| if (quic_args->GetBoolean(kQuicStoreServerConfigsInProperties, |
| &quic_store_server_configs_in_properties)) { |
| session_params->quic_max_server_configs_stored_in_properties = |
| net::kMaxQuicServersToPersist; |
| } |
| |
| int quic_max_server_configs_stored_in_properties = 0; |
| if (quic_args->GetInteger( |
| kQuicMaxServerConfigsStoredInProperties, |
| &quic_max_server_configs_stored_in_properties)) { |
| session_params->quic_max_server_configs_stored_in_properties = |
| static_cast<size_t>(quic_max_server_configs_stored_in_properties); |
| } |
| |
| int quic_idle_connection_timeout_seconds = 0; |
| if (quic_args->GetInteger(kQuicIdleConnectionTimeoutSeconds, |
| &quic_idle_connection_timeout_seconds)) { |
| session_params->quic_idle_connection_timeout_seconds = |
| quic_idle_connection_timeout_seconds; |
| } |
| |
| bool quic_close_sessions_on_ip_change = false; |
| if (quic_args->GetBoolean(kQuicCloseSessionsOnIpChange, |
| &quic_close_sessions_on_ip_change)) { |
| session_params->quic_close_sessions_on_ip_change = |
| quic_close_sessions_on_ip_change; |
| } |
| |
| bool quic_migrate_sessions_on_network_change = false; |
| if (quic_args->GetBoolean(kQuicMigrateSessionsOnNetworkChange, |
| &quic_migrate_sessions_on_network_change)) { |
| session_params->quic_migrate_sessions_on_network_change = |
| quic_migrate_sessions_on_network_change; |
| } |
| |
| std::string quic_user_agent_id; |
| if (quic_args->GetString(kQuicUserAgentId, &quic_user_agent_id)) { |
| session_params->quic_user_agent_id = quic_user_agent_id; |
| } |
| |
| bool quic_migrate_sessions_early = false; |
| if (quic_args->GetBoolean(kQuicMigrateSessionsEarly, |
| &quic_migrate_sessions_early)) { |
| session_params->quic_migrate_sessions_early = |
| quic_migrate_sessions_early; |
| } |
| |
| bool quic_disable_bidirectional_streams = false; |
| if (quic_args->GetBoolean(kQuicDisableBidirectionalStreams, |
| &quic_disable_bidirectional_streams)) { |
| session_params->quic_disable_bidirectional_streams = |
| quic_disable_bidirectional_streams; |
| } |
| |
| bool quic_race_cert_verification = false; |
| if (quic_args->GetBoolean(kQuicRaceCertVerification, |
| &quic_race_cert_verification)) { |
| session_params->quic_race_cert_verification = |
| quic_race_cert_verification; |
| } |
| |
| } else if (it.key() == kAsyncDnsFieldTrialName) { |
| const base::DictionaryValue* async_dns_args = nullptr; |
| if (!it.value().GetAsDictionary(&async_dns_args)) { |
| LOG(ERROR) << "\"" << it.key() << "\" config params \"" << it.value() |
| << "\" is not a dictionary value"; |
| effective_experimental_options->Remove(it.key(), nullptr); |
| continue; |
| } |
| async_dns_args->GetBoolean(kAsyncDnsEnable, &async_dns_enable); |
| } else if (it.key() == kStaleDnsFieldTrialName) { |
| const base::DictionaryValue* stale_dns_args = nullptr; |
| if (!it.value().GetAsDictionary(&stale_dns_args)) { |
| LOG(ERROR) << "\"" << it.key() << "\" config params \"" << it.value() |
| << "\" is not a dictionary value"; |
| effective_experimental_options->Remove(it.key(), nullptr); |
| continue; |
| } |
| if (stale_dns_args->GetBoolean(kStaleDnsEnable, &stale_dns_enable) && |
| stale_dns_enable) { |
| int delay; |
| if (stale_dns_args->GetInteger(kStaleDnsDelayMs, &delay)) |
| stale_dns_options.delay = base::TimeDelta::FromMilliseconds(delay); |
| int max_expired_time_ms; |
| if (stale_dns_args->GetInteger(kStaleDnsMaxExpiredTimeMs, |
| &max_expired_time_ms)) { |
| stale_dns_options.max_expired_time = |
| base::TimeDelta::FromMilliseconds(max_expired_time_ms); |
| } |
| int max_stale_uses; |
| if (stale_dns_args->GetInteger(kStaleDnsMaxStaleUses, &max_stale_uses)) |
| stale_dns_options.max_stale_uses = max_stale_uses; |
| bool allow_other_network; |
| if (stale_dns_args->GetBoolean(kStaleDnsAllowOtherNetwork, |
| &allow_other_network)) { |
| stale_dns_options.allow_other_network = allow_other_network; |
| } |
| bool persist; |
| if (stale_dns_args->GetBoolean(kStaleDnsPersist, &persist)) |
| enable_host_cache_persistence = persist; |
| int persist_timer; |
| if (stale_dns_args->GetInteger(kStaleDnsPersistTimer, &persist_timer)) |
| host_cache_persistence_delay_ms = persist_timer; |
| } |
| } else if (it.key() == kHostResolverRulesFieldTrialName) { |
| const base::DictionaryValue* host_resolver_rules_args = nullptr; |
| if (!it.value().GetAsDictionary(&host_resolver_rules_args)) { |
| LOG(ERROR) << "\"" << it.key() << "\" config params \"" << it.value() |
| << "\" is not a dictionary value"; |
| effective_experimental_options->Remove(it.key(), nullptr); |
| continue; |
| } |
| host_resolver_rules_enable = host_resolver_rules_args->GetString( |
| kHostResolverRules, &host_resolver_rules_string); |
| } else if (it.key() == kDisableIPv6OnWifi) { |
| if (!it.value().GetAsBoolean(&disable_ipv6_on_wifi)) { |
| LOG(ERROR) << "\"" << it.key() << "\" config params \"" << it.value() |
| << "\" is not a bool"; |
| effective_experimental_options->Remove(it.key(), nullptr); |
| continue; |
| } |
| } else if (it.key() == kSSLKeyLogFile) { |
| std::string ssl_key_log_file_string; |
| if (it.value().GetAsString(&ssl_key_log_file_string)) { |
| DCHECK(file_task_runner); |
| base::FilePath ssl_key_log_file(ssl_key_log_file_string); |
| if (!ssl_key_log_file.empty() && file_task_runner) { |
| // SetSSLKeyLogFile is only safe to call before any SSLClientSockets |
| // are created. This should not be used if there are multiple |
| // CronetEngine. |
| // TODO(xunjieli): Expose this as a stable API after crbug.com/458365 |
| // is resolved. |
| net::SSLClientSocket::SetSSLKeyLogFile(ssl_key_log_file, |
| file_task_runner); |
| } |
| } |
| } else if (it.key() == kNetworkQualityEstimatorFieldTrialName) { |
| const base::DictionaryValue* nqe_args = nullptr; |
| if (!it.value().GetAsDictionary(&nqe_args)) { |
| LOG(ERROR) << "\"" << it.key() << "\" config params \"" << it.value() |
| << "\" is not a dictionary value"; |
| effective_experimental_options->Remove(it.key(), nullptr); |
| continue; |
| } |
| |
| bool persistent_caching_enabled; |
| if (nqe_args->GetBoolean(kNQEPersistentCacheReadingEnabled, |
| &persistent_caching_enabled)) { |
| nqe_persistent_caching_enabled = persistent_caching_enabled; |
| } |
| |
| std::string nqe_option; |
| if (nqe_args->GetString(net::kForceEffectiveConnectionType, |
| &nqe_option)) { |
| net::EffectiveConnectionType forced_effective_connection_type = |
| net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN; |
| bool effective_connection_type_available = |
| net::GetEffectiveConnectionTypeForName( |
| nqe_option, &forced_effective_connection_type); |
| if (!effective_connection_type_available) { |
| LOG(ERROR) << "\"" << nqe_option |
| << "\" is not a valid effective connection type value"; |
| } else { |
| nqe_forced_effective_connection_type = |
| forced_effective_connection_type; |
| } |
| } |
| |
| } else { |
| LOG(WARNING) << "Unrecognized Cronet experimental option \"" << it.key() |
| << "\" with params \"" << it.value(); |
| effective_experimental_options->Remove(it.key(), nullptr); |
| } |
| } |
| |
| if (async_dns_enable || stale_dns_enable || host_resolver_rules_enable || |
| disable_ipv6_on_wifi) { |
| CHECK(net_log) << "All DNS-related experiments require NetLog."; |
| std::unique_ptr<net::HostResolver> host_resolver; |
| if (stale_dns_enable) { |
| DCHECK(!disable_ipv6_on_wifi); |
| host_resolver.reset(new StaleHostResolver( |
| net::HostResolver::CreateDefaultResolverImpl(net_log), |
| stale_dns_options)); |
| } else { |
| host_resolver = net::HostResolver::CreateDefaultResolver(net_log); |
| } |
| if (disable_ipv6_on_wifi) |
| host_resolver->SetNoIPv6OnWifi(true); |
| if (async_dns_enable) |
| host_resolver->SetDnsClientEnabled(true); |
| if (host_resolver_rules_enable) { |
| std::unique_ptr<net::MappedHostResolver> remapped_resolver( |
| new net::MappedHostResolver(std::move(host_resolver))); |
| remapped_resolver->SetRulesFromString(host_resolver_rules_string); |
| host_resolver = std::move(remapped_resolver); |
| } |
| context_builder->set_host_resolver(std::move(host_resolver)); |
| } |
| } |
| |
| void URLRequestContextConfig::ConfigureURLRequestContextBuilder( |
| net::URLRequestContextBuilder* context_builder, |
| net::NetLog* net_log, |
| const scoped_refptr<base::SequencedTaskRunner>& file_task_runner) { |
| DCHECK(net_log); |
| |
| std::string config_cache; |
| if (http_cache != DISABLED) { |
| net::URLRequestContextBuilder::HttpCacheParams cache_params; |
| if (http_cache == DISK && !storage_path.empty()) { |
| cache_params.type = net::URLRequestContextBuilder::HttpCacheParams::DISK; |
| cache_params.path = |
| base::FilePath(storage_path) |
| .Append(FILE_PATH_LITERAL(kDiskCacheDirectoryName)); |
| } else { |
| cache_params.type = |
| net::URLRequestContextBuilder::HttpCacheParams::IN_MEMORY; |
| } |
| cache_params.max_size = http_cache_max_size; |
| context_builder->EnableHttpCache(cache_params); |
| } else { |
| context_builder->DisableHttpCache(); |
| } |
| context_builder->set_user_agent(user_agent); |
| context_builder->set_sdch_enabled(enable_sdch); |
| net::HttpNetworkSession::Params session_params; |
| session_params.enable_http2 = enable_spdy; |
| session_params.enable_quic = enable_quic; |
| if (enable_quic) |
| session_params.quic_user_agent_id = quic_user_agent_id; |
| |
| ParseAndSetExperimentalOptions(context_builder, &session_params, net_log, |
| file_task_runner); |
| context_builder->set_http_network_session_params(session_params); |
| |
| std::unique_ptr<net::CertVerifier> cert_verifier; |
| if (mock_cert_verifier) { |
| // Because |context_builder| expects CachingCertVerifier, wrap |
| // |mock_cert_verifier| into a CachingCertVerifier. |
| cert_verifier = base::MakeUnique<net::CachingCertVerifier>( |
| std::move(mock_cert_verifier)); |
| } else { |
| // net::CertVerifier::CreateDefault() returns a CachingCertVerifier. |
| cert_verifier = net::CertVerifier::CreateDefault(); |
| } |
| context_builder->SetCertVerifier(std::move(cert_verifier)); |
| // Certificate Transparency is intentionally ignored in Cronet. |
| // See //net/docs/certificate-transparency.md for more details. |
| context_builder->set_ct_verifier( |
| base::MakeUnique<net::DoNothingCTVerifier>()); |
| context_builder->set_ct_policy_enforcer( |
| base::MakeUnique<DoNothingCTPolicyEnforcer>()); |
| // TODO(mef): Use |config| to set cookies. |
| } |
| |
| URLRequestContextConfigBuilder::URLRequestContextConfigBuilder() {} |
| URLRequestContextConfigBuilder::~URLRequestContextConfigBuilder() {} |
| |
| std::unique_ptr<URLRequestContextConfig> |
| URLRequestContextConfigBuilder::Build() { |
| return base::MakeUnique<URLRequestContextConfig>( |
| enable_quic, quic_user_agent_id, enable_spdy, enable_sdch, enable_brotli, |
| http_cache, http_cache_max_size, load_disable_cache, storage_path, |
| user_agent, experimental_options, std::move(mock_cert_verifier), |
| enable_network_quality_estimator, |
| bypass_public_key_pinning_for_local_trust_anchors, cert_verifier_data); |
| } |
| |
| } // namespace cronet |