xref: /aosp_15_r20/external/cronet/net/third_party/quiche/src/quiche/quic/tools/connect_tunnel.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2022 The Chromium Authors. All rights reserved.
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_tunnel.h"
6 
7 #include <cstdint>
8 #include <optional>
9 #include <string>
10 #include <utility>
11 #include <vector>
12 
13 #include "absl/container/flat_hash_set.h"
14 #include "absl/status/status.h"
15 #include "absl/status/statusor.h"
16 #include "absl/strings/str_cat.h"
17 #include "absl/strings/string_view.h"
18 #include "absl/types/span.h"
19 #include "quiche/quic/core/quic_error_codes.h"
20 #include "quiche/quic/core/quic_server_id.h"
21 #include "quiche/quic/core/socket_factory.h"
22 #include "quiche/quic/platform/api/quic_socket_address.h"
23 #include "quiche/quic/tools/quic_backend_response.h"
24 #include "quiche/quic/tools/quic_name_lookup.h"
25 #include "quiche/quic/tools/quic_simple_server_backend.h"
26 #include "quiche/common/platform/api/quiche_logging.h"
27 #include "quiche/common/platform/api/quiche_mem_slice.h"
28 #include "quiche/spdy/core/http2_header_block.h"
29 
30 namespace quic {
31 
32 namespace {
33 
34 // Arbitrarily chosen. No effort has been made to figure out an optimal size.
35 constexpr size_t kReadSize = 4 * 1024;
36 
ValidateHeadersAndGetAuthority(const spdy::Http2HeaderBlock & request_headers)37 std::optional<QuicServerId> ValidateHeadersAndGetAuthority(
38     const spdy::Http2HeaderBlock& request_headers) {
39   QUICHE_DCHECK(request_headers.contains(":method"));
40   QUICHE_DCHECK(request_headers.find(":method")->second == "CONNECT");
41   QUICHE_DCHECK(!request_headers.contains(":protocol"));
42 
43   auto scheme_it = request_headers.find(":scheme");
44   if (scheme_it != request_headers.end()) {
45     QUICHE_DVLOG(1) << "CONNECT request contains unexpected scheme: "
46                     << scheme_it->second;
47     return std::nullopt;
48   }
49 
50   auto path_it = request_headers.find(":path");
51   if (path_it != request_headers.end()) {
52     QUICHE_DVLOG(1) << "CONNECT request contains unexpected path: "
53                     << path_it->second;
54     return std::nullopt;
55   }
56 
57   auto authority_it = request_headers.find(":authority");
58   if (authority_it == request_headers.end() || authority_it->second.empty()) {
59     QUICHE_DVLOG(1) << "CONNECT request missing authority";
60     return std::nullopt;
61   }
62 
63   // A valid CONNECT authority must contain host and port and nothing else, per
64   // https://www.rfc-editor.org/rfc/rfc9110.html#name-connect. This matches the
65   // host and port parsing rules for QuicServerId.
66   std::optional<QuicServerId> server_id =
67       QuicServerId::ParseFromHostPortString(authority_it->second);
68   if (!server_id.has_value()) {
69     QUICHE_DVLOG(1) << "CONNECT request authority is malformed: "
70                     << authority_it->second;
71     return std::nullopt;
72   }
73 
74   return server_id;
75 }
76 
ValidateAuthority(const QuicServerId & authority,const absl::flat_hash_set<QuicServerId> & acceptable_destinations)77 bool ValidateAuthority(
78     const QuicServerId& authority,
79     const absl::flat_hash_set<QuicServerId>& acceptable_destinations) {
80   if (acceptable_destinations.contains(authority)) {
81     return true;
82   }
83 
84   QUICHE_DVLOG(1) << "CONNECT request authority: "
85                   << authority.ToHostPortString()
86                   << " is not an acceptable allow-listed destiation ";
87   return false;
88 }
89 
90 }  // namespace
91 
ConnectTunnel(QuicSimpleServerBackend::RequestHandler * client_stream_request_handler,SocketFactory * socket_factory,absl::flat_hash_set<QuicServerId> acceptable_destinations)92 ConnectTunnel::ConnectTunnel(
93     QuicSimpleServerBackend::RequestHandler* client_stream_request_handler,
94     SocketFactory* socket_factory,
95     absl::flat_hash_set<QuicServerId> acceptable_destinations)
96     : acceptable_destinations_(std::move(acceptable_destinations)),
97       socket_factory_(socket_factory),
98       client_stream_request_handler_(client_stream_request_handler) {
99   QUICHE_DCHECK(client_stream_request_handler_);
100   QUICHE_DCHECK(socket_factory_);
101 }
102 
~ConnectTunnel()103 ConnectTunnel::~ConnectTunnel() {
104   // Expect client and destination sides of tunnel to both be closed before
105   // destruction.
106   QUICHE_DCHECK_EQ(client_stream_request_handler_, nullptr);
107   QUICHE_DCHECK(!IsConnectedToDestination());
108   QUICHE_DCHECK(!receive_started_);
109 }
110 
OpenTunnel(const spdy::Http2HeaderBlock & request_headers)111 void ConnectTunnel::OpenTunnel(const spdy::Http2HeaderBlock& request_headers) {
112   QUICHE_DCHECK(!IsConnectedToDestination());
113 
114   std::optional<QuicServerId> authority =
115       ValidateHeadersAndGetAuthority(request_headers);
116   if (!authority.has_value()) {
117     TerminateClientStream(
118         "invalid request headers",
119         QuicResetStreamError::FromIetf(QuicHttp3ErrorCode::MESSAGE_ERROR));
120     return;
121   }
122 
123   if (!ValidateAuthority(authority.value(), acceptable_destinations_)) {
124     TerminateClientStream(
125         "disallowed request authority",
126         QuicResetStreamError::FromIetf(QuicHttp3ErrorCode::REQUEST_REJECTED));
127     return;
128   }
129 
130   QuicSocketAddress address =
131       tools::LookupAddress(AF_UNSPEC, authority.value());
132   if (!address.IsInitialized()) {
133     TerminateClientStream("host resolution error");
134     return;
135   }
136 
137   destination_socket_ =
138       socket_factory_->CreateTcpClientSocket(address,
139                                              /*receive_buffer_size=*/0,
140                                              /*send_buffer_size=*/0,
141                                              /*async_visitor=*/this);
142   QUICHE_DCHECK(destination_socket_);
143 
144   absl::Status connect_result = destination_socket_->ConnectBlocking();
145   if (!connect_result.ok()) {
146     TerminateClientStream(
147         "error connecting TCP socket to destination server: " +
148         connect_result.ToString());
149     return;
150   }
151 
152   QUICHE_DVLOG(1) << "CONNECT tunnel opened from stream "
153                   << client_stream_request_handler_->stream_id() << " to "
154                   << authority.value().ToHostPortString();
155 
156   SendConnectResponse();
157   BeginAsyncReadFromDestination();
158 }
159 
IsConnectedToDestination() const160 bool ConnectTunnel::IsConnectedToDestination() const {
161   return !!destination_socket_;
162 }
163 
SendDataToDestination(absl::string_view data)164 void ConnectTunnel::SendDataToDestination(absl::string_view data) {
165   QUICHE_DCHECK(IsConnectedToDestination());
166   QUICHE_DCHECK(!data.empty());
167 
168   absl::Status send_result =
169       destination_socket_->SendBlocking(std::string(data));
170   if (!send_result.ok()) {
171     TerminateClientStream("TCP error sending data to destination server: " +
172                           send_result.ToString());
173   }
174 }
175 
OnClientStreamClose()176 void ConnectTunnel::OnClientStreamClose() {
177   QUICHE_DCHECK(client_stream_request_handler_);
178 
179   QUICHE_DVLOG(1) << "CONNECT stream "
180                   << client_stream_request_handler_->stream_id() << " closed";
181 
182   client_stream_request_handler_ = nullptr;
183 
184   if (IsConnectedToDestination()) {
185     // TODO(ericorth): Consider just calling shutdown() on the socket rather
186     // than fully disconnecting in order to allow a graceful TCP FIN stream
187     // shutdown per
188     // https://www.rfc-editor.org/rfc/rfc9114.html#name-the-connect-method.
189     // Would require shutdown support in the socket library, and would need to
190     // deal with the tunnel/socket outliving the client stream.
191     destination_socket_->Disconnect();
192   }
193 
194   // Clear socket pointer.
195   destination_socket_.reset();
196 }
197 
ConnectComplete(absl::Status)198 void ConnectTunnel::ConnectComplete(absl::Status /*status*/) {
199   // Async connect not expected.
200   QUICHE_NOTREACHED();
201 }
202 
ReceiveComplete(absl::StatusOr<quiche::QuicheMemSlice> data)203 void ConnectTunnel::ReceiveComplete(
204     absl::StatusOr<quiche::QuicheMemSlice> data) {
205   QUICHE_DCHECK(IsConnectedToDestination());
206   QUICHE_DCHECK(receive_started_);
207 
208   receive_started_ = false;
209 
210   if (!data.ok()) {
211     if (client_stream_request_handler_) {
212       TerminateClientStream("TCP error receiving data from destination server");
213     } else {
214       // This typically just means a receive operation was cancelled on calling
215       // destination_socket_->Disconnect().
216       QUICHE_DVLOG(1) << "TCP error receiving data from destination server "
217                          "after stream already closed.";
218     }
219     return;
220   } else if (data.value().empty()) {
221     OnDestinationConnectionClosed();
222     return;
223   }
224 
225   QUICHE_DCHECK(client_stream_request_handler_);
226   client_stream_request_handler_->SendStreamData(data.value().AsStringView(),
227                                                  /*close_stream=*/false);
228 
229   BeginAsyncReadFromDestination();
230 }
231 
SendComplete(absl::Status)232 void ConnectTunnel::SendComplete(absl::Status /*status*/) {
233   // Async send not expected.
234   QUICHE_NOTREACHED();
235 }
236 
BeginAsyncReadFromDestination()237 void ConnectTunnel::BeginAsyncReadFromDestination() {
238   QUICHE_DCHECK(IsConnectedToDestination());
239   QUICHE_DCHECK(client_stream_request_handler_);
240   QUICHE_DCHECK(!receive_started_);
241 
242   receive_started_ = true;
243   destination_socket_->ReceiveAsync(kReadSize);
244 }
245 
OnDestinationConnectionClosed()246 void ConnectTunnel::OnDestinationConnectionClosed() {
247   QUICHE_DCHECK(IsConnectedToDestination());
248   QUICHE_DCHECK(client_stream_request_handler_);
249 
250   QUICHE_DVLOG(1) << "CONNECT stream "
251                   << client_stream_request_handler_->stream_id()
252                   << " destination connection closed";
253   destination_socket_->Disconnect();
254 
255   // Clear socket pointer.
256   destination_socket_.reset();
257 
258   // Extra check that nothing in the Disconnect could lead to terminating the
259   // stream.
260   QUICHE_DCHECK(client_stream_request_handler_);
261 
262   client_stream_request_handler_->SendStreamData("", /*close_stream=*/true);
263 }
264 
SendConnectResponse()265 void ConnectTunnel::SendConnectResponse() {
266   QUICHE_DCHECK(IsConnectedToDestination());
267   QUICHE_DCHECK(client_stream_request_handler_);
268 
269   spdy::Http2HeaderBlock response_headers;
270   response_headers[":status"] = "200";
271 
272   QuicBackendResponse response;
273   response.set_headers(std::move(response_headers));
274   // Need to leave the stream open after sending the CONNECT response.
275   response.set_response_type(QuicBackendResponse::INCOMPLETE_RESPONSE);
276 
277   client_stream_request_handler_->OnResponseBackendComplete(&response);
278 }
279 
TerminateClientStream(absl::string_view error_description,QuicResetStreamError error_code)280 void ConnectTunnel::TerminateClientStream(absl::string_view error_description,
281                                           QuicResetStreamError error_code) {
282   QUICHE_DCHECK(client_stream_request_handler_);
283 
284   std::string error_description_str =
285       error_description.empty() ? ""
286                                 : absl::StrCat(" due to ", error_description);
287   QUICHE_DVLOG(1) << "Terminating CONNECT stream "
288                   << client_stream_request_handler_->stream_id()
289                   << " with error code " << error_code.ietf_application_code()
290                   << error_description_str;
291 
292   client_stream_request_handler_->TerminateStreamWithError(error_code);
293 }
294 
295 }  // namespace quic
296