xref: /aosp_15_r20/external/cronet/net/quic/quic_proxy_datagram_client_socket.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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