// Copyright 2017 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 "base/bind.h" #include "base/command_line.h" #include "base/files/file_util.h" #include "base/memory/ref_counted_memory.h" #include "base/test/bind.h" #include "base/test/scoped_feature_list.h" #include "build/build_config.h" #include "content/browser/storage_partition_impl.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/network_service_instance.h" #include "content/public/browser/url_data_source.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_ui_controller.h" #include "content/public/browser/web_ui_controller_factory.h" #include "content/public/common/content_features.h" #include "content/public/common/content_switches.h" #include "content/public/common/network_service_util.h" #include "content/public/common/url_utils.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_browser_test.h" #include "content/public/test/content_browser_test_utils.h" #include "content/public/test/simple_url_loader_test_helper.h" #include "content/public/test/test_utils.h" #include "content/shell/browser/shell.h" #include "content/test/content_browser_test_utils_internal.h" #include "mojo/public/cpp/bindings/remote.h" #include "net/base/features.h" #include "net/disk_cache/disk_cache.h" #include "net/dns/mock_host_resolver.h" #include "net/http/http_response_headers.h" #include "net/test/embedded_test_server/default_handlers.h" #include "net/test/embedded_test_server/http_request.h" #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" #include "services/network/public/cpp/features.h" #include "services/network/public/cpp/network_switches.h" #include "services/network/public/cpp/resource_request.h" #include "services/network/public/cpp/simple_url_loader.h" #include "services/network/public/mojom/network_service_test.mojom.h" #include "services/network/test/udp_socket_test_util.h" #if defined(OS_ANDROID) #include "base/android/application_status_listener.h" #endif namespace content { namespace { class WebUITestWebUIControllerFactory : public WebUIControllerFactory { public: std::unique_ptr CreateWebUIControllerForURL( WebUI* web_ui, const GURL& url) override { std::string foo(url.path()); if (url.path() == "/nobinding/") web_ui->SetBindings(0); return HasWebUIScheme(url) ? std::make_unique(web_ui) : nullptr; } WebUI::TypeID GetWebUIType(BrowserContext* browser_context, const GURL& url) override { return HasWebUIScheme(url) ? reinterpret_cast(1) : nullptr; } bool UseWebUIForURL(BrowserContext* browser_context, const GURL& url) override { return HasWebUIScheme(url); } bool UseWebUIBindingsForURL(BrowserContext* browser_context, const GURL& url) override { return HasWebUIScheme(url); } }; class TestWebUIDataSource : public URLDataSource { public: TestWebUIDataSource() {} ~TestWebUIDataSource() override {} std::string GetSource() override { return "webui"; } void StartDataRequest(const GURL& url, const WebContents::Getter& wc_getter, URLDataSource::GotDataCallback callback) override { std::string dummy_html = "Foo"; scoped_refptr response = base::RefCountedString::TakeString(&dummy_html); std::move(callback).Run(response.get()); } std::string GetMimeType(const std::string& path) override { return "text/html"; } private: DISALLOW_COPY_AND_ASSIGN(TestWebUIDataSource); }; class NetworkServiceBrowserTest : public ContentBrowserTest { public: NetworkServiceBrowserTest() { EXPECT_TRUE(embedded_test_server()->Start()); EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); WebUIControllerFactory::RegisterFactory(&factory_); } bool ExecuteScript(const std::string& script) { bool xhr_result = false; // The JS call will fail if disallowed because the process will be killed. bool execute_result = ExecuteScriptAndExtractBool(shell(), script, &xhr_result); return xhr_result && execute_result; } bool FetchResource(const GURL& url, bool synchronous = false) { if (!url.is_valid()) return false; std::string script = JsReplace( "var xhr = new XMLHttpRequest();" "xhr.open('GET', $1, $2);" "xhr.onload = function (e) {" " if (xhr.readyState === 4) {" " window.domAutomationController.send(xhr.status === 200);" " }" "};" "xhr.onerror = function () {" " window.domAutomationController.send(false);" "};" "try {" " xhr.send(null);" "} catch (error) {" " window.domAutomationController.send(false);" "}", url, !synchronous); return ExecuteScript(script); } bool CheckCanLoadHttp() { return FetchResource(embedded_test_server()->GetURL("/echo")); } void SetUpOnMainThread() override { URLDataSource::Add(shell()->web_contents()->GetBrowserContext(), std::make_unique()); } void SetUpCommandLine(base::CommandLine* command_line) override { // Since we assume exploited renderer process, it can bypass the same origin // policy at will. Simulate that by passing the disable-web-security flag. command_line->AppendSwitch(switches::kDisableWebSecurity); IsolateAllSitesForTesting(command_line); } base::FilePath GetCacheDirectory() { return temp_dir_.GetPath(); } base::FilePath GetCacheIndexDirectory() { return GetCacheDirectory().AppendASCII("index-dir"); } void LoadURL(const GURL& url, network::mojom::URLLoaderFactory* loader_factory) { std::unique_ptr request = std::make_unique(); request->url = url; url::Origin origin = url::Origin::Create(url); request->trusted_params = network::ResourceRequest::TrustedParams(); request->trusted_params->isolation_info = net::IsolationInfo::CreateForInternalRequest(origin); request->site_for_cookies = request->trusted_params->isolation_info.site_for_cookies(); SimpleURLLoaderTestHelper simple_loader_helper; std::unique_ptr simple_loader = network::SimpleURLLoader::Create(std::move(request), TRAFFIC_ANNOTATION_FOR_TESTS); simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie( loader_factory, simple_loader_helper.GetCallback()); simple_loader_helper.WaitForCallback(); ASSERT_TRUE(simple_loader_helper.response_body()); } private: WebUITestWebUIControllerFactory factory_; base::ScopedTempDir temp_dir_; DISALLOW_COPY_AND_ASSIGN(NetworkServiceBrowserTest); }; // Verifies that WebUI pages with WebUI bindings can't make network requests. IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, WebUIBindingsNoHttp) { GURL test_url(GetWebUIURL("webui/")); EXPECT_TRUE(NavigateToURL(shell(), test_url)); RenderProcessHostBadIpcMessageWaiter kill_waiter( shell()->web_contents()->GetMainFrame()->GetProcess()); ASSERT_FALSE(CheckCanLoadHttp()); EXPECT_EQ(bad_message::RPH_MOJO_PROCESS_ERROR, kill_waiter.Wait()); } // Verifies that WebUI pages without WebUI bindings can make network requests. IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, NoWebUIBindingsHttp) { GURL test_url(GetWebUIURL("webui/nobinding/")); EXPECT_TRUE(NavigateToURL(shell(), test_url)); ASSERT_TRUE(CheckCanLoadHttp()); } // Verifies the filesystem URLLoaderFactory's check, using // ChildProcessSecurityPolicyImpl::CanRequestURL is properly rejected. IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, FileSystemBindingsCorrectOrigin) { GURL test_url(GetWebUIURL("webui/nobinding/")); EXPECT_TRUE(NavigateToURL(shell(), test_url)); // Note: must be filesystem scheme (obviously). // file: is not a safe web scheme (see IsWebSafeScheme), // and /etc/passwd fails the CanCommitURL check. GURL file_url("filesystem:file:///etc/passwd"); EXPECT_FALSE(FetchResource(file_url)); } IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, SimpleUrlLoader_NoAuthWhenNoWebContents) { auto request = std::make_unique(); request->url = embedded_test_server()->GetURL("/auth-basic?password="); auto loader = network::SimpleURLLoader::Create(std::move(request), TRAFFIC_ANNOTATION_FOR_TESTS); auto loader_factory = BrowserContext::GetDefaultStoragePartition( shell()->web_contents()->GetBrowserContext()) ->GetURLLoaderFactoryForBrowserProcess(); scoped_refptr headers; base::RunLoop loop; loader->DownloadHeadersOnly( loader_factory.get(), base::BindOnce( [](base::OnceClosure quit_closure, scoped_refptr* rh_out, scoped_refptr rh_in) { *rh_out = rh_in; std::move(quit_closure).Run(); }, loop.QuitClosure(), &headers)); loop.Run(); ASSERT_TRUE(headers.get()); ASSERT_EQ(headers->response_code(), 401); } #if defined(OS_ANDROID) IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, HttpCacheWrittenToDiskOnApplicationStateChange) { base::ScopedAllowBlockingForTesting allow_blocking; // Create network context with cache pointing to the temp cache dir. mojo::Remote network_context; network::mojom::NetworkContextParamsPtr context_params = network::mojom::NetworkContextParams::New(); context_params->cert_verifier_params = GetCertVerifierParams(network::mojom::CertVerifierCreationParams::New()); context_params->http_cache_path = GetCacheDirectory(); GetNetworkService()->CreateNetworkContext( network_context.BindNewPipeAndPassReceiver(), std::move(context_params)); network::mojom::URLLoaderFactoryParamsPtr params = network::mojom::URLLoaderFactoryParams::New(); params->process_id = network::mojom::kBrowserProcessId; params->automatically_assign_isolation_info = true; params->is_corb_enabled = false; params->is_trusted = true; mojo::Remote loader_factory; network_context->CreateURLLoaderFactory( loader_factory.BindNewPipeAndPassReceiver(), std::move(params)); // Load a URL and check the cache index size. LoadURL(embedded_test_server()->GetURL("/cachetime"), loader_factory.get()); int64_t directory_size = base::ComputeDirectorySize(GetCacheIndexDirectory()); // Load another URL, cache index should not be written to disk yet. LoadURL(embedded_test_server()->GetURL("/cachetime?foo"), loader_factory.get()); EXPECT_EQ(directory_size, base::ComputeDirectorySize(GetCacheIndexDirectory())); // After application state changes, cache index should be written to disk. base::android::ApplicationStatusListener::NotifyApplicationStateChange( base::android::APPLICATION_STATE_HAS_STOPPED_ACTIVITIES); base::RunLoop().RunUntilIdle(); FlushNetworkServiceInstanceForTesting(); disk_cache::FlushCacheThreadForTesting(); EXPECT_GT(base::ComputeDirectorySize(GetCacheIndexDirectory()), directory_size); } class NetworkConnectionObserver : public network::NetworkConnectionTracker::NetworkConnectionObserver { public: NetworkConnectionObserver() { content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this); content::GetNetworkConnectionTracker()->GetConnectionType( &last_connection_type_, base::BindOnce(&NetworkConnectionObserver::OnConnectionChanged, base::Unretained(this))); } ~NetworkConnectionObserver() override { content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver( this); } void WaitForConnectionType(network::mojom::ConnectionType type) { type_to_wait_for_ = type; if (last_connection_type_ == type_to_wait_for_) return; run_loop_ = std::make_unique(); run_loop_->Run(); } // network::NetworkConnectionTracker::NetworkConnectionObserver: void OnConnectionChanged(network::mojom::ConnectionType type) override { last_connection_type_ = type; if (run_loop_ && type_to_wait_for_ == type) run_loop_->Quit(); } private: network::mojom::ConnectionType type_to_wait_for_ = network::mojom::ConnectionType::CONNECTION_UNKNOWN; network::mojom::ConnectionType last_connection_type_ = network::mojom::ConnectionType::CONNECTION_UNKNOWN; std::unique_ptr run_loop_; }; IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, ConnectionTypeChangeSyncedToNetworkProcess) { NetworkConnectionObserver observer; net::NetworkChangeNotifier::NotifyObserversOfConnectionTypeChangeForTests( net::NetworkChangeNotifier::CONNECTION_WIFI); observer.WaitForConnectionType( network::mojom::ConnectionType::CONNECTION_WIFI); net::NetworkChangeNotifier::NotifyObserversOfConnectionTypeChangeForTests( net::NetworkChangeNotifier::CONNECTION_ETHERNET); observer.WaitForConnectionType( network::mojom::ConnectionType::CONNECTION_ETHERNET); } #endif IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, MemoryPressureSentToNetworkProcess) { if (IsInProcessNetworkService()) return; mojo::Remote network_service_test; GetNetworkService()->BindTestInterface( network_service_test.BindNewPipeAndPassReceiver()); // TODO(crbug.com/901026): Make sure the network process is started to avoid a // deadlock on Android. network_service_test.FlushForTesting(); mojo::ScopedAllowSyncCallForTesting allow_sync_call; base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level = base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE; network_service_test->GetLatestMemoryPressureLevel(&memory_pressure_level); EXPECT_EQ(memory_pressure_level, base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE); base::MemoryPressureListener::NotifyMemoryPressure( base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL); base::RunLoop().RunUntilIdle(); FlushNetworkServiceInstanceForTesting(); network_service_test->GetLatestMemoryPressureLevel(&memory_pressure_level); EXPECT_EQ(memory_pressure_level, base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL); } // Verifies that sync XHRs don't hang if the network service crashes. IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, SyncXHROnCrash) { if (IsInProcessNetworkService()) return; mojo::PendingRemote pending_network_service_test; GetNetworkService()->BindTestInterface( pending_network_service_test.InitWithNewPipeAndPassReceiver()); net::EmbeddedTestServer http_server; http_server.AddDefaultHandlers(GetTestDataFilePath()); http_server.RegisterRequestMonitor(base::BindLambdaForTesting( [&](const net::test_server::HttpRequest& request) { if (request.relative_url == "/hung") { mojo::Remote network_service_test( std::move(pending_network_service_test)); network_service_test->SimulateCrash(); } })); EXPECT_TRUE(http_server.Start()); EXPECT_TRUE(NavigateToURL(shell(), http_server.GetURL("/empty.html"))); FetchResource(http_server.GetURL("/hung"), true); // If the renderer is hung the test will hang. } // Verifies that sync cookie calls don't hang if the network service crashes. IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, SyncCookieGetOnCrash) { if (IsInProcessNetworkService()) return; mojo::Remote network_service_test; GetNetworkService()->BindTestInterface( network_service_test.BindNewPipeAndPassReceiver()); network_service_test->CrashOnGetCookieList(); EXPECT_TRUE( NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html"))); ASSERT_TRUE( content::ExecuteScript(shell()->web_contents(), "document.cookie")); // If the renderer is hung the test will hang. } int64_t GetPreloadedFirstPartySetCountFromNetworkService() { DCHECK(!content::IsInProcessNetworkService()); mojo::Remote network_service_test; content::GetNetworkService()->BindTestInterface( network_service_test.BindNewPipeAndPassReceiver()); network_service_test.FlushForTesting(); mojo::ScopedAllowSyncCallForTesting allow_sync_call; int64_t count = 0; EXPECT_TRUE( network_service_test->GetPreloadedFirstPartySetEntriesCount(&count)); return count; } class NetworkServiceWithFirstPartySetBrowserTest : public NetworkServiceBrowserTest { public: void SetUpCommandLine(base::CommandLine* command_line) override { NetworkServiceBrowserTest::SetUpCommandLine(command_line); command_line->AppendSwitchASCII( network::switches::kUseFirstPartySet, "https://example.com,https://member1.com,https://member2.com"); } }; IN_PROC_BROWSER_TEST_F(NetworkServiceWithFirstPartySetBrowserTest, GetsUseFirstPartySetSwitch) { if (IsInProcessNetworkService()) return; EXPECT_EQ(GetPreloadedFirstPartySetCountFromNetworkService(), 2); SimulateNetworkServiceCrash(); EXPECT_EQ(GetPreloadedFirstPartySetCountFromNetworkService(), 2); } // Tests that CORS is performed by the network service when |factory_override| // is used. IN_PROC_BROWSER_TEST_F(NetworkServiceBrowserTest, FactoryOverride) { class TestURLLoaderFactory final : public network::mojom::URLLoaderFactory { public: void CreateLoaderAndStart( mojo::PendingReceiver receiver, int32_t routing_id, int32_t request_id, uint32_t options, const network::ResourceRequest& resource_request, mojo::PendingRemote pending_client, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) override { mojo::Remote client( std::move(pending_client)); EXPECT_EQ(resource_request.url, GURL("https://www.example.com/hello.txt")); if (resource_request.method == "OPTIONS") { has_received_preflight_ = true; auto response = network::mojom::URLResponseHead::New(); response->headers = base::MakeRefCounted("HTTP/1.1 200 OK"); response->headers->SetHeader("access-control-allow-origin", "https://www2.example.com"); response->headers->SetHeader("access-control-allow-methods", "*"); client->OnReceiveResponse(std::move(response)); } else if (resource_request.method == "custom-method") { has_received_request_ = true; auto response = network::mojom::URLResponseHead::New(); response->headers = base::MakeRefCounted( "HTTP/1.1 202 Accepted"); response->headers->SetHeader("access-control-allow-origin", "https://www2.example.com"); client->OnReceiveResponse(std::move(response)); client->OnComplete(network::URLLoaderCompletionStatus(net::OK)); } else { client->OnComplete( network::URLLoaderCompletionStatus(net::ERR_INVALID_ARGUMENT)); } } void Clone(mojo::PendingReceiver receiver) override { NOTREACHED(); } bool has_received_preflight() const { return has_received_preflight_; } bool has_received_request() const { return has_received_request_; } private: bool has_received_preflight_ = false; bool has_received_request_ = false; }; // Create a request that will trigger a CORS preflight request. auto request = std::make_unique(); request->url = GURL("https://www.example.com/hello.txt"); request->mode = network::mojom::RequestMode::kCors; request->credentials_mode = network::mojom::CredentialsMode::kSameOrigin; request->method = "custom-method"; request->request_initiator = url::Origin::Create(GURL("https://www2.example.com/")); // Inject TestURLLoaderFactory as the factory override. auto test_loader_factory = std::make_unique(); mojo::Receiver test_loader_factory_receiver( test_loader_factory.get()); mojo::Remote loader_factory_remote; auto loader = network::SimpleURLLoader::Create(std::move(request), TRAFFIC_ANNOTATION_FOR_TESTS); auto params = network::mojom::URLLoaderFactoryParams::New(); params->process_id = 0; params->factory_override = network::mojom::URLLoaderFactoryOverride::New(); params->factory_override->overriding_factory = test_loader_factory_receiver.BindNewPipeAndPassRemote(); BrowserContext::GetDefaultStoragePartition( shell()->web_contents()->GetBrowserContext()) ->GetNetworkContext() ->CreateURLLoaderFactory( loader_factory_remote.BindNewPipeAndPassReceiver(), std::move(params)); scoped_refptr headers; // Perform the request. base::RunLoop loop; loader->DownloadHeadersOnly( loader_factory_remote.get(), base::BindLambdaForTesting( [&](scoped_refptr passed_headers) { headers = passed_headers; loop.Quit(); })); loop.Run(); ASSERT_TRUE(headers.get()); EXPECT_EQ(headers->response_code(), 202); EXPECT_TRUE(test_loader_factory->has_received_preflight()); EXPECT_TRUE(test_loader_factory->has_received_request()); } class NetworkServiceInProcessBrowserTest : public ContentBrowserTest { public: NetworkServiceInProcessBrowserTest() { std::vector features; features.push_back(features::kNetworkServiceInProcess); scoped_feature_list_.InitWithFeatures(features, std::vector()); } void SetUpOnMainThread() override { host_resolver()->AddRule("*", "127.0.0.1"); EXPECT_TRUE(embedded_test_server()->Start()); } private: base::test::ScopedFeatureList scoped_feature_list_; DISALLOW_COPY_AND_ASSIGN(NetworkServiceInProcessBrowserTest); }; // Verifies that in-process network service works. IN_PROC_BROWSER_TEST_F(NetworkServiceInProcessBrowserTest, Basic) { GURL test_url = embedded_test_server()->GetURL("foo.com", "/echo"); StoragePartitionImpl* partition = static_cast( BrowserContext::GetDefaultStoragePartition( shell()->web_contents()->GetBrowserContext())); EXPECT_TRUE(NavigateToURL(shell(), test_url)); ASSERT_EQ(net::OK, LoadBasicRequest(partition->GetNetworkContext(), test_url)); } class NetworkServiceInvalidLogBrowserTest : public ContentBrowserTest { public: NetworkServiceInvalidLogBrowserTest() = default; void SetUpCommandLine(base::CommandLine* command_line) override { command_line->AppendSwitchASCII(network::switches::kLogNetLog, "/abc/def"); } void SetUpOnMainThread() override { host_resolver()->AddRule("*", "127.0.0.1"); EXPECT_TRUE(embedded_test_server()->Start()); } private: DISALLOW_COPY_AND_ASSIGN(NetworkServiceInvalidLogBrowserTest); }; // Verifies that an invalid --log-net-log flag won't crash the browser. IN_PROC_BROWSER_TEST_F(NetworkServiceInvalidLogBrowserTest, Basic) { GURL test_url = embedded_test_server()->GetURL("foo.com", "/echo"); StoragePartitionImpl* partition = static_cast( BrowserContext::GetDefaultStoragePartition( shell()->web_contents()->GetBrowserContext())); EXPECT_TRUE(NavigateToURL(shell(), test_url)); ASSERT_EQ(net::OK, LoadBasicRequest(partition->GetNetworkContext(), test_url)); } // Test fixture for using a NetworkService that has a non-default limit on the // number of allowed open UDP sockets. class NetworkServiceWithUDPSocketLimit : public NetworkServiceBrowserTest { public: NetworkServiceWithUDPSocketLimit() { base::FieldTrialParams params; params[net::features::kLimitOpenUDPSocketsMax.name] = base::NumberToString(kMaxUDPSockets); scoped_feature_list_.InitAndEnableFeatureWithParameters( net::features::kLimitOpenUDPSockets, params); } protected: static constexpr int kMaxUDPSockets = 4; // Creates and synchronously connects a UDPSocket using |network_context|. // Returns the network error for Connect(). int ConnectUDPSocketSync( mojo::Remote* network_context, mojo::Remote* socket) { network_context->get()->CreateUDPSocket( socket->BindNewPipeAndPassReceiver(), mojo::NullRemote()); // The address of this endpoint doesn't matter, since Connect() will not // actually send any datagrams, and is only being called to verify the // socket limit enforcement. net::IPEndPoint remote_addr(net::IPAddress(127, 0, 0, 1), 8080); network::mojom::UDPSocketOptionsPtr options = network::mojom::UDPSocketOptions::New(); net::IPEndPoint local_addr; network::test::UDPSocketTestHelper helper(socket); return helper.ConnectSync(remote_addr, std::move(options), &local_addr); } // Creates a NetworkContext using default parameters. mojo::Remote CreateNetworkContext() { mojo::Remote network_context; network::mojom::NetworkContextParamsPtr context_params = network::mojom::NetworkContextParams::New(); context_params->cert_verifier_params = GetCertVerifierParams( network::mojom::CertVerifierCreationParams::New()); GetNetworkService()->CreateNetworkContext( network_context.BindNewPipeAndPassReceiver(), std::move(context_params)); return network_context; } private: base::test::ScopedFeatureList scoped_feature_list_; }; // Tests calling Connect() on |kMaxUDPSockets + 4| sockets. The first // kMaxUDPSockets should succeed, whereas the last 4 should fail with // ERR_INSUFFICIENT_RESOURCES due to having exceeding the global bound. IN_PROC_BROWSER_TEST_F(NetworkServiceWithUDPSocketLimit, UDPSocketBoundEnforced) { constexpr size_t kNumContexts = 2; mojo::Remote network_contexts[kNumContexts] = {CreateNetworkContext(), CreateNetworkContext()}; mojo::Remote sockets[kMaxUDPSockets]; // Try to connect the maximum number of UDP sockets (|kMaxUDPSockets|), // spread evenly between 2 NetworkContexts. These should succeed as the // global limit has not been reached yet. This assumes there are no // other consumers of UDP sockets in the browser yet. for (size_t i = 0; i < kMaxUDPSockets; ++i) { auto* network_context = &network_contexts[i % kNumContexts]; EXPECT_EQ(net::OK, ConnectUDPSocketSync(network_context, &sockets[i])); } // Try to connect an additional 4 sockets, alternating between each of the // NetworkContexts. These should all fail with ERR_INSUFFICIENT_RESOURCES as // the limit has already been reached. Spreading across NetworkContext // is done to ensure the socket limit is global and not per // NetworkContext. for (size_t i = 0; i < 4; ++i) { auto* network_context = &network_contexts[i % kNumContexts]; mojo::Remote socket; EXPECT_EQ(net::ERR_INSUFFICIENT_RESOURCES, ConnectUDPSocketSync(network_context, &socket)); } } } // namespace } // namespace content