// Copyright 2021 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/quic/dedicated_web_transport_http3_client.h" #include #include "base/memory/raw_ptr.h" #include "base/strings/strcat.h" #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" #include "net/base/schemeful_site.h" #include "net/cert/mock_cert_verifier.h" #include "net/dns/mock_host_resolver.h" #include "net/proxy_resolution/configured_proxy_resolution_service.h" #include "net/quic/crypto/proof_source_chromium.h" #include "net/test/test_data_directory.h" #include "net/test/test_with_task_environment.h" #include "net/third_party/quiche/src/quiche/quic/test_tools/crypto_test_utils.h" #include "net/third_party/quiche/src/quiche/quic/test_tools/quic_test_backend.h" #include "net/tools/quic/quic_simple_server.h" #include "net/tools/quic/quic_simple_server_socket.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_builder.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" #include "url/origin.h" namespace net::test { namespace { using ::quic::test::MemSliceFromString; using ::testing::_; using ::testing::DoAll; using ::testing::Optional; using ::testing::SaveArg; class MockVisitor : public WebTransportClientVisitor { public: MOCK_METHOD(void, OnConnected, (scoped_refptr), (override)); MOCK_METHOD(void, OnConnectionFailed, (const WebTransportError&), (override)); MOCK_METHOD(void, OnClosed, (const absl::optional&), (override)); MOCK_METHOD(void, OnError, (const WebTransportError&), (override)); MOCK_METHOD0(OnIncomingBidirectionalStreamAvailable, void()); MOCK_METHOD0(OnIncomingUnidirectionalStreamAvailable, void()); MOCK_METHOD1(OnDatagramReceived, void(base::StringPiece)); MOCK_METHOD0(OnCanCreateNewOutgoingBidirectionalStream, void()); MOCK_METHOD0(OnCanCreateNewOutgoingUnidirectionalStream, void()); MOCK_METHOD1(OnDatagramProcessed, void(absl::optional)); }; // A clock that only mocks out WallNow(), but uses real Now() and // ApproximateNow(). Useful for certificate verification. class TestWallClock : public quic::QuicClock { public: quic::QuicTime Now() const override { return quic::QuicChromiumClock::GetInstance()->Now(); } quic::QuicTime ApproximateNow() const override { return quic::QuicChromiumClock::GetInstance()->ApproximateNow(); } quic::QuicWallTime WallNow() const override { return wall_now_; } void set_wall_now(quic::QuicWallTime now) { wall_now_ = now; } private: quic::QuicWallTime wall_now_ = quic::QuicWallTime::Zero(); }; class TestConnectionHelper : public quic::QuicConnectionHelperInterface { public: const quic::QuicClock* GetClock() const override { return &clock_; } quic::QuicRandom* GetRandomGenerator() override { return quic::QuicRandom::GetInstance(); } quiche::QuicheBufferAllocator* GetStreamSendBufferAllocator() override { return &allocator_; } TestWallClock& clock() { return clock_; } private: TestWallClock clock_; quiche::SimpleBufferAllocator allocator_; }; class DedicatedWebTransportHttp3Test : public TestWithTaskEnvironment { public: DedicatedWebTransportHttp3Test() { quic::QuicEnableVersion(quic::ParsedQuicVersion::RFCv1()); origin_ = url::Origin::Create(GURL{"https://example.org"}); isolation_key_ = NetworkIsolationKey(SchemefulSite(origin_), SchemefulSite(origin_)); URLRequestContextBuilder builder; builder.set_proxy_resolution_service( ConfiguredProxyResolutionService::CreateDirect()); auto cert_verifier = std::make_unique(); cert_verifier->set_default_result(OK); builder.SetCertVerifier(std::move(cert_verifier)); auto host_resolver = std::make_unique(); host_resolver->rules()->AddRule("test.example.com", "127.0.0.1"); builder.set_host_resolver(std::move(host_resolver)); auto helper = std::make_unique(); helper_ = helper.get(); auto quic_context = std::make_unique(std::move(helper)); quic_context->params()->supported_versions.clear(); // This is required to bypass the check that only allows known certificate // roots in QUIC. quic_context->params()->origins_to_force_quic_on.insert( HostPortPair("test.example.com", 0)); builder.set_quic_context(std::move(quic_context)); builder.set_net_log(NetLog::Get()); context_ = builder.Build(); // By default, quit on error instead of waiting for RunLoop() to time out. ON_CALL(visitor_, OnConnectionFailed(_)) .WillByDefault([this](const WebTransportError& error) { LOG(ERROR) << "Connection failed: " << error; run_loop_->Quit(); }); ON_CALL(visitor_, OnError(_)) .WillByDefault([this](const WebTransportError& error) { LOG(ERROR) << "Connection error: " << error; run_loop_->Quit(); }); } ~DedicatedWebTransportHttp3Test() override { if (server_ != nullptr) { server_->Shutdown(); } } GURL GetURL(const std::string& suffix) { return GURL{base::StrCat( {"https://test.example.com:", base::NumberToString(port_), suffix})}; } void StartServer(std::unique_ptr proof_source = nullptr) { if (proof_source == nullptr) { proof_source = quic::test::crypto_test_utils::ProofSourceForTesting(); } backend_.set_enable_webtransport(true); server_ = std::make_unique( std::move(proof_source), quic::QuicConfig(), quic::QuicCryptoServerConfig::ConfigOptions(), quic::AllSupportedVersions(), &backend_); ASSERT_TRUE(server_->CreateUDPSocketAndListen( quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), /*port=*/0))); port_ = server_->server_address().port(); } void Run() { run_loop_ = std::make_unique(); run_loop_->Run(); } auto StopRunning() { return [this]() { run_loop_->Quit(); }; } protected: quic::test::QuicFlagSaver flags_; // Save/restore all QUIC flag values. std::unique_ptr context_; std::unique_ptr client_; raw_ptr helper_; // Owned by |context_|. ::testing::NiceMock visitor_; std::unique_ptr server_; std::unique_ptr run_loop_; quic::test::QuicTestBackend backend_; int port_ = 0; url::Origin origin_; NetworkIsolationKey isolation_key_; }; TEST_F(DedicatedWebTransportHttp3Test, Connect) { StartServer(); client_ = std::make_unique( GetURL("/echo"), origin_, &visitor_, isolation_key_, context_.get(), WebTransportParameters()); EXPECT_CALL(visitor_, OnConnected(_)).WillOnce(StopRunning()); client_->Connect(); Run(); ASSERT_TRUE(client_->session() != nullptr); client_->Close(absl::nullopt); EXPECT_CALL(visitor_, OnClosed(_)).WillOnce(StopRunning()); Run(); } // TODO(https://crbug.com/1288036): The test is flaky on Mac and iOS. #if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_MAC) #define MAYBE_CloseTimeout DISABLED_CloseTimeout #else #define MAYBE_CloseTimeout CloseTimeout #endif TEST_F(DedicatedWebTransportHttp3Test, MAYBE_CloseTimeout) { StartServer(); client_ = std::make_unique( GetURL("/echo"), origin_, &visitor_, isolation_key_, context_.get(), WebTransportParameters()); EXPECT_CALL(visitor_, OnConnected(_)).WillOnce(StopRunning()); client_->Connect(); Run(); ASSERT_TRUE(client_->session() != nullptr); // Delete the server and put up a no-op socket in its place to simulate the // traffic being dropped. Note that this is normally not a supported way of // shutting down a QuicServer, and will generate a lot of errors in the logs. server_.reset(); IPEndPoint bind_address(IPAddress::IPv6AllZeros(), port_); auto noop_socket = std::make_unique(/*net_log=*/nullptr, NetLogSource()); noop_socket->AllowAddressReuse(); ASSERT_GE(noop_socket->Listen(bind_address), 0); client_->Close(absl::nullopt); EXPECT_CALL(visitor_, OnError(_)).WillOnce(StopRunning()); Run(); } TEST_F(DedicatedWebTransportHttp3Test, CloseReason) { StartServer(); client_ = std::make_unique( GetURL("/session-close"), origin_, &visitor_, isolation_key_, context_.get(), WebTransportParameters()); EXPECT_CALL(visitor_, OnConnected(_)).WillOnce(StopRunning()); client_->Connect(); Run(); ASSERT_TRUE(client_->session() != nullptr); quic::WebTransportStream* stream = client_->session()->OpenOutgoingUnidirectionalStream(); ASSERT_TRUE(stream != nullptr); EXPECT_TRUE(stream->Write("42 test error")); EXPECT_TRUE(stream->SendFin()); WebTransportCloseInfo close_info(42, "test error"); absl::optional received_close_info; EXPECT_CALL(visitor_, OnClosed(_)) .WillOnce(DoAll(StopRunning(), SaveArg<0>(&received_close_info))); Run(); EXPECT_THAT(received_close_info, Optional(close_info)); } } // namespace } // namespace net::test