1 // Copyright 2024 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 "net/quic/quic_proxy_datagram_client_socket.h"
6
7 #include "base/functional/bind.h"
8 #include "base/functional/callback_helpers.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/values.h"
11 #include "net/base/net_errors.h"
12 #include "net/base/proxy_chain.h"
13 #include "net/base/proxy_delegate.h"
14 #include "net/http/http_log_util.h"
15 #include "net/http/http_response_headers.h"
16 #include "net/log/net_log_source.h"
17 #include "net/log/net_log_source_type.h"
18 #include "net/spdy/spdy_http_utils.h"
19
20 namespace net {
21
QuicProxyDatagramClientSocket(const GURL & url,const ProxyChain & proxy_chain,const std::string & user_agent,const NetLogWithSource & source_net_log,ProxyDelegate * proxy_delegate)22 QuicProxyDatagramClientSocket::QuicProxyDatagramClientSocket(
23 const GURL& url,
24 const ProxyChain& proxy_chain,
25 const std::string& user_agent,
26 const NetLogWithSource& source_net_log,
27 ProxyDelegate* proxy_delegate)
28 : url_(url),
29 proxy_chain_(proxy_chain),
30 proxy_delegate_(proxy_delegate),
31 user_agent_(user_agent),
32 net_log_(NetLogWithSource::Make(
33 source_net_log.net_log(),
34 NetLogSourceType::QUIC_PROXY_DATAGRAM_CLIENT_SOCKET)) {
35 CHECK_GE(proxy_chain.length(), 1u);
36 request_.method = "CONNECT";
37 request_.url = url_;
38
39 net_log_.BeginEventReferencingSource(NetLogEventType::SOCKET_ALIVE,
40 source_net_log.source());
41 }
42
~QuicProxyDatagramClientSocket()43 QuicProxyDatagramClientSocket::~QuicProxyDatagramClientSocket() {
44 Close();
45 net_log_.EndEvent(NetLogEventType::SOCKET_ALIVE);
46 }
47
GetConnectResponseInfo() const48 const HttpResponseInfo* QuicProxyDatagramClientSocket::GetConnectResponseInfo()
49 const {
50 return response_.headers.get() ? &response_ : nullptr;
51 }
52
IsConnected() const53 bool QuicProxyDatagramClientSocket::IsConnected() const {
54 return next_state_ == STATE_CONNECT_COMPLETE && stream_handle_->IsOpen();
55 }
56
ConnectViaStream(const IPEndPoint & local_address,const IPEndPoint & proxy_peer_address,std::unique_ptr<QuicChromiumClientStream::Handle> stream,CompletionOnceCallback callback)57 int QuicProxyDatagramClientSocket::ConnectViaStream(
58 const IPEndPoint& local_address,
59 const IPEndPoint& proxy_peer_address,
60 std::unique_ptr<QuicChromiumClientStream::Handle> stream,
61 CompletionOnceCallback callback) {
62 DCHECK(connect_callback_.is_null());
63
64 local_address_ = local_address;
65 proxy_peer_address_ = proxy_peer_address;
66 stream_handle_ = std::move(stream);
67
68 if (!stream_handle_->IsOpen()) {
69 return ERR_CONNECTION_CLOSED;
70 }
71
72 // Register stream to receive HTTP/3 datagrams.
73 stream_handle_->RegisterHttp3DatagramVisitor(this);
74 datagram_visitor_registered_ = true;
75
76 DCHECK_EQ(STATE_DISCONNECTED, next_state_);
77 next_state_ = STATE_SEND_REQUEST;
78
79 int rv = DoLoop(OK);
80 if (rv == ERR_IO_PENDING) {
81 connect_callback_ = std::move(callback);
82 }
83 return rv;
84 }
85
Connect(const IPEndPoint & address)86 int QuicProxyDatagramClientSocket::Connect(const IPEndPoint& address) {
87 NOTREACHED();
88 return ERR_NOT_IMPLEMENTED;
89 }
90
ConnectAsync(const IPEndPoint & address,CompletionOnceCallback callback)91 int QuicProxyDatagramClientSocket::ConnectAsync(
92 const IPEndPoint& address,
93 CompletionOnceCallback callback) {
94 NOTREACHED();
95 return ERR_NOT_IMPLEMENTED;
96 }
97
ConnectUsingDefaultNetworkAsync(const IPEndPoint & address,CompletionOnceCallback callback)98 int QuicProxyDatagramClientSocket::ConnectUsingDefaultNetworkAsync(
99 const IPEndPoint& address,
100 CompletionOnceCallback callback) {
101 NOTREACHED();
102 return ERR_NOT_IMPLEMENTED;
103 }
104
ConnectUsingNetwork(handles::NetworkHandle network,const IPEndPoint & address)105 int QuicProxyDatagramClientSocket::ConnectUsingNetwork(
106 handles::NetworkHandle network,
107 const IPEndPoint& address) {
108 NOTREACHED();
109 return ERR_NOT_IMPLEMENTED;
110 }
111
ConnectUsingDefaultNetwork(const IPEndPoint & address)112 int QuicProxyDatagramClientSocket::ConnectUsingDefaultNetwork(
113 const IPEndPoint& address) {
114 NOTREACHED();
115 return ERR_NOT_IMPLEMENTED;
116 }
117
ConnectUsingNetworkAsync(handles::NetworkHandle network,const IPEndPoint & address,CompletionOnceCallback callback)118 int QuicProxyDatagramClientSocket::ConnectUsingNetworkAsync(
119 handles::NetworkHandle network,
120 const IPEndPoint& address,
121 CompletionOnceCallback callback) {
122 NOTREACHED();
123 return ERR_NOT_IMPLEMENTED;
124 }
125
Close()126 void QuicProxyDatagramClientSocket::Close() {
127 connect_callback_.Reset();
128 read_callback_.Reset();
129 read_buf_len_ = 0;
130 read_buf_ = nullptr;
131
132 next_state_ = STATE_DISCONNECTED;
133
134 if (datagram_visitor_registered_) {
135 stream_handle_->UnregisterHttp3DatagramVisitor();
136 datagram_visitor_registered_ = false;
137 }
138 stream_handle_->Reset(quic::QUIC_STREAM_CANCELLED);
139 }
140
SetReceiveBufferSize(int32_t size)141 int QuicProxyDatagramClientSocket::SetReceiveBufferSize(int32_t size) {
142 return OK;
143 }
144
SetSendBufferSize(int32_t size)145 int QuicProxyDatagramClientSocket::SetSendBufferSize(int32_t size) {
146 return OK;
147 }
148
OnHttp3Datagram(quic::QuicStreamId stream_id,absl::string_view payload)149 void QuicProxyDatagramClientSocket::OnHttp3Datagram(
150 quic::QuicStreamId stream_id,
151 absl::string_view payload) {
152 DCHECK_EQ(stream_id, stream_handle_->id())
153 << "Received datagram for unexpected stream.";
154
155 quic::QuicDataReader reader(payload);
156 uint64_t context_id;
157 if (!reader.ReadVarInt62(&context_id)) {
158 DLOG(WARNING)
159 << "Ignoring HTTP Datagram payload. Failed to read context ID";
160 return;
161 }
162 if (context_id != 0) {
163 DLOG(WARNING) << "Ignoring HTTP Datagram with unrecognized context ID "
164 << context_id;
165 return;
166 }
167 absl::string_view http_payload = reader.ReadRemainingPayload();
168
169 // If there's a read callback, process the payload immediately.
170 if (read_callback_) {
171 int result;
172 int bytes_read = http_payload.size();
173 if (http_payload.size() > static_cast<std::size_t>(read_buf_len_)) {
174 result = ERR_MSG_TOO_BIG;
175 } else {
176 CHECK(read_buf_ != nullptr);
177 CHECK(read_buf_len_ > 0);
178
179 std::memcpy(read_buf_->data(), http_payload.data(), http_payload.size());
180 result = bytes_read;
181 }
182
183 read_buf_ = nullptr;
184 read_buf_len_ = 0;
185 std::move(read_callback_).Run(result);
186
187 } else {
188 base::UmaHistogramBoolean(kMaxQueueSizeHistogram,
189 datagrams_.size() >= kMaxDatagramQueueSize);
190 if (datagrams_.size() >= kMaxDatagramQueueSize) {
191 DLOG(WARNING) << "Dropping datagram because queue is full";
192 return;
193 }
194
195 // If no read callback, store the payload in the queue.
196 datagrams_.emplace(http_payload.data(), http_payload.size());
197 }
198 }
199
200 // Silently ignore unknown capsules.
OnUnknownCapsule(quic::QuicStreamId stream_id,const quiche::UnknownCapsule & capsule)201 void QuicProxyDatagramClientSocket::OnUnknownCapsule(
202 quic::QuicStreamId stream_id,
203 const quiche::UnknownCapsule& capsule) {}
204
205 // TODO(crbug.com/1524411) Implement method.
GetBoundNetwork() const206 handles::NetworkHandle QuicProxyDatagramClientSocket::GetBoundNetwork() const {
207 return handles::kInvalidNetworkHandle;
208 }
209
210 // TODO(crbug.com/1524411): Implement method.
ApplySocketTag(const SocketTag & tag)211 void QuicProxyDatagramClientSocket::ApplySocketTag(const SocketTag& tag) {}
212
SetMulticastInterface(uint32_t interface_index)213 int QuicProxyDatagramClientSocket::SetMulticastInterface(
214 uint32_t interface_index) {
215 NOTREACHED();
216 return ERR_NOT_IMPLEMENTED;
217 }
218
SetIOSNetworkServiceType(int ios_network_service_type)219 void QuicProxyDatagramClientSocket::SetIOSNetworkServiceType(
220 int ios_network_service_type) {}
221
GetPeerAddress(IPEndPoint * address) const222 int QuicProxyDatagramClientSocket::GetPeerAddress(IPEndPoint* address) const {
223 *address = proxy_peer_address_;
224 return OK;
225 }
226
GetLocalAddress(IPEndPoint * address) const227 int QuicProxyDatagramClientSocket::GetLocalAddress(IPEndPoint* address) const {
228 *address = local_address_;
229 return OK;
230 }
231
UseNonBlockingIO()232 void QuicProxyDatagramClientSocket::UseNonBlockingIO() {
233 NOTREACHED();
234 }
235
SetDoNotFragment()236 int QuicProxyDatagramClientSocket::SetDoNotFragment() {
237 NOTREACHED();
238 return ERR_NOT_IMPLEMENTED;
239 }
240
SetRecvTos()241 int QuicProxyDatagramClientSocket::SetRecvTos() {
242 NOTREACHED();
243 return ERR_NOT_IMPLEMENTED;
244 }
245
SetTos(net::DiffServCodePoint dscp,net::EcnCodePoint ecn)246 int QuicProxyDatagramClientSocket::SetTos(net::DiffServCodePoint dscp,
247 net::EcnCodePoint ecn) {
248 return OK;
249 }
250
SetMsgConfirm(bool confirm)251 void QuicProxyDatagramClientSocket::SetMsgConfirm(bool confirm) {
252 NOTREACHED();
253 }
254
NetLog() const255 const NetLogWithSource& QuicProxyDatagramClientSocket::NetLog() const {
256 return net_log_;
257 }
258
GetLastTos() const259 net::DscpAndEcn QuicProxyDatagramClientSocket::GetLastTos() const {
260 return {net::DSCP_DEFAULT, net::ECN_DEFAULT};
261 }
262
Read(IOBuffer * buf,int buf_len,CompletionOnceCallback callback)263 int QuicProxyDatagramClientSocket::Read(IOBuffer* buf,
264 int buf_len,
265 CompletionOnceCallback callback) {
266 CHECK(connect_callback_.is_null());
267 CHECK(read_callback_.is_null());
268 CHECK(!read_buf_);
269 CHECK(read_buf_len_ == 0);
270
271 if (next_state_ == STATE_DISCONNECTED) {
272 return ERR_SOCKET_NOT_CONNECTED;
273 }
274
275 // Return 0 if stream closed, signaling end-of-file or no more data.
276 if (!stream_handle_->IsOpen()) {
277 return 0;
278 }
279
280 // If there are datagrams available, attempt to read the first one into the
281 // buffer.
282 if (!datagrams_.empty()) {
283 auto& datagram = datagrams_.front();
284 int result;
285 int bytes_read = datagram.size();
286
287 if (datagram.size() > static_cast<std::size_t>(buf_len)) {
288 result = ERR_MSG_TOO_BIG;
289 } else {
290 std::memcpy(buf->data(), datagram.data(), datagram.size());
291 result = bytes_read;
292 }
293 datagrams_.pop();
294 return result;
295 }
296
297 // Save read callback so we can call it next time we receive a datagram.
298 read_callback_ = std::move(callback);
299 read_buf_ = buf;
300 read_buf_len_ = buf_len;
301 return ERR_IO_PENDING;
302 }
303
Write(IOBuffer * buf,int buf_len,CompletionOnceCallback callback,const NetworkTrafficAnnotationTag & traffic_annotation)304 int QuicProxyDatagramClientSocket::Write(
305 IOBuffer* buf,
306 int buf_len,
307 CompletionOnceCallback callback,
308 const NetworkTrafficAnnotationTag& traffic_annotation) {
309 DCHECK(connect_callback_.is_null());
310
311 if (next_state_ != STATE_CONNECT_COMPLETE) {
312 return ERR_SOCKET_NOT_CONNECTED;
313 }
314
315 net_log_.AddByteTransferEvent(NetLogEventType::SOCKET_BYTES_SENT, buf_len,
316 buf->data());
317
318 absl::string_view packet(buf->data(), buf_len);
319 int rv = stream_handle_->WriteConnectUdpPayload(packet);
320 if (rv == OK) {
321 return buf_len;
322 }
323 return rv;
324 }
325
OnIOComplete(int result)326 void QuicProxyDatagramClientSocket::OnIOComplete(int result) {
327 DCHECK_NE(STATE_DISCONNECTED, next_state_);
328 int rv = DoLoop(result);
329 if (rv != ERR_IO_PENDING) {
330 // Connect() finished (successfully or unsuccessfully).
331 DCHECK(!connect_callback_.is_null());
332 std::move(connect_callback_).Run(rv);
333 }
334 }
335
DoLoop(int last_io_result)336 int QuicProxyDatagramClientSocket::DoLoop(int last_io_result) {
337 DCHECK_NE(next_state_, STATE_DISCONNECTED);
338 int rv = last_io_result;
339 do {
340 State state = next_state_;
341 next_state_ = STATE_DISCONNECTED;
342 // TODO(crbug.com/326437102): Add support for generate auth token request
343 // and complete states.
344 switch (state) {
345 case STATE_SEND_REQUEST:
346 DCHECK_EQ(OK, rv);
347 net_log_.BeginEvent(
348 NetLogEventType::HTTP_TRANSACTION_TUNNEL_SEND_REQUEST);
349 rv = DoSendRequest();
350 break;
351 case STATE_SEND_REQUEST_COMPLETE:
352 net_log_.EndEventWithNetErrorCode(
353 NetLogEventType::HTTP_TRANSACTION_TUNNEL_SEND_REQUEST, rv);
354 rv = DoSendRequestComplete(rv);
355 break;
356 case STATE_READ_REPLY:
357 rv = DoReadReply();
358 break;
359 case STATE_READ_REPLY_COMPLETE:
360 rv = DoReadReplyComplete(rv);
361 net_log_.EndEventWithNetErrorCode(
362 NetLogEventType::HTTP_TRANSACTION_TUNNEL_READ_HEADERS, rv);
363 break;
364 default:
365 NOTREACHED() << "bad state";
366 rv = ERR_UNEXPECTED;
367 break;
368 }
369 } while (rv != ERR_IO_PENDING && next_state_ != STATE_DISCONNECTED &&
370 next_state_ != STATE_CONNECT_COMPLETE);
371 return rv;
372 }
373
DoSendRequest()374 int QuicProxyDatagramClientSocket::DoSendRequest() {
375 next_state_ = STATE_SEND_REQUEST_COMPLETE;
376
377 if (!url_.has_host()) {
378 return ERR_ADDRESS_INVALID;
379 }
380 std::string host = url_.host();
381 int port = url_.IntPort();
382 std::string host_and_port =
383 url_.has_port() ? base::StrCat({host, ":", base::NumberToString(port)})
384 : host;
385 request_.extra_headers.SetHeader(HttpRequestHeaders::kHost, host_and_port);
386
387 HttpRequestHeaders authorization_headers;
388 // TODO(crbug.com/326437102): Add Proxy-Authentication headers.
389 request_.extra_headers.MergeFrom(authorization_headers);
390
391 if (proxy_delegate_) {
392 HttpRequestHeaders proxy_delegate_headers;
393 proxy_delegate_->OnBeforeTunnelRequest(proxy_chain(), proxy_chain_index(),
394 &proxy_delegate_headers);
395 request_.extra_headers.MergeFrom(proxy_delegate_headers);
396 }
397
398 if (!user_agent_.empty()) {
399 request_.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent,
400 user_agent_);
401 }
402
403 request_.extra_headers.SetHeader("capsule-protocol", "?1");
404
405 // Generate a fake request line for logging purposes.
406 std::string request_line =
407 base::StringPrintf("CONNECT-UDP %s HTTP/3\r\n", url_.path().c_str());
408 NetLogRequestHeaders(net_log_,
409 NetLogEventType::HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
410 request_line, &request_.extra_headers);
411
412 spdy::Http2HeaderBlock headers;
413 CreateSpdyHeadersFromHttpRequestForExtendedConnect(
414 request_, /*priority=*/std::nullopt, "connect-udp",
415 request_.extra_headers, &headers);
416
417 return stream_handle_->WriteHeaders(std::move(headers), false, nullptr);
418 }
419
DoSendRequestComplete(int result)420 int QuicProxyDatagramClientSocket::DoSendRequestComplete(int result) {
421 if (result >= 0) {
422 // Wait for HEADERS frame from the server
423 next_state_ = STATE_READ_REPLY; // STATE_READ_REPLY_COMPLETE;
424 result = OK;
425 }
426
427 if (result >= 0 || result == ERR_IO_PENDING) {
428 // Emit extra event so can use the same events as HttpProxyClientSocket.
429 net_log_.BeginEvent(NetLogEventType::HTTP_TRANSACTION_TUNNEL_READ_HEADERS);
430 }
431
432 return result;
433 }
434
DoReadReply()435 int QuicProxyDatagramClientSocket::DoReadReply() {
436 next_state_ = STATE_READ_REPLY_COMPLETE;
437
438 int rv = stream_handle_->ReadInitialHeaders(
439 &response_header_block_,
440 base::BindOnce(
441 &QuicProxyDatagramClientSocket::OnReadResponseHeadersComplete,
442 weak_factory_.GetWeakPtr()));
443 if (rv == ERR_IO_PENDING) {
444 return ERR_IO_PENDING;
445 }
446 if (rv < 0) {
447 return rv;
448 }
449
450 return ProcessResponseHeaders(response_header_block_);
451 }
452
DoReadReplyComplete(int result)453 int QuicProxyDatagramClientSocket::DoReadReplyComplete(int result) {
454 if (result < 0) {
455 return result;
456 }
457
458 NetLogResponseHeaders(
459 net_log_, NetLogEventType::HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
460 response_.headers.get());
461
462 // TODO(crbug.com/326437102): Add case for Proxy Authentication.
463 if (proxy_delegate_) {
464 int rv = proxy_delegate_->OnTunnelHeadersReceived(
465 proxy_chain(), proxy_chain_index(), *response_.headers);
466 if (rv != OK) {
467 CHECK_NE(ERR_IO_PENDING, rv);
468 return rv;
469 }
470 }
471
472 switch (response_.headers->response_code()) {
473 case 200: // OK
474 next_state_ = STATE_CONNECT_COMPLETE;
475 return OK;
476
477 default:
478 // Ignore response to avoid letting the proxy impersonate the target
479 // server. (See http://crbug.com/137891.)
480 return ERR_TUNNEL_CONNECTION_FAILED;
481 }
482 }
483
OnReadResponseHeadersComplete(int result)484 void QuicProxyDatagramClientSocket::OnReadResponseHeadersComplete(int result) {
485 // Convert the now-populated spdy::Http2HeaderBlock to HttpResponseInfo
486 if (result > 0) {
487 result = ProcessResponseHeaders(response_header_block_);
488 }
489
490 if (result != ERR_IO_PENDING) {
491 OnIOComplete(result);
492 }
493 }
494
ProcessResponseHeaders(const spdy::Http2HeaderBlock & headers)495 int QuicProxyDatagramClientSocket::ProcessResponseHeaders(
496 const spdy::Http2HeaderBlock& headers) {
497 if (SpdyHeadersToHttpResponse(headers, &response_) != OK) {
498 DLOG(WARNING) << "Invalid headers";
499 return ERR_QUIC_PROTOCOL_ERROR;
500 }
501 return OK;
502 }
503
504 } // namespace net
505