Avi Drissman | 8ba1bad | 2022-09-13 19:22:36 | [diff] [blame] | 1 | // Copyright 2022 The Chromium Authors |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "components/cronet/cronet_context.h" |
| 6 | |
| 7 | #include <latch> |
| 8 | |
Sean Maher | e672a66 | 2023-01-09 21:42:28 | [diff] [blame] | 9 | #include "base/task/single_thread_task_runner.h" |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 10 | #include "base/test/bind.h" |
| 11 | #include "base/test/task_environment.h" |
| 12 | #include "components/cronet/cronet_global_state.h" |
| 13 | #include "components/cronet/url_request_context_config.h" |
| 14 | #include "net/base/mock_network_change_notifier.h" |
| 15 | #include "net/base/request_priority.h" |
| 16 | #include "net/cert/cert_verifier.h" |
| 17 | #include "net/proxy_resolution/proxy_config_service_fixed.h" |
Stefano Duo | 0d3e86d | 2022-03-24 13:09:10 | [diff] [blame] | 18 | #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 19 | #include "net/url_request/url_request.h" |
| 20 | #include "net/url_request/url_request_context.h" |
| 21 | #include "testing/gtest/include/gtest/gtest.h" |
| 22 | |
| 23 | #if BUILDFLAG(IS_ANDROID) |
| 24 | #include "base/android/build_info.h" |
| 25 | #endif // BUILDFLAG(IS_ANDROID) |
| 26 | |
| 27 | namespace cronet { |
| 28 | |
| 29 | namespace { |
| 30 | |
| 31 | class NoOpCronetContextCallback : public CronetContext::Callback { |
| 32 | public: |
| 33 | NoOpCronetContextCallback() = default; |
| 34 | |
| 35 | NoOpCronetContextCallback(const NoOpCronetContextCallback&) = delete; |
| 36 | NoOpCronetContextCallback& operator=(const NoOpCronetContextCallback&) = |
| 37 | delete; |
| 38 | |
| 39 | void OnInitNetworkThread() override {} |
| 40 | |
| 41 | void OnDestroyNetworkThread() override {} |
| 42 | |
| 43 | void OnEffectiveConnectionTypeChanged( |
| 44 | net::EffectiveConnectionType effective_connection_type) override {} |
| 45 | |
| 46 | void OnRTTOrThroughputEstimatesComputed( |
| 47 | int32_t http_rtt_ms, |
| 48 | int32_t transport_rtt_ms, |
| 49 | int32_t downstream_throughput_kbps) override {} |
| 50 | |
| 51 | void OnRTTObservation(int32_t rtt_ms, |
| 52 | int32_t timestamp_ms, |
| 53 | net::NetworkQualityObservationSource source) override {} |
| 54 | |
| 55 | void OnThroughputObservation( |
| 56 | int32_t throughput_kbps, |
| 57 | int32_t timestamp_ms, |
| 58 | net::NetworkQualityObservationSource source) override {} |
| 59 | |
| 60 | void OnStopNetLogCompleted() override {} |
| 61 | |
| 62 | ~NoOpCronetContextCallback() override = default; |
| 63 | }; |
| 64 | |
| 65 | std::unique_ptr<URLRequestContextConfig> CreateSimpleURLRequestContextConfig() { |
| 66 | return URLRequestContextConfig::CreateURLRequestContextConfig( |
| 67 | // Enable QUIC. |
| 68 | true, |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 69 | // Enable SPDY. |
| 70 | true, |
| 71 | // Enable Brotli. |
| 72 | false, |
| 73 | // Type of http cache. |
| 74 | URLRequestContextConfig::HttpCacheType::DISK, |
| 75 | // Max size of http cache in bytes. |
| 76 | 1024000, |
| 77 | // Disable caching for HTTP responses. Other information may be stored |
| 78 | // in the cache. |
| 79 | false, |
| 80 | // Storage path for http cache and cookie storage. |
| 81 | "/data/data/org.chromium.net/app_cronet_test/test_storage", |
| 82 | // Accept-Language request header field. |
| 83 | "foreign-language", |
| 84 | // User-Agent request header field. |
| 85 | "fake agent", |
| 86 | // JSON encoded experimental options. |
| 87 | "", |
| 88 | // MockCertVerifier to use for testing purposes. |
| 89 | std::unique_ptr<net::CertVerifier>(), |
| 90 | // Enable network quality estimator. |
| 91 | false, |
| 92 | // Enable Public Key Pinning bypass for local trust anchors. |
| 93 | true, |
| 94 | // Optional network thread priority. |
Arthur Sonzogni | c571efb | 2024-01-26 20:26:18 | [diff] [blame] | 95 | std::nullopt); |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 96 | } |
| 97 | |
| 98 | class NetworkTasksTest : public testing::Test { |
| 99 | protected: |
| 100 | NetworkTasksTest() |
Stefano Duo | 6b82fbc | 2022-08-17 08:20:30 | [diff] [blame] | 101 | : ncn_(net::NetworkChangeNotifier::CreateMockIfNeeded()), |
| 102 | scoped_ncn_( |
| 103 | std::make_unique<net::test::ScopedMockNetworkChangeNotifier>()), |
| 104 | network_thread_(std::make_unique<base::Thread>("network")), |
| 105 | file_thread_(std::make_unique<base::Thread>("Network File Thread")), |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 106 | network_tasks_(new CronetContext::NetworkTasks( |
| 107 | CreateSimpleURLRequestContextConfig(), |
Stefano Duo | 03391ac | 2022-03-21 18:20:27 | [diff] [blame] | 108 | std::make_unique<NoOpCronetContextCallback>())) { |
Stefano Duo | 6b82fbc | 2022-08-17 08:20:30 | [diff] [blame] | 109 | base::Thread::Options options; |
| 110 | options.message_pump_type = base::MessagePumpType::IO; |
| 111 | network_thread_->StartWithOptions(std::move(options)); |
| 112 | network_task_runner_ = network_thread_->task_runner(); |
| 113 | |
| 114 | file_thread_->Start(); |
| 115 | file_task_runner_ = file_thread_->task_runner(); |
| 116 | |
| 117 | scoped_ncn_->mock_network_change_notifier()->ForceNetworkHandlesSupported(); |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 118 | Initialize(); |
| 119 | } |
| 120 | |
Stefano Duo | 6b82fbc | 2022-08-17 08:20:30 | [diff] [blame] | 121 | ~NetworkTasksTest() override { |
Tom Sepez | 22c8ba03 | 2023-02-23 23:12:37 | [diff] [blame] | 122 | PostToNetworkThreadSync(base::BindOnce( |
| 123 | // Deletion ocurrs as a result of the argument going out of scope. |
| 124 | [](std::unique_ptr<CronetContext::NetworkTasks> tasks_to_be_deleted) {}, |
| 125 | std::move(network_tasks_))); |
Stefano Duo | 6b82fbc | 2022-08-17 08:20:30 | [diff] [blame] | 126 | } |
| 127 | |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 128 | void Initialize() { |
| 129 | PostToNetworkThreadSync( |
| 130 | base::BindOnce(&CronetContext::NetworkTasks::Initialize, |
Tom Sepez | 22c8ba03 | 2023-02-23 23:12:37 | [diff] [blame] | 131 | base::Unretained(network_tasks_.get()), |
| 132 | network_task_runner_, file_task_runner_, |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 133 | std::make_unique<net::ProxyConfigServiceFixed>( |
| 134 | net::ProxyConfigWithAnnotation::CreateDirect()))); |
| 135 | } |
| 136 | |
Stefano Duo | 6527ed4 | 2022-07-29 09:25:44 | [diff] [blame] | 137 | void SpawnNetworkBoundURLRequestContext(net::handles::NetworkHandle network) { |
Devon Loehr | dcb8b46 | 2024-08-01 20:17:28 | [diff] [blame] | 138 | PostToNetworkThreadSync(base::BindLambdaForTesting([=, this]() { |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 139 | network_tasks_->SpawnNetworkBoundURLRequestContextForTesting(network); |
| 140 | })); |
| 141 | } |
| 142 | |
Stefano Duo | 6527ed4 | 2022-07-29 09:25:44 | [diff] [blame] | 143 | void CheckURLRequestContextExistence(net::handles::NetworkHandle network, |
| 144 | bool expected) { |
Stefano Duo | 6b82fbc | 2022-08-17 08:20:30 | [diff] [blame] | 145 | std::atomic_bool context_exists = false; |
| 146 | PostToNetworkThreadSync(base::BindLambdaForTesting([&]() { |
| 147 | context_exists.store( |
| 148 | network_tasks_->URLRequestContextExistsForTesting(network)); |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 149 | })); |
Stefano Duo | 6b82fbc | 2022-08-17 08:20:30 | [diff] [blame] | 150 | EXPECT_EQ(expected, context_exists.load()); |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 151 | } |
| 152 | |
Stefano Duo | 6527ed4 | 2022-07-29 09:25:44 | [diff] [blame] | 153 | void CreateURLRequest(net::handles::NetworkHandle network) { |
Stefano Duo | 6b82fbc | 2022-08-17 08:20:30 | [diff] [blame] | 154 | std::atomic_bool url_request_created = false; |
Stefano Duo | 0d3e86d | 2022-03-24 13:09:10 | [diff] [blame] | 155 | PostToNetworkThreadSync(base::BindLambdaForTesting([&]() { |
| 156 | auto* context = network_tasks_->GetURLRequestContext(network); |
| 157 | url_request_ = context->CreateRequest(GURL("http://www.foo.com"), |
| 158 | net::DEFAULT_PRIORITY, nullptr, |
| 159 | TRAFFIC_ANNOTATION_FOR_TESTS); |
Stefano Duo | 6b82fbc | 2022-08-17 08:20:30 | [diff] [blame] | 160 | url_request_created = !!url_request_; |
Stefano Duo | 0d3e86d | 2022-03-24 13:09:10 | [diff] [blame] | 161 | })); |
Stefano Duo | 6b82fbc | 2022-08-17 08:20:30 | [diff] [blame] | 162 | EXPECT_TRUE(url_request_created); |
Stefano Duo | 0d3e86d | 2022-03-24 13:09:10 | [diff] [blame] | 163 | } |
| 164 | |
| 165 | void ReleaseURLRequest() { |
| 166 | PostToNetworkThreadSync( |
| 167 | base::BindLambdaForTesting([&]() { url_request_.reset(); })); |
| 168 | } |
| 169 | |
Stefano Duo | 6527ed4 | 2022-07-29 09:25:44 | [diff] [blame] | 170 | void MaybeDestroyURLRequestContext(net::handles::NetworkHandle network) { |
Stefano Duo | 0d3e86d | 2022-03-24 13:09:10 | [diff] [blame] | 171 | PostToNetworkThreadSync(base::BindLambdaForTesting( |
| 172 | [&]() { network_tasks_->MaybeDestroyURLRequestContext(network); })); |
| 173 | } |
| 174 | |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 175 | void PostToNetworkThreadSync(base::OnceCallback<void()> callback) { |
| 176 | std::latch callback_executed{1}; |
| 177 | auto wait_for_callback = base::BindLambdaForTesting( |
| 178 | [&callback_executed]() { callback_executed.count_down(); }); |
| 179 | network_task_runner_->PostTask( |
| 180 | FROM_HERE, std::move(callback).Then(std::move(wait_for_callback))); |
| 181 | callback_executed.wait(); |
| 182 | } |
| 183 | |
| 184 | base::test::TaskEnvironment task_environment_; |
| 185 | std::unique_ptr<net::NetworkChangeNotifier> ncn_; |
Stefano Duo | 6b82fbc | 2022-08-17 08:20:30 | [diff] [blame] | 186 | std::unique_ptr<net::test::ScopedMockNetworkChangeNotifier> scoped_ncn_; |
| 187 | std::unique_ptr<base::Thread> network_thread_; |
| 188 | std::unique_ptr<base::Thread> file_thread_; |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 189 | scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_; |
| 190 | scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_; |
Tom Sepez | 22c8ba03 | 2023-02-23 23:12:37 | [diff] [blame] | 191 | std::unique_ptr<CronetContext::NetworkTasks> network_tasks_; |
Stefano Duo | 0d3e86d | 2022-03-24 13:09:10 | [diff] [blame] | 192 | std::unique_ptr<net::URLRequest> url_request_; |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 193 | }; |
| 194 | |
| 195 | TEST_F(NetworkTasksTest, NetworkBoundContextLifetime) { |
| 196 | #if BUILDFLAG(IS_ANDROID) |
Stefano Duo | 6527ed4 | 2022-07-29 09:25:44 | [diff] [blame] | 197 | constexpr net::handles::NetworkHandle kNetwork = 1; |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 198 | |
| 199 | CheckURLRequestContextExistence(kNetwork, false); |
| 200 | SpawnNetworkBoundURLRequestContext(kNetwork); |
| 201 | CheckURLRequestContextExistence(kNetwork, true); |
| 202 | |
| 203 | // Once the network disconnects the context should be destroyed. |
Stefano Duo | 6b82fbc | 2022-08-17 08:20:30 | [diff] [blame] | 204 | scoped_ncn_->mock_network_change_notifier()->NotifyNetworkDisconnected( |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 205 | kNetwork); |
| 206 | CheckURLRequestContextExistence(kNetwork, false); |
Stefano Duo | 6b82fbc | 2022-08-17 08:20:30 | [diff] [blame] | 207 | #else |
| 208 | GTEST_SKIP() << "Network binding is supported only on Android"; |
| 209 | #endif // BUILDFLAG(IS_ANDROID) |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 210 | } |
| 211 | |
Stefano Duo | 0d3e86d | 2022-03-24 13:09:10 | [diff] [blame] | 212 | TEST_F(NetworkTasksTest, NetworkBoundContextWithPendingRequest) { |
| 213 | #if BUILDFLAG(IS_ANDROID) |
Stefano Duo | 6527ed4 | 2022-07-29 09:25:44 | [diff] [blame] | 214 | constexpr net::handles::NetworkHandle kNetwork = 1; |
Stefano Duo | 0d3e86d | 2022-03-24 13:09:10 | [diff] [blame] | 215 | |
| 216 | CheckURLRequestContextExistence(kNetwork, false); |
| 217 | SpawnNetworkBoundURLRequestContext(kNetwork); |
| 218 | CheckURLRequestContextExistence(kNetwork, true); |
| 219 | |
| 220 | // If after a network disconnection there are still pending requests, the |
| 221 | // context should not be destroyed to avoid UAFs (URLRequests can reference |
| 222 | // their associated URLRequestContext). |
| 223 | CreateURLRequest(kNetwork); |
Stefano Duo | 6b82fbc | 2022-08-17 08:20:30 | [diff] [blame] | 224 | CheckURLRequestContextExistence(kNetwork, true); |
| 225 | scoped_ncn_->mock_network_change_notifier()->QueueNetworkDisconnected( |
Stefano Duo | 0d3e86d | 2022-03-24 13:09:10 | [diff] [blame] | 226 | kNetwork); |
| 227 | CheckURLRequestContextExistence(kNetwork, true); |
| 228 | |
| 229 | // Once the URLRequest is destroyed, MaybeDestroyURLRequestContext should be |
| 230 | // able to destroy the context. |
| 231 | ReleaseURLRequest(); |
| 232 | MaybeDestroyURLRequestContext(kNetwork); |
| 233 | CheckURLRequestContextExistence(kNetwork, false); |
Stefano Duo | 6b82fbc | 2022-08-17 08:20:30 | [diff] [blame] | 234 | #else |
| 235 | GTEST_SKIP() << "Network binding is supported only on Android"; |
| 236 | #endif // BUILDFLAG(IS_ANDROID) |
Stefano Duo | 0d3e86d | 2022-03-24 13:09:10 | [diff] [blame] | 237 | } |
| 238 | |
Stefano Duo | da85dc0 | 2022-03-10 14:07:29 | [diff] [blame] | 239 | } // namespace |
| 240 | |
| 241 | } // namespace cronet |