xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/quic/tools/connect_udp_tunnel_test.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2022 The Chromium Authors
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 "quiche/quic/tools/connect_udp_tunnel.h"
6 
7 #include <memory>
8 #include <string>
9 
10 #include "absl/status/status.h"
11 #include "absl/status/statusor.h"
12 #include "absl/strings/str_cat.h"
13 #include "absl/strings/string_view.h"
14 #include "quiche/quic/core/connecting_client_socket.h"
15 #include "quiche/quic/core/http/quic_spdy_stream.h"
16 #include "quiche/quic/core/quic_connection_id.h"
17 #include "quiche/quic/core/quic_error_codes.h"
18 #include "quiche/quic/core/quic_types.h"
19 #include "quiche/quic/core/socket_factory.h"
20 #include "quiche/quic/platform/api/quic_socket_address.h"
21 #include "quiche/quic/platform/api/quic_test_loopback.h"
22 #include "quiche/quic/test_tools/quic_test_utils.h"
23 #include "quiche/quic/tools/quic_simple_server_backend.h"
24 #include "quiche/common/masque/connect_udp_datagram_payload.h"
25 #include "quiche/common/platform/api/quiche_googleurl.h"
26 #include "quiche/common/platform/api/quiche_mem_slice.h"
27 #include "quiche/common/platform/api/quiche_test.h"
28 #include "quiche/common/platform/api/quiche_url_utils.h"
29 
30 namespace quic::test {
31 namespace {
32 
33 using ::testing::_;
34 using ::testing::AnyOf;
35 using ::testing::Eq;
36 using ::testing::Ge;
37 using ::testing::Gt;
38 using ::testing::HasSubstr;
39 using ::testing::InvokeWithoutArgs;
40 using ::testing::IsEmpty;
41 using ::testing::Matcher;
42 using ::testing::NiceMock;
43 using ::testing::Pair;
44 using ::testing::Property;
45 using ::testing::Return;
46 using ::testing::StrictMock;
47 using ::testing::UnorderedElementsAre;
48 
49 constexpr QuicStreamId kStreamId = 100;
50 
51 class MockStream : public QuicSpdyStream {
52  public:
MockStream(QuicSpdySession * spdy_session)53   explicit MockStream(QuicSpdySession* spdy_session)
54       : QuicSpdyStream(kStreamId, spdy_session, BIDIRECTIONAL) {}
55 
OnBodyAvailable()56   void OnBodyAvailable() override {}
57 
58   MOCK_METHOD(MessageStatus, SendHttp3Datagram, (absl::string_view data),
59               (override));
60 };
61 
62 class MockRequestHandler : public QuicSimpleServerBackend::RequestHandler {
63  public:
connection_id() const64   QuicConnectionId connection_id() const override {
65     return TestConnectionId(41212);
66   }
stream_id() const67   QuicStreamId stream_id() const override { return kStreamId; }
peer_host() const68   std::string peer_host() const override { return "127.0.0.1"; }
69 
70   MOCK_METHOD(QuicSpdyStream*, GetStream, (), (override));
71   MOCK_METHOD(void, OnResponseBackendComplete,
72               (const QuicBackendResponse* response), (override));
73   MOCK_METHOD(void, SendStreamData, (absl::string_view data, bool close_stream),
74               (override));
75   MOCK_METHOD(void, TerminateStreamWithError, (QuicResetStreamError error),
76               (override));
77 };
78 
79 class MockSocketFactory : public SocketFactory {
80  public:
81   MOCK_METHOD(std::unique_ptr<ConnectingClientSocket>, CreateTcpClientSocket,
82               (const QuicSocketAddress& peer_address,
83                QuicByteCount receive_buffer_size,
84                QuicByteCount send_buffer_size,
85                ConnectingClientSocket::AsyncVisitor* async_visitor),
86               (override));
87   MOCK_METHOD(std::unique_ptr<ConnectingClientSocket>,
88               CreateConnectingUdpClientSocket,
89               (const QuicSocketAddress& peer_address,
90                QuicByteCount receive_buffer_size,
91                QuicByteCount send_buffer_size,
92                ConnectingClientSocket::AsyncVisitor* async_visitor),
93               (override));
94 };
95 
96 class MockSocket : public ConnectingClientSocket {
97  public:
98   MOCK_METHOD(absl::Status, ConnectBlocking, (), (override));
99   MOCK_METHOD(void, ConnectAsync, (), (override));
100   MOCK_METHOD(void, Disconnect, (), (override));
101   MOCK_METHOD(absl::StatusOr<QuicSocketAddress>, GetLocalAddress, (),
102               (override));
103   MOCK_METHOD(absl::StatusOr<quiche::QuicheMemSlice>, ReceiveBlocking,
104               (QuicByteCount max_size), (override));
105   MOCK_METHOD(void, ReceiveAsync, (QuicByteCount max_size), (override));
106   MOCK_METHOD(absl::Status, SendBlocking, (std::string data), (override));
107   MOCK_METHOD(absl::Status, SendBlocking, (quiche::QuicheMemSlice data),
108               (override));
109   MOCK_METHOD(void, SendAsync, (std::string data), (override));
110   MOCK_METHOD(void, SendAsync, (quiche::QuicheMemSlice data), (override));
111 };
112 
113 class ConnectUdpTunnelTest : public quiche::test::QuicheTest {
114  public:
SetUp()115   void SetUp() override {
116 #if defined(_WIN32)
117     WSADATA wsa_data;
118     const WORD version_required = MAKEWORD(2, 2);
119     ASSERT_EQ(WSAStartup(version_required, &wsa_data), 0);
120 #endif
121     auto socket = std::make_unique<StrictMock<MockSocket>>();
122     socket_ = socket.get();
123     ON_CALL(socket_factory_,
124             CreateConnectingUdpClientSocket(
125                 AnyOf(QuicSocketAddress(TestLoopback4(), kAcceptablePort),
126                       QuicSocketAddress(TestLoopback6(), kAcceptablePort)),
127                 _, _, &tunnel_))
128         .WillByDefault(Return(ByMove(std::move(socket))));
129 
130     EXPECT_CALL(request_handler_, GetStream()).WillRepeatedly(Return(&stream_));
131   }
132 
133  protected:
134   static constexpr absl::string_view kAcceptableTarget = "localhost";
135   static constexpr uint16_t kAcceptablePort = 977;
136 
137   NiceMock<MockQuicConnectionHelper> connection_helper_;
138   NiceMock<MockAlarmFactory> alarm_factory_;
139   NiceMock<MockQuicSpdySession> session_{new NiceMock<MockQuicConnection>(
140       &connection_helper_, &alarm_factory_, Perspective::IS_SERVER)};
141   StrictMock<MockStream> stream_{&session_};
142 
143   StrictMock<MockRequestHandler> request_handler_;
144   NiceMock<MockSocketFactory> socket_factory_;
145   StrictMock<MockSocket>* socket_;
146 
147   ConnectUdpTunnel tunnel_{
148       &request_handler_,
149       &socket_factory_,
150       "server_label",
151       /*acceptable_targets=*/
152       {{std::string(kAcceptableTarget), kAcceptablePort},
153        {TestLoopback4().ToString(), kAcceptablePort},
154        {absl::StrCat("[", TestLoopback6().ToString(), "]"), kAcceptablePort}}};
155 };
156 
TEST_F(ConnectUdpTunnelTest,OpenTunnel)157 TEST_F(ConnectUdpTunnelTest, OpenTunnel) {
158   EXPECT_CALL(*socket_, ConnectBlocking()).WillOnce(Return(absl::OkStatus()));
159   EXPECT_CALL(*socket_, ReceiveAsync(Gt(0)));
160   EXPECT_CALL(*socket_, Disconnect()).WillOnce(InvokeWithoutArgs([this]() {
161     tunnel_.ReceiveComplete(absl::CancelledError());
162   }));
163 
164   EXPECT_CALL(
165       request_handler_,
166       OnResponseBackendComplete(
167           AllOf(Property(&QuicBackendResponse::response_type,
168                          QuicBackendResponse::INCOMPLETE_RESPONSE),
169                 Property(&QuicBackendResponse::headers,
170                          UnorderedElementsAre(Pair(":status", "200"),
171                                               Pair("Capsule-Protocol", "?1"))),
172                 Property(&QuicBackendResponse::trailers, IsEmpty()),
173                 Property(&QuicBackendResponse::body, IsEmpty()))));
174 
175   spdy::Http2HeaderBlock request_headers;
176   request_headers[":method"] = "CONNECT";
177   request_headers[":protocol"] = "connect-udp";
178   request_headers[":authority"] = "proxy.test";
179   request_headers[":scheme"] = "https";
180   request_headers[":path"] = absl::StrCat(
181       "/.well-known/masque/udp/", kAcceptableTarget, "/", kAcceptablePort, "/");
182 
183   tunnel_.OpenTunnel(request_headers);
184   EXPECT_TRUE(tunnel_.IsTunnelOpenToTarget());
185   tunnel_.OnClientStreamClose();
186   EXPECT_FALSE(tunnel_.IsTunnelOpenToTarget());
187 }
188 
TEST_F(ConnectUdpTunnelTest,OpenTunnelToIpv4LiteralTarget)189 TEST_F(ConnectUdpTunnelTest, OpenTunnelToIpv4LiteralTarget) {
190   EXPECT_CALL(*socket_, ConnectBlocking()).WillOnce(Return(absl::OkStatus()));
191   EXPECT_CALL(*socket_, ReceiveAsync(Gt(0)));
192   EXPECT_CALL(*socket_, Disconnect()).WillOnce(InvokeWithoutArgs([this]() {
193     tunnel_.ReceiveComplete(absl::CancelledError());
194   }));
195 
196   EXPECT_CALL(
197       request_handler_,
198       OnResponseBackendComplete(
199           AllOf(Property(&QuicBackendResponse::response_type,
200                          QuicBackendResponse::INCOMPLETE_RESPONSE),
201                 Property(&QuicBackendResponse::headers,
202                          UnorderedElementsAre(Pair(":status", "200"),
203                                               Pair("Capsule-Protocol", "?1"))),
204                 Property(&QuicBackendResponse::trailers, IsEmpty()),
205                 Property(&QuicBackendResponse::body, IsEmpty()))));
206 
207   spdy::Http2HeaderBlock request_headers;
208   request_headers[":method"] = "CONNECT";
209   request_headers[":protocol"] = "connect-udp";
210   request_headers[":authority"] = "proxy.test";
211   request_headers[":scheme"] = "https";
212   request_headers[":path"] =
213       absl::StrCat("/.well-known/masque/udp/", TestLoopback4().ToString(), "/",
214                    kAcceptablePort, "/");
215 
216   tunnel_.OpenTunnel(request_headers);
217   EXPECT_TRUE(tunnel_.IsTunnelOpenToTarget());
218   tunnel_.OnClientStreamClose();
219   EXPECT_FALSE(tunnel_.IsTunnelOpenToTarget());
220 }
221 
TEST_F(ConnectUdpTunnelTest,OpenTunnelToIpv6LiteralTarget)222 TEST_F(ConnectUdpTunnelTest, OpenTunnelToIpv6LiteralTarget) {
223   EXPECT_CALL(*socket_, ConnectBlocking()).WillOnce(Return(absl::OkStatus()));
224   EXPECT_CALL(*socket_, ReceiveAsync(Gt(0)));
225   EXPECT_CALL(*socket_, Disconnect()).WillOnce(InvokeWithoutArgs([this]() {
226     tunnel_.ReceiveComplete(absl::CancelledError());
227   }));
228 
229   EXPECT_CALL(
230       request_handler_,
231       OnResponseBackendComplete(
232           AllOf(Property(&QuicBackendResponse::response_type,
233                          QuicBackendResponse::INCOMPLETE_RESPONSE),
234                 Property(&QuicBackendResponse::headers,
235                          UnorderedElementsAre(Pair(":status", "200"),
236                                               Pair("Capsule-Protocol", "?1"))),
237                 Property(&QuicBackendResponse::trailers, IsEmpty()),
238                 Property(&QuicBackendResponse::body, IsEmpty()))));
239 
240   std::string path;
241   ASSERT_TRUE(quiche::ExpandURITemplate(
242       "/.well-known/masque/udp/{target_host}/{target_port}/",
243       {{"target_host", absl::StrCat("[", TestLoopback6().ToString(), "]")},
244        {"target_port", absl::StrCat(kAcceptablePort)}},
245       &path));
246 
247   spdy::Http2HeaderBlock request_headers;
248   request_headers[":method"] = "CONNECT";
249   request_headers[":protocol"] = "connect-udp";
250   request_headers[":authority"] = "proxy.test";
251   request_headers[":scheme"] = "https";
252   request_headers[":path"] = path;
253 
254   tunnel_.OpenTunnel(request_headers);
255   EXPECT_TRUE(tunnel_.IsTunnelOpenToTarget());
256   tunnel_.OnClientStreamClose();
257   EXPECT_FALSE(tunnel_.IsTunnelOpenToTarget());
258 }
259 
TEST_F(ConnectUdpTunnelTest,OpenTunnelWithMalformedRequest)260 TEST_F(ConnectUdpTunnelTest, OpenTunnelWithMalformedRequest) {
261   EXPECT_CALL(request_handler_,
262               TerminateStreamWithError(Property(
263                   &QuicResetStreamError::ietf_application_code,
264                   static_cast<uint64_t>(QuicHttp3ErrorCode::MESSAGE_ERROR))));
265 
266   spdy::Http2HeaderBlock request_headers;
267   request_headers[":method"] = "CONNECT";
268   request_headers[":protocol"] = "connect-udp";
269   request_headers[":authority"] = "proxy.test";
270   request_headers[":scheme"] = "https";
271   // No ":path" header.
272 
273   tunnel_.OpenTunnel(request_headers);
274   EXPECT_FALSE(tunnel_.IsTunnelOpenToTarget());
275   tunnel_.OnClientStreamClose();
276 }
277 
TEST_F(ConnectUdpTunnelTest,OpenTunnelWithUnacceptableTarget)278 TEST_F(ConnectUdpTunnelTest, OpenTunnelWithUnacceptableTarget) {
279   EXPECT_CALL(request_handler_,
280               OnResponseBackendComplete(AllOf(
281                   Property(&QuicBackendResponse::response_type,
282                            QuicBackendResponse::REGULAR_RESPONSE),
283                   Property(&QuicBackendResponse::headers,
284                            UnorderedElementsAre(
285                                Pair(":status", "403"),
286                                Pair("Proxy-Status",
287                                     HasSubstr("destination_ip_prohibited")))),
288                   Property(&QuicBackendResponse::trailers, IsEmpty()))));
289 
290   spdy::Http2HeaderBlock request_headers;
291   request_headers[":method"] = "CONNECT";
292   request_headers[":protocol"] = "connect-udp";
293   request_headers[":authority"] = "proxy.test";
294   request_headers[":scheme"] = "https";
295   request_headers[":path"] = "/.well-known/masque/udp/unacceptable.test/100/";
296 
297   tunnel_.OpenTunnel(request_headers);
298   EXPECT_FALSE(tunnel_.IsTunnelOpenToTarget());
299   tunnel_.OnClientStreamClose();
300 }
301 
TEST_F(ConnectUdpTunnelTest,ReceiveFromTarget)302 TEST_F(ConnectUdpTunnelTest, ReceiveFromTarget) {
303   static constexpr absl::string_view kData = "\x11\x22\x33\x44\x55";
304 
305   EXPECT_CALL(*socket_, ConnectBlocking()).WillOnce(Return(absl::OkStatus()));
306   EXPECT_CALL(*socket_, ReceiveAsync(Ge(kData.size()))).Times(2);
307   EXPECT_CALL(*socket_, Disconnect()).WillOnce(InvokeWithoutArgs([this]() {
308     tunnel_.ReceiveComplete(absl::CancelledError());
309   }));
310 
311   EXPECT_CALL(request_handler_, OnResponseBackendComplete(_));
312 
313   EXPECT_CALL(
314       stream_,
315       SendHttp3Datagram(
316           quiche::ConnectUdpDatagramUdpPacketPayload(kData).Serialize()))
317       .WillOnce(Return(MESSAGE_STATUS_SUCCESS));
318 
319   spdy::Http2HeaderBlock request_headers;
320   request_headers[":method"] = "CONNECT";
321   request_headers[":protocol"] = "connect-udp";
322   request_headers[":authority"] = "proxy.test";
323   request_headers[":scheme"] = "https";
324   request_headers[":path"] = absl::StrCat(
325       "/.well-known/masque/udp/", kAcceptableTarget, "/", kAcceptablePort, "/");
326 
327   tunnel_.OpenTunnel(request_headers);
328 
329   // Simulate receiving `kData`.
330   tunnel_.ReceiveComplete(MemSliceFromString(kData));
331 
332   tunnel_.OnClientStreamClose();
333 }
334 
TEST_F(ConnectUdpTunnelTest,SendToTarget)335 TEST_F(ConnectUdpTunnelTest, SendToTarget) {
336   static constexpr absl::string_view kData = "\x11\x22\x33\x44\x55";
337 
338   EXPECT_CALL(*socket_, ConnectBlocking()).WillOnce(Return(absl::OkStatus()));
339   EXPECT_CALL(*socket_, ReceiveAsync(Gt(0)));
340   EXPECT_CALL(*socket_, SendBlocking(Matcher<std::string>(Eq(kData))))
341       .WillOnce(Return(absl::OkStatus()));
342   EXPECT_CALL(*socket_, Disconnect()).WillOnce(InvokeWithoutArgs([this]() {
343     tunnel_.ReceiveComplete(absl::CancelledError());
344   }));
345 
346   EXPECT_CALL(request_handler_, OnResponseBackendComplete(_));
347 
348   spdy::Http2HeaderBlock request_headers;
349   request_headers[":method"] = "CONNECT";
350   request_headers[":protocol"] = "connect-udp";
351   request_headers[":authority"] = "proxy.test";
352   request_headers[":scheme"] = "https";
353   request_headers[":path"] = absl::StrCat(
354       "/.well-known/masque/udp/", kAcceptableTarget, "/", kAcceptablePort, "/");
355 
356   tunnel_.OpenTunnel(request_headers);
357   tunnel_.OnHttp3Datagram(
358       kStreamId, quiche::ConnectUdpDatagramUdpPacketPayload(kData).Serialize());
359   tunnel_.OnClientStreamClose();
360 }
361 
362 }  // namespace
363 }  // namespace quic::test
364