blob: 733c6cc7e45f843c3b78570d5493907b30bab18e [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/quic/quic_proxy_datagram_client_socket.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "net/base/net_errors.h"
#include "net/base/proxy_chain.h"
#include "net/base/proxy_delegate.h"
#include "net/http/http_log_util.h"
#include "net/http/http_response_headers.h"
#include "net/log/net_log_source.h"
#include "net/log/net_log_source_type.h"
#include "net/spdy/spdy_http_utils.h"
namespace net {
QuicProxyDatagramClientSocket::QuicProxyDatagramClientSocket(
const GURL& url,
const ProxyChain& proxy_chain,
const std::string& user_agent,
const NetLogWithSource& source_net_log,
ProxyDelegate* proxy_delegate)
: url_(url),
proxy_chain_(proxy_chain),
proxy_delegate_(proxy_delegate),
user_agent_(user_agent),
net_log_(NetLogWithSource::Make(
source_net_log.net_log(),
NetLogSourceType::QUIC_PROXY_DATAGRAM_CLIENT_SOCKET)) {
CHECK_GE(proxy_chain.length(), 1u);
request_.method = "CONNECT";
request_.url = url_;
net_log_.BeginEventReferencingSource(NetLogEventType::SOCKET_ALIVE,
source_net_log.source());
}
QuicProxyDatagramClientSocket::~QuicProxyDatagramClientSocket() {
Close();
net_log_.EndEvent(NetLogEventType::SOCKET_ALIVE);
}
const HttpResponseInfo* QuicProxyDatagramClientSocket::GetConnectResponseInfo()
const {
return response_.headers.get() ? &response_ : nullptr;
}
bool QuicProxyDatagramClientSocket::IsConnected() const {
return next_state_ == STATE_CONNECT_COMPLETE && stream_handle_->IsOpen();
}
int QuicProxyDatagramClientSocket::ConnectViaStream(
const IPEndPoint& local_address,
const IPEndPoint& proxy_peer_address,
std::unique_ptr<QuicChromiumClientStream::Handle> stream,
CompletionOnceCallback callback) {
DCHECK(connect_callback_.is_null());
local_address_ = local_address;
proxy_peer_address_ = proxy_peer_address;
stream_handle_ = std::move(stream);
if (!stream_handle_->IsOpen()) {
return ERR_CONNECTION_CLOSED;
}
// Register stream to receive HTTP/3 datagrams.
stream_handle_->RegisterHttp3DatagramVisitor(this);
datagram_visitor_registered_ = true;
DCHECK_EQ(STATE_DISCONNECTED, next_state_);
next_state_ = STATE_SEND_REQUEST;
int rv = DoLoop(OK);
if (rv == ERR_IO_PENDING) {
connect_callback_ = std::move(callback);
}
return rv;
}
int QuicProxyDatagramClientSocket::Connect(const IPEndPoint& address) {
NOTREACHED();
return ERR_NOT_IMPLEMENTED;
}
int QuicProxyDatagramClientSocket::ConnectAsync(
const IPEndPoint& address,
CompletionOnceCallback callback) {
NOTREACHED();
return ERR_NOT_IMPLEMENTED;
}
int QuicProxyDatagramClientSocket::ConnectUsingDefaultNetworkAsync(
const IPEndPoint& address,
CompletionOnceCallback callback) {
NOTREACHED();
return ERR_NOT_IMPLEMENTED;
}
int QuicProxyDatagramClientSocket::ConnectUsingNetwork(
handles::NetworkHandle network,
const IPEndPoint& address) {
NOTREACHED();
return ERR_NOT_IMPLEMENTED;
}
int QuicProxyDatagramClientSocket::ConnectUsingDefaultNetwork(
const IPEndPoint& address) {
NOTREACHED();
return ERR_NOT_IMPLEMENTED;
}
int QuicProxyDatagramClientSocket::ConnectUsingNetworkAsync(
handles::NetworkHandle network,
const IPEndPoint& address,
CompletionOnceCallback callback) {
NOTREACHED();
return ERR_NOT_IMPLEMENTED;
}
void QuicProxyDatagramClientSocket::Close() {
connect_callback_.Reset();
read_callback_.Reset();
read_buf_len_ = 0;
read_buf_ = nullptr;
next_state_ = STATE_DISCONNECTED;
if (datagram_visitor_registered_) {
stream_handle_->UnregisterHttp3DatagramVisitor();
datagram_visitor_registered_ = false;
}
stream_handle_->Reset(quic::QUIC_STREAM_CANCELLED);
}
int QuicProxyDatagramClientSocket::SetReceiveBufferSize(int32_t size) {
return OK;
}
int QuicProxyDatagramClientSocket::SetSendBufferSize(int32_t size) {
return OK;
}
void QuicProxyDatagramClientSocket::OnHttp3Datagram(
quic::QuicStreamId stream_id,
std::string_view payload) {
DCHECK_EQ(stream_id, stream_handle_->id())
<< "Received datagram for unexpected stream.";
quic::QuicDataReader reader(payload);
uint64_t context_id;
if (!reader.ReadVarInt62(&context_id)) {
DLOG(WARNING)
<< "Ignoring HTTP Datagram payload. Failed to read context ID";
return;
}
if (context_id != 0) {
DLOG(WARNING) << "Ignoring HTTP Datagram with unrecognized context ID "
<< context_id;
return;
}
std::string_view http_payload = reader.ReadRemainingPayload();
// If there's a read callback, process the payload immediately.
if (read_callback_) {
int result;
int bytes_read = http_payload.size();
if (http_payload.size() > static_cast<std::size_t>(read_buf_len_)) {
result = ERR_MSG_TOO_BIG;
} else {
CHECK(read_buf_ != nullptr);
CHECK(read_buf_len_ > 0);
std::memcpy(read_buf_->data(), http_payload.data(), http_payload.size());
result = bytes_read;
}
read_buf_ = nullptr;
read_buf_len_ = 0;
std::move(read_callback_).Run(result);
} else {
base::UmaHistogramBoolean(kMaxQueueSizeHistogram,
datagrams_.size() >= kMaxDatagramQueueSize);
if (datagrams_.size() >= kMaxDatagramQueueSize) {
DLOG(WARNING) << "Dropping datagram because queue is full";
return;
}
// If no read callback, store the payload in the queue.
datagrams_.emplace(http_payload.data(), http_payload.size());
}
}
// Silently ignore unknown capsules.
void QuicProxyDatagramClientSocket::OnUnknownCapsule(
quic::QuicStreamId stream_id,
const quiche::UnknownCapsule& capsule) {}
// TODO(crbug.com/41497362) Implement method.
handles::NetworkHandle QuicProxyDatagramClientSocket::GetBoundNetwork() const {
return handles::kInvalidNetworkHandle;
}
// TODO(crbug.com/41497362): Implement method.
void QuicProxyDatagramClientSocket::ApplySocketTag(const SocketTag& tag) {}
int QuicProxyDatagramClientSocket::SetMulticastInterface(
uint32_t interface_index) {
NOTREACHED();
return ERR_NOT_IMPLEMENTED;
}
void QuicProxyDatagramClientSocket::SetIOSNetworkServiceType(
int ios_network_service_type) {}
int QuicProxyDatagramClientSocket::GetPeerAddress(IPEndPoint* address) const {
*address = proxy_peer_address_;
return OK;
}
int QuicProxyDatagramClientSocket::GetLocalAddress(IPEndPoint* address) const {
*address = local_address_;
return OK;
}
void QuicProxyDatagramClientSocket::UseNonBlockingIO() {
NOTREACHED();
}
int QuicProxyDatagramClientSocket::SetDoNotFragment() {
NOTREACHED();
return ERR_NOT_IMPLEMENTED;
}
int QuicProxyDatagramClientSocket::SetRecvTos() {
NOTREACHED();
return ERR_NOT_IMPLEMENTED;
}
int QuicProxyDatagramClientSocket::SetTos(net::DiffServCodePoint dscp,
net::EcnCodePoint ecn) {
return OK;
}
void QuicProxyDatagramClientSocket::SetMsgConfirm(bool confirm) {
NOTREACHED();
}
const NetLogWithSource& QuicProxyDatagramClientSocket::NetLog() const {
return net_log_;
}
net::DscpAndEcn QuicProxyDatagramClientSocket::GetLastTos() const {
return {net::DSCP_DEFAULT, net::ECN_DEFAULT};
}
int QuicProxyDatagramClientSocket::Read(IOBuffer* buf,
int buf_len,
CompletionOnceCallback callback) {
CHECK(connect_callback_.is_null());
CHECK(read_callback_.is_null());
CHECK(!read_buf_);
CHECK(read_buf_len_ == 0);
if (next_state_ == STATE_DISCONNECTED) {
return ERR_SOCKET_NOT_CONNECTED;
}
// Return 0 if stream closed, signaling end-of-file or no more data.
if (!stream_handle_->IsOpen()) {
return 0;
}
// If there are datagrams available, attempt to read the first one into the
// buffer.
if (!datagrams_.empty()) {
auto& datagram = datagrams_.front();
int result;
int bytes_read = datagram.size();
if (datagram.size() > static_cast<std::size_t>(buf_len)) {
result = ERR_MSG_TOO_BIG;
} else {
std::memcpy(buf->data(), datagram.data(), datagram.size());
result = bytes_read;
}
datagrams_.pop();
return result;
}
// Save read callback so we can call it next time we receive a datagram.
read_callback_ = std::move(callback);
read_buf_ = buf;
read_buf_len_ = buf_len;
return ERR_IO_PENDING;
}
int QuicProxyDatagramClientSocket::Write(
IOBuffer* buf,
int buf_len,
CompletionOnceCallback callback,
const NetworkTrafficAnnotationTag& traffic_annotation) {
DCHECK(connect_callback_.is_null());
if (next_state_ != STATE_CONNECT_COMPLETE) {
return ERR_SOCKET_NOT_CONNECTED;
}
net_log_.AddByteTransferEvent(NetLogEventType::SOCKET_BYTES_SENT, buf_len,
buf->data());
std::string_view packet(buf->data(), buf_len);
int rv = stream_handle_->WriteConnectUdpPayload(packet);
if (rv == OK) {
return buf_len;
}
return rv;
}
void QuicProxyDatagramClientSocket::OnIOComplete(int result) {
DCHECK_NE(STATE_DISCONNECTED, next_state_);
int rv = DoLoop(result);
if (rv != ERR_IO_PENDING) {
// Connect() finished (successfully or unsuccessfully).
DCHECK(!connect_callback_.is_null());
std::move(connect_callback_).Run(rv);
}
}
int QuicProxyDatagramClientSocket::DoLoop(int last_io_result) {
DCHECK_NE(next_state_, STATE_DISCONNECTED);
int rv = last_io_result;
do {
State state = next_state_;
next_state_ = STATE_DISCONNECTED;
// TODO(crbug.com/326437102): Add support for generate auth token request
// and complete states.
switch (state) {
case STATE_SEND_REQUEST:
DCHECK_EQ(OK, rv);
net_log_.BeginEvent(
NetLogEventType::HTTP_TRANSACTION_TUNNEL_SEND_REQUEST);
rv = DoSendRequest();
break;
case STATE_SEND_REQUEST_COMPLETE:
net_log_.EndEventWithNetErrorCode(
NetLogEventType::HTTP_TRANSACTION_TUNNEL_SEND_REQUEST, rv);
rv = DoSendRequestComplete(rv);
break;
case STATE_READ_REPLY:
rv = DoReadReply();
break;
case STATE_READ_REPLY_COMPLETE:
rv = DoReadReplyComplete(rv);
net_log_.EndEventWithNetErrorCode(
NetLogEventType::HTTP_TRANSACTION_TUNNEL_READ_HEADERS, rv);
break;
default:
NOTREACHED() << "bad state";
rv = ERR_UNEXPECTED;
break;
}
} while (rv != ERR_IO_PENDING && next_state_ != STATE_DISCONNECTED &&
next_state_ != STATE_CONNECT_COMPLETE);
return rv;
}
int QuicProxyDatagramClientSocket::DoSendRequest() {
next_state_ = STATE_SEND_REQUEST_COMPLETE;
if (!url_.has_host()) {
return ERR_ADDRESS_INVALID;
}
std::string host = url_.host();
int port = url_.IntPort();
std::string host_and_port =
url_.has_port() ? base::StrCat({host, ":", base::NumberToString(port)})
: host;
request_.extra_headers.SetHeader(HttpRequestHeaders::kHost, host_and_port);
HttpRequestHeaders authorization_headers;
// TODO(crbug.com/326437102): Add Proxy-Authentication headers.
request_.extra_headers.MergeFrom(authorization_headers);
if (proxy_delegate_) {
HttpRequestHeaders proxy_delegate_headers;
int result = proxy_delegate_->OnBeforeTunnelRequest(
proxy_chain(), proxy_chain_index(), &proxy_delegate_headers);
if (result < 0) {
return result;
}
request_.extra_headers.MergeFrom(proxy_delegate_headers);
}
if (!user_agent_.empty()) {
request_.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent,
user_agent_);
}
request_.extra_headers.SetHeader("capsule-protocol", "?1");
// Generate a fake request line for logging purposes.
std::string request_line =
base::StringPrintf("CONNECT-UDP %s HTTP/3\r\n", url_.path().c_str());
NetLogRequestHeaders(net_log_,
NetLogEventType::HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
request_line, &request_.extra_headers);
spdy::Http2HeaderBlock headers;
CreateSpdyHeadersFromHttpRequestForExtendedConnect(
request_, /*priority=*/std::nullopt, "connect-udp",
request_.extra_headers, &headers);
return stream_handle_->WriteHeaders(std::move(headers), false, nullptr);
}
int QuicProxyDatagramClientSocket::DoSendRequestComplete(int result) {
if (result >= 0) {
// Wait for HEADERS frame from the server
next_state_ = STATE_READ_REPLY; // STATE_READ_REPLY_COMPLETE;
result = OK;
}
if (result >= 0 || result == ERR_IO_PENDING) {
// Emit extra event so can use the same events as HttpProxyClientSocket.
net_log_.BeginEvent(NetLogEventType::HTTP_TRANSACTION_TUNNEL_READ_HEADERS);
}
return result;
}
int QuicProxyDatagramClientSocket::DoReadReply() {
next_state_ = STATE_READ_REPLY_COMPLETE;
int rv = stream_handle_->ReadInitialHeaders(
&response_header_block_,
base::BindOnce(
&QuicProxyDatagramClientSocket::OnReadResponseHeadersComplete,
weak_factory_.GetWeakPtr()));
if (rv == ERR_IO_PENDING) {
return ERR_IO_PENDING;
}
if (rv < 0) {
return rv;
}
return ProcessResponseHeaders(response_header_block_);
}
int QuicProxyDatagramClientSocket::DoReadReplyComplete(int result) {
if (result < 0) {
return result;
}
NetLogResponseHeaders(
net_log_, NetLogEventType::HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
response_.headers.get());
// TODO(crbug.com/326437102): Add case for Proxy Authentication.
if (proxy_delegate_) {
int rv = proxy_delegate_->OnTunnelHeadersReceived(
proxy_chain(), proxy_chain_index(), *response_.headers);
if (rv != OK) {
CHECK_NE(ERR_IO_PENDING, rv);
return rv;
}
}
switch (response_.headers->response_code()) {
case 200: // OK
next_state_ = STATE_CONNECT_COMPLETE;
return OK;
default:
// Ignore response to avoid letting the proxy impersonate the target
// server. (See http://crbug.com/137891.)
return ERR_TUNNEL_CONNECTION_FAILED;
}
}
void QuicProxyDatagramClientSocket::OnReadResponseHeadersComplete(int result) {
// Convert the now-populated spdy::Http2HeaderBlock to HttpResponseInfo
if (result > 0) {
result = ProcessResponseHeaders(response_header_block_);
}
if (result != ERR_IO_PENDING) {
OnIOComplete(result);
}
}
int QuicProxyDatagramClientSocket::ProcessResponseHeaders(
const spdy::Http2HeaderBlock& headers) {
if (SpdyHeadersToHttpResponse(headers, &response_) != OK) {
DLOG(WARNING) << "Invalid headers";
return ERR_QUIC_PROTOCOL_ERROR;
}
return OK;
}
} // namespace net