blob: f52afce3557766891bca16b5c952c93ac3cb05d4 [file] [log] [blame]
// Copyright 2022 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 "quiche/quic/tools/connect_tunnel.h"
#include <cstdint>
#include <utility>
#include "absl/container/flat_hash_set.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "quiche/quic/core/connecting_client_socket.h"
#include "quiche/quic/core/quic_connection_id.h"
#include "quiche/quic/core/quic_error_codes.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/core/socket_factory.h"
#include "quiche/quic/platform/api/quic_socket_address.h"
#include "quiche/quic/platform/api/quic_test_loopback.h"
#include "quiche/quic/test_tools/quic_test_utils.h"
#include "quiche/quic/tools/quic_backend_response.h"
#include "quiche/quic/tools/quic_simple_server_backend.h"
#include "quiche/common/platform/api/quiche_mem_slice.h"
#include "quiche/common/platform/api/quiche_test.h"
#include "quiche/spdy/core/http2_header_block.h"
namespace quic::test {
namespace {
using ::testing::_;
using ::testing::AllOf;
using ::testing::AnyOf;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Ge;
using ::testing::Gt;
using ::testing::InvokeWithoutArgs;
using ::testing::IsEmpty;
using ::testing::Matcher;
using ::testing::NiceMock;
using ::testing::Pair;
using ::testing::Property;
using ::testing::Return;
using ::testing::StrictMock;
class MockRequestHandler : public QuicSimpleServerBackend::RequestHandler {
public:
QuicConnectionId connection_id() const override {
return TestConnectionId(41212);
}
QuicStreamId stream_id() const override { return 100; }
std::string peer_host() const override { return "127.0.0.1"; }
MOCK_METHOD(QuicSpdyStream*, GetStream, (), (override));
MOCK_METHOD(void, OnResponseBackendComplete,
(const QuicBackendResponse* response), (override));
MOCK_METHOD(void, SendStreamData, (absl::string_view data, bool close_stream),
(override));
MOCK_METHOD(void, TerminateStreamWithError, (QuicResetStreamError error),
(override));
};
class MockSocketFactory : public SocketFactory {
public:
MOCK_METHOD(std::unique_ptr<ConnectingClientSocket>, CreateTcpClientSocket,
(const quic::QuicSocketAddress& peer_address,
QuicByteCount receive_buffer_size,
QuicByteCount send_buffer_size,
ConnectingClientSocket::AsyncVisitor* async_visitor),
(override));
MOCK_METHOD(std::unique_ptr<ConnectingClientSocket>,
CreateConnectingUdpClientSocket,
(const quic::QuicSocketAddress& peer_address,
QuicByteCount receive_buffer_size,
QuicByteCount send_buffer_size,
ConnectingClientSocket::AsyncVisitor* async_visitor),
(override));
};
class MockSocket : public ConnectingClientSocket {
public:
MOCK_METHOD(absl::Status, ConnectBlocking, (), (override));
MOCK_METHOD(void, ConnectAsync, (), (override));
MOCK_METHOD(void, Disconnect, (), (override));
MOCK_METHOD(absl::StatusOr<QuicSocketAddress>, GetLocalAddress, (),
(override));
MOCK_METHOD(absl::StatusOr<quiche::QuicheMemSlice>, ReceiveBlocking,
(QuicByteCount max_size), (override));
MOCK_METHOD(void, ReceiveAsync, (QuicByteCount max_size), (override));
MOCK_METHOD(absl::Status, SendBlocking, (std::string data), (override));
MOCK_METHOD(absl::Status, SendBlocking, (quiche::QuicheMemSlice data),
(override));
MOCK_METHOD(void, SendAsync, (std::string data), (override));
MOCK_METHOD(void, SendAsync, (quiche::QuicheMemSlice data), (override));
};
class ConnectTunnelTest : public quiche::test::QuicheTest {
public:
void SetUp() override {
#if defined(_WIN32)
WSADATA wsa_data;
const WORD version_required = MAKEWORD(2, 2);
ASSERT_EQ(WSAStartup(version_required, &wsa_data), 0);
#endif
auto socket = std::make_unique<StrictMock<MockSocket>>();
socket_ = socket.get();
ON_CALL(socket_factory_,
CreateTcpClientSocket(
AnyOf(QuicSocketAddress(TestLoopback4(), kAcceptablePort),
QuicSocketAddress(TestLoopback6(), kAcceptablePort)),
_, _, &tunnel_))
.WillByDefault(Return(ByMove(std::move(socket))));
}
protected:
static constexpr absl::string_view kAcceptableDestination = "localhost";
static constexpr uint16_t kAcceptablePort = 977;
StrictMock<MockRequestHandler> request_handler_;
NiceMock<MockSocketFactory> socket_factory_;
StrictMock<MockSocket>* socket_;
ConnectTunnel tunnel_{
&request_handler_,
&socket_factory_,
/*acceptable_destinations=*/
{{std::string(kAcceptableDestination), kAcceptablePort},
{TestLoopback4().ToString(), kAcceptablePort},
{absl::StrCat("[", TestLoopback6().ToString(), "]"), kAcceptablePort}}};
};
TEST_F(ConnectTunnelTest, OpenTunnel) {
EXPECT_CALL(*socket_, ConnectBlocking()).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*socket_, ReceiveAsync(Gt(0)));
EXPECT_CALL(*socket_, Disconnect()).WillOnce(InvokeWithoutArgs([this]() {
tunnel_.ReceiveComplete(absl::CancelledError());
}));
spdy::Http2HeaderBlock expected_response_headers;
expected_response_headers[":status"] = "200";
QuicBackendResponse expected_response;
expected_response.set_headers(std::move(expected_response_headers));
expected_response.set_response_type(QuicBackendResponse::INCOMPLETE_RESPONSE);
EXPECT_CALL(request_handler_,
OnResponseBackendComplete(
AllOf(Property(&QuicBackendResponse::response_type,
QuicBackendResponse::INCOMPLETE_RESPONSE),
Property(&QuicBackendResponse::headers,
ElementsAre(Pair(":status", "200"))),
Property(&QuicBackendResponse::trailers, IsEmpty()),
Property(&QuicBackendResponse::body, IsEmpty()))));
spdy::Http2HeaderBlock request_headers;
request_headers[":method"] = "CONNECT";
request_headers[":authority"] =
absl::StrCat(kAcceptableDestination, ":", kAcceptablePort);
tunnel_.OpenTunnel(request_headers);
EXPECT_TRUE(tunnel_.IsConnectedToDestination());
tunnel_.OnClientStreamClose();
EXPECT_FALSE(tunnel_.IsConnectedToDestination());
}
TEST_F(ConnectTunnelTest, OpenTunnelToIpv4LiteralDestination) {
EXPECT_CALL(*socket_, ConnectBlocking()).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*socket_, ReceiveAsync(Gt(0)));
EXPECT_CALL(*socket_, Disconnect()).WillOnce(InvokeWithoutArgs([this]() {
tunnel_.ReceiveComplete(absl::CancelledError());
}));
spdy::Http2HeaderBlock expected_response_headers;
expected_response_headers[":status"] = "200";
QuicBackendResponse expected_response;
expected_response.set_headers(std::move(expected_response_headers));
expected_response.set_response_type(QuicBackendResponse::INCOMPLETE_RESPONSE);
EXPECT_CALL(request_handler_,
OnResponseBackendComplete(
AllOf(Property(&QuicBackendResponse::response_type,
QuicBackendResponse::INCOMPLETE_RESPONSE),
Property(&QuicBackendResponse::headers,
ElementsAre(Pair(":status", "200"))),
Property(&QuicBackendResponse::trailers, IsEmpty()),
Property(&QuicBackendResponse::body, IsEmpty()))));
spdy::Http2HeaderBlock request_headers;
request_headers[":method"] = "CONNECT";
request_headers[":authority"] =
absl::StrCat(TestLoopback4().ToString(), ":", kAcceptablePort);
tunnel_.OpenTunnel(request_headers);
EXPECT_TRUE(tunnel_.IsConnectedToDestination());
tunnel_.OnClientStreamClose();
EXPECT_FALSE(tunnel_.IsConnectedToDestination());
}
TEST_F(ConnectTunnelTest, OpenTunnelToIpv6LiteralDestination) {
EXPECT_CALL(*socket_, ConnectBlocking()).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*socket_, ReceiveAsync(Gt(0)));
EXPECT_CALL(*socket_, Disconnect()).WillOnce(InvokeWithoutArgs([this]() {
tunnel_.ReceiveComplete(absl::CancelledError());
}));
spdy::Http2HeaderBlock expected_response_headers;
expected_response_headers[":status"] = "200";
QuicBackendResponse expected_response;
expected_response.set_headers(std::move(expected_response_headers));
expected_response.set_response_type(QuicBackendResponse::INCOMPLETE_RESPONSE);
EXPECT_CALL(request_handler_,
OnResponseBackendComplete(
AllOf(Property(&QuicBackendResponse::response_type,
QuicBackendResponse::INCOMPLETE_RESPONSE),
Property(&QuicBackendResponse::headers,
ElementsAre(Pair(":status", "200"))),
Property(&QuicBackendResponse::trailers, IsEmpty()),
Property(&QuicBackendResponse::body, IsEmpty()))));
spdy::Http2HeaderBlock request_headers;
request_headers[":method"] = "CONNECT";
request_headers[":authority"] =
absl::StrCat("[", TestLoopback6().ToString(), "]:", kAcceptablePort);
tunnel_.OpenTunnel(request_headers);
EXPECT_TRUE(tunnel_.IsConnectedToDestination());
tunnel_.OnClientStreamClose();
EXPECT_FALSE(tunnel_.IsConnectedToDestination());
}
TEST_F(ConnectTunnelTest, OpenTunnelWithMalformedRequest) {
EXPECT_CALL(request_handler_,
TerminateStreamWithError(Property(
&QuicResetStreamError::ietf_application_code,
static_cast<uint64_t>(QuicHttp3ErrorCode::MESSAGE_ERROR))));
spdy::Http2HeaderBlock request_headers;
request_headers[":method"] = "CONNECT";
// No ":authority" header.
tunnel_.OpenTunnel(request_headers);
EXPECT_FALSE(tunnel_.IsConnectedToDestination());
tunnel_.OnClientStreamClose();
}
TEST_F(ConnectTunnelTest, OpenTunnelWithUnacceptableDestination) {
EXPECT_CALL(
request_handler_,
TerminateStreamWithError(Property(
&QuicResetStreamError::ietf_application_code,
static_cast<uint64_t>(QuicHttp3ErrorCode::REQUEST_REJECTED))));
spdy::Http2HeaderBlock request_headers;
request_headers[":method"] = "CONNECT";
request_headers[":authority"] = "unacceptable.test:100";
tunnel_.OpenTunnel(request_headers);
EXPECT_FALSE(tunnel_.IsConnectedToDestination());
tunnel_.OnClientStreamClose();
}
TEST_F(ConnectTunnelTest, ReceiveFromDestination) {
static constexpr absl::string_view kData = "\x11\x22\x33\x44\x55";
EXPECT_CALL(*socket_, ConnectBlocking()).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*socket_, ReceiveAsync(Ge(kData.size()))).Times(2);
EXPECT_CALL(*socket_, Disconnect()).WillOnce(InvokeWithoutArgs([this]() {
tunnel_.ReceiveComplete(absl::CancelledError());
}));
EXPECT_CALL(request_handler_, OnResponseBackendComplete(_));
EXPECT_CALL(request_handler_, SendStreamData(kData, /*close_stream=*/false));
spdy::Http2HeaderBlock request_headers;
request_headers[":method"] = "CONNECT";
request_headers[":authority"] =
absl::StrCat(kAcceptableDestination, ":", kAcceptablePort);
tunnel_.OpenTunnel(request_headers);
// Simulate receiving `kData`.
tunnel_.ReceiveComplete(MemSliceFromString(kData));
tunnel_.OnClientStreamClose();
}
TEST_F(ConnectTunnelTest, SendToDestination) {
static constexpr absl::string_view kData = "\x11\x22\x33\x44\x55";
EXPECT_CALL(*socket_, ConnectBlocking()).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*socket_, ReceiveAsync(Gt(0)));
EXPECT_CALL(*socket_, SendBlocking(Matcher<std::string>(Eq(kData))))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*socket_, Disconnect()).WillOnce(InvokeWithoutArgs([this]() {
tunnel_.ReceiveComplete(absl::CancelledError());
}));
EXPECT_CALL(request_handler_, OnResponseBackendComplete(_));
spdy::Http2HeaderBlock request_headers;
request_headers[":method"] = "CONNECT";
request_headers[":authority"] =
absl::StrCat(kAcceptableDestination, ":", kAcceptablePort);
tunnel_.OpenTunnel(request_headers);
tunnel_.SendDataToDestination(kData);
tunnel_.OnClientStreamClose();
}
TEST_F(ConnectTunnelTest, DestinationDisconnect) {
EXPECT_CALL(*socket_, ConnectBlocking()).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*socket_, ReceiveAsync(Gt(0)));
EXPECT_CALL(*socket_, Disconnect());
EXPECT_CALL(request_handler_, OnResponseBackendComplete(_));
EXPECT_CALL(request_handler_, SendStreamData("", /*close_stream=*/true));
spdy::Http2HeaderBlock request_headers;
request_headers[":method"] = "CONNECT";
request_headers[":authority"] =
absl::StrCat(kAcceptableDestination, ":", kAcceptablePort);
tunnel_.OpenTunnel(request_headers);
// Simulate receiving empty data.
tunnel_.ReceiveComplete(quiche::QuicheMemSlice());
EXPECT_FALSE(tunnel_.IsConnectedToDestination());
tunnel_.OnClientStreamClose();
}
TEST_F(ConnectTunnelTest, DestinationTcpConnectionError) {
EXPECT_CALL(*socket_, ConnectBlocking()).WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(*socket_, ReceiveAsync(Gt(0)));
EXPECT_CALL(*socket_, Disconnect());
EXPECT_CALL(request_handler_, OnResponseBackendComplete(_));
EXPECT_CALL(request_handler_,
TerminateStreamWithError(Property(
&QuicResetStreamError::ietf_application_code,
static_cast<uint64_t>(QuicHttp3ErrorCode::CONNECT_ERROR))));
spdy::Http2HeaderBlock request_headers;
request_headers[":method"] = "CONNECT";
request_headers[":authority"] =
absl::StrCat(kAcceptableDestination, ":", kAcceptablePort);
tunnel_.OpenTunnel(request_headers);
// Simulate receving error.
tunnel_.ReceiveComplete(absl::UnknownError("error"));
tunnel_.OnClientStreamClose();
}
} // namespace
} // namespace quic::test