| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/network_service_instance_impl.h" |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| |
| #include "base/base_paths.h" |
| #include "base/callback_list.h" |
| #include "base/check.h" |
| #include "base/command_line.h" |
| #include "base/environment.h" |
| #include "base/feature_list.h" |
| #include "base/files/file.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/location.h" |
| #include "base/message_loop/message_pump_type.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/no_destructor.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "base/threading/sequence_local_storage_slot.h" |
| #include "base/threading/thread.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "build/build_config.h" |
| #include "content/browser/browser_main_loop.h" |
| #include "content/browser/first_party_sets/first_party_sets_handler_impl.h" |
| #include "content/browser/network/http_cache_backend_file_operations_factory.h" |
| #include "content/browser/network/socket_broker_impl.h" |
| #include "content/browser/network_sandbox_grant_result.h" |
| #include "content/browser/network_service_client.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/network_service_instance.h" |
| #include "content/public/browser/network_service_util.h" |
| #include "content/public/browser/service_process_host.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_features.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "net/base/features.h" |
| #include "net/base/network_change_notifier.h" |
| #include "net/first_party_sets/global_first_party_sets.h" |
| #include "net/log/file_net_log_observer.h" |
| #include "net/log/net_log_util.h" |
| #include "sandbox/policy/features.h" |
| #include "services/cert_verifier/cert_verifier_service_factory.h" |
| #include "services/cert_verifier/public/mojom/cert_verifier_service_factory.mojom.h" |
| #include "services/network/network_service.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/cpp/network_switches.h" |
| #include "services/network/public/mojom/net_log.mojom.h" |
| #include "services/network/public/mojom/network_change_manager.mojom.h" |
| #include "services/network/public/mojom/network_context.mojom.h" |
| #include "services/network/public/mojom/network_interface_change_listener.mojom.h" |
| #include "services/network/public/mojom/network_service.mojom.h" |
| #include "services/network/public/mojom/network_service_test.mojom.h" |
| #include "services/network/public/mojom/socket_broker.mojom.h" |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| #include "content/browser/network_sandbox.h" |
| #endif |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "content/browser/network/network_service_process_tracker_win.h" |
| #endif |
| |
| #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| #include "content/browser/system_dns_resolution/system_dns_resolver.h" |
| #include "services/network/public/mojom/system_dns_resolution.mojom-forward.h" |
| #endif |
| |
| #if BUILDFLAG(IS_LINUX) |
| #include "net/base/address_map_linux.h" |
| #include "net/base/address_tracker_linux.h" |
| #include "services/network/public/mojom/network_interface_change_listener.mojom.h" |
| #endif |
| |
| namespace content { |
| |
| namespace { |
| |
| #if BUILDFLAG(IS_POSIX) |
| // Environment variable pointing to Kerberos credential cache file. |
| constexpr char kKrb5CCEnvName[] = "KRB5CCNAME"; |
| // Environment variable pointing to Kerberos config file. |
| constexpr char kKrb5ConfEnvName[] = "KRB5_CONFIG"; |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| // File paths to the Kerberos credentials cache and configuration. The `FILE:` |
| // prefix describes the type of credentials cache used. The `/home/chronos/user` |
| // subpath corresponds to a bind mount of the active user. |
| constexpr char kKrb5CCFilePath[] = "FILE:/home/chronos/user/kerberos/krb5cc"; |
| constexpr char kKrb5ConfFilePath[] = "/home/chronos/user/kerberos/krb5.conf"; |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| bool g_force_create_network_service_directly = false; |
| mojo::Remote<network::mojom::NetworkService>* g_network_service_remote = |
| nullptr; |
| network::NetworkConnectionTracker* g_network_connection_tracker; |
| bool g_network_service_is_responding = false; |
| |
| // A directory name that is created below the http cache path and passed to the |
| // network context when creating a network context with cache enabled. |
| // This must be a directory below the main cache path so operations such as |
| // resetting the cache via HttpCacheParams.reset_cache can function correctly |
| // as they rely on having access to the parent directory of the cache. |
| const base::FilePath::CharType kCacheDataDirectoryName[] = |
| FILE_PATH_LITERAL("Cache_Data"); |
| |
| std::unique_ptr<network::NetworkService>& GetLocalNetworkService() { |
| static base::SequenceLocalStorageSlot< |
| std::unique_ptr<network::NetworkService>> |
| service; |
| return service.GetOrCreateValue(); |
| } |
| |
| // If this feature is enabled, the Network Service will run on its own thread |
| // when running in-process; otherwise it will run on the IO thread. |
| // |
| // On Chrome OS, the Network Service must run on the IO thread because |
| // ProfileIOData and NetworkContext both try to set up NSS, which has to be |
| // called from the IO thread. |
| BASE_FEATURE(kNetworkServiceDedicatedThread, |
| "NetworkServiceDedicatedThread", |
| #if BUILDFLAG(IS_CHROMEOS) |
| base::FEATURE_DISABLED_BY_DEFAULT |
| #else |
| base::FEATURE_ENABLED_BY_DEFAULT |
| #endif |
| ); |
| |
| base::Thread& GetNetworkServiceDedicatedThread() { |
| static base::NoDestructor<base::Thread> thread{"NetworkService"}; |
| DCHECK(base::FeatureList::IsEnabled(kNetworkServiceDedicatedThread)); |
| return *thread; |
| } |
| |
| // The instance NetworkService used when hosting the service in-process. This is |
| // set up by |CreateInProcessNetworkServiceOnThread()| and destroyed by |
| // |ShutDownNetworkService()|. |
| network::NetworkService* g_in_process_instance = nullptr; |
| |
| static NetworkServiceClient* g_client = nullptr; |
| |
| void CreateInProcessNetworkServiceOnThread( |
| mojo::PendingReceiver<network::mojom::NetworkService> receiver) { |
| g_in_process_instance = new network::NetworkService( |
| nullptr /* registry */, std::move(receiver), |
| true /* delay_initialization_until_set_client */); |
| } |
| |
| // A utility function to make it clear what behavior is expected by the network |
| // context instance depending on the various errors that can happen during data |
| // migration. |
| // |
| // If this function returns 'true' then the `data_directory` should be used (if |
| // specified in the network context params). If this function returns 'false' |
| // then the `unsandboxed_data_path` should be used. |
| bool IsSafeToUseDataPath(SandboxGrantResult result) { |
| switch (result) { |
| case SandboxGrantResult::kSuccess: |
| // A migration occurred, and it was successful. |
| return true; |
| case SandboxGrantResult::kFailedToGrantSandboxAccessToCache: |
| case SandboxGrantResult::kFailedToCreateCacheDirectory: |
| // A failure to grant create or grant access to the cache dir does not |
| // affect the providence of the data contained in `data_directory` as the |
| // migration could have still occurred. |
| // |
| // These cases are handled internally and so this case should never be |
| // hit. It is undefined behavior to proceed in this case so CHECK here. |
| base::ImmediateCrash(); |
| case SandboxGrantResult::kFailedToCreateDataDirectory: |
| // A failure to create the `data_directory` is fatal, and the |
| // `unsandboxed_data_path` should be used. |
| return false; |
| case SandboxGrantResult::kFailedToCopyData: |
| // A failure to copy the data from `unsandboxed_data_path` to the |
| // `data_directory` is fatal, and the `unsandboxed_data_path` should be |
| // used. |
| return false; |
| case SandboxGrantResult::kFailedToDeleteOldData: |
| // This is not fatal, as the new data has been correctly migrated, and the |
| // deletion will be retried at a later time. |
| return true; |
| case SandboxGrantResult::kFailedToGrantSandboxAccessToData: |
| // If the sandbox could not be granted access to the new data dir, then |
| // don't attempt to migrate. This means that the old |
| // `unsandboxed_data_path` should be used. |
| return false; |
| case SandboxGrantResult::kDidNotAttemptToGrantSandboxAccess: |
| // No migration was attempted either because of platform constraints or |
| // because the network context had no valid data paths (e.g. in-memory or |
| // incognito), or `unsandboxed_data_path` was not specified. |
| // `data_directory` should be used in this case (if present). |
| return true; |
| case SandboxGrantResult::kFailedToCreateCheckpointFile: |
| // This is fatal, as a failure to create the checkpoint file means that |
| // the next time the same network context is used, the data in |
| // `unsandboxed_data_path` will be re-copied to the new `data_directory` |
| // and thus any changes to the data will be discarded. So in this case, |
| // `unsandboxed_data_path` should be used. |
| return false; |
| case SandboxGrantResult::kNoMigrationRequested: |
| // The caller supplied an `unsandboxed_data_path` but did not trigger a |
| // migration so the data should be read from the `unsandboxed_data_path`. |
| return false; |
| case SandboxGrantResult::kMigrationAlreadySucceeded: |
| // Migration has already taken place, so `data_directory` contains the |
| // valid data. |
| return true; |
| case SandboxGrantResult::kMigrationAlreadySucceededWithNoAccess: |
| // If the sandbox could not be granted access to the new data dir, but the |
| // migration has already happened to `data_directory`. This means that the |
| // sandbox might not have access to the data but `data_directory` should |
| // still be used because it's been migrated. |
| return true; |
| } |
| } |
| |
| void CreateNetworkContextInternal( |
| mojo::PendingReceiver<network::mojom::NetworkContext> context, |
| network::mojom::NetworkContextParamsPtr params, |
| SandboxGrantResult grant_access_result) { |
| TRACE_EVENT0("loading", "CreateNetworkContextInternal"); |
| // These two histograms are logged from elsewhere, so don't log them twice. |
| DCHECK(grant_access_result != |
| SandboxGrantResult::kFailedToCreateCacheDirectory); |
| DCHECK(grant_access_result != |
| SandboxGrantResult::kFailedToGrantSandboxAccessToCache); |
| base::UmaHistogramEnumeration("NetworkService.GrantSandboxResult", |
| grant_access_result); |
| |
| if (grant_access_result != SandboxGrantResult::kSuccess && |
| grant_access_result != |
| SandboxGrantResult::kDidNotAttemptToGrantSandboxAccess && |
| grant_access_result != SandboxGrantResult::kNoMigrationRequested && |
| grant_access_result != SandboxGrantResult::kMigrationAlreadySucceeded) { |
| PLOG(ERROR) << "Encountered error while migrating network context data or " |
| "granting sandbox access for " |
| << (params->file_paths |
| ? params->file_paths->data_directory.path() |
| : base::FilePath()) |
| << ". Result: " << static_cast<int>(grant_access_result); |
| } |
| |
| if (!IsSafeToUseDataPath(grant_access_result)) { |
| // Unsafe to use new `data_directory`. This means that a migration was |
| // attempted, and `unsandboxed_data_path` contains the still-valid set of |
| // data. Swap the parameters to instruct the network service to use this |
| // path for the network context. This of course will mean that if the |
| // network service is running sandboxed then this data might not be |
| // accessible, but does provide a pathway to user recovery, as the sandbox |
| // can just be disabled in this case. |
| DCHECK(params->file_paths->unsandboxed_data_path.has_value()); |
| params->file_paths->data_directory = |
| *params->file_paths->unsandboxed_data_path; |
| } |
| |
| if (network::TransferableDirectory::IsOpenForTransferRequired()) { |
| if (params->file_paths) { |
| if (params->file_paths->http_cache_directory) { |
| params->file_paths->http_cache_directory->OpenForTransfer(); |
| } |
| if (params->file_paths->shared_dictionary_directory) { |
| params->file_paths->shared_dictionary_directory->OpenForTransfer(); |
| } |
| params->file_paths->data_directory.OpenForTransfer(); |
| } |
| } |
| |
| // This might recreate g_client if the network service needed to be restarted. |
| auto* network_service = GetNetworkService(); |
| |
| #if BUILDFLAG(IS_WIN) |
| // If the browser has started shutting down, it is possible that either a) |
| // `g_client` was never created if shutdown started before the network service |
| // was created, or b) the network service might have crashed meaning |
| // `g_client` is the client for the already-crashed Network Service, and a new |
| // network service never started. It's not safe to bind the socket broker in |
| // either of these cases so skip the binding since the browser is shutting |
| // down anyway. |
| if (!GetContentClient()->browser()->IsShuttingDown() && |
| GetContentClient()->browser()->ShouldSandboxNetworkService() && |
| !params->socket_brokers) { |
| params->socket_brokers = network::mojom::SocketBrokerRemotes::New(); |
| params->socket_brokers->client = g_client->BindSocketBroker(); |
| params->socket_brokers->server = g_client->BindSocketBroker(); |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| |
| network_service->CreateNetworkContext(std::move(context), std::move(params)); |
| } |
| |
| scoped_refptr<base::SequencedTaskRunner>& GetNetworkTaskRunnerStorage() { |
| static base::NoDestructor<scoped_refptr<base::SequencedTaskRunner>> storage; |
| return *storage; |
| } |
| |
| void CreateInProcessNetworkService( |
| mojo::PendingReceiver<network::mojom::NetworkService> receiver) { |
| TRACE_EVENT0("loading", "CreateInProcessNetworkService"); |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner; |
| if (base::FeatureList::IsEnabled(kNetworkServiceDedicatedThread)) { |
| base::Thread::Options options(base::MessagePumpType::IO, 0); |
| GetNetworkServiceDedicatedThread().StartWithOptions(std::move(options)); |
| task_runner = GetNetworkServiceDedicatedThread().task_runner(); |
| task_runner->PostTask( |
| FROM_HERE, base::BindOnce([]() { |
| mojo::InterfaceEndpointClient::SetThreadNameSuffixForMetrics( |
| "NetworkService"); |
| })); |
| } else { |
| task_runner = GetIOThreadTaskRunner({}); |
| } |
| |
| GetNetworkTaskRunnerStorage() = std::move(task_runner); |
| |
| GetNetworkTaskRunner()->PostTask( |
| FROM_HERE, base::BindOnce(&CreateInProcessNetworkServiceOnThread, |
| std::move(receiver))); |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) |
| // Runs a self-owned SystemDnsResolverMojoImpl. This is meant to run on a |
| // high-priority thread pool. |
| void RunSystemDnsResolverOnThreadPool( |
| mojo::PendingReceiver<network::mojom::SystemDnsResolver> dns_receiver) { |
| mojo::MakeSelfOwnedReceiver( |
| std::make_unique<content::SystemDnsResolverMojoImpl>(), |
| std::move(dns_receiver)); |
| } |
| #endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) || |
| // BUILDFLAG(IS_CHROMEOS) |
| |
| network::mojom::NetworkServiceParamsPtr CreateNetworkServiceParams() { |
| network::mojom::NetworkServiceParamsPtr network_service_params = |
| network::mojom::NetworkServiceParams::New(); |
| network_service_params->initial_connection_type = |
| network::mojom::ConnectionType( |
| net::NetworkChangeNotifier::GetConnectionType()); |
| network_service_params->initial_connection_subtype = |
| network::mojom::ConnectionSubtype( |
| net::NetworkChangeNotifier::GetConnectionSubtype()); |
| network_service_params->default_observer = |
| g_client->BindURLLoaderNetworkServiceObserver(); |
| network_service_params->first_party_sets_enabled = |
| GetContentClient()->browser()->IsFirstPartySetsEnabled(); |
| |
| #if BUILDFLAG(IS_LINUX) |
| if (base::FeatureList::IsEnabled( |
| net::features::kAddressTrackerLinuxIsProxied) && |
| IsOutOfProcessNetworkService()) { |
| auto [address_map, online_links] = |
| net::NetworkChangeNotifier::GetAddressMapOwner() |
| ->GetAddressTrackerLinux() |
| ->GetInitialDataAndStartRecordingDiffs(); |
| network_service_params->initial_address_map = |
| network::mojom::InitialAddressMap::New(std::move(address_map), |
| std::move(online_links)); |
| } |
| #endif // BUILDFLAG(IS_LINUX) |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| // On ChromeOS, the network service is always out of process (unless |
| // --single-process is set on the command-line). In any case, we set Kerberos |
| // environment variables during the service initialization. |
| network_service_params->environment.push_back( |
| network::mojom::EnvironmentVariable::New(kKrb5CCEnvName, |
| kKrb5CCFilePath)); |
| network_service_params->environment.push_back( |
| network::mojom::EnvironmentVariable::New(kKrb5ConfEnvName, |
| kKrb5ConfFilePath)); |
| #elif BUILDFLAG(IS_POSIX) |
| // Send Kerberos environment variables to the network service, if it's running |
| // in another process. |
| if (IsOutOfProcessNetworkService()) { |
| std::unique_ptr<base::Environment> env(base::Environment::Create()); |
| std::optional<std::string> value = env->GetVar(kKrb5CCEnvName); |
| if (value.has_value()) { |
| network_service_params->environment.push_back( |
| network::mojom::EnvironmentVariable::New(kKrb5CCEnvName, *value)); |
| } |
| value = env->GetVar(kKrb5ConfEnvName); |
| if (value.has_value()) { |
| network_service_params->environment.push_back( |
| network::mojom::EnvironmentVariable::New(kKrb5ConfEnvName, *value)); |
| } |
| } |
| #endif // BUILDFLAG(IS_POSIX) |
| |
| #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) |
| if (GetContentClient() |
| ->browser() |
| ->ShouldRunOutOfProcessSystemDnsResolution() && |
| IsOutOfProcessNetworkService()) { |
| mojo::PendingRemote<network::mojom::SystemDnsResolver> dns_remote; |
| scoped_refptr<base::SequencedTaskRunner> thread_pool_task_runner = |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {base::TaskPriority::USER_BLOCKING}); |
| thread_pool_task_runner->PostTask( |
| FROM_HERE, base::BindOnce(RunSystemDnsResolverOnThreadPool, |
| dns_remote.InitWithNewPipeAndPassReceiver())); |
| network_service_params->system_dns_resolver = std::move(dns_remote); |
| } |
| #endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) |
| |
| network_service_params->ip_protection_proxy_bypass_policy = |
| GetContentClient()->browser()->GetIpProtectionProxyBypassPolicy(); |
| return network_service_params; |
| } |
| |
| void CreateNetworkServiceOnIOForTesting( |
| mojo::PendingReceiver<network::mojom::NetworkService> receiver, |
| base::WaitableEvent* completion_event) { |
| if (GetLocalNetworkService()) { |
| GetLocalNetworkService()->Bind(std::move(receiver)); |
| return; |
| } |
| |
| GetLocalNetworkService() = std::make_unique<network::NetworkService>( |
| nullptr /* registry */, std::move(receiver), |
| true /* delay_initialization_until_set_client */); |
| GetLocalNetworkService()->Initialize( |
| network::mojom::NetworkServiceParams::New(), |
| true /* mock_network_change_notifier */); |
| if (completion_event) |
| completion_event->Signal(); |
| } |
| |
| void BindNetworkChangeManagerReceiver( |
| mojo::PendingReceiver<network::mojom::NetworkChangeManager> receiver) { |
| GetNetworkService()->GetNetworkChangeManager(std::move(receiver)); |
| } |
| |
| base::RepeatingCallbackList<void(bool)>& GetProcessGoneHandlersList() { |
| static base::NoDestructor<base::RepeatingCallbackList<void(bool)>> s_list; |
| return *s_list; |
| } |
| |
| void OnNetworkServiceProcessGone(bool crashed) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(g_network_service_remote); |
| DCHECK(g_network_service_remote->is_bound()); |
| DCHECK(!crashed || !g_network_service_remote->is_connected()); |
| GetProcessGoneHandlersList().Notify(crashed); |
| } |
| |
| // Parses the desired granularity of NetLog capturing specified by the command |
| // line. |
| net::NetLogCaptureMode GetNetCaptureModeFromCommandLine( |
| const base::CommandLine& command_line) { |
| std::string_view switch_name = network::switches::kNetLogCaptureMode; |
| |
| if (command_line.HasSwitch(switch_name)) { |
| std::string value = command_line.GetSwitchValueASCII(switch_name); |
| |
| if (value == "HeavilyRedacted") { |
| return net::NetLogCaptureMode::kHeavilyRedacted; |
| } |
| if (value == "Default") |
| return net::NetLogCaptureMode::kDefault; |
| if (value == "IncludeSensitive") |
| return net::NetLogCaptureMode::kIncludeSensitive; |
| if (value == "Everything") |
| return net::NetLogCaptureMode::kEverything; |
| |
| // Warn when using the old command line switches. |
| if (value == "IncludeCookiesAndCredentials") { |
| LOG(ERROR) << "Deprecated value for --" << switch_name |
| << ". Use IncludeSensitive instead"; |
| return net::NetLogCaptureMode::kIncludeSensitive; |
| } |
| if (value == "IncludeSocketBytes") { |
| LOG(ERROR) << "Deprecated value for --" << switch_name |
| << ". Use Everything instead"; |
| return net::NetLogCaptureMode::kEverything; |
| } |
| |
| LOG(ERROR) << "Unrecognized value for --" << switch_name; |
| } |
| |
| return net::NetLogCaptureMode::kDefault; |
| } |
| |
| std::optional<base::TimeDelta> GetNetLogDurationFromCommandLine( |
| const base::CommandLine& command_line) { |
| std::string_view switch_name = network::switches::kLogNetLogDuration; |
| |
| if (!command_line.HasSwitch(switch_name)) { |
| return std::nullopt; |
| } |
| |
| std::string duration_str = command_line.GetSwitchValueASCII(switch_name); |
| int duration_sec = 0; |
| if (base::StringToInt(duration_str, &duration_sec) && duration_sec > 0) { |
| return base::Seconds(duration_sec); |
| } |
| |
| return std::nullopt; |
| } |
| // Parse the maximum file size for the NetLog, if one was specified. |
| // kNoLimit indicates no, valid, maximum size was specified. |
| base::StrictNumeric<uint64_t> GetNetLogMaximumFileSizeFromCommandLine( |
| const base::CommandLine& command_line) { |
| std::string_view switch_name = network::switches::kNetLogMaxSizeMb; |
| |
| if (!command_line.HasSwitch(switch_name)) { |
| return net::FileNetLogObserver::kNoLimit; |
| } |
| |
| std::string value = command_line.GetSwitchValueASCII(switch_name); |
| |
| if (value.empty()) { |
| return net::FileNetLogObserver::kNoLimit; |
| } |
| |
| // 32 bits for the input is fine, a max size of ~2 PB ought to be enough for |
| // anybody. |
| uint32_t max_size_megabytes; |
| bool valid = base::StringToUint(value, &max_size_megabytes); |
| |
| if (!valid) { |
| return net::FileNetLogObserver::kNoLimit; |
| } |
| |
| // Value is currently in megabytes, convert to bytes. 1024*1024 == 2^20 == |
| // left shift by 20 bits |
| uint64_t max_size_bytes = uint64_t{max_size_megabytes} << 20; |
| return max_size_bytes; |
| } |
| |
| } // namespace |
| |
| uint64_t GetNetLogMaximumFileSizeFromCommandLineForTesting( // IN-TEST |
| const base::CommandLine& command_line) { |
| return GetNetLogMaximumFileSizeFromCommandLine(command_line); |
| } |
| |
| class NetworkServiceInstancePrivate { |
| public: |
| // Opens the specified file, blocking until the file is open. Used to open |
| // files specified by network::switches::kLogNetLog or |
| // network::switches::kSSLKeyLogFile. Since these arguments can be used to |
| // debug startup behavior, asynchronously opening the file on another thread |
| // would result in losing data, hence the need for blocking open operations. |
| // |file_flags| specifies the flags passed to the base::File constructor call. |
| // |
| // ThreadRestrictions needs to be able to friend the class/method to allow |
| // blocking, but can't friend CONTENT_EXPORT methods, so have it friend |
| // NetworkServiceInstancePrivate instead of GetNetworkService(). |
| static base::File BlockingOpenFile(const base::FilePath& path, |
| int file_flags) { |
| base::ScopedAllowBlocking allow_blocking; |
| return base::File(path, file_flags); |
| } |
| }; |
| |
| network::mojom::NetworkService* GetNetworkService() { |
| if (!g_network_service_remote) |
| g_network_service_remote = new mojo::Remote<network::mojom::NetworkService>; |
| if (!g_network_service_remote->is_bound() || |
| !g_network_service_remote->is_connected()) { |
| bool service_was_bound = g_network_service_remote->is_bound(); |
| g_network_service_remote->reset(); |
| if (GetContentClient()->browser()->IsShuttingDown()) { |
| // This happens at system shutdown, since in other scenarios the network |
| // process would only be torn down once the message loop stopped running. |
| // We don't want to start the network service again so just create message |
| // pipe that's not bound to stop consumers from requesting creation of the |
| // service. |
| auto receiver = g_network_service_remote->BindNewPipeAndPassReceiver(); |
| auto leaked_pipe = receiver.PassPipe().release(); |
| } else { |
| if (!g_force_create_network_service_directly) { |
| mojo::PendingReceiver<network::mojom::NetworkService> receiver = |
| g_network_service_remote->BindNewPipeAndPassReceiver(); |
| g_network_service_remote->set_disconnect_handler( |
| base::BindOnce(&OnNetworkServiceProcessGone, /*crashed=*/true)); |
| if (IsInProcessNetworkService()) { |
| CreateInProcessNetworkService(std::move(receiver)); |
| } else { |
| if (service_was_bound) |
| LOG(ERROR) << "Network service crashed, restarting service."; |
| ServiceProcessHost::Launch(std::move(receiver), |
| ServiceProcessHost::Options() |
| .WithDisplayName(u"Network Service") |
| .Pass()); |
| } |
| } else { |
| DCHECK(IsInProcessNetworkService()) |
| << "If the network service is created directly, the test must not " |
| "request an out of process network service."; |
| // This should only be reached in unit tests. |
| if (BrowserThread::CurrentlyOn(BrowserThread::IO)) { |
| CreateNetworkServiceOnIOForTesting( |
| g_network_service_remote->BindNewPipeAndPassReceiver(), |
| /*completion_event=*/nullptr); |
| } else { |
| base::WaitableEvent event; |
| GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| CreateNetworkServiceOnIOForTesting, |
| g_network_service_remote->BindNewPipeAndPassReceiver(), |
| base::Unretained(&event))); |
| event.Wait(); |
| } |
| } |
| |
| delete g_client; // In case we're recreating the network service. |
| g_client = new NetworkServiceClient(); |
| |
| (*g_network_service_remote)->SetParams(CreateNetworkServiceParams()); |
| g_client->OnNetworkServiceInitialized(g_network_service_remote->get()); |
| |
| g_network_service_is_responding = false; |
| g_network_service_remote->QueryVersion(base::BindOnce( |
| [](uint32_t) { g_network_service_is_responding = true; })); |
| |
| const base::CommandLine* command_line = |
| base::CommandLine::ForCurrentProcess(); |
| if (command_line->HasSwitch(network::switches::kLogNetLog)) { |
| base::FilePath log_path = |
| command_line->GetSwitchValuePath(network::switches::kLogNetLog); |
| if (log_path.empty()) { |
| log_path = GetContentClient()->browser()->GetNetLogDefaultDirectory(); |
| if (!log_path.empty()) |
| log_path = log_path.Append(FILE_PATH_LITERAL("netlog.json")); |
| } |
| |
| base::File file = NetworkServiceInstancePrivate::BlockingOpenFile( |
| log_path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| if (!file.IsValid()) { |
| LOG(ERROR) << "Failed opening NetLog: " << log_path.value(); |
| } else { |
| (*g_network_service_remote) |
| ->StartNetLog( |
| std::move(file), |
| GetNetLogMaximumFileSizeFromCommandLine(*command_line), |
| GetNetCaptureModeFromCommandLine(*command_line), |
| GetContentClient()->browser()->GetNetLogConstants(), |
| GetNetLogDurationFromCommandLine(*command_line)); |
| } |
| } |
| |
| base::FilePath ssl_key_log_path; |
| if (command_line->HasSwitch(network::switches::kSSLKeyLogFile)) { |
| UMA_HISTOGRAM_ENUMERATION(kSSLKeyLogFileHistogram, |
| SSLKeyLogFileAction::kSwitchFound); |
| ssl_key_log_path = |
| command_line->GetSwitchValuePath(network::switches::kSSLKeyLogFile); |
| LOG_IF(WARNING, ssl_key_log_path.empty()) |
| << "ssl-key-log-file argument missing"; |
| } else { |
| std::unique_ptr<base::Environment> env(base::Environment::Create()); |
| std::optional<std::string> env_str = env->GetVar("SSLKEYLOGFILE"); |
| if (env_str.has_value()) { |
| UMA_HISTOGRAM_ENUMERATION(kSSLKeyLogFileHistogram, |
| SSLKeyLogFileAction::kEnvVarFound); |
| #if BUILDFLAG(IS_WIN) |
| // base::Environment returns environment variables in UTF-8 on |
| // Windows. |
| ssl_key_log_path = base::FilePath(base::UTF8ToWide(*env_str)); |
| #else |
| ssl_key_log_path = base::FilePath(*env_str); |
| #endif |
| } |
| } |
| |
| if (!ssl_key_log_path.empty()) { |
| base::File file = NetworkServiceInstancePrivate::BlockingOpenFile( |
| ssl_key_log_path, |
| base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND); |
| if (!file.IsValid()) { |
| LOG(ERROR) << "Failed opening SSL key log file: " |
| << ssl_key_log_path.value(); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION(kSSLKeyLogFileHistogram, |
| SSLKeyLogFileAction::kLogFileEnabled); |
| (*g_network_service_remote)->SetSSLKeyLogFile(std::move(file)); |
| } |
| } |
| |
| if (FirstPartySetsHandlerImpl::GetInstance()->IsEnabled()) { |
| if (std::optional<net::GlobalFirstPartySets> sets = |
| FirstPartySetsHandlerImpl::GetInstance()->GetSets( |
| base::BindOnce([](net::GlobalFirstPartySets sets) { |
| GetNetworkService()->SetFirstPartySets(std::move(sets)); |
| })); |
| sets.has_value()) { |
| g_network_service_remote->get()->SetFirstPartySets( |
| std::move(sets.value())); |
| } |
| } |
| |
| GetContentClient()->browser()->OnNetworkServiceCreated( |
| g_network_service_remote->get()); |
| } |
| } |
| return g_network_service_remote->get(); |
| } |
| |
| base::CallbackListSubscription RegisterNetworkServiceProcessGoneHandler( |
| NetworkServiceProcessGoneHandler handler) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!handler.is_null()); |
| |
| return GetProcessGoneHandlersList().Add(std::move(handler)); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| net::NetworkChangeNotifier* GetNetworkChangeNotifier() { |
| return BrowserMainLoop::GetInstance()->network_change_notifier(); |
| } |
| #endif |
| |
| void FlushNetworkServiceInstanceForTesting() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (g_network_service_remote) |
| g_network_service_remote->FlushForTesting(); |
| } |
| |
| network::NetworkConnectionTracker* GetNetworkConnectionTracker() { |
| DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (!g_network_connection_tracker) { |
| g_network_connection_tracker = new network::NetworkConnectionTracker( |
| base::BindRepeating(&BindNetworkChangeManagerReceiver)); |
| } |
| return g_network_connection_tracker; |
| } |
| |
| void GetNetworkConnectionTrackerFromUIThread( |
| base::OnceCallback<void(network::NetworkConnectionTracker*)> callback) { |
| GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT}) |
| ->PostTaskAndReplyWithResult(FROM_HERE, |
| base::BindOnce(&GetNetworkConnectionTracker), |
| std::move(callback)); |
| } |
| |
| network::NetworkConnectionTrackerAsyncGetter |
| CreateNetworkConnectionTrackerAsyncGetter() { |
| return base::BindRepeating(&content::GetNetworkConnectionTrackerFromUIThread); |
| } |
| |
| void SetNetworkConnectionTrackerForTesting( |
| network::NetworkConnectionTracker* network_connection_tracker) { |
| DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (g_network_connection_tracker != network_connection_tracker) { |
| DCHECK(!g_network_connection_tracker || !network_connection_tracker); |
| g_network_connection_tracker = network_connection_tracker; |
| } |
| } |
| |
| const scoped_refptr<base::SequencedTaskRunner>& GetNetworkTaskRunner() { |
| DCHECK(IsInProcessNetworkService()); |
| return GetNetworkTaskRunnerStorage(); |
| } |
| |
| void ForceCreateNetworkServiceDirectlyForTesting() { |
| ForceInProcessNetworkService(); |
| g_force_create_network_service_directly = true; |
| } |
| |
| void ResetNetworkServiceForTesting() { |
| ShutDownNetworkService(); |
| } |
| |
| void ShutDownNetworkService() { |
| delete g_network_service_remote; |
| g_network_service_remote = nullptr; |
| delete g_client; |
| g_client = nullptr; |
| if (g_in_process_instance) { |
| GetNetworkTaskRunner()->DeleteSoon(FROM_HERE, g_in_process_instance); |
| g_in_process_instance = nullptr; |
| } |
| GetNetworkTaskRunnerStorage().reset(); |
| } |
| |
| void RestartNetworkService() { |
| ShutDownNetworkService(); |
| GetNetworkService(); |
| OnNetworkServiceProcessGone(/*crashed=*/false); |
| } |
| |
| namespace { |
| |
| cert_verifier::mojom::CertVerifierServiceFactory* |
| g_cert_verifier_service_factory_for_testing = nullptr; |
| |
| std::unique_ptr<cert_verifier::CertVerifierServiceFactoryImpl>& |
| GetCertVerifierServiceFactoryImplStorage() { |
| #if BUILDFLAG(IS_CHROMEOS) |
| // See the comment in GetCertVerifierServiceFactory() for the thread-affinity |
| // of the CertVerifierService. |
| DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::IO) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| #else |
| DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| #endif |
| static base::SequenceLocalStorageSlot< |
| std::unique_ptr<cert_verifier::CertVerifierServiceFactoryImpl>> |
| service_factory_slot; |
| return service_factory_slot.GetOrCreateValue(); |
| } |
| |
| void RunInProcessCertVerifierServiceFactory( |
| mojo::PendingReceiver<cert_verifier::mojom::CertVerifierServiceFactory> |
| receiver) { |
| GetCertVerifierServiceFactoryImplStorage() = |
| std::make_unique<cert_verifier::CertVerifierServiceFactoryImpl>( |
| std::move(receiver)); |
| } |
| |
| // Owns the CertVerifierServiceFactory used by the browser. |
| // Lives on the UI thread. |
| mojo::Remote<cert_verifier::mojom::CertVerifierServiceFactory>& |
| GetCertVerifierServiceFactoryRemoteStorage() { |
| static base::SequenceLocalStorageSlot< |
| mojo::Remote<cert_verifier::mojom::CertVerifierServiceFactory>> |
| cert_verifier_service_factory_remote; |
| return cert_verifier_service_factory_remote.GetOrCreateValue(); |
| } |
| |
| } // namespace |
| |
| // Returns a pointer to a CertVerifierServiceFactory usable on the UI thread. |
| cert_verifier::mojom::CertVerifierServiceFactory* |
| GetCertVerifierServiceFactory() { |
| DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (g_cert_verifier_service_factory_for_testing) |
| return g_cert_verifier_service_factory_for_testing; |
| |
| mojo::Remote<cert_verifier::mojom::CertVerifierServiceFactory>& |
| factory_remote_storage = GetCertVerifierServiceFactoryRemoteStorage(); |
| if (!factory_remote_storage.is_bound() || |
| !factory_remote_storage.is_connected()) { |
| factory_remote_storage.reset(); |
| #if BUILDFLAG(IS_CHROMEOS) |
| // In-process CertVerifierService should run on the IO thread because it |
| // interacts with IO-bound NSS and ChromeOS user slots. See for example |
| // InitializeNSSForChromeOSUser() or CertDbInitializerIOImpl. |
| GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&RunInProcessCertVerifierServiceFactory, |
| factory_remote_storage.BindNewPipeAndPassReceiver())); |
| #else |
| RunInProcessCertVerifierServiceFactory( |
| factory_remote_storage.BindNewPipeAndPassReceiver()); |
| #endif |
| } |
| return factory_remote_storage.get(); |
| } |
| |
| mojo::Remote<cert_verifier::mojom::CertVerifierServiceFactory>& |
| GetCertVerifierServiceFactoryRemoteForTesting() { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // The Remote isn't used if g_cert_verifier_service_factory_for_testing is |
| // registered, so any test trying to do both is doing something wrong. |
| CHECK(!g_cert_verifier_service_factory_for_testing); |
| |
| return GetCertVerifierServiceFactoryRemoteStorage(); |
| } |
| |
| cert_verifier::CertVerifierServiceFactoryImpl* |
| GetCertVerifierServiceFactoryForTesting() { |
| // The same comment about CHECK(!g_cert_verifier_service_factory_for_testing) |
| // from GetCertVerifierServiceFactoryRemoteForTesting() applies here, but |
| // since this method could be called on the IO thread, it is not CHECKed here. |
| |
| // TODO(crbug.com/40693524): This depends on the cert verifier service |
| // and the network service both being in the same process as the unit test. |
| // The network service is taken care of by `UnitTestTestSuite` calling |
| // `ForceCreateNetworkServiceDirectlyForTesting()`, but if the cert verifier |
| // service is moved to a separate process as well, something similar will |
| // need to be done for that to be testable. |
| return GetCertVerifierServiceFactoryImplStorage().get(); |
| } |
| |
| network::mojom::CertVerifierServiceRemoteParamsPtr GetCertVerifierParams( |
| cert_verifier::mojom::CertVerifierCreationParamsPtr |
| cert_verifier_creation_params) { |
| return GetCertVerifierParamsWithUpdater( |
| std::move(cert_verifier_creation_params), mojo::NullReceiver()); |
| } |
| |
| network::mojom::CertVerifierServiceRemoteParamsPtr |
| GetCertVerifierParamsWithUpdater( |
| cert_verifier::mojom::CertVerifierCreationParamsPtr |
| cert_verifier_creation_params, |
| mojo::PendingReceiver<cert_verifier::mojom::CertVerifierServiceUpdater> |
| cert_verifier_updater_remote) { |
| mojo::PendingRemote<cert_verifier::mojom::CertVerifierService> |
| cert_verifier_remote; |
| mojo::PendingReceiver<cert_verifier::mojom::CertVerifierServiceClient> |
| cert_verifier_client; |
| |
| GetCertVerifierServiceFactory()->GetNewCertVerifier( |
| cert_verifier_remote.InitWithNewPipeAndPassReceiver(), |
| std::move(cert_verifier_updater_remote), |
| cert_verifier_client.InitWithNewPipeAndPassRemote(), |
| std::move(cert_verifier_creation_params)); |
| |
| return network::mojom::CertVerifierServiceRemoteParams::New( |
| std::move(cert_verifier_remote), std::move(cert_verifier_client)); |
| } |
| |
| void SetCertVerifierServiceFactoryForTesting( |
| cert_verifier::mojom::CertVerifierServiceFactory* service_factory) { |
| g_cert_verifier_service_factory_for_testing = service_factory; |
| } |
| |
| void CreateNetworkContextInNetworkService( |
| mojo::PendingReceiver<network::mojom::NetworkContext> context, |
| network::mojom::NetworkContextParamsPtr params) { |
| TRACE_EVENT0("loading", "CreateNetworkContextInNetworkService"); |
| DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (params->http_cache_enabled && params->file_paths && |
| params->file_paths->http_cache_directory) { |
| params->file_paths->http_cache_directory = |
| params->file_paths->http_cache_directory->path().Append( |
| kCacheDataDirectoryName); |
| } |
| |
| const bool has_valid_http_cache_path = |
| params->http_cache_enabled && params->file_paths && |
| params->file_paths->http_cache_directory && |
| !params->file_paths->http_cache_directory->path().empty(); |
| const bool brokering_is_enabled = |
| IsOutOfProcessNetworkService() && |
| base::FeatureList::IsEnabled( |
| features::kBrokerFileOperationsOnDiskCacheInNetworkService); |
| if (has_valid_http_cache_path && brokering_is_enabled) { |
| mojo::MakeSelfOwnedReceiver( |
| std::make_unique<HttpCacheBackendFileOperationsFactory>( |
| params->file_paths->http_cache_directory->path()), |
| params->http_cache_file_operations_factory |
| .InitWithNewPipeAndPassReceiver()); |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| // On Android, if a cookie_manager pending receiver was passed then migration |
| // should not be attempted as the cookie file is already being accessed by the |
| // browser instance. |
| if (params->cookie_manager) { |
| if (params->file_paths) { |
| // No migration should ever be attempted under this configuration. |
| DCHECK(!params->file_paths->unsandboxed_data_path); |
| } |
| CreateNetworkContextInternal( |
| std::move(context), std::move(params), |
| SandboxGrantResult::kDidNotAttemptToGrantSandboxAccess); |
| return; |
| } |
| |
| // Note: This logic is duplicated from MaybeGrantAccessToDataPath to this fast |
| // path. This should be kept in sync if there are any changes to the logic. |
| SandboxGrantResult grant_result = SandboxGrantResult::kNoMigrationRequested; |
| if (!params->file_paths) { |
| // No file paths (e.g. in-memory context) so nothing to do. |
| grant_result = SandboxGrantResult::kDidNotAttemptToGrantSandboxAccess; |
| } else { |
| // If no `unsandboxed_data_path` is supplied, it means this is network |
| // context has been created by Android Webview, which does not understand |
| // the concept of `unsandboxed_data_path`. In this case, `data_directory` |
| // should always be used, if present. |
| if (!params->file_paths->unsandboxed_data_path) |
| grant_result = SandboxGrantResult::kDidNotAttemptToGrantSandboxAccess; |
| } |
| // Create network context immediately without thread hops. |
| CreateNetworkContextInternal(std::move(context), std::move(params), |
| grant_result); |
| #else |
| // Restrict disk access to a certain path (on another thread) and continue |
| // with network context creation. |
| GrantSandboxAccessOnThreadPool( |
| std::move(params), |
| base::BindOnce(&CreateNetworkContextInternal, std::move(context))); |
| #endif // BUILDFLAG(IS_ANDROID) |
| } |
| |
| } // namespace content |