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 <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/numbers.h"
17 #include "absl/strings/str_cat.h"
18 #include "absl/strings/str_split.h"
19 #include "absl/strings/string_view.h"
20 #include "absl/types/span.h"
21 #include "quiche/quic/core/quic_error_codes.h"
22 #include "quiche/quic/core/quic_server_id.h"
23 #include "quiche/quic/core/socket_factory.h"
24 #include "quiche/quic/platform/api/quic_socket_address.h"
25 #include "quiche/quic/tools/quic_backend_response.h"
26 #include "quiche/quic/tools/quic_name_lookup.h"
27 #include "quiche/quic/tools/quic_simple_server_backend.h"
28 #include "quiche/common/masque/connect_udp_datagram_payload.h"
29 #include "quiche/common/platform/api/quiche_googleurl.h"
30 #include "quiche/common/platform/api/quiche_logging.h"
31 #include "quiche/common/platform/api/quiche_mem_slice.h"
32 #include "quiche/common/platform/api/quiche_url_utils.h"
33 #include "quiche/common/structured_headers.h"
34 #include "quiche/spdy/core/http2_header_block.h"
35
36 namespace quic {
37
38 namespace structured_headers = quiche::structured_headers;
39
40 namespace {
41
42 // Arbitrarily chosen. No effort has been made to figure out an optimal size.
43 constexpr size_t kReadSize = 4 * 1024;
44
45 // Only support the default path
46 // ("/.well-known/masque/udp/{target_host}/{target_port}/")
ValidateAndParseTargetFromPath(absl::string_view path)47 std::optional<QuicServerId> ValidateAndParseTargetFromPath(
48 absl::string_view path) {
49 std::string canonicalized_path_str;
50 url::StdStringCanonOutput canon_output(&canonicalized_path_str);
51 url::Component path_component;
52 url::CanonicalizePath(path.data(), url::Component(0, path.size()),
53 &canon_output, &path_component);
54 if (!path_component.is_nonempty()) {
55 QUICHE_DVLOG(1) << "CONNECT-UDP request with non-canonicalizable path: "
56 << path;
57 return std::nullopt;
58 }
59 canon_output.Complete();
60 absl::string_view canonicalized_path =
61 absl::string_view(canonicalized_path_str)
62 .substr(path_component.begin, path_component.len);
63
64 std::vector<absl::string_view> path_split =
65 absl::StrSplit(canonicalized_path, '/');
66 if (path_split.size() != 7 || !path_split[0].empty() ||
67 path_split[1] != ".well-known" || path_split[2] != "masque" ||
68 path_split[3] != "udp" || path_split[4].empty() ||
69 path_split[5].empty() || !path_split[6].empty()) {
70 QUICHE_DVLOG(1) << "CONNECT-UDP request with bad path: "
71 << canonicalized_path;
72 return std::nullopt;
73 }
74
75 std::optional<std::string> decoded_host =
76 quiche::AsciiUrlDecode(path_split[4]);
77 if (!decoded_host.has_value()) {
78 QUICHE_DVLOG(1) << "CONNECT-UDP request with undecodable host: "
79 << path_split[4];
80 return std::nullopt;
81 }
82 // Empty host checked above after path split. Expect decoding to never result
83 // in an empty decoded host from non-empty encoded host.
84 QUICHE_DCHECK(!decoded_host->empty());
85
86 std::optional<std::string> decoded_port =
87 quiche::AsciiUrlDecode(path_split[5]);
88 if (!decoded_port.has_value()) {
89 QUICHE_DVLOG(1) << "CONNECT-UDP request with undecodable port: "
90 << path_split[5];
91 return std::nullopt;
92 }
93 // Empty port checked above after path split. Expect decoding to never result
94 // in an empty decoded port from non-empty encoded port.
95 QUICHE_DCHECK(!decoded_port->empty());
96
97 int parsed_port_number = url::ParsePort(
98 decoded_port->data(), url::Component(0, decoded_port->size()));
99 // Negative result is either invalid or unspecified, either of which is
100 // disallowed for this parse. Port 0 is technically valid but reserved and not
101 // really usable in practice, so easiest to just disallow it here.
102 if (parsed_port_number <= 0) {
103 QUICHE_DVLOG(1) << "CONNECT-UDP request with bad port: " << *decoded_port;
104 return std::nullopt;
105 }
106 // Expect url::ParsePort() to validate port is uint16_t and otherwise return
107 // negative number checked for above.
108 QUICHE_DCHECK_LE(parsed_port_number, std::numeric_limits<uint16_t>::max());
109
110 return QuicServerId(*decoded_host, static_cast<uint16_t>(parsed_port_number));
111 }
112
113 // Validate header expectations from RFC 9298, section 3.4.
ValidateHeadersAndGetTarget(const spdy::Http2HeaderBlock & request_headers)114 std::optional<QuicServerId> ValidateHeadersAndGetTarget(
115 const spdy::Http2HeaderBlock& request_headers) {
116 QUICHE_DCHECK(request_headers.contains(":method"));
117 QUICHE_DCHECK(request_headers.find(":method")->second == "CONNECT");
118 QUICHE_DCHECK(request_headers.contains(":protocol"));
119 QUICHE_DCHECK(request_headers.find(":protocol")->second == "connect-udp");
120
121 auto authority_it = request_headers.find(":authority");
122 if (authority_it == request_headers.end() || authority_it->second.empty()) {
123 QUICHE_DVLOG(1) << "CONNECT-UDP request missing authority";
124 return std::nullopt;
125 }
126 // For toy server simplicity, skip validating that the authority matches the
127 // current server.
128
129 auto scheme_it = request_headers.find(":scheme");
130 if (scheme_it == request_headers.end() || scheme_it->second.empty()) {
131 QUICHE_DVLOG(1) << "CONNECT-UDP request missing scheme";
132 return std::nullopt;
133 } else if (scheme_it->second != "https") {
134 QUICHE_DVLOG(1) << "CONNECT-UDP request contains unexpected scheme: "
135 << scheme_it->second;
136 return std::nullopt;
137 }
138
139 auto path_it = request_headers.find(":path");
140 if (path_it == request_headers.end() || path_it->second.empty()) {
141 QUICHE_DVLOG(1) << "CONNECT-UDP request missing path";
142 return std::nullopt;
143 }
144 std::optional<QuicServerId> target_server_id =
145 ValidateAndParseTargetFromPath(path_it->second);
146
147 return target_server_id;
148 }
149
ValidateTarget(const QuicServerId & target,const absl::flat_hash_set<QuicServerId> & acceptable_targets)150 bool ValidateTarget(
151 const QuicServerId& target,
152 const absl::flat_hash_set<QuicServerId>& acceptable_targets) {
153 if (acceptable_targets.contains(target)) {
154 return true;
155 }
156
157 QUICHE_DVLOG(1)
158 << "CONNECT-UDP request target is not an acceptable allow-listed target: "
159 << target.ToHostPortString();
160 return false;
161 }
162
163 } // namespace
164
ConnectUdpTunnel(QuicSimpleServerBackend::RequestHandler * client_stream_request_handler,SocketFactory * socket_factory,std::string server_label,absl::flat_hash_set<QuicServerId> acceptable_targets)165 ConnectUdpTunnel::ConnectUdpTunnel(
166 QuicSimpleServerBackend::RequestHandler* client_stream_request_handler,
167 SocketFactory* socket_factory, std::string server_label,
168 absl::flat_hash_set<QuicServerId> acceptable_targets)
169 : acceptable_targets_(std::move(acceptable_targets)),
170 socket_factory_(socket_factory),
171 server_label_(std::move(server_label)),
172 client_stream_request_handler_(client_stream_request_handler) {
173 QUICHE_DCHECK(client_stream_request_handler_);
174 QUICHE_DCHECK(socket_factory_);
175 QUICHE_DCHECK(!server_label_.empty());
176 }
177
~ConnectUdpTunnel()178 ConnectUdpTunnel::~ConnectUdpTunnel() {
179 // Expect client and target sides of tunnel to both be closed before
180 // destruction.
181 QUICHE_DCHECK(!IsTunnelOpenToTarget());
182 QUICHE_DCHECK(!receive_started_);
183 QUICHE_DCHECK(!datagram_visitor_registered_);
184 }
185
OpenTunnel(const spdy::Http2HeaderBlock & request_headers)186 void ConnectUdpTunnel::OpenTunnel(
187 const spdy::Http2HeaderBlock& request_headers) {
188 QUICHE_DCHECK(!IsTunnelOpenToTarget());
189
190 std::optional<QuicServerId> target =
191 ValidateHeadersAndGetTarget(request_headers);
192 if (!target.has_value()) {
193 // Malformed request.
194 TerminateClientStream(
195 "invalid request headers",
196 QuicResetStreamError::FromIetf(QuicHttp3ErrorCode::MESSAGE_ERROR));
197 return;
198 }
199
200 if (!ValidateTarget(*target, acceptable_targets_)) {
201 SendErrorResponse("403", "destination_ip_prohibited",
202 "disallowed proxy target");
203 return;
204 }
205
206 // TODO(ericorth): Validate that the IP address doesn't fall into diallowed
207 // ranges per RFC 9298, Section 7.
208 QuicSocketAddress address = tools::LookupAddress(AF_UNSPEC, *target);
209 if (!address.IsInitialized()) {
210 SendErrorResponse("500", "dns_error", "host resolution error");
211 return;
212 }
213
214 target_socket_ = socket_factory_->CreateConnectingUdpClientSocket(
215 address,
216 /*receive_buffer_size=*/0,
217 /*send_buffer_size=*/0,
218 /*async_visitor=*/this);
219 QUICHE_DCHECK(target_socket_);
220
221 absl::Status connect_result = target_socket_->ConnectBlocking();
222 if (!connect_result.ok()) {
223 SendErrorResponse(
224 "502", "destination_ip_unroutable",
225 absl::StrCat("UDP socket error: ", connect_result.ToString()));
226 return;
227 }
228
229 QUICHE_DVLOG(1) << "CONNECT-UDP tunnel opened from stream "
230 << client_stream_request_handler_->stream_id() << " to "
231 << target->ToHostPortString();
232
233 client_stream_request_handler_->GetStream()->RegisterHttp3DatagramVisitor(
234 this);
235 datagram_visitor_registered_ = true;
236
237 SendConnectResponse();
238 BeginAsyncReadFromTarget();
239 }
240
IsTunnelOpenToTarget() const241 bool ConnectUdpTunnel::IsTunnelOpenToTarget() const { return !!target_socket_; }
242
OnClientStreamClose()243 void ConnectUdpTunnel::OnClientStreamClose() {
244 QUICHE_CHECK(client_stream_request_handler_);
245
246 QUICHE_DVLOG(1) << "CONNECT-UDP stream "
247 << client_stream_request_handler_->stream_id() << " closed";
248
249 if (datagram_visitor_registered_) {
250 client_stream_request_handler_->GetStream()
251 ->UnregisterHttp3DatagramVisitor();
252 datagram_visitor_registered_ = false;
253 }
254 client_stream_request_handler_ = nullptr;
255
256 if (IsTunnelOpenToTarget()) {
257 target_socket_->Disconnect();
258 }
259
260 // Clear socket pointer.
261 target_socket_.reset();
262 }
263
ConnectComplete(absl::Status)264 void ConnectUdpTunnel::ConnectComplete(absl::Status /*status*/) {
265 // Async connect not expected.
266 QUICHE_NOTREACHED();
267 }
268
ReceiveComplete(absl::StatusOr<quiche::QuicheMemSlice> data)269 void ConnectUdpTunnel::ReceiveComplete(
270 absl::StatusOr<quiche::QuicheMemSlice> data) {
271 QUICHE_DCHECK(IsTunnelOpenToTarget());
272 QUICHE_DCHECK(receive_started_);
273
274 receive_started_ = false;
275
276 if (!data.ok()) {
277 if (client_stream_request_handler_) {
278 QUICHE_LOG(WARNING) << "Error receiving CONNECT-UDP data from target: "
279 << data.status();
280 } else {
281 // This typically just means a receive operation was cancelled on calling
282 // target_socket_->Disconnect().
283 QUICHE_DVLOG(1) << "Error receiving CONNECT-UDP data from target after "
284 "stream already closed.";
285 }
286 return;
287 }
288
289 QUICHE_DCHECK(client_stream_request_handler_);
290 quiche::ConnectUdpDatagramUdpPacketPayload payload(data->AsStringView());
291 client_stream_request_handler_->GetStream()->SendHttp3Datagram(
292 payload.Serialize());
293
294 BeginAsyncReadFromTarget();
295 }
296
SendComplete(absl::Status)297 void ConnectUdpTunnel::SendComplete(absl::Status /*status*/) {
298 // Async send not expected.
299 QUICHE_NOTREACHED();
300 }
301
OnHttp3Datagram(QuicStreamId stream_id,absl::string_view payload)302 void ConnectUdpTunnel::OnHttp3Datagram(QuicStreamId stream_id,
303 absl::string_view payload) {
304 QUICHE_DCHECK(IsTunnelOpenToTarget());
305 QUICHE_DCHECK_EQ(stream_id, client_stream_request_handler_->stream_id());
306 QUICHE_DCHECK(!payload.empty());
307
308 std::unique_ptr<quiche::ConnectUdpDatagramPayload> parsed_payload =
309 quiche::ConnectUdpDatagramPayload::Parse(payload);
310 if (!parsed_payload) {
311 QUICHE_DVLOG(1) << "Ignoring HTTP Datagram payload, due to inability to "
312 "parse as CONNECT-UDP payload.";
313 return;
314 }
315
316 switch (parsed_payload->GetType()) {
317 case quiche::ConnectUdpDatagramPayload::Type::kUdpPacket:
318 SendUdpPacketToTarget(parsed_payload->GetUdpProxyingPayload());
319 break;
320 case quiche::ConnectUdpDatagramPayload::Type::kUnknown:
321 QUICHE_DVLOG(1)
322 << "Ignoring HTTP Datagram payload with unrecognized context ID.";
323 }
324 }
325
BeginAsyncReadFromTarget()326 void ConnectUdpTunnel::BeginAsyncReadFromTarget() {
327 QUICHE_DCHECK(IsTunnelOpenToTarget());
328 QUICHE_DCHECK(client_stream_request_handler_);
329 QUICHE_DCHECK(!receive_started_);
330
331 receive_started_ = true;
332 target_socket_->ReceiveAsync(kReadSize);
333 }
334
SendUdpPacketToTarget(absl::string_view packet)335 void ConnectUdpTunnel::SendUdpPacketToTarget(absl::string_view packet) {
336 absl::Status send_result = target_socket_->SendBlocking(std::string(packet));
337 if (!send_result.ok()) {
338 QUICHE_LOG(WARNING) << "Error sending CONNECT-UDP datagram to target: "
339 << send_result;
340 }
341 }
342
SendConnectResponse()343 void ConnectUdpTunnel::SendConnectResponse() {
344 QUICHE_DCHECK(IsTunnelOpenToTarget());
345 QUICHE_DCHECK(client_stream_request_handler_);
346
347 spdy::Http2HeaderBlock response_headers;
348 response_headers[":status"] = "200";
349
350 std::optional<std::string> capsule_protocol_value =
351 structured_headers::SerializeItem(structured_headers::Item(true));
352 QUICHE_CHECK(capsule_protocol_value.has_value());
353 response_headers["Capsule-Protocol"] = *capsule_protocol_value;
354
355 QuicBackendResponse response;
356 response.set_headers(std::move(response_headers));
357 // Need to leave the stream open after sending the CONNECT response.
358 response.set_response_type(QuicBackendResponse::INCOMPLETE_RESPONSE);
359
360 client_stream_request_handler_->OnResponseBackendComplete(&response);
361 }
362
SendErrorResponse(absl::string_view status,absl::string_view proxy_status_error,absl::string_view error_details)363 void ConnectUdpTunnel::SendErrorResponse(absl::string_view status,
364 absl::string_view proxy_status_error,
365 absl::string_view error_details) {
366 QUICHE_DCHECK(!status.empty());
367 QUICHE_DCHECK(!proxy_status_error.empty());
368 QUICHE_DCHECK(!error_details.empty());
369 QUICHE_DCHECK(client_stream_request_handler_);
370
371 #ifndef NDEBUG
372 // Expect a valid status code (number, 100 to 599 inclusive) and not a
373 // Successful code (200 to 299 inclusive).
374 int status_num = 0;
375 bool is_num = absl::SimpleAtoi(status, &status_num);
376 QUICHE_DCHECK(is_num);
377 QUICHE_DCHECK_GE(status_num, 100);
378 QUICHE_DCHECK_LT(status_num, 600);
379 QUICHE_DCHECK(status_num < 200 || status_num >= 300);
380 #endif // !NDEBUG
381
382 spdy::Http2HeaderBlock headers;
383 headers[":status"] = status;
384
385 structured_headers::Item proxy_status_item(server_label_);
386 structured_headers::Item proxy_status_error_item(
387 std::string{proxy_status_error});
388 structured_headers::Item proxy_status_details_item(
389 std::string{error_details});
390 structured_headers::ParameterizedMember proxy_status_member(
391 std::move(proxy_status_item),
392 {{"error", std::move(proxy_status_error_item)},
393 {"details", std::move(proxy_status_details_item)}});
394 std::optional<std::string> proxy_status_value =
395 structured_headers::SerializeList({proxy_status_member});
396 QUICHE_CHECK(proxy_status_value.has_value());
397 headers["Proxy-Status"] = *proxy_status_value;
398
399 QuicBackendResponse response;
400 response.set_headers(std::move(headers));
401
402 client_stream_request_handler_->OnResponseBackendComplete(&response);
403 }
404
TerminateClientStream(absl::string_view error_description,QuicResetStreamError error_code)405 void ConnectUdpTunnel::TerminateClientStream(
406 absl::string_view error_description, QuicResetStreamError error_code) {
407 QUICHE_DCHECK(client_stream_request_handler_);
408
409 std::string error_description_str =
410 error_description.empty() ? ""
411 : absl::StrCat(" due to ", error_description);
412 QUICHE_DVLOG(1) << "Terminating CONNECT stream "
413 << client_stream_request_handler_->stream_id()
414 << " with error code " << error_code.ietf_application_code()
415 << error_description_str;
416
417 client_stream_request_handler_->TerminateStreamWithError(error_code);
418 }
419
420 } // namespace quic
421